bytecodeとは? ソースとハードウェアの間のレイヤー
bytecodeは、マシンコードよりコンパクトで移植性のある中間命令セットで、ネイティブのCPU命令の代わりに仮想マシンが実行します。
多くの言語はマシンコードへ直接コンパイルしません。代わりにbytecodeへコンパイルします。これは仮想マシンと呼ばれるruntimeによって実行されるよう設計された、低レベルでプラットフォーム非依存の形式です。Java、C#、Pythonはいずれもbytecodeを使います。これが「一度書けばどこでも動く」という仕掛けです。一度コンパイルすれば、そのbytecode向けのどのVMでも実行できます。
bytecodeの位置
bytecodeは、人間が読めるソースと生のマシン命令の間に位置します。あなたのソースよりマシンに近いものの、最終ステップをVMが担うため、OSやCPUアーキテクチャをまたいで移植できる程度には抽象的です。
そもそもなぜbytecodeを使うのか
- 移植性: 同じbytecodeが、対応するVMのあるどのプラットフォームでも動く。
- 毎回ソースを再parseするより速い読み込み。
- 言語が最適化の対象にできる安定したターゲット。
- ホットなコードパスのjust-in-timeコンパイルの基盤。
bytecode vs マシンコード
マシンコードは1つのCPUアーキテクチャに固有で、ハードウェア上で直接動きます。bytecodeは汎用的で、仮想マシンによって実行 (解釈またはさらにコンパイル) される必要があります。移植性のあるbytecodeと実際のプロセッサを橋渡しするのがVMです。
bytecodeがどう実行されるか
VMはbytecodeを命令ごとに解釈することも、just-in-timeコンパイラを使って頻繁に走るbytecodeをその場でネイティブコードへ翻訳することもできます。これが、長時間動くJavaや.NETのプログラムがウォームアップ後に速くなる理由です。
簡単な例
Pythonスクリプトを実行すると、インタプリタは各モジュールをbytecodeにコンパイルし、それを __pycache__ ディレクトリに .pyc ファイルとしてcacheすることがあります。次の実行では、ソースの再parseを省いてbytecodeを直接読み込めます。
CIにおけるbytecode
buildステップは、後の段階が消費するbytecodeのartifact (JAR、DLL、cacheされた .pyc ファイル) を生成することがよくあります。これらをrunをまたいでcacheするとpipelineが速くなりますが、破損したり部分的なcache取得は紛らわしい失敗を招くことがあります。マネージドrunner (Latchkey) はcacheを温かく保ち、一時的なcacheやregistryの不調を自動でリトライします。
重要なポイント
- bytecodeは、CPUが直接ではなく仮想マシンが実行する、移植性のある中間形式です。
- 一度書けばどこでも動く移植性と、より速いプログラム読み込みを可能にします。
- VMはbytecodeを解釈するか、ホットなパスをネイティブコードへJITコンパイルします。