首頁 後端開發 Python教學 提高工作口譯員的記憶效率

提高工作口譯員的記憶效率

Dec 26, 2024 pm 01:30 PM

Improving memory efficiency in a working interpreter

生命週期是 Rust 和人類經驗的一個迷人特徵。這是一個技術博客,所以我們專注於前者。誠然,我在利用 Rust 的生命週期安全地借用資料方面採用得很慢。在孟菲斯(我用Rust 編寫的Python 解釋器)的樹形行走實現中,我幾乎不利用生命週期(通過不斷克隆),並且盡可能地反复躲避借用檢查器(通過使用內部可變性,也不斷) 。

我的 Rustaceans 同胞們,我今天來這裡是為了告訴你們這一切現在結束了。讀懂我的嘴唇……不再有捷徑。

好吧好吧,說實話。什麼是捷徑,什麼是正確的方法,是優先事項和視角的問題。我們都犯過錯誤,我來這裡是為了為我的錯誤負責。

在我第一次安裝 rustc 六週後,我開始寫一個解釋器,因為我沒有冷靜。撇開這些長篇大論和故作姿態,讓我們開始今天的講座,了解如何使用生命週期作為我們的生命線來改進我臃腫的解釋器代碼庫。

識別並避免克隆數據

Rust 生命週期是一種提供編譯時保證的機制,確保任何引用都不會比它們所引用的物件的壽命長。它們使我們能夠避免 C 和 C 的「懸空指標」問題。

這是假設您完全利用它們!當您想要避免與管理生命週期相關的複雜性時,克隆是一種方便的解決方法,但缺點是增加了記憶體使用量,並且每次複製資料時都會出現輕微的延遲。

使用生命週期也迫使你更慣用地思考 Rust 中的所有者和借用,這是我渴望做的。

我選擇了我的第一個候選作為來自 Python 輸入檔的標記。當我坐在 Amtrak 上時,我最初的實現嚴重依賴 ChatGPT 指導,使用了以下流程:

  1. 我們將 Python 文字傳遞給建構器
  2. 建構器建立一個詞法分析器,它對輸入流進行標記
  3. 然後,建構器建立一個解析器,它複製令牌流以保存自己的副本
  4. 建構器用於建立解釋器,它反覆向解析器請求下一個解析語句並對其進行評估,直到我們到達令牌流的末尾

複製令牌流的便利之處在於,在步驟 3 之後可以自由刪除詞法分析器。透過更新我的架構,讓詞法分析器擁有令牌,而解析器只借用它們,現在需要保留詞法分析器活得更久。 Rust 生命週期將為我們保證這一點:只要解析器存在並持有對借用令牌的引用,編譯器就會保證擁有這些令牌的詞法分析器仍然存在,從而確保有效的引用。

就像所有程式碼一樣,這最終是一個比我預期更大的變化。讓我們看看為什麼!

新的解析器

在更新解析器以從詞法分析器借用令牌之前,它看起來像這樣。今天討論的兩個有興趣的領域是 tokens 和 current_token。我們不知道 Vec 有多大。是,但它顯然是我們的(即我們沒有借用它)。

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,
        }
    }
}
登入後複製
登入後複製

從 Lexer 借用代幣後,它看起來相當相似,但現在我們看到了 LIFETIME!透過將標記連接到生命週期 'a,Rust 編譯器將不允許標記的擁有者(即我們的詞法分析器)和標記本身被刪除,而我們的解析器仍然引用它們。這感覺安全又別緻!

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,
        }
    }
}
登入後複製
登入後複製

您可能會注意到的另一個小差異是這一行:

static EOF: Token = Token::Eof;
登入後複製

這是一個小的優化,當我的解析器朝著「記憶體高效」的方向發展時,我就開始考慮這個優化。新模型允許我僅實例化一個令牌並重複引用 &EOF,而不是每次解析器需要檢查它是否位於文字流末尾時都實例化一個新的 Token::Eof。

同樣,這是一個小優化,但它說明了每條數據在內存中只存在一次並且每個消費者在需要時引用它的更大思維方式,Rust 既鼓勵你這樣做,又緊握著你的手路。

說到最佳化,我真的應該對前後的記憶體使用情況進行基準測試。既然我沒有,我對此事無話可說。

正如我之前提到的,將 Lexer 和 Parser 的生命週期結合在一起對我的 Builder 模式產生了很大的影響。讓我們看看它是什麼樣子的!

新的建構器:MemphisContext

在我上面描述的流程中,還記得我如何提到一旦解析器創建了自己的標記副本就可以刪除詞法分析器嗎?這無意中影響了我的 Builder 的設計,它的目的是成為支援編排 Lexer、Parser 和 Interpreter 互動的元件,無論您是從 Python 文字流還是 Python 檔案的路徑開始。

正如您在下面看到的,此設計還有一些其他不理想的方面:

  1. 需要呼叫危險的向下轉換方法來取得解釋器。
  2. 為什麼我認為可以將解析器返回到每個單元測試,然後將其傳回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()))
            );
        }
    }
}
登入後複製

下面是新的 MemphisContext 介面。該機制在內部管理 Lexer 生命週期(使我們的引用保持足夠長的存活時間,以使我們的解析器滿意!)並且僅公開運行此測試所需的內容。

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,
        }
    }
}
登入後複製
登入後複製

context.run_and_return_interpreter() 仍然有點笨重,並且涉及到我將來可能要解決的另一個設計問題:當您運行解釋器時,您是否只想返回最終返回值或允許您訪問任意值的東西從符號表?該方法選擇後一種方法。我實際上認為有一個案例可以做到這兩點,並且會不斷調整我的 API 以允許這樣做。

順便說一句,這項變更提高了我評估任何一段 Python 程式碼的能力。如果你還記得我的 WebAssembly 傳奇,我當時必須依靠我的交叉檢查 TreewalkAdapter 來做到這一點。現在,我們的 Wasm 介面更加簡潔。

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,
        }
    }
}
登入後複製
登入後複製

介面 context.evaluate_oneshot() 傳回表達式結果而不是完整的符號表。我想知道是否有更好的方法來確保任何“oneshot”方法只能在上下文中運行一次,從而確保沒有消費者在有狀態上下文中使用它們。我會繼續努力!

這值得嗎?

孟菲斯首先是一次學習練習,所以這絕對值得!

除了在 Lexer 和 Parser 之間共用標記之外,我還建立了一個介面來評估 Python 程式碼,並且樣板程式碼明顯減少。雖然共享資料帶來了額外的複雜性,但這些變化帶來了明顯的好處:減少記憶體使用,透過更嚴格的生命週期管理來提高安全保證,以及更易於維護和擴展的簡化 API。

我選擇相信這是正確的方法,主要是為了維護我的自尊。最終,我的目標是編寫清晰反映軟體和電腦工程原理的程式碼。我們現在可以打開孟菲斯來源,指向代幣的單一所有者,晚上可以睡個好覺了!

訂閱並保存[無任何]

如果您想將更多類似的貼文直接發送到您的收件匣,您可以在這裡訂閱!

別處

除了指導軟體工程師之外,我還寫了我在自營職業和晚期診斷自閉症方面的經驗。更少的程式碼和相同數量的笑話。

  • 湖效應咖啡,第一章 - From Scratch dot org

以上是提高工作口譯員的記憶效率的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

Python vs.C:申請和用例 Python vs.C:申請和用例 Apr 12, 2025 am 12:01 AM

Python适合数据科学、Web开发和自动化任务,而C 适用于系统编程、游戏开发和嵌入式系统。Python以简洁和强大的生态系统著称,C 则以高性能和底层控制能力闻名。

您可以在2小時內學到多少python? 您可以在2小時內學到多少python? Apr 09, 2025 pm 04:33 PM

兩小時內可以學到Python的基礎知識。 1.學習變量和數據類型,2.掌握控制結構如if語句和循環,3.了解函數的定義和使用。這些將幫助你開始編寫簡單的Python程序。

Python:遊戲,Guis等 Python:遊戲,Guis等 Apr 13, 2025 am 12:14 AM

Python在遊戲和GUI開發中表現出色。 1)遊戲開發使用Pygame,提供繪圖、音頻等功能,適合創建2D遊戲。 2)GUI開發可選擇Tkinter或PyQt,Tkinter簡單易用,PyQt功能豐富,適合專業開發。

2小時的Python計劃:一種現實的方法 2小時的Python計劃:一種現實的方法 Apr 11, 2025 am 12:04 AM

2小時內可以學會Python的基本編程概念和技能。 1.學習變量和數據類型,2.掌握控制流(條件語句和循環),3.理解函數的定義和使用,4.通過簡單示例和代碼片段快速上手Python編程。

Python與C:學習曲線和易用性 Python與C:學習曲線和易用性 Apr 19, 2025 am 12:20 AM

Python更易學且易用,C 則更強大但複雜。 1.Python語法簡潔,適合初學者,動態類型和自動內存管理使其易用,但可能導致運行時錯誤。 2.C 提供低級控制和高級特性,適合高性能應用,但學習門檻高,需手動管理內存和類型安全。

Python:探索其主要應用程序 Python:探索其主要應用程序 Apr 10, 2025 am 09:41 AM

Python在web開發、數據科學、機器學習、自動化和腳本編寫等領域有廣泛應用。 1)在web開發中,Django和Flask框架簡化了開發過程。 2)數據科學和機器學習領域,NumPy、Pandas、Scikit-learn和TensorFlow庫提供了強大支持。 3)自動化和腳本編寫方面,Python適用於自動化測試和系統管理等任務。

Python和時間:充分利用您的學習時間 Python和時間:充分利用您的學習時間 Apr 14, 2025 am 12:02 AM

要在有限的時間內最大化學習Python的效率,可以使用Python的datetime、time和schedule模塊。 1.datetime模塊用於記錄和規劃學習時間。 2.time模塊幫助設置學習和休息時間。 3.schedule模塊自動化安排每週學習任務。

Python:多功能編程的力量 Python:多功能編程的力量 Apr 17, 2025 am 12:09 AM

Python因其簡潔與強大而備受青睞,適用於從初學者到高級開發者的各種需求。其多功能性體現在:1)易學易用,語法簡單;2)豐富的庫和框架,如NumPy、Pandas等;3)跨平台支持,可在多種操作系統上運行;4)適合腳本和自動化任務,提升工作效率。

See all articles