Cinder は、Meta の内部パフォーマンス重視の CPython 3.10 製品バージョンです。これには、バイトコード インライン キャッシュ、コルーチンの積極的評価、メソッドアットアタイム JIT、および型注釈を使用して JIT でのパフォーマンスが向上する型に特化したバイトコードを出力する実験的なバイトコード コンパイラなど、多数のパフォーマンスの最適化が含まれています。
Cinder は Instagram の始まりであり、Meta のますます多くの Python アプリケーションで使用されるようになりました。
CPython の詳細については、 README.cpython.rst
参照してください。
短い答え: いいえ。
この作業の一部を CPython にアップストリームする可能性についての会話を促進し、CPython のパフォーマンスに取り組む人々の間での重複作業を減らすために、Cinder を一般公開しました。
Cinder は、他人が使用できるように洗練されておらず、文書化されていません。私たちは、これが CPython の代替となることを望んでいません。このコードを利用可能にする際の目標は、統合された高速な CPython です。そのため、実稼働環境で Cinder を実行しますが、そうすることを選択した場合は、お客様ご自身で実行してください。外部のバグ レポートの修正やプル リクエストのレビューに取り組むことはできません。当社は、Cinder が実稼働ワークロードに対して十分に安定しており、高速であることを確認していますが、外部ワークロードやユースケースに対するその安定性、正確性、パフォーマンスについては保証しません。
ただし、動的言語ランタイムの経験があり、Cinder を高速化するアイデアがある場合は、または、CPython に取り組んでいて、CPython を改善するためのインスピレーションとして Cinder を使用したい (または、Cinder から CPython への上流部分を支援したい) 場合は、ご連絡ください。ぜひチャットしたいです!
Cinder は CPython と同じように構築する必要があります。 configure
てmake -j
。ただし、Cinder のほとんどの開発と使用は Meta の非常に特殊なコンテキストで行われるため、他の環境ではあまり実行しません。したがって、Cinder を構築して実行する最も信頼性の高い方法は、GitHub CI ワークフローから Docker ベースのセットアップを再利用することです。
自分で構築せずに動作する Cinder を入手したいだけの場合は、ランタイム Docker イメージが最も簡単です (リポジトリのクローンは必要ありません!)。
docker run -it --rm ghcr.io/facebookincubator/cinder-runtime:cinder-3.10
自分で構築したい場合:
git clone https://github.com/facebookincubator/cinder
docker run -v "$PWD/cinder:/vol" -w /vol -it --rm ghcr.io/facebookincubator/cinder/python-build-env:latest bash
./configure && make
Cinder は Linux x64 上でのみビルドまたはテストされることに注意してください。それ以外のもの (macOS を含む) はおそらく動作しません。上の Docker イメージは Fedora Linux ベースで、Cinder リポジトリの Docker 仕様ファイル.github/workflows/python-build-env/Dockerfile
から構築されています。
興味深いかもしれない新しいテスト対象がいくつかあります。 make testcinder
開発環境で問題となるいくつかのテストをスキップすることを除けば、 make test
とほぼ同じです。 make testcinder_jit
JIT を完全に有効にしてテスト スイートを実行するため、すべての関数が JIT 化されます。 make testruntime
JIT の一連の C++ gtest 単体テストを実行します。そして、 make test_strict_module
strict モジュールのテスト スイートを実行します (以下を参照)。
これらの手順では、PGO/LTO 最適化が有効になっていない Cinder Python バイナリが生成されるため、これらの手順を使用して Python ワークロードの高速化を期待しないでください。
Cinder Explorer は、Cinder が Python コードをソースからアセンブリまでコンパイルする方法を確認できるライブ プレイグラウンドです。ぜひ試してみてください。機能リクエストやバグレポートをお気軽に提出してください。 Cinder Explorer は、残りの部分と同様に、ベストエフォート ベースで「サポート」されることに留意してください。
Instagram はマルチプロセス Web サーバー アーキテクチャを使用しています。親プロセスが開始され、初期化作業 (コードのロードなど) が実行され、クライアント要求を処理するために数十のワーカー プロセスがフォークされます。ワーカー プロセスは、さまざまな理由 (メモリ リーク、コードのデプロイメントなど) で定期的に再起動され、寿命は比較的短いです。このモデルでは、オブジェクトの参照カウントが変更されると、OS は親プロセスに割り当てられたオブジェクトを含むページ全体をコピーする必要があります。実際には、親プロセスに割り当てられたオブジェクトはワーカーよりも長く存続します。それらの参照カウントに関連する作業はすべて不要です。
Instagram には非常に大規模な Python コードベースがあり、存続期間の長いオブジェクトの参照カウントによるコピーオンライトによるオーバーヘッドが重大であることが判明しました。私たちは、参照カウントからオブジェクトをオプトアウトする方法を提供する「不滅インスタンス」と呼ばれるソリューションを開発しました。詳細については、「Include/object.h」を参照してください。この機能は Py_IMMORTAL_INSTANCES を定義することで制御され、Cinder ではデフォルトで有効になります。これは本番環境では大きな成果 (~5%) でしたが、直線コードの速度が遅くなります。参照カウント操作は頻繁に発生するため、この機能が有効になっている場合は、オブジェクトが参照カウントに参加しているかどうかを確認する必要があります。
「シャドウコード」または「シャドウ バイトコード」は、特殊なインタプリタの実装です。汎用 Python オペコードの実行における特定の最適化可能なケースを監視し、(ホット関数の場合) それらのオペコードを特殊なバージョンに動的に置き換えます。シャドウコードのコアはShadowcode/shadowcode.c
にありますが、特殊なバイトコードの実装は残りの eval ループとともにPython/ceval.c
にあります。 Shadowcode 固有のテストはLib/test/test_shadowcode.py
にあります。
これは、CPython 3.11 に組み込まれる特殊な適応型インタプリタ (PEP-659) と精神的に似ています。
Instagram サーバーは非同期負荷の高いワークロードであり、Web リクエストごとに数十万の非同期タスクがトリガーされる可能性があり、その多くは (メモ化された値のおかげで) 一時停止することなく完了できます。
私たちは、vectorcall プロトコルを拡張して、呼び出し元がこの呼び出しをすぐに待っていることを示す新しいフラグCi_Py_AWAITED_CALL_MARKER
を渡しました。
すぐに待機される非同期関数呼び出しで使用すると、呼び出された関数を完了するまで、または最初の一時停止に至るまで、すぐに (積極的に) 評価できます。関数が中断せずに完了した場合は、追加のヒープ割り当てなしで、すぐに値を返すことができます。
async Gather と併用すると、渡された awaitable のセットを即座に (積極的に) 評価できるため、同期的に完了できるコルーチン、完了した Future、メモ化された値などの複数のタスクの作成とスケジュールのコストを回避できる可能性があります。
これらの最適化により、CPU 効率が大幅 (約 5%) 向上しました。
これは主にPython/ceval.c
で新しい VectorCall フラグCi_Py_AWAITED_CALL_MARKER
を介して実装されており、呼び出し元がこの呼び出しを直ちに待っていることを示します。 IS_AWAITED()
マクロとこの Vectorcall フラグの使用法を探してください。
Cinder JIT は、C++ で実装されたメソッドごとのカスタム JIT です。これは、 -X jit
フラグまたはPYTHONJIT=1
環境変数によって有効になります。ほぼすべての Python オペコードをサポートしており、多くの Python パフォーマンス ベンチマークで 1.5 ~ 4 倍の速度向上を達成できます。
デフォルトでは、有効にすると、呼び出されるすべての関数が JIT コンパイルされます。これにより、めったに呼び出されない関数の JIT コンパイルのオーバーヘッドにより、プログラムが高速化されるのではなく、遅くなる可能性があります。オプション-X jit-list-file=/path/to/jitlist.txt
またはPYTHONJITLISTFILE=/path/to/jitlist.txt
は、完全修飾関数名 ( path.to.module:funcname
の形式) を含むテキスト ファイルを指すことができます。 path.to.module:funcname
またはpath.to.module:ClassName.method_name
)、1 行に 1 つ、JIT コンパイルする必要があります。このオプションは、運用プロファイリング データから派生したホット関数のセットのみをコンパイルするために使用します。 (JIT のより一般的なアプローチは、関数が頻繁に呼び出されることが確認されたときに関数を動的にコンパイルすることです。私たちの運用アーキテクチャはフォーク前の Web サーバーであるため、これを実装する価値はまだありません。メモリ共有の理由として、ワーカーがフォークされる前の初期プロセスですべての JIT コンパイルを前もって実行したいと考えています。つまり、どの関数を JIT コンパイルするかを決定する前に、プロセス中のワークロードを観察することができません。)
JIT はJit/
ディレクトリに存在し、その C++ テストはRuntimeTests/
に存在します (これらをmake testruntime
で実行します)。 Lib/test/test_cinderjit.py
には、そのための Python テストもいくつかあります。 make testcinder_jit
を介して JIT で CPython テスト スイート全体を実行するため、これらは網羅的なものではありません。これらは、CPython テスト スイートでは見つからない JIT のエッジ ケースをカバーします。
JIT の動作に影響を与えるその他の-X
オプションと環境変数については、 Jit/pyjit.cpp
参照してください。このファイルには、Python コードにいくつかの JIT ユーティリティを公開するcinderjit
モジュールも定義されています (例: 特定の関数のコンパイルの強制、関数がコンパイルされているかどうかの確認、JIT の無効化など)。 cinderjit.disable()
今後のコンパイルを無効にするだけであることに注意してください。すべての既知の関数を即座にコンパイルし、既存の JIT コンパイル関数を保持します。
JIT は、まず Python バイトコードを高レベル中間表現 (HIR) に下げます。これはJit/hir/
に実装されています。 HIR は Python バイトコードにかなり密接にマップしますが、スタック マシンではなくレジスタ マシンであり、少し低レベルであり、型付けされており、Python バイトコードによって隠蔽されているがパフォーマンスにとって重要な詳細 (特に参照カウント) は次のとおりです。 HIR で明示的に公開されます。 HIR は SSA 形式に変換され、いくつかの最適化パスが実行され、HIR オペコードの refcount とメモリ効果に関するメタデータに従って参照カウント操作が自動的に挿入されます。
その後、HIR は、アセンブリを抽象化した低レベル中間表現 (LIR) に引き下げられ、 Jit/lir/
で実装されます。 LIR ではレジスタ割り当てを行い、いくつかの追加の最適化をパスし、最後に優れた asmjit ライブラリを使用して LIR をアセンブリ ( Jit/codegen/
内) に下げます。
JIT は初期段階にあります。すでにインタプリタ ループのオーバーヘッドを排除でき、多くの関数のパフォーマンスが大幅に向上していますが、私たちは可能な最適化の表面をなぞり始めたにすぎません。一般的なコンパイラ最適化の多くはまだ実装されていません。最適化の優先順位は、主に Instagram の制作ワークロードの特性によって決まります。
厳密なモジュールは、いくつかのものが 1 つにまとめられたものです。
1. モジュールの最上位コードを実行すると、そのモジュールの外部に副作用が発生しないことを検証できる静的アナライザー。
2. Python のデフォルトのモジュール タイプの代わりに使用できる不変のStrictModule
タイプ。
3. Python モジュール ローダーは、厳密モードにオプトインしたモジュール (モジュールの先頭のimport __strict__
経由) を認識し、それらを分析してインポートの副作用がないことを検証し、それらをStrictModule
オブジェクトとしてsys.modules
に設定できます。
静的 Python は、型注釈を利用して、型に特化した型チェックされた Python バイトコードを出力するバイトコード コンパイラーです。 Cinder JIT と併用すると、多くの場合、MyPyC や Cython と同様のパフォーマンスを実現しながら、純粋な Python 開発者エクスペリエンス (通常の Python 構文、追加のコンパイル手順なし) を提供できます。静的 Python と Cinder JIT は、Richards ベンチマークの型付きバージョンで標準 CPython の 18 倍のパフォーマンスを達成します。 Instagram では、運用環境で Static Python を使用して、パフォーマンスを低下させることなく、プライマリ Web サーバー コードベースのすべての Cython モジュールを置き換えることに成功しました。
静的 Python コンパイラは、Python 3 の標準ライブラリから削除され、その後外部で保守および更新されてきた Python compiler
モジュールの上に構築されています。このコンパイラはLib/compiler
の Cinder に組み込まれています。静的 Python コンパイラーはLib/compiler/static/
に実装されており、そのテストはLib/test/test_compiler/test_static.py
にあります。
静的 Python モジュールで定義されたクラスには、( __init__
での型付きクラス属性と注釈付き割り当ての検査に基づいて) 型付きスロットが自動的に与えられ、これらの型のインスタンスに対する属性のロードとストアでは、新しいSTORE_FIELD
およびLOAD_FIELD
オペコードが使用され、JIT では直接コードになります。 LOAD_ATTR
またはSTORE_ATTR
の間接的な操作を行わずに、オブジェクト内の固定メモリ オフセットからロード/ストアします。クラスは、以下で説明するINVOKE_*
オペコードで使用するために、メソッドの vtable も取得します。これらの機能のランタイム サポートはStaticPython/classloader.h
およびStaticPython/classloader.c
にあります。
静的 Python 関数は、指定された引数の型が型注釈と一致するかどうかをチェックする非表示のプロローグで始まり、一致しない場合はTypeError
発生させます。静的 Python 関数から別の静的 Python 関数への呼び出しでは、このオペコードがスキップされます (型はコンパイラーによってすでに検証されているため)。静的呼び出しから静的呼び出しにより、典型的な Python 関数呼び出しのオーバーヘッドの多くを回避することもできます。呼び出された関数またはメソッドに関するメタデータを伴うINVOKE_FUNCTION
またはINVOKE_METHOD
オペコードを発行します。これに加えて、オプションで不変モジュール ( StrictModule
経由) と型 ( cinder.freeze_type()
経由。現在、インポート ローダーの厳密な静的モジュールのすべての型に適用されますが、将来的には静的 Python の固有の部分になる可能性があります) をコンパイルします。呼び出し先の署名に関する -time の知識により、(JIT 内で) 多くの Python 関数呼び出しを、C 言語よりもわずかに多くのオーバーヘッドで、x64 呼び出し規約を使用した固定メモリ アドレスへの直接呼び出しに変換できます。関数呼び出し。
静的 Python は依然として段階的に型付けされており、部分的にのみ注釈が付けられているコードや、通常の Python の動的動作にフォールバックすることで未知の型を使用するコードをサポートします。場合によっては (静的に不明な型の値が return アノテーションを持つ関数から返された場合など)、ランタイム型が予期される型と一致しない場合にTypeError
発生させるランタイムCAST
オペコードが挿入されます。
静的 Python は、マシン整数、ブール、倍精度、ベクトル/配列の新しい型もサポートします。 JIT では、これらはボックス化されていない値として処理され、たとえばプリミティブ整数演算により Python のオーバーヘッドがすべて回避されます。組み込み型の一部の操作 (リストまたは辞書の添字、またはlen()
など) も最適化されます。
Cinder は、静的モジュールを自動的に検出し、クロスモジュール コンパイルで静的モジュールとしてロードできる厳密/静的モジュール ローダーを介して静的モジュールの段階的な導入をサポートします。ローダーは、ファイルの先頭にあるimport __static__
およびimport __strict__
アノテーションを探し、モジュールを適切にコンパイルします。ローダーを有効にするには、次の 3 つのオプションのいずれかがあります。
1. from cinderx.compiler.strict.loader import install; install()
。
PYTHONINSTALLSTRICTLOADER=1
設定します。./python -X install-strict-loader application.py
を実行します。あるいは、 ./python -m compiler --static some_module.py
使用してすべてのコードを静的にコンパイルすることもできます。これにより、モジュールが静的 Python としてコンパイルされ、実行されます。
詳細なドキュメントについては、 CinderDoc/static_python.rst
参照してください。