<<追記>>
いろいろ試した結果、ひとつの文をコンパイルし終わるたびにtcc_deleteを呼び出して、一回 ライブラリをリセットしてやらないといけないみたい。
いろいろ試した結果、ひとつの文をコンパイルし終わるたびにtcc_deleteを呼び出して、一回 ライブラリをリセットしてやらないといけないみたい。
今回のコードだと使いにくいので、あとで書き直しますー
この記事の続きです
tccのインストール
今回はPythonからlibtccを呼び出し、C言語のソースプログラムをネイティブの機械語にコンパイルして実行するためのラッパークラスを書きます。(win32用)
こちらのモジュールを参考にしました。
Cinpy – C in Python
方法としては二つ考えられまして
PythonからC言語ソースをJITコンパイラで実行できると、ものによっては、かなり高速化できるのでは、と期待。
-PythonからC言語の関数に文字列を渡して実行。
主な処理はC言語で書く (こっちも書きました)
-Python側でlibtccの関数を実行する
今回は、2番目の方法にしました。
そのほうが、クラスなども使えますし、多分、細かいところを改良しやすいんじゃなかろうか、という読みです。
tccでC言語ソースプログラムをコンパイルしてC関数を取得する場合、ヒープ操作(malloc)が必要になります。
まずは、tccのライブラリのmallocをPythonから使用できるようにします。
以下を mem.c として保存。
<<少し修正しました>>
//+---------------------------------------------------------------------------
//
// mem.c - memory manipulate function for DLL
//
#include <stdio.h>
#include <stdlib.h>
void* _calloc(size_t x, size_t y){ return calloc(x,y); }
void* _malloc(size_t x){ return malloc( x );}
void* _realloc(void* x, size_t y){ return realloc(x,y ); }
void _free(void* x){ return free(x);}
コマンドラインにて、tccでDLLを作成。
tcc -shared -rdynamic mem.c
mem.dllが出来ているはず。
これはPythonプログラムから使用します。
以下を libtcc.py として保存
#!/usr/local/bin/python
# -*- coding: utf-8 -*-
import ctypes,os
LIB_FLAG=True
class Tcc(object):
_libtcc=None
_libmem=None #tccのmalloc関数
def __init__(self,libtcc="libtcc.dll",mem="mem.dll"):
self.memlist=[]
if not Tcc._libtcc:
Tcc._libtcc=ctypes.cdll.LoadLibrary(libtcc)
if not Tcc._libmem:
Tcc._libmem=ctypes.cdll.LoadLibrary(mem)
_libtcc=Tcc._libtcc
stat=self.tccstate=_libtcc.tcc_new()
_libtcc.tcc_set_output_type(stat,0)
#カレントフォルダ、モジュールの置かれたフォルダを
#ライブラリパスに追加
if LIB_FLAG:
#add libpath
_libtcc.tcc_add_library_path(stat,os.getcwd())
if __name__!="__main__":
_libtcc.tcc_add_library_path(os.path.split(__file__)[0])
#ライブラリ関連関数は まだ 未テスト。 あとで調べます
def add_library_path(self,library_path):
u"-L オプション相当。ライブラリ検索パスを追加"
return self._libtcc.tcc_add_library_path(self.tccstate,library_path)
def add_library_path(self,library_name):
u"-l オプション相当。 使用する外部ライブラリを追加"
return self._libtcc.tcc_add_library(self.tccstate,library_name)
def eval(self,func_name,cfunctype,csrc):
lib=self._libtcc
stat=self.tccstate
lib.tcc_compile_string(stat,csrc)
memsz=lib.tcc_relocate(stat,None)
if memsz :
mem=self._libmem._malloc(memsz)
self.memlist.append(mem)
lib.tcc_relocate(stat,mem)
retp=lib.tcc_get_symbol(stat,func_name)
return cfunctype(retp)
def __del__(self):
self._libtcc.tcc_delete(self.tccstate)
#方針転換のためコメントアウト
#メモリを開放しなければ、JITコンパイラを終了しても
#引き続きC関数が使えるため。
#C関数のメモリはPythonインタプリタ終了時に開放される(はず)
#Python実行中に、mallocしたメモリを開放するには、別の機構が必要になります
#メモリクリーナー・クラス、とか、、、
if False :
for mem in self.memlist:
try:
self._libmem._free(mem)
except:
pass
#test
if __name__=="__main__":
csrc="""
int fib(n)
{
if (n <= 2)
return 1;
else
return fib(n-1) + fib(n-2);
}
"""
tcc=Tcc()
rettype=ctypes.c_long #C関数 戻り値の型
argtypes=[ctypes.c_long] #C関数 引数の型リスト
cprototype=ctypes.CFUNCTYPE(rettype,*argtypes) #関数プロトタイプ宣言
f=tcc.eval("fib",cprototype,csrc)
tcc=None #コンパイラ終了
result=[1,1, 1, 2, 3, 5,8,13,21,34,55,89,144]
for i,r in enumerate(result):
ret=f(i)
#print ret
assert ret==r
print "done"
その後調べた結果、ひとつの文をコンパイルするたびに
tcc=None
を実行してやらないといけないことが判明。あまりクラスにする意味がなかったかも。。。
PythonからC言語ソースをJITコンパイラで実行できると、ものによっては、かなり高速化できるのでは、と期待。
今後は、Blenderなどの組み込み環境でも動くのか、などなど、すこしずつ時間のあるときに調査したいと思います。