CPythonのぺーパーホールオプティマイザは、無条件ジャンプ(JUMP_ABSOLUTE、JUMP_FORWARD、RETURN_VALUE、RAISE_VARARGS)の後に続く命令のシーケンスをスキャンし、他のブランチからのエントリーポイントがない到達不可能なブロックを見つけます。識別されると、これらのデッドインストラクションを削除してキャッシュの圧力を軽減し、命令の密度を改善します。
Pythonの例外処理テーブル、ループ構造、および条件付きジャンプは、コードオブジェクトの**co_codeシーケンスへの絶対バイトオフセットとしてターゲットロケーションを格納しているため、オプティマイザは生存している各命令の前に削除されたバイト数を追跡する再配置マップを構築する必要があります。その後、すべてのジャンプ命令と例外ハンドラの範囲を反復処理し、ターゲット位置での累積削除数を引き算することでターゲットオフセットを調整します。これにより、SETUP_FINALLYブロック、FOR_ITER**ループ、ユーザー定義のジャンプが、前のバイトコードが圧縮された後でも正しいオペコードに到達できるようになります。
データパイプラインチームは、ETLユーティリティのスタートアップスクリプトに**if DEBUG:フラグで保護された広範なデバッグロギングブロックが含まれていることに気付きました。このDEBUGはFalseに設定されたモジュールレベルの定数でした。条件が静的に偽であるにもかかわらず、コンパイルされたバイトコードには依然としてロギングロジックが含まれており、.pyc**ファイルサイズは40%増加し、プロダクションサーバーでの命令キャッシュのローカリティがわずかに低下しました。
彼らは3つの異なるアプローチを評価しました。
最初に、彼らはデプロイ前にデバッグコードを削除するためにCプリプロセッサやJinja2テンプレートを使用することを考えました。このアプローチは、プロダクション環境でゼロのデバッグバイトコードを保証しますが、複雑なビルドステップの依存関係を導入し、開発コードベースとプロダクションコードベースの間で微妙な乖離のリスクが生じ、実行中のバイトコードと一致しないソースコードのデバッグが複雑になりました。
次に、彼らはすべてのデバッグブロックをサブモジュール内の別々の関数にリファクタリングすることを評価しましたが、Pythonのインポートシステムはモジュール全体を一度にコンパイルし、呼び出されない関数もモジュールの辞書にコードオブジェクトとして残ります。ぺーパーホールオプティマイザは手続き間のデッドコードの排除を行わないため、バイトコードのサイズは変更されませんでした。
第三に、彼らはCPythonのコンパイルパイプラインを調査し、ぺーパーホールオプティマイザが**if False:構文の後のコードを自動的に削除することを発見しました。なぜなら、コンパイラがブロックの周りに無条件ジャンプを生成し、ぺーパーホールパスが到達不可能な尾を削除するからです。disモジュールを使ってRETURN_VALUEまたはJUMP_FORWARDの後にデッドコードが無いことを確認することで、最適化がアクティブであることを確認しました。彼らはこの組み込みメカニズムに頼ることを選び、DEBUGが実行時に計算された変数ではなくリテラルのFalse**であることを確認し、追加のツールなしでコンパイルされたバイトコードのサイズを35%削減しました。
ぺーパーホールオプティマイザが、前のジャンプターゲットが計算されたジャンプ命令によってアドレス指定されているときに到達不可能なコードを削除しないのはなぜですか?
計算されたジャンプは、スタック上の値に基づいてランタイムでその宛先を決定します。例えば、**MATCH**文や動的ディスパッチパターンのようなものです。オプティマイザは静的にどのオフセットがターゲットになり得るかを知ることができないため、すべての命令がエントリーポイントである可能性を考慮して、慎重に仮定しなければなりません。そのため、無条件ジャンプや制御フローグラフの静的分析によって明らかに到達不可能と証明されているコードのみが削除され、動的ディスパッチのターゲットになる可能性のあるブロックは保存されます。
オプティマイザは、ジャンププレースホルダーとして使用されるNOP命令を削除する際に、例外ハンドラテーブル(co_exceptiontable)をどのように扱いますか?
コンパイラはまだ知られていない前方の位置にジャンプを生成する場合、しばしばプレースホルダーやパディングとして**NOP(無操作)命令を発行し、後でジャンプターゲットを修正します。ぺーパーホールオプティマイゼーション中に、これらのNOPが削除されてスペースが節約されます。オプティマイザは、元のオフセットと最終オフセットとの双方向マッピングを維持します。例外テーブルを処理するときには、try/exceptブロックのstart、end、およびhandlerオフセットを格納し、削除されたバイトの累積デルタを各エントリに適用します。NOPが例外範囲内に存在する場合、その削除はend**オフセットを左にシフトさせ、保護されたバイトコード範囲が正確なまま残り、例外が正しい境界で捕捉されることを保証します。
ぺーパーホールオプティマイザがCコンパイラのようにパイプライン効率を向上させるために独立命令を再配置するのを妨げるのは何ですか?
Pythonのバイトコードは、トレースバック生成に使用される評価スタックのセマンティクスおよび行番号テーブルに密接に結びついています。命令を再配置すると、例えば**LOAD_CONSTをLOAD_NAMEの前に移動すると、例外が発生した際のスタックの状態が変わり、トレースバックで報告される行番号が変更されたり、インタープリタループによって必要とされるスタック深さの不変条件が破られたりする可能性があります。さらに、Pythonはフレームオブジェクトのイントロスペクションやf_lasti**(命令ポインタ)を許可しているため、任意の再配置は、決定論的なオフセットからソースへのマッピングに依存するデバッガやプロファイラを破壊する可能性があります。したがって、オプティマイザは到達不可能なコードを削除し、ジャンプをリダイレクトすることに制限され、実行可能な命令の相対的な順序を変更することはありません。