※現在、ブログ記事を移行中のため一部表示が崩れる場合がございます。
順次修正対応にあたっておりますので何卒ご了承いただけますよう、お願い致します。

Cython(1) まず動かしてみよう


2022年 06月 21日

Pythonのプログラムをコンパイルして高速に動かすことができるようなので、まず超簡単なPythonのプログラムを用意してみよう。

ということで、とても短いPythonの関数を用意した。

# [Python]  1からnまでの和を求めるだけのテストプログラム
def sumtest(n):
	sum = 0
	for i in range(1,n+1):
    		sum += i
	return sum

とりあえず、この関数が正しいという動作チェックをしておこう。ここでは、ipython3 にて対話的に確認してみた。

In [1]: from sumtest import sumtest

In [2]: sumtest(100)                                                                     	 
Out[2]: 5050

Cythonのプログラムは拡張子をpyxにすることになっている。それで、sumtest.pyをコピーしてsumtest.pyxをつくり、コメントだけ、ちょっと変更してみた。

# [Cython]  1からnまでの和を求めるだけのテストプログラム
def sumtest(n):
	sum = 0
	for i in range(1,n+1):
    		sum += i
	return sum

Cythonのソースコードが用意できたので、さっそくコンパイルしてみよう。
コンパイル方法はいくつか用意されているが、何も覚えなくても済む楽な方法を紹介しようと思う。
それが、以下のsetup.pyを用意することである。

##  	python3 setup.py build_ext --inplace

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

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

1行目のコメントは、このプログラムの走らせ方のメモである。先頭の##を除いてコマンドとして入力すれば、コンパイルしてくれる。
2つのimportは、こう書く決まりだと思ってほしい。
最後のsetup関数が、実際にコンパイルするところで、cythonize関数に、Cythonのソースファイルを与える。今回は1つのファイルだが、複数のファイルをコンパイルしたいときには、並べて書けばよい。

とりあえずコンパイルするとこういう感じになる。

$ python3 setup.py build_ext --inplace
Compiling sumtest.pyx because it changed.
[1/1] Cythonizing sumtest.pyx
/home/fuji/.local/lib/python3.8/site-packages/Cython/Compiler/Main.py:346: FutureWarning: Cython directive 'language_level' not set, using '3str' for now (Py3). This has changed from earlier releases! File: /home/fuji/Study/Cython/TechBlog/sumtest.pyx
  tree = Parsing.p_module(s, pxd, full_module_name)
running build_ext
building 'sumtest' extension
x86_64-linux-gnu-gcc -pthread -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O2 -Wall -g -fstack-protector-strong -Wformat -Werror=format-security -g -fwrapv -O2 -g -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -fPIC -I/usr/include/python3.8 -c sumtest.c -o build/temp.linux-x86_64-3.8/sumtest.o
x86_64-linux-gnu-gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions -Wl,-Bsymbolic-functions -Wl,-z,relro -g -fwrapv -O2 -Wl,-Bsymbolic-functions -Wl,-z,relro -g -fwrapv -O2 -g -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 build/temp.linux-x86_64-3.8/sumtest.o -o /home/fuji/Study/Cython/TechBlog/sumtest.cpython-38-x86_64-linux-gnu.so
fuji@ryzen:~/Study/Cython/TechBlog$

いっぱい出てきたけれど、エラーらしきものは無いようなので、詳しく見なくても大丈夫です。
コンパイルが済んだから、現フォルダの中がどうなっているかを確認します。

$ ls -l sum*
-rw-rw-r-- 1 fuji fuji 208667  6月  2 19:24 sumtest.c
-rwxrwxr-x 1 fuji fuji 218912  6月  2 19:24 sumtest.cpython-38-x86_64-linux-gnu.so
-rw-rw-r-- 1 fuji fuji	169  5月 30 18:18 sumtest.py
-rw-rw-r-- 1 fuji fuji	169  5月 30 18:12 sumtest.pyx
-rw-rw-r-- 1 fuji fuji	252  5月 30 18:39 sumtestcheck.py
$ wc sumtest.c
  5451  17906 208667 sumtest.c

sumtest.cというCのソースファイルと、.soのついたファイルができている。
sumtest.cは、5451行もある長大なCのソースコードであり、とても人間が読めるようなものではない。万一予期しないCコードが生成されたのではないかと疑わしいときに確認するくらいである。サイズの表示(208667)はバイト単位である。
sumtest.cpython-38-x86_64-linux-gnu.soが出来上がったシェアードオブジェクト(so)で、ファイル名の中間にシステムに関する情報が挿入されている。とても短い関数をコンパイルしただけなのに、218KBもあるファイルができてしまったが、今どきのコンピュータには十分なメモリがあるはずなので、問題にはならないだろう。

コンパイルしてできた .so をインポートして動作確認してみよう。
まったく同じ方法で実行できるということなので、import し、関数を呼び出してみた。

In [1]: from sumtest import sumtest

In [2]: sumtest(100)
Out[2]: 5050

Pythonのときと全く同じですね。これでは、Pythonの関数が呼び出されたのか、Cythonの関数が呼び出された分からないですね。
次回は、そのあたりをきちんと調べようと思う。