introduction to cython
DESCRIPTION
PyCon APAC 2013発表資料 プログラミング言語 Cythonの紹介TRANSCRIPT
Cythonによる
拡張モジュール開発
2013/9/14 PyCon APAC 2013 Atsuo Ishimoto
自己紹介
2
いしもと
石本 敦夫 あつお
p 書籍「パーフェクトPython」 著者の一員
p http://www.gembook.org p python.jp ドメインの管理者 p @atsuoishimoto
Pythonの拡張モジュール
3
p 通常、C/C++で開発し、Pythonスクリプトから利用できる関数・データ型を提供
p Pythonが動的にロードする共有ライブラリ(*.so, *.pyd)
p 用途 l Pythonからは呼び出せないCライブラリへ
のインターフェース l Pythonではパフォーマンス不足
拡張モジュールのめんどくささ
4
C/C++が必要
/* 一般的なC言語の例 */ int i;main(){for(;i["]<i;++i){-‐-‐i;}"];read('-‐'-‐'-‐',i+++"hell\ o, world!\n",'/'/'/'));}read(j,i,p){write(j/p+p,i-‐-‐-‐j,i/i);}
The International Obfuscated C Code Contest http://www.ioccc.org/1984/anonymous.c
拡張モジュールのめんどくささ
5
関数・データ型定義がめんどう
static PyMemberDef xx_memberlist[] = { ... static PyGetSetDef xx_getsetlist[] = { ... static PyTypeObject xx_Type = { ... static PyMethodDef xx_methods[] = { ... static PyModuleDef xx_module = { ... PyMODINIT_FUNC PyInit_xx(void) { ...
拡張モジュールのめんどくささ
6
参照カウント管理
p Pythonのオブジェクトは、オブジェクトの被参照数を正確にカウントする必要がある
p PyObject_SetItem()、PyList_SetItem()、 PyList_SET_ITEM() の違いを覚えてますか?
p 間違えればメモリリークかコアダンプ
Cythonとは
7
Python専用プログラミング言語
p Pythonの拡張モジュールを開発するための専用プログラミング言語
p Pythonのほぼ上位互換 p インタープリタではなくコンパイラ p Python2/3対応 p http://www.cython.org
Cythonの起源
8
Pyrex
Cython
Fork
最終リリースは 2010/4/12
2002/4/3 version 0.1 リリース
Cythonを採用したプロジェクト
9
p lxml http://lxml.de/
p Sage http://www.sagemath.org/
p SciPy http://www.scipy.org/
p PyYAML https://bitbucket.org/xi/pyyaml
Pythonの構文で C言語と同じ処理を書ける
10
/* C言語 */ void spam() { void *p = malloc(100); if (!p) { return; } if (!ham()) { goto exit; } egg(); exit: free(p); }
# Cython def spam(): cdef void *p = malloc(100) if p: try: if not ham(): return egg() finally: free(p)
Cythonの文法
11
基本は
Python2 とほぼ同じ!
def qsort(L): if len(L) <= 1: return L return ( qsort([lt for lt in L[1:] if lt < L[0]]) + [L[0]] + qsort( [ge for ge in L[1:] if ge >= L[0]]))
http://code.activestate.com/recipes/66473-just-for-fun-quicksort-in-3-lines/
Cythonの関数・型定義
12
かんたん
cdef class Spam: cdef double attr1 cdef public double public_attr cdef readonly double public_attr2 def ham(self): return self.attr1
アーリーバインディング
13
変数の型宣言
も できる!
def spam(dict d): return len(d)
型チェックは静的・動的両方
def spam(dict d): cdef list L L = d # コンパイルエラーに # ならない
Cythonコードの最適化
14
cdef int i for i in range(100): …
cdef int i = 0 while i < 100: … i += 1
オブジェクトアクセスの最適化
15
Cythonコード 生成されるCコード
型宣言なし item = obj[n]
item = PyObject_GetItem( obj, n)
型宣言あり
cdef tuple obj cdef int n item = obj[n]
item = PyTuple_GET_ITEM( obj, n)
C/C++ライブラリの利用
16
# Python.h の PyMem_Malloc() を宣言 cdef extern from "Python.h" void* PyMem_Malloc(size_t n) def spam(): cdef void *p p = PyMem_Malloc(100) if not p: raise MemoryError()
ヘッダファイルから関数や構造体をインクルード
定義済みライブラリ
17
標準Cランタイム関数などは定義済み
from libc.math cimport sin def std_sin(x): return sin(x*x)
p 関数・構造体・定数などをCythonで定義してある
p cimport文でインポートするだけで利用可能
定義済みライブラリ(抜粋)
18
種類 モジュール名
Python API cpython.object PyObject_XXXの定義
python.dict PyDict_XXXの定義
…
C標準ライブラリ libc.stdio stdio.hで定義された関数
libc.stdlib stdlib.hで定義された関数
…
C++標準ライブラリ libcpp.list std::listの定義
libcpp.string std::string の定義
…
numpy numpy numpy API の定義
OpenMP openmp OpenMP API の定義
Posix標準ライブラリ posix.fcntl fcntl.hで定義された関数
…
C/C++のデータ型
19
cdef キーワードで変数宣言
def spam():
cdef double value value = 100.0 * 200 return value
ポインタや配列も
def spam(s):
cdef char p,*q p = s[0] q = &p return q[0] # *qは不可
自動型変換
20
def spam(n): cdef int number number = n
p Pythonオブジェクトを、Cのint型の値に変換
p 変換不能な場合は例外を送出
int number = PyInt_AS_LONG(n)
文字列の自動変換
21
def spam(L): cdef char *s = "ham" L.append(s)
p 文字列 s を Pythonのstrオブジェクト(Python3ではbytes)に変換
p strオブジェクト -> char * も変換される
GIL制御
22
言語としてGILをサポート
p GIL: Global Interpreter Lock
p Pythonスクリプトが、複数のスレッドで同時に実行されないように制御する仕組み
p PythonのC APIを使わない処理の間は、GILを開放すると並列処理の効率が向上するケースも
with nogil: #GILを開放し、他のスレッド #でPython実行を許可する f = fopen(fname,"w") …
C++サポート
23
from collections import defaultdict def freq(values): # 要素に、同じ値が何個あるか # 数え上げる d = defaultdict(int) for v in values: d[v] += 1
# distutils: language = c++ from libcpp.map cimport map def freq(list values): # std::map を使用
cdef map[int, int] d cdef int v for v in values: d[v] += 1
Distutils:で、C++ファイルの生成を指示
Cythonのビルド
24
Cythonソースファイル (*.pyx *.pyd *.pxi) cythonコマンド
Cソースファイル (*.c *.cpp)
Cコンパイラ・リンカ
Python拡張モジュール (*.so *.pyd)
Distutilsでビルド
25
from distutils.core import setup from Cython.Build import cythonize setup( name = "hello", ext_modules = cythonize( 'hello.pyx'))
通常の拡張モジュールと同じく、setup.py
を作成
setup.pyで ビルド・インストール
$ python setup.py build_ext $ python setup.py install
対話コンソールでビルド
26
pyximport.install()で、pyxファイルを自動的にビルドしてイ
ンポート
# hello.pyxをコンパイルし、 # 拡張モジュールをインポートする
>>> import pyximport >>> pyximport.install() (None,pyximport. …)
>>> import hello
(コンパイル・リンクオプションを指定する場合には使えない)
CythonはPythonより速い?
27
Pythonはインタープリタだから遅い
Cythonはコンパイルして実行するから速い
ベンチマーク
28
def newton(n): guess = n/2 better = (guess + n/guess)/2 while better != guess: guess = better better = (guess + n/guess)/2 return guess
パフォーマンス比較
29
$ python -m timeit -c 'import pyx_newton;pyx_newton.newton(10.**100)'
10000 loops, best of 3: 21.7 usec per loop
$ python -m timeit -c 'import py_newton;py_newton.newton(10.**100)'
10000 loops, best of 3: 36.3 usec per loop
Python版
Cython版
(Python3.3.1/Cython 0.19.1)
Cython化だけでは速くならない
30
Pythonインタープリタは優秀
p バイトコードインタープリタのオーバヘッドは確かにあるが…
Pythonオブジェクトの、動的な比較・演算APIが問題
p PyNumber_TrueDivide、 PyObject_RichCompareなど
p CythonもPythonも、同じAPIを使って演算を行うので、大きな差は出ない
Cのデータ型で演算を行う
31
def newton(double n): cdef double guess, better guess = n/2 better = (guess + n/guess)/2 while better != guess: guess = better better = (guess + n/guess)/2 return guess
32
21.7 usec
36.3 usec Python版
Cython版
Cython(型指定)版
100000 loops, best of 3: 2.89 usec per loop
• Cython版では、0除算でZeroDivisionError例外を送出するなどの処理があるため、Pure C版より若干遅い
関数の呼び出しコスト
33
def tak(x, y, z): if x <= y: return z return tak( tak(x-‐1, y, z), tak(y-‐1, z, x), tak(z-‐1, x, y))
34
Python版
$ python -m timeit -s "import tak" "tak.tak(18, 9, 0)"
10 loops, best of 3: 2.74 sec per loop
Cython版
$ python -m timeit -s "import tak" "tak.tak(18, 9, 0)"
10 loops, best of 3: 1.47 sec per loop
処理時間はほとんど関数呼び出し
35
Pythonの関数オブジェクトは重たい
p 引数の動的な受け渡し p フレームオブジェクトの作成
Cの関数を定義
36
cdef int c_tak(int x, int y, int z): if x <= y: return z return c_tak( c_tak(x-‐1, y, z), c_tak(y-‐1, z, x), c_tak(z-‐1, x, y)) def tak(x, y, z): return c_tak(x, y, z)
37
Python版
$ python -m timeit -s "import tak" "tak.tak(18, 9, 0)"
10 loops, best of 3: 2.74 sec per loop
Cython版
$ python -m timeit -s "import tak" "tak.tak(18, 9, 0)"
10 loops, best of 3: 1.47 sec per loop
Cython(cdef)版
$ python -m timeit -s "import tak" "tak.tak(18, 9, 0)"
10 loops, best of 3: 36.9 msec per loop
C言語の関数
38
できるだけC/C++の関数を呼び出す
p 呼び出しコスト:低 p インライン化も可能 p Pythonからは呼び出せない
ご清聴ありがとうございました
39