新しいPythonインタプリタを開き、それを閉じ、そして再び新しいPythonインタプリタを開いた場合、これらのプロセスはどの程度異なっているのでしょうか? Hood Chatham氏のLanguage Summitでの講演は、Pythonインタプリタのための、毎回同じ初期化処理を最初から行うのではなく、初期化が完了した後のPythonプロセスのメモリの「スナップショット」を利用する、安全な代替の「ブートストラップ」方法を提供することを目的としていました。

Pyodideでのスナップショットのテストでは、スナップショットからPythonインタプリタを読み込む場合、スナップショットなしで最初からPythonインタプリタを読み込む場合に比べて、単純な「Hello, world」プログラムの実行速度が約4倍速くなることが確認されました(0.353秒 vs. 1.406秒)。 Node.jsはv4.2.4でV8スナップショットを実装し、起動時間が約33%改善されましたが、その後、v4.8.4でスナップショットが無効にされました。これは、Hood氏が議論したかった問題に関連しています。

予期しないランダム化

この問題は、Pythonや多くの他の言語で共通の問題であり、ハッシュアルゴリズムの衝突によるサービス拒否(DoS)攻撃です。 このセキュリティ上の問題は、Java、Ruby、PHPなど、多くのプログラミング言語で同時に報告されました。

Pythonは、Python 3.3で、他の多くのプログラミング言語と同様に、ハッシュ関数(hash())の計算にランダムな「ソルト」を追加することで、この問題を修正しました。 このソルトは、プログラムの起動時にランダム化され、リモートの攻撃者が入力データのハッシュ値を制御することを防ぎます。 これにより、オブジェクトのハッシュ値が均等に分布していることを前提とする、極端に不均衡なデータ構造(例えば、辞書や集合)を作成することで、CPUによるサービス拒否を引き起こす可能性があります。

初期化後にPythonプロセスのメモリをスナップショット化し、復元することの危険性は、Randall Munroe氏のxkcd「Random Number」(以下の画像、CC-BY-NC 2.5ライセンス)で表現されています。 この画像では、「4」という固定値が、過去のある時点に公平なサイコロのロールによって選択され、新しい乱数が要求されるたびに再利用されています。

もしPythonプロセスが「メモリのスナップショットからの復元」を開始した場合、ハッシュシードの乱数化に関して、本来は毎回ランダム化されるべき値が、決定的な値となり、すべてのスナップショットで共有されることになります。Hood氏は、この状況が、特にライブラリやプログラムにおいて、既存の利用方法を阻害する可能性があると指摘しました。

Hood氏は、Pythonの言語モデルに「初期化フェーズ」という概念を追加することを提案しました。これは、RPythonやSPyといった、すでに「初期化フェーズへの明示的なエントリポイント」を導入している実行環境を参考にしています。これらのエントリポイントは、それぞれ、コードの不要な部分の削除や、事前評価に役立ちますが、CPythonへのメモリのスナップショット機能の追加に関連する問題を解決するために再利用できる可能性があります。この初期化フェーズは、再初期化時に再度呼び出すことができ、これにより、実行環境やサードパーティのライブラリが、メモリのスナップショット機能が実装された場合でも、プログラムに安全にランダム性を再導入することができます。

議論

Stefan Behnel氏は、Pythonがすでに提供している、同様の機能について言及しました。それは、atexitモジュールで、Pythonプロセスが終了する際に呼び出されるコールバック関数を登録するためのAPIを提供しています。Stefan氏は、再初期化コールバック関数が、Hood氏が説明したニーズを満たすことができるかどうか疑問に思いました。

Peter Bierma氏は、初期化時間の改善が、起動時間を十分に短縮し、結果的にスナップショット機能が不要になる可能性があるかどうか疑問に思いました。Hood氏は、Pythonの初期化には、モジュールのインポートなど、「膨大な作業」があり、初期化を高速化するための提案は、「メモリのスナップショットと再初期化よりも、桁違いに複雑」であると答えました。「私はスナップショットの実装方法を知っていますが、インタプリタを高速化する方法は知りません。」

トーマス・ワウターズ氏は、PEP 810で定義され、Python 3.15で導入される「遅延インポート」が、初期化の遅延といった問題の一部を解決できるのではないかと考えていました。しかし、フッド氏は慎重な姿勢を示し、遅延インポートが、特にPythonにメモリのスナップショット機能が実装された場合、特定のユースケースにおいて「状況を悪化させる可能性がある」と述べました。遅延インポートとは、インポートされたモジュールが、そのモジュールのメンバーがアクセスされるまで解析および実行されない仕組みです。したがって、特定のPythonプログラムの実行においてインポートが不要な場合、そのコストはインタプリタによって支払われません。

一方、スナップショットは、すべてのインポートを解析および実行するコストを一度だけ支払い、状態を保存し、その後、このプロセスをスキップするために、将来の実行で「復元」することで、パフォーマンスに貢献します。しかし、インポートが初期段階で解決されない場合(例えば、遅延インポートが使用されている場合)、インポートはスナップショットに保存されず、メモリのスナップショット機能が実装されていても、各Python実行でそのコストが支払われることになります。

フッド氏は、自身が関心を持っているユースケースについて、いくつかの詳細を共有しました。それは、PythonをWebAssemblyやエッジコンピューティングサービスで使用することです。エッジコンピューティングサービスは、実行時にファイルシステムにアクセスできない場合がありますが、これは遅延インポートが必要とする条件です。

会話は、モジュールのフリーズ(固定化)という話題に移りました。パブロ・ガリンド・サルガド氏は、アリババで行われた、メモリダンプとメタデータテーブルを使用したスナップショットに関する先行技術を引用しました。Faster CPythonチームの経験から、グイド・ヴァン・ロッサム氏は、モジュールの深いフリーズが「起動時間にそれほど影響を与えない」一方で、複雑さを増すため、「チームの観点からは、それほど価値がない」と述べました。チームは、「なぜそれが起動時間に影響を与えないのかを深く調べる時間」をあまり費やしませんでした。

エリック・スノウ氏は、別の検討されたアプローチとして、オブジェクトのメモリをフリーズし、「コピーオンライト(COW)の一種」を導入することを提案しました。これは、多くのオブジェクトが変更されないという前提に基づいているため、データのコピーを回避できる可能性があるからです。