検索
ホームページバックエンド開発Python チュートリアル作業中のインタープリタでのメモリ効率の向上

Improving memory efficiency in a working interpreter

ライフタイムは、Rust と人間の経験の魅力的な機能です。これは技術的なブログなので、前者に焦点を当てましょう。確かに私は、Rust でデータを安全に借用するためにライフタイムを活用するのが遅かったです。 Rust で書かれた私の Python インタプリタである Memphis のツリーウォーク実装では、(絶え間なくクローンを作成することによって) ライフタイムをほとんど利用せず、可能な限り (これも絶え間なく内部可変性を使用することによって) 借用チェッカーを繰り返し回避しています。

Rustacean の皆さん、私は今日ここに来て、これがもう終わったことをお伝えします。私の唇を読んでください……もう近道はありません。

わかりました、わかりました、本当のことを言いましょう。何が近道であり、何が正しい方法であるかは、優先順位と視点の問題です。私たちは皆、間違いを犯したことがあります。私は自分の責任を取るためにここにいます。

私は、rustc を最初にインストールしてから 6 週間後にインタープリタを書き始めました。寒気がしなかったからです。そのような嫌がらせや態度はさておき、肥大化したインタープリターのコードベースを改善するためにライフラインをライフラインとして使用する方法について、今日の講義を始めましょう。

クローンデータの特定と回避

Rust のライフタイムは、参照が参照先のオブジェクトより長く存続しないことをコンパイル時に保証するメカニズムです。これらにより、 C と C の「ダングリング ポインター」問題を回避できます。

これは、それらを活用することを前提としています。クローン作成は、ライフタイムの管理に伴う複雑さを回避したい場合に便利な回避策ですが、メモリ使用量が増加し、データがコピーされるたびに若干の遅延が発生するという欠点があります。

ライフタイムを使用すると、Rust での所有者と借入についてより慣用的に考える必要があります。これは私が熱望していたことです。

最初の候補を Python 入力ファイルからのトークンとして選択しました。私の元の実装では、アムトラックに乗っていたときに ChatGPT ガイダンスに大きく依存しており、次のフローを使用していました。

  1. Python テキストをビルダーに渡します
  2. ビルダーは入力ストリームをトークン化するレクサーを作成します
  3. その後、ビルダーはパーサーを作成し、トークン ストリームのクローンを作成して独自のコピーを保持します
  4. ビルダーはインタープリターを作成するために使用されます。インタープリターは、パーサーに次の解析済みステートメントを繰り返し要求し、トークン ストリームの最後に到達するまでそれを評価します

トークン ストリームのクローン作成の便利な点は、ステップ 3 の後にレクサーを自由に削除できることです。レクサーがトークンを所有し、パーサーがトークンを借用するようにアーキテクチャを更新すると、レクサーはそのままにしておく必要があります。もっと長く生きています。 Rust のライフタイムはこれを保証します。借用したトークンへの参照を保持するパーサーが存在する限り、コンパイラーはそれらのトークンを所有するレクサーがまだ存在していることを保証し、有効な参照を保証します。

すべてのコードと同様に、これも予想よりも大きな変更となりました。その理由を見てみましょう!

新しいパーサー

レクサーからトークンを借用するためにパーサーを更新する前は、次のようになっていました。今日の議論で関心のある 2 つのフィールドは、token と 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,
        }
    }
}
</state></token></token></state>

レクサーからトークンを借りた後はかなり似ていますが、今度は LIFETIME! が表示されます。トークンをライフタイム 'a に接続することで、Rust コンパイラは、パーサーがまだトークンを参照している間、トークンの所有者 (レクサー) とトークン自体を削除することを許可しません。これは安全でおしゃれな感じです!

static EOF: Token = Token::Eof;

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

impl Parser {
    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,
        }
    }
}
</state></state>

もう 1 つの小さな違いに気づくかもしれませんが、次の行です。

static EOF: Token = Token::Eof;

これは、パーサーが「メモリ効率の高い」方向に進んでから検討し始めた小さな最適化です。パーサーがテキスト ストリームの最後にあるかどうかを確認する必要があるたびに新しい Token::Eof をインスタンス化するのではなく、新しいモデルでは 1 つのトークンのみをインスタンス化し、&EOF を繰り返し参照することができました。

繰り返しになりますが、これは小さな最適化ですが、各データはメモリ内に 1 回だけ存在し、すべてのコンシューマは必要なときにそれを参照するだけであるという、より大きな考え方を物語っています。Rust は、これを実行することを奨励し、しっかりとサポートします。

最適化と言えば、その前後でメモリ使用量のベンチマークを行うべきでした。私はそうしなかったので、この件に関してはこれ以上言うことはありません。

前にほのめかしたように、レクサーとパーサーの存続期間を結びつけることは、ビルダー パターンに大きな影響を与えます。それがどのようなものか見てみましょう!

新しいビルダー: MemphisContext

上で説明したフローで、パーサーがトークンの独自のコピーを作成するとすぐにレクサーが削除される可能性があると述べたことを覚えていますか?これは、Python テキスト ストリームから始めるか Python ファイルへのパスから始めるかに関係なく、レクサー、パーサー、およびインタープリターの相互作用の調整をサポートするコンポーネントになることを目的とした私のビルダーの設計に意図せず影響を与えていました。

以下に示すように、この設計には他にも理想的ではない側面がいくつかあります。

  1. インタプリタを取得するには危険なダウンキャスト メソッドを呼び出す必要があります。
  2. なぜ私は、すべての単体テストにパーサーを返して、すぐにinterpreter.run(&mut parser) に戻すだけで問題ないと思ったのでしょうか?!
fn downcast<t: interpreterentrypoint>(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()))
            );
        }
    }
}
</interpreter></t:>

以下は新しい MemphisContext インターフェースです。このメカニズムは、レクサーの有効期間を内部で管理し (パーサーを満足させるのに十分な長さの参照を存続させるため)、このテストの実行に必要なもののみを公開します。

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,
        }
    }
}
</state></token></token></state>

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 {
    state: Container<state>,
    tokens: &'a [Token],
    current_token: &'a Token,
    position: usize,
    line_number: usize,
    delimiter_depth: usize,
}

impl Parser {
    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,
        }
    }
}
</state></state>

インターフェイス context.evaluate_oneshot() は、完全なシンボル テーブルではなく式の結果を返します。 「ワンショット」メソッドがコンテキスト上で 1 回だけ動作し、コンシューマがステートフル コンテキストでメソッドを使用しないようにする、より良い方法はないのだろうか。これからも煮詰めていきます!

これには価値がありましたか?

メンフィスは何よりも学習の場であるため、これは絶対に価値がありました!

レクサーとパーサーの間でトークンを共有することに加えて、ボイラープレートを大幅に減らして Python コードを評価するインターフェイスを作成しました。データの共有によりさらに複雑さが増しましたが、これらの変更により、メモリ使用量の削減、厳格なライフタイム管理による安全性保証の向上、保守と拡張が容易になった合理化された API など、明らかなメリットがもたらされました。

私は、主に自尊心を維持するために、これが正しいアプローチだったと信じることにしました。最終的には、ソフトウェアとコンピューター エンジニアリングの原則を明確に反映したコードを書くことを目指しています。これで、メンフィスのソースを開き、トークンの単一所有者を指定して、夜はぐっすり眠ることができるようになりました!

定期購入して [何もせずに] 節約しましょう

このような投稿をさらに直接受信トレイに受け取りたい場合は、ここから購読できます!

他の場所

私はソフトウェア エンジニアの指導に加えて、自営業や晩期に診断された自閉症を乗り越えた経験についても書いています。コードは減り、ジョークの数は同じです。

  • Lake-Effect Coffee、第 1 章 - ゼロから作る dot org

以上が作業中のインタープリタでのメモリ効率の向上の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
リストと配列間の要素ごとの操作のパフォーマンスの違いを説明します。リストと配列間の要素ごとの操作のパフォーマンスの違いを説明します。May 06, 2025 am 12:15 AM

ArsareSareBetterElement-WiseOperationsduetof of ActassandoptimizedImplementations.1)ArrayshaveContigUousMoryFordiRectAccess.2)ListSareFlexibleButSlowerDueTopotentialDynamicresizizizizing.3)

numpyアレイ全体で数学操作を効率的に実行するにはどうすればよいですか?numpyアレイ全体で数学操作を効率的に実行するにはどうすればよいですか?May 06, 2025 am 12:15 AM

Numpyの配列全体の数学的操作は、ベクトル化された操作を通じて効率的に実装できます。 1)追加(arr 2)などの簡単な演算子を使用して、配列で操作を実行します。 2)Numpyは、基礎となるC言語ライブラリを使用して、コンピューティング速度を向上させます。 3)乗算、分割、指数などの複雑な操作を実行できます。 4)放送操作に注意して、配列の形状が互換性があることを確認します。 5)np.sum()などのnumpy関数を使用すると、パフォーマンスが大幅に向上する可能性があります。

Pythonアレイに要素を挿入するにはどうすればよいですか?Pythonアレイに要素を挿入するにはどうすればよいですか?May 06, 2025 am 12:14 AM

Pythonでは、要素をリストに挿入するための2つの主要な方法があります。1)挿入(インデックス、値)メソッドを使用して、指定されたインデックスに要素を挿入できますが、大きなリストの先頭に挿入することは非効率的です。 2)Append(Value)メソッドを使用して、リストの最後に要素を追加します。これは非常に効率的です。大規模なリストの場合、append()を使用するか、dequeまたはnumpy配列を使用してパフォーマンスを最適化することを検討することをお勧めします。

UNIXとWindowsの両方でPythonスクリプト実行可能ファイルをどのように作成できますか?UNIXとWindowsの両方でPythonスクリプト実行可能ファイルをどのように作成できますか?May 06, 2025 am 12:13 AM

tomakeapythonscriptexecutableonbothunixandwindows:1)addashebangline(#!/usr/bin/envpython3)andusechmod xtomakeitexecutableonix.2)onwindows、sursepythonisinstalledassandassassociated with.pyfiles、またはruseabatchfile(run.bat)tor。

スクリプトを実行しようとしているときに「コマンドが見つからない」エラーが表示された場合、何を確認する必要がありますか?スクリプトを実行しようとしているときに「コマンドが見つからない」エラーが表示された場合、何を確認する必要がありますか?May 06, 2025 am 12:03 AM

「commandnotfound」エラーに遭遇した場合、次のポイントを確認する必要があります。1。スクリプトが存在し、パスが正しいことを確認します。 2.ファイルの権限を確認し、CHMODを使用して、必要に応じて実行権限を追加します。 3.スクリプトインタープリターがインストールされ、パスにインストールされていることを確認してください。 4.スクリプトの先頭にあるShebangラインが正しいことを確認します。そうすることで、スクリプトの操作の問題を効果的に解決し、コーディングプロセスがスムーズであることを確認できます。

数値データを保存するためのリストよりも一般的にメモリ効率が高いのはなぜですか?数値データを保存するためのリストよりも一般的にメモリ効率が高いのはなぜですか?May 05, 2025 am 12:15 AM

AlaySaregenerallymorememory-effictient forstring forstring inumericaldataduetotheirfixed-sizenature anddirectmoryaccess.1)AraysstoreElementsinaCourowlock、Reducingoverheadfrompointertersormetadata.2)リスト

PythonリストをPythonアレイに変換するにはどうすればよいですか?PythonリストをPythonアレイに変換するにはどうすればよいですか?May 05, 2025 am 12:10 AM

ToconvertaPythonlisttoanarray,usethearraymodule:1)Importthearraymodule,2)Createalist,3)Usearray(typecode,list)toconvertit,specifyingthetypecodelike'i'forintegers.Thisconversionoptimizesmemoryusageforhomogeneousdata,enhancingperformanceinnumericalcomp

同じPythonリストに異なるデータ型を保存できますか?例を挙げてください。同じPythonリストに異なるデータ型を保存できますか?例を挙げてください。May 05, 2025 am 12:10 AM

Pythonリストは、さまざまな種類のデータを保存できます。サンプルリストには、整数、文字列、フローティングポイント番号、ブール膜、ネストされたリスト、辞書が含まれています。リストの柔軟性は、データ処理とプロトタイピングにおいて価値がありますが、コードの読みやすさと保守性を確保するためには注意して使用する必要があります。

See all articles

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

mPDF

mPDF

mPDF は、UTF-8 でエンコードされた HTML から PDF ファイルを生成できる PHP ライブラリです。オリジナルの作者である Ian Back は、Web サイトから「オンザフライ」で PDF ファイルを出力し、さまざまな言語を処理するために mPDF を作成しました。 HTML2FPDF などのオリジナルのスクリプトよりも遅く、Unicode フォントを使用すると生成されるファイルが大きくなりますが、CSS スタイルなどをサポートし、多くの機能強化が施されています。 RTL (アラビア語とヘブライ語) や CJK (中国語、日本語、韓国語) を含むほぼすべての言語をサポートします。ネストされたブロックレベル要素 (P、DIV など) をサポートします。

PhpStorm Mac バージョン

PhpStorm Mac バージョン

最新(2018.2.1)のプロフェッショナル向けPHP統合開発ツール

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強力な PHP 統合開発環境

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

DVWA

DVWA

Damn Vulnerable Web App (DVWA) は、非常に脆弱な PHP/MySQL Web アプリケーションです。その主な目的は、セキュリティ専門家が法的環境でスキルとツールをテストするのに役立ち、Web 開発者が Web アプリケーションを保護するプロセスをより深く理解できるようにし、教師/生徒が教室環境で Web アプリケーションを教え/学習できるようにすることです。安全。 DVWA の目標は、シンプルでわかりやすいインターフェイスを通じて、さまざまな難易度で最も一般的な Web 脆弱性のいくつかを実践することです。このソフトウェアは、