LLVM & Python
llvmlite
每個版本的 llvmlite 都會綁定一個版本的 LLVM, 所以需要安裝對應版本的 LLVM 才能順利編譯。
LLVM 的 C++ API 變化很大, 並且持續在每版發生, 造成基於這之上的 Binding 都要不斷地追著新版的 C++ API 跑, 無法穩定。
於是一些 Numba 的開發者們決定開發 llvmlite 作為新的 Binding, 這些開發者大部份也是 llvmpy 的開發者, 因此 llvmlite 解決了 llvmpy 先前碰到的一些問題。
llvmlite 使用 C API, C API 是設計給外部使用的比較穩定。 IR 部份 llvmlite 則採用純 Python 的 IR builder 來避免多次的 FFI calls (原先 llvmpy 多次 FFI 的方式很慢), IR 建完後再傳給 LLVM IR parser 處理, 而 LLVM IR 的文字表示是比較穩定的表示法。
安裝 llvmlite
事先需確認系統已安裝對應的 LLVM 版本
git clone https://github.com/numba/llvmlite
cd llvmlite
python setup.py build
python runtests.py
python setup.py install
練習
Example 1
int sum(int a, int b) {
return a + b;
}
from ctypes import CFUNCTYPE, c_int
import llvmlite.ir as ll
import llvmlite.binding as llvm
########################################
# 說明
########################################
# 以下將藉由 llvmlite 實作出這個簡單的 C Function:
#
# int sum(int a, int b) {
# return a + b;
# }
# 內容修改自
# http://eli.thegreenplace.net/2015/building-and-using-llvmlite-a-basic-example
########################################
# 建立 LLVM IR
########################################
# 建立 Module 和 Function Prototype
module = ll.Module()
func_ty = ll.FunctionType(ll.IntType(32), [ll.IntType(32), ll.IntType(32)])
func = ll.Function(module, func_ty, name='sum')
func.args[0].name = 'a'
func.args[1].name = 'b'
# 實作 Function
bb_entry = func.append_basic_block('entry')
irbuilder = ll.IRBuilder(bb_entry)
tmp = irbuilder.add(func.args[0], func.args[1])
ret = irbuilder.ret(tmp)
# 輸出產生的 LLVM IR
print('=== LLVM IR')
print(module)
########################################
# LLVM IR 轉成 Binary
########################################
# Code Generation 相關的初始化
llvm.initialize()
llvm.initialize_native_target()
llvm.initialize_native_asmprinter()
# 把 LLVM IR 從可讀的文字 (.ll) 轉換成 in-memory 的形式
# (LLVM 對程式碼有三種等價的表示方式,
# 分別為 in-memory、bitcode(.bc)、可讀的組語(.ll))
llvm_module = llvm.parse_assembly(str(module))
# 取得 Host 的相關資訊
tm = llvm.Target.from_default_triple().create_target_machine()
# 利用 MCJIT 把 module 編成 machine code
# (需先利用 create_mcjit_compiler 建立 Execution Engine)
with llvm.create_mcjit_compiler(llvm_module, tm) as ee:
# 確保該 Execution Engine 所擁有的所有 module 都已經處理過,
# 而且可以拿來執行
ee.finalize_object()
# 取得 JIT 後產生的 Function 的 Memory Address
cfptr = ee.get_pointer_to_function(llvm_module.get_function('sum'))
# 把 Memory Address 轉換成可以呼叫的 Python Function (利用 CFUNCTYPE)
cfunc = CFUNCTYPE(c_int, c_int, c_int)(cfptr)
# 輸出產生的組語
print('=== Assembly')
print(tm.emit_assembly(llvm_module))
# 開始使用 JIT 產生的 Function
res = cfunc(17, 42)
print('The result is', res)
Numba
Continuum 的 NumbaPro 已經被官方 Deprecated 了, 其中 Code Generation 的部份都被移進 Open Source 的 Numba, 而 CUDA library function 則被移進 Accelerate (也包含 Intel MKL 的一些功能)。
from numba import jit
@jit
def f(a, b):
return a + b
print(f)
print(f.signatures)
print(f.nopython_signatures)
f(1, 2)
print(f.signatures)
print(f.nopython_signatures)
f(0.1, 2)
print(f.signatures)
print(f.nopython_signatures)
f(0.1, 0.2)
print(f.signatures)
print(f.nopython_signatures)
f.inspect_types()
CPUDispatcher(<function f at 0x7f3e3df17048>)
[]
[]
[(int64, int64)]
[(int64, int64) -> int64]
[(int64, int64), (float64, int64)]
[(int64, int64) -> int64, (float64, int64) -> float64]
[(int64, int64), (float64, int64), (float64, float64)]
[(int64, int64) -> int64, (float64, int64) -> float64, (float64, float64) -> float64]
...
f (float64, float64)
--------------------------------------------------------------------------------
# File: simple.py
# --- LINE 6 ---
@jit
# --- LINE 7 ---
def f(a, b):
# --- LINE 8 ---
# label 0
# a = arg(0, name=a) :: float64
# b = arg(1, name=b) :: float64
# $0.3 = a + b :: float64
# del b
# del a
# $0.4 = cast(value=$0.3) :: float64
# del $0.3
# return $0.4
return a + b