Numba のコードをちょっと読んだメモ
Numba は LLVM を使って Python のコードを JIT するライブラリ。
ちゃんと速い。https://gist.github.com/iwiwi/9228787711a353e115ffcdee21f1a882
@jit からコンパイル部分に到達するまで
@jit→decorators._jit→ dispatcher というものが返される- 適当に動かした例では、dispatcher は
registry.CPUDispatcherのようだ - こいつにはほぼ全く実装がない
- 基底クラスは
dispatcher.Dispatcher- こいつの
_compilerとかいかにもコンパイルしそう
- こいつの
- その基底クラスは
_DispatcherBase - その基底クラスは
_dispatcher.Dispatcherで C extension 内 __call__時に JIT してると思うけど、__call__は_dispatcher.Dispatcherのものが呼ばれているようだ_dispatcher.c内の関数Dispatcher_callに相当- (後で調べる①:どこで返り値の型を推定した? → 分かった:返り値の型はここでは推定する必要がなく引数の型だけで良い)
- (後で調べる②:なんで C extension に入ってるんだろう? → 速度のためだろうか・・・?)
_compile_for_argsは_DispatcherBaseで定義されており、self.compileを呼ぶcompileはDispatcherで定義されており、キャッシュ等を調べたあとself._compiler.compileを呼ぶ- 適当に動かした例では、
_compilerは_FunctionCompilerのようだ _FunctionCompiler.compileはcompiler.py内のcompile_extraを呼ぶcompiler.compile_extraはcompiler.Pipeline.complile_extraを呼ぶ- (見逃してなければ Python の関数はまだ Python の関数オブジェクトそのまま来てる)
compiler.Pipeline.complile_extraこの辺が重要そう。- こいつは Python 関数オブジェクトをバイトコード化し、それをコンパイルする
- ここから呼び出される
_compile_coreを見るとコンパイルのプロセスが書かれている
コンパイルのパイプライン
以下は nopython モードでのコンパイルのパイプライン
- Python bytecode を取り出す
- Python bytecode を Numba IR に変換
- Numba IR の後処理をする
- 型を推定・アノテートする
- LLVM IR を生成・コンパイル
1. Python bytecode を取り出す
bytecode.py内のget_code_objectで、関数.__code__で取り出す
2. Python bytecode を Numba IR に変換
interpreter.py内のInterpreterクラスにop_HOGE_PIYO関数が大量にある- Numba の IR は
ir.py内に定義されるもので、Interpreterはこれを生成する
3. Numba IR の後処理をする
postproc.py内のPostProcessor- ちゃんと読んでないけどあんまり長くないのであまり重要ではないと信じて進む
4. 型を推定・アノテートする
typeinfer.TypeInfererが作られるTypeInfererに色々な事前情報を与える(seed_ほげほげ)build_constraintで式等による伝搬ルールを列挙するpropagateで constraint による情報を伝播し型を決めていく(収束までループするアルゴリズム)
5. LLVM IR を生成・コンパイル
Pipeine.stage_nopython_backend→Pipeline._backend→targetctx.codegen()targetctxは多分CPUContextやCUDATargetContextCPUContext.codegenはJITCPUCodegenに行くJITCPUCodegenの基底クラスBaseCPUCodegenで LLVM が llvmlite 経由で呼ばれている_engineを見るとMCJITを使っていることが分かる
メモ
Python から LLVM 叩くならこいつ便利そう https://github.com/numba/llvmlite