Rumah pembangunan bahagian belakang Tutorial Python Meningkatkan kecekapan ingatan dalam penterjemah yang berfungsi

Meningkatkan kecekapan ingatan dalam penterjemah yang berfungsi

Dec 26, 2024 pm 01:30 PM

Improving memory efficiency in a working interpreter

Hayat hidup ialah ciri yang menarik bagi Rust dan pengalaman manusia. Ini adalah blog teknikal, jadi mari kita fokus pada yang pertama. Saya diakui adalah pengguna yang lambat untuk memanfaatkan jangka hayat untuk meminjam data dengan selamat dalam Rust. Dalam pelaksanaan treewalk Memphis, penterjemah Python saya yang ditulis dalam Rust, saya hampir tidak memanfaatkan jangka hayat (dengan mengklon tanpa henti) dan saya berulang kali mengelak pemeriksa pinjaman (dengan menggunakan kebolehubah dalaman, juga tanpa henti) apabila boleh.

Rakyat Rustacean saya, saya di sini hari ini untuk memberitahu anda bahawa ini berakhir sekarang. Baca bibir saya…tiada jalan pintas lagi.

Okey okey, biar betul. Apa itu jalan pintas berbanding cara yang betul adalah soal keutamaan dan perspektif. Kita semua telah melakukan kesilapan, dan saya di sini untuk bertanggungjawab ke atas diri saya.

Saya mula menulis jurubahasa enam minggu selepas saya mula-mula memasang rustc kerana saya tidak mempunyai kesejukan. Dengan sikap yang meleret-leret dan postur itu, mari kita mulakan kuliah hari ini tentang cara kita boleh menggunakan jangka hayat sebagai talian hayat kita untuk menambah baik pangkalan kod jurubahasa saya yang kembung.

Mengenal pasti dan mengelakkan data klon

Satu Hayat karat ialah mekanisme yang menyediakan jaminan masa kompilasi bahawa sebarang rujukan tidak melebihi umur objek yang dirujuknya. Ia membolehkan kita mengelakkan masalah "penunjuk berjuntai" daripada C dan C .

Ini mengandaikan anda memanfaatkannya sama sekali! Pengklonan ialah penyelesaian yang mudah apabila anda ingin mengelakkan kerumitan yang berkaitan dengan pengurusan jangka hayat, walaupun kelemahannya ialah peningkatan penggunaan memori dan sedikit kelewatan yang berkaitan dengan setiap kali data disalin.

Menggunakan seumur hidup juga memaksa anda untuk berfikir lebih idiomatik tentang pemilik dan meminjam dalam Rust, yang saya ingin lakukan.

Saya memilih calon pertama saya sebagai token daripada fail input Python. Pelaksanaan asal saya, yang sangat bergantung pada panduan ChatGPT semasa saya duduk di Amtrak, menggunakan aliran ini:

  1. kami menghantar teks Python kami kepada Builder
  2. Pembina mencipta Lexer, yang menandakan aliran input
  3. Pembina kemudian mencipta Parser, yang mengklonkan aliran token untuk menyimpan salinannya sendiri
  4. Pembina digunakan untuk mencipta Jurubahasa, yang berulang kali meminta Parser untuk pernyataan yang dihuraikan seterusnya dan menilainya sehingga kami sampai ke penghujung aliran token

Aspek yang mudah untuk mengklon aliran token ialah Lexer bebas untuk digugurkan selepas langkah 3. Dengan mengemas kini seni bina saya supaya Lexer memiliki token dan Parser hanya meminjamnya, Lexer kini perlu kekal hidup lebih lama lagi. Jangka hayat karat akan menjamin ini untuk kami: selagi Parser wujud memegang rujukan kepada token yang dipinjam, pengkompil akan menjamin bahawa Lexer yang memiliki token tersebut masih wujud, memastikan rujukan yang sah.

Seperti semua kod selalu, ini akhirnya menjadi perubahan yang lebih besar daripada yang saya jangkakan. Mari lihat sebabnya!

Penghurai baharu

Sebelum mengemas kini Parser untuk meminjam token daripada Lexer, ia kelihatan seperti ini. Dua bidang yang diminati untuk perbincangan hari ini ialah token dan current_token. Kami tidak tahu berapa besar Vec adalah, tetapi ia jelas milik kami (iaitu kami tidak meminjamnya).

pub struct Parser {
    state: Container<State>,
    tokens: Vec<Token>,
    current_token: Token,
    position: usize,
    line_number: usize,
    delimiter_depth: usize,
}

impl Parser {
    pub fn new(tokens: Vec<Token>, state: Container<State>) -> Self {
        let current_token = tokens.first().cloned().unwrap_or(Token::Eof);
        Parser {
            state,
            tokens,
            current_token,
            position: 0,
            line_number: 1,
            delimiter_depth: 0,
        }
    }
}
Salin selepas log masuk
Salin selepas log masuk

Selepas meminjam token daripada Lexer, ia kelihatan agak serupa, tetapi kini kita melihat SEUMUR HIDUP! Dengan menyambungkan token kepada 'a seumur hidup, pengkompil Rust tidak akan membenarkan pemilik token (iaitu Lexer kami) dan token itu sendiri digugurkan semasa Parser kami masih merujuknya. Ini rasa selamat dan mewah!

static EOF: Token = Token::Eof;

/// A recursive-descent parser which attempts to encode the full Python grammar.
pub struct Parser<'a> {
    state: Container<State>,
    tokens: &'a [Token],
    current_token: &'a Token,
    position: usize,
    line_number: usize,
    delimiter_depth: usize,
}

impl<'a> Parser<'a> {
    pub fn new(tokens: &'a [Token], state: Container<State>) -> Self {
        let current_token = tokens.first().unwrap_or(&EOF);
        Parser {
            state,
            tokens,
            current_token,
            position: 0,
            line_number: 1,
            delimiter_depth: 0,
        }
    }
}
Salin selepas log masuk
Salin selepas log masuk

Satu lagi perbezaan kecil yang anda mungkin perasan ialah baris ini:

static EOF: Token = Token::Eof;
Salin selepas log masuk

Ini adalah pengoptimuman kecil yang saya mula pertimbangkan sebaik sahaja Penghurai saya bergerak ke arah "cekap ingatan". Daripada membuat instantiat Token baharu::Eof setiap kali Parser perlu menyemak sama ada ia berada di penghujung strim teks, model baharu membenarkan saya membuat instantiat hanya satu token dan rujukan &EOF berulang kali.

Sekali lagi, ini adalah pengoptimuman kecil, tetapi ia merujuk kepada pemikiran yang lebih besar bagi setiap data yang wujud hanya sekali dalam ingatan dan setiap pengguna hanya merujuknya apabila diperlukan, yang mana Rust menggalakkan anda untuk melakukannya dan memegang tangan anda dengan kemas caranya.

Bercakap tentang pengoptimuman, saya sepatutnya menanda aras penggunaan memori sebelum dan selepas. Memandangkan saya tidak melakukannya, saya tidak mempunyai apa-apa lagi untuk diperkatakan mengenai perkara itu.

Seperti yang saya nyatakan sebelum ini, mengikat seumur hidup Lexer dan Parser saya bersama-sama memberi impak yang besar pada corak Builder saya. Mari lihat rupanya!

Pembina baharu: MemphisContext

Dalam aliran yang saya terangkan di atas, ingat bagaimana saya menyebut bahawa Lexer boleh digugurkan sebaik sahaja Parser mencipta salinan tokennya sendiri? Ini secara tidak sengaja telah mempengaruhi reka bentuk Builder saya, yang bertujuan untuk menjadi komponen yang menyokong mendalangi interaksi Lexer, Parser dan Interpreter, sama ada anda bermula dengan strim teks Python atau laluan ke fail Python.

Seperti yang anda lihat di bawah, terdapat beberapa aspek lain yang tidak sesuai untuk reka bentuk ini:

  1. perlu memanggil kaedah downcast berbahaya untuk mendapatkan Jurubahasa.
  2. kenapa saya fikir tidak mengapa untuk memulangkan Parser kepada setiap ujian unit hanya untuk meneruskannya terus ke interpreter.run(&mut parser)?!
fn downcast<T: InterpreterEntrypoint + 'static>(input: T) -> Interpreter {
    let any_ref: &dyn Any = &input as &dyn Any;
    any_ref.downcast_ref::<Interpreter>().unwrap().clone()
}

fn init(text: &str) -> (Parser, Interpreter) {
    let (parser, interpreter) = Builder::new().text(text).build();

    (parser, downcast(interpreter))
}


#[test]
fn function_definition() {
     let input = r#"
def add(x, y):
    return x + y

a = add(2, 3)
"#;
    let (mut parser, mut interpreter) = init(input);

    match interpreter.run(&mut parser) {
        Err(e) => panic!("Interpreter error: {:?}", e),
        Ok(_) => {
            assert_eq!(
                interpreter.state.read("a"),
                Some(ExprResult::Integer(5.store()))
            );
        }
    }
}
Salin selepas log masuk

Di bawah ialah antara muka MemphisContext baharu. Mekanisme ini menguruskan seumur hidup Lexer secara dalaman (untuk memastikan rujukan kami kekal cukup lama untuk memastikan Parser kami gembira!) dan hanya mendedahkan perkara yang diperlukan untuk menjalankan ujian ini.

pub struct Parser {
    state: Container<State>,
    tokens: Vec<Token>,
    current_token: Token,
    position: usize,
    line_number: usize,
    delimiter_depth: usize,
}

impl Parser {
    pub fn new(tokens: Vec<Token>, state: Container<State>) -> Self {
        let current_token = tokens.first().cloned().unwrap_or(Token::Eof);
        Parser {
            state,
            tokens,
            current_token,
            position: 0,
            line_number: 1,
            delimiter_depth: 0,
        }
    }
}
Salin selepas log masuk
Salin selepas log masuk

context.run_and_return_interpreter() masih agak kikuk dan bercakap kepada masalah reka bentuk lain yang mungkin saya atasi: apabila anda menjalankan penterjemah, adakah anda mahu memulangkan hanya nilai pulangan akhir atau sesuatu yang membolehkan anda mengakses nilai sewenang-wenangnya daripada jadual simbol? Kaedah ini memilih pendekatan yang terakhir. Saya sebenarnya berpendapat ada kes untuk melakukan kedua-duanya, dan akan terus mengubahsuai API saya untuk membenarkan perkara ini semasa kita pergi.

Secara kebetulan, perubahan ini meningkatkan keupayaan saya untuk menilai sekeping kod Python yang sewenang-wenangnya. Jika anda ingat dari saga WebAssembly saya, saya terpaksa bergantung pada semak silang TreewalkAdapter saya untuk melakukannya pada masa itu. Kini, antara muka Wasm kami jauh lebih bersih.

static EOF: Token = Token::Eof;

/// A recursive-descent parser which attempts to encode the full Python grammar.
pub struct Parser<'a> {
    state: Container<State>,
    tokens: &'a [Token],
    current_token: &'a Token,
    position: usize,
    line_number: usize,
    delimiter_depth: usize,
}

impl<'a> Parser<'a> {
    pub fn new(tokens: &'a [Token], state: Container<State>) -> Self {
        let current_token = tokens.first().unwrap_or(&EOF);
        Parser {
            state,
            tokens,
            current_token,
            position: 0,
            line_number: 1,
            delimiter_depth: 0,
        }
    }
}
Salin selepas log masuk
Salin selepas log masuk

Konteks antara muka.evaluate_oneshot() mengembalikan hasil ungkapan dan bukannya jadual simbol penuh. Saya tertanya-tanya sama ada terdapat cara yang lebih baik untuk memastikan mana-mana kaedah "oneshot" hanya boleh beroperasi pada konteks sekali, memastikan tiada pengguna menggunakannya dalam konteks stateful. Saya akan terus merenungnya!

Adakah ini berbaloi?

Memphis ialah latihan pembelajaran yang pertama sekali, jadi ini sangat berbaloi!

Selain berkongsi token antara Lexer dan Parser, saya mencipta antara muka untuk menilai kod Python dengan boilerplate yang kurang ketara. Walaupun perkongsian data memperkenalkan kerumitan tambahan, perubahan ini membawa faedah yang jelas: penggunaan memori yang dikurangkan, jaminan keselamatan yang dipertingkatkan melalui pengurusan seumur hidup yang lebih ketat dan API diperkemas yang lebih mudah untuk diselenggara dan dilanjutkan.

Saya memilih untuk mempercayai ini adalah pendekatan yang betul, kebanyakannya untuk mengekalkan harga diri saya. Akhirnya, saya berhasrat untuk menulis kod yang menggambarkan dengan jelas prinsip perisian dan kejuruteraan komputer. Kami kini boleh membuka sumber Memphis, menunjuk kepada pemilik tunggal token dan tidur dengan nyenyak pada waktu malam!

Langgan & Simpan [pada apa-apa]

Jika anda ingin mendapatkan lebih banyak siaran seperti ini terus ke peti masuk anda, anda boleh melanggan di sini!

Di tempat lain

Selain membimbing jurutera perisian, saya juga menulis tentang pengalaman saya mengemudi bekerja sendiri dan autisme yang didiagnosis lewat. Kurang kod dan bilangan jenaka yang sama.

  • Kopi Kesan Tasik, Bab 1 - Dari titik awal org

Atas ialah kandungan terperinci Meningkatkan kecekapan ingatan dalam penterjemah yang berfungsi. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn

Alat AI Hot

Undresser.AI Undress

Undresser.AI Undress

Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover

AI Clothes Remover

Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Undress AI Tool

Undress AI Tool

Gambar buka pakaian secara percuma

Clothoff.io

Clothoff.io

Penyingkiran pakaian AI

Video Face Swap

Video Face Swap

Tukar muka dalam mana-mana video dengan mudah menggunakan alat tukar muka AI percuma kami!

Alat panas

Notepad++7.3.1

Notepad++7.3.1

Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina

SublimeText3 versi Cina

Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1

Hantar Studio 13.0.1

Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6

Dreamweaver CS6

Alat pembangunan web visual

SublimeText3 versi Mac

SublimeText3 versi Mac

Perisian penyuntingan kod peringkat Tuhan (SublimeText3)

Python vs C: Aplikasi dan kes penggunaan dibandingkan Python vs C: Aplikasi dan kes penggunaan dibandingkan Apr 12, 2025 am 12:01 AM

Python sesuai untuk sains data, pembangunan web dan tugas automasi, manakala C sesuai untuk pengaturcaraan sistem, pembangunan permainan dan sistem tertanam. Python terkenal dengan kesederhanaan dan ekosistem yang kuat, manakala C dikenali dengan keupayaan kawalan dan keupayaan kawalan yang mendasari.

Berapa banyak python yang boleh anda pelajari dalam 2 jam? Berapa banyak python yang boleh anda pelajari dalam 2 jam? Apr 09, 2025 pm 04:33 PM

Anda boleh mempelajari asas -asas Python dalam masa dua jam. 1. Belajar pembolehubah dan jenis data, 2. Struktur kawalan induk seperti jika pernyataan dan gelung, 3 memahami definisi dan penggunaan fungsi. Ini akan membantu anda mula menulis program python mudah.

Python: Permainan, GUI, dan banyak lagi Python: Permainan, GUI, dan banyak lagi Apr 13, 2025 am 12:14 AM

Python cemerlang dalam permainan dan pembangunan GUI. 1) Pembangunan permainan menggunakan pygame, menyediakan lukisan, audio dan fungsi lain, yang sesuai untuk membuat permainan 2D. 2) Pembangunan GUI boleh memilih tkinter atau pyqt. TKInter adalah mudah dan mudah digunakan, PYQT mempunyai fungsi yang kaya dan sesuai untuk pembangunan profesional.

Rancangan Python 2 jam: Pendekatan yang realistik Rancangan Python 2 jam: Pendekatan yang realistik Apr 11, 2025 am 12:04 AM

Anda boleh mempelajari konsep pengaturcaraan asas dan kemahiran Python dalam masa 2 jam. 1. Belajar Pembolehubah dan Jenis Data, 2.

Python vs C: Lengkung pembelajaran dan kemudahan penggunaan Python vs C: Lengkung pembelajaran dan kemudahan penggunaan Apr 19, 2025 am 12:20 AM

Python lebih mudah dipelajari dan digunakan, manakala C lebih kuat tetapi kompleks. 1. Sintaks Python adalah ringkas dan sesuai untuk pemula. Penaipan dinamik dan pengurusan memori automatik menjadikannya mudah digunakan, tetapi boleh menyebabkan kesilapan runtime. 2.C menyediakan kawalan peringkat rendah dan ciri-ciri canggih, sesuai untuk aplikasi berprestasi tinggi, tetapi mempunyai ambang pembelajaran yang tinggi dan memerlukan memori manual dan pengurusan keselamatan jenis.

Python: meneroka aplikasi utamanya Python: meneroka aplikasi utamanya Apr 10, 2025 am 09:41 AM

Python digunakan secara meluas dalam bidang pembangunan web, sains data, pembelajaran mesin, automasi dan skrip. 1) Dalam pembangunan web, kerangka Django dan Flask memudahkan proses pembangunan. 2) Dalam bidang sains data dan pembelajaran mesin, numpy, panda, scikit-learn dan perpustakaan tensorflow memberikan sokongan yang kuat. 3) Dari segi automasi dan skrip, Python sesuai untuk tugas -tugas seperti ujian automatik dan pengurusan sistem.

Python dan Masa: Memanfaatkan masa belajar anda Python dan Masa: Memanfaatkan masa belajar anda Apr 14, 2025 am 12:02 AM

Untuk memaksimumkan kecekapan pembelajaran Python dalam masa yang terhad, anda boleh menggunakan modul, masa, dan modul Python. 1. Modul DateTime digunakan untuk merakam dan merancang masa pembelajaran. 2. Modul Masa membantu menetapkan kajian dan masa rehat. 3. Modul Jadual secara automatik mengatur tugas pembelajaran mingguan.

Python: Kekuatan pengaturcaraan serba boleh Python: Kekuatan pengaturcaraan serba boleh Apr 17, 2025 am 12:09 AM

Python sangat disukai kerana kesederhanaan dan kuasa, sesuai untuk semua keperluan dari pemula hingga pemaju canggih. Kepelbagaiannya dicerminkan dalam: 1) mudah dipelajari dan digunakan, sintaks mudah; 2) perpustakaan dan kerangka yang kaya, seperti numpy, panda, dan sebagainya; 3) sokongan silang platform, yang boleh dijalankan pada pelbagai sistem operasi; 4) Sesuai untuk tugas skrip dan automasi untuk meningkatkan kecekapan kerja.

See all articles