Cython(2) 実行時間の比較をしたけれど


2022年 07月 05日

Python/Cython どちらが呼ばれるか?

PythonとCythonを同じモジュール名、関数名にしたとき、どちらがimportされ、どちらの関数が呼び出されるかを確認してみよう。そのために、Python/Cythonの関数の最初に、呼び出されたことを示すメッセージを出力するようにした。

# [Python]  1からnまでの和を求めるだけのテストプログラム
def sumtest(n):
	print("Python sumtest2.sumtest({}) が呼ばれた".format(n))
	sum = 0
	for i in range(1,n+1):
    	sum += i
	return sum
# [Cython]  1からnまでの和を求めるだけのテストプログラム
def sumtest(n):
	print("Cython sumtest2.sumtest({}) が呼ばれた".format(n))
	sum = 0
	for i in range(1,n+1):
    		sum += i
	return sum

setup.py に1行追加した。
このままだとsumtest.pyxとsumtest2.pyxの両方をコンパイルしそうだけれど、sumtest.pyxがコンパイル済みであると、無駄にコンパイルはしない。なので、適当にベタベタ並べて書いても、必要なだけしかコンパイルはしない。

##  compile command:
##  	python3 setup.py build_ext --inplace

from distutils.core import setup
from Cython.Build import cythonize

setup(ext_modules=cythonize(['sumtest.pyx']))
setup(ext_modules=cythonize(['sumtest2.pyx']))

さて、コンパイルする前に、実行してみよう。

In [1]: from sumtest2 import sumtest

In [2]: sumtest(100)
Python sumtest2.sumtest(100) が呼ばれた
Out[2]: 5050

確かに、Python版のsumtest関数が呼び出されているようだ。

ここで、コンパイルしてから同じ手順で実行してみよう。

In [1]: from sumtest2 import sumtest 

In [2]: sumtest(100) 
Cython sumtest2.sumtest(100) が呼ばれた
Out[2]: 5050

sumtest2.pyも存在するのであるが、sumest2.pyxをコンパイルしたシェアードオブジェクト(Cython版)の方が呼び出されていることが分かる。

このことは、何を意味しているだろうか。

同じモジュール名で、Pythonl版とCython版(.so)が混在する時、Cython版がインポートされるということである。
これは、Python版のソース(.py)はそのままにして、同一名のCythonソース(.pyx)を作り、それをコンパイルし、動作させるとCython版が動くということである。同じモジュール名の.pyと.soができたとき、.soの方が優先される。
実際のプログラム開発では、ファイル数が増えるが、この優先順位のおかげでPython版とCython版を同一名で済ますことができ、とても便利である。

実行時間の計測

さて、本題である実行時間の計測をしてみよう。
そのために、次のプログラムを用意した。

from sumtest2 import sumtest
import time

time_start = time.time()
n = 1000000      	# 100万までの和
s = sumtest(n)
time_end = time.time()
time_gap = time_end - time_start

print("sumtest({}) = {}\t経過時間 = {} 秒".format(n,s,time_gap))

まず、Python版が呼び出されるように、sumtest2の.cと.soを消去してから実行してみた。

$ python3 sumtestcheck.py
Python sumtest2.sumtest(1000000) が呼ばれた
sumtest(1000000) = 500000500000    経過時間 = 0.04223752021789551 秒

次に、コンパイルしてから、同じコマンドで実行してみた。

$ python3 sumtestcheck.py
Cython sumtest2.sumtest(1000000) が呼ばれた
sumtest(1000000) = 500000500000    経過時間 = 0.025923490524291992 秒

42ミリ秒が26ミリ秒になったので、速度が約1.6倍になった。でも、まだ100倍速には程遠いが、そのくらい高速にするにはどのように手を入れればよいのだろうか。(つづく)