マーダヴァ・ライプニッツの円周率の公式を、PythonとCythonで実現したが、C言語ではどのように書けばよいのだろうか。
関数の宣言自体は以下のようになる。ファイル名は、Cythonで自動生成されるCのファイルと被らないように、最後に_cを追加した。
double pi_leibniz(int n) {
double sum = 0.0;
for( int k=1; k<n; k+=4 )
sum += 4.0/k - 4.0/(k+2);
return sum;
}
さて、この関数をPythonから呼びたいのだが、どうすれば良いだろうか?
普通は、CのプログラムをCythonでがっちりラッピングするだろう。つまり、Pythonから見ると、一度Cythonの関数を呼んで、そのCythonの関数がCの関数を呼び出す訳である。間にCythonをかませることで、とても自由度が高い。
しかし、今回はズルをしようと思う。
とりあえず、円周率の計算が、CythonとCとでどのくらい差があるかを見たいだけである。
PythonからCの関数を直接呼び出せるだろうか。Cの関数(を含むファイル)をコンパイルすると、.oができるが、これではPythonから呼び出せそうもない。
.soファイルができれば、もしかしてPythonから直接呼び出せるかもしれない。
今使っているのは、Linux上のCコンパイラであるgccである。これには、あまりにも膨大な量のオプションが指定できるのだが、.soというのはリンカーに関するオプション指定で何とかなるかも知れない。
gccのオンラインマニュアルのリンカー部分を調べると..
Linker Options
object-file-name -fuse-ld=linker -llibrary -nostartfiles
-nodefaultlibs -nolibc -nostdlib -e entry --entry=entry -pie
-pthread -r -rdynamic -s -static -static-pie -static-libgcc
-static-libstdc++ -static-libasan -static-libtsan -static-liblsan
-static-libubsan -shared -shared-libgcc -symbolic -T script
-Wl,option -Xlinker option -u symbol -z keyword
-shared というオプションがあるので、これを使ってみよう。
$ gcc -shared -o pi_leibniz.so pi_leibniz_c.c
$ ls pi_leibniz.so
pi_leibniz.so
どうやら、シェアードライブラリができた。
Pythonのドキュメントの中にctypes – Pythonのための外部関数ライブラリというページがあり、「このライブラリは C と互換性のあるデータ型を提供し、動的リンク/共有ライブラリ内の関数呼び出しを可能にします」という説明があるので、用意したCの関数を直接Pythonから呼び出せるかもしれない。
コンパイルしてできたのが”pi_euler.so” で、これをPythonの次の1行だけでロードできるようだ。
libpic = ctypes.cdll.LoadLibrary("./pi_leibniz.so")
あとは、いままでと同じようにimport して、呼び出せば動くかも知れない。
import ctypes
import time
import math
from pi_leibniz import pi_leibniz
libpic = ctypes.cdll.LoadLibrary("./pi_leibniz.so")
for m in range(1,10):
n = 10**m
tm_start = time.time()
euler = pi_leibniz(n)
tm_end = time.time()
print("{:12d}\t{}\t{:.16f}\t{:15.10f} sec".format(n,euler, euler-math.pi, tm_end-tm_start))
$ python3 pi_leibniz_c_test.py
10 2.9760461760461765 -0.1655464775436166 0.0000014305 sec
100 3.1215946525910097 -0.0199980009987835 0.0000009537 sec
1000 3.1395926555897824 -0.0019999980000107 0.0000014305 sec
10000 3.1413926535917893 -0.0001999999980038 0.0000083447 sec
100000 3.141572653589808 -0.0000199999999850 0.0000808239 sec
1000000 3.1415906535898936 -0.0000019999998995 0.0008053780 sec
10000000 3.14159245358981 -0.0000001999999832 0.0080292225 sec
100000000 3.1415926335405047 -0.0000000200492885 0.0579414368 sec
1000000000 3.1415926445762157 -0.0000000090135774 0.5647144318 sec
計算精度は、Cython版と全く同じである。
実行時間も殆ど同じになっている。ということは、この例では、CythonはCと同じ速度とみなして問題ないようだ。
今回は、PythonからCの関数を呼び出すのに、手抜きの方法で行ったので、その正当性をここに書いておこう。
プログラミングの世界では、プログラマの三大美徳というのがあり、怠惰(Laziness)、短気(Impatience)、傲慢(Hubris)のことである。今回はその中の怠惰を実践してみた。現実のプログラミングの世界では、こういうことがとても重要になる。
この言葉は、Perlの開発者で、オープンソース界の著名人であるラリー・ウォールの言葉である。