Cython(5) 円周率の計算速度をCと比較


2022年 07月 21日

マーダヴァ・ライプニッツの円周率の公式を、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の開発者で、オープンソース界の著名人であるラリー・ウォールの言葉である。