introduction to cython

39
Cython による 拡張モジュール 開発 2013/9/14 PyCon APAC 2013 Atsuo Ishimoto

Upload: atsuo-ishimoto

Post on 02-Jul-2015

10.252 views

Category:

Technology


0 download

DESCRIPTION

PyCon APAC 2013発表資料 プログラミング言語 Cythonの紹介

TRANSCRIPT

Page 1: Introduction to cython

Cythonによる

拡張モジュール開発

2013/9/14 PyCon APAC 2013 Atsuo Ishimoto

Page 2: Introduction to cython

自己紹介

2

いしもと

石本 敦夫 あつお

p 書籍「パーフェクトPython」 著者の一員

p http://www.gembook.org p python.jp ドメインの管理者 p @atsuoishimoto

Page 3: Introduction to cython

Pythonの拡張モジュール

3

p 通常、C/C++で開発し、Pythonスクリプトから利用できる関数・データ型を提供

p Pythonが動的にロードする共有ライブラリ(*.so, *.pyd)

p 用途 l  Pythonからは呼び出せないCライブラリへ

のインターフェース l  Pythonではパフォーマンス不足

Page 4: Introduction to cython

拡張モジュールのめんどくささ

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

Page 5: Introduction to cython

拡張モジュールのめんどくささ

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) { ...

Page 6: Introduction to cython

拡張モジュールのめんどくささ

6

参照カウント管理

p Pythonのオブジェクトは、オブジェクトの被参照数を正確にカウントする必要がある

p PyObject_SetItem()、PyList_SetItem()、 PyList_SET_ITEM()  の違いを覚えてますか?

p 間違えればメモリリークかコアダンプ

Page 7: Introduction to cython

Cythonとは

7

Python専用プログラミング言語

p Pythonの拡張モジュールを開発するための専用プログラミング言語

p Pythonのほぼ上位互換 p インタープリタではなくコンパイラ p Python2/3対応 p http://www.cython.org

Page 8: Introduction to cython

Cythonの起源

8

Pyrex

Cython

Fork

最終リリースは 2010/4/12

2002/4/3 version 0.1 リリース

Page 9: Introduction to cython

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

Page 10: Introduction to cython

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)  

Page 11: Introduction to cython

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/

Page 12: Introduction to cython

Cythonの関数・型定義

12

かんたん

cdef  class  Spam:          cdef  double  attr1          cdef  public  double  public_attr          cdef  readonly  double  public_attr2                    def  ham(self):                  return  self.attr1  

Page 13: Introduction to cython

アーリーバインディング

13

変数の型宣言

も できる!

def  spam(dict  d):          return  len(d)  

型チェックは静的・動的両方

def  spam(dict  d):      cdef  list  L       L  =  d     #  コンパイルエラーに     #  ならない  

Page 14: Introduction to cython

Cythonコードの最適化

14

cdef  int  i  for  i  in  range(100):          …  

cdef  int  i  =  0  while  i  <  100:          …          i  +=  1  

Page 15: Introduction to cython

オブジェクトアクセスの最適化

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)  

Page 16: Introduction to cython

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()  

ヘッダファイルから関数や構造体をインクルード

Page 17: Introduction to cython

定義済みライブラリ

17

標準Cランタイム関数などは定義済み

from  libc.math  cimport  sin      def  std_sin(x):          return  sin(x*x)  

p 関数・構造体・定数などをCythonで定義してある

p cimport文でインポートするだけで利用可能

Page 18: Introduction to cython

定義済みライブラリ(抜粋)

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で定義された関数

Page 19: Introduction to cython

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は不可  

Page 20: Introduction to cython

自動型変換

20

def  spam(n):          cdef  int  number          number  =  n    

p Pythonオブジェクトを、Cのint型の値に変換

p 変換不能な場合は例外を送出

int  number  =  PyInt_AS_LONG(n)  

Page 21: Introduction to cython

文字列の自動変換

21

def  spam(L):          cdef  char  *s  =  "ham"          L.append(s)  

p 文字列 s を Pythonのstrオブジェクト(Python3ではbytes)に変換

p strオブジェクト -> char * も変換される

Page 22: Introduction to cython

GIL制御

22

言語としてGILをサポート

p GIL: Global Interpreter Lock

p Pythonスクリプトが、複数のスレッドで同時に実行されないように制御する仕組み

p PythonのC APIを使わない処理の間は、GILを開放すると並列処理の効率が向上するケースも

with  nogil:        #GILを開放し、他のスレッド        #でPython実行を許可する        f  =  fopen(fname,"w")        …  

Page 23: Introduction to cython

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++ファイルの生成を指示

Page 24: Introduction to cython

Cythonのビルド

24

Cythonソースファイル (*.pyx *.pyd *.pxi) cythonコマンド

Cソースファイル (*.c *.cpp)

Cコンパイラ・リンカ

Python拡張モジュール (*.so *.pyd)

Page 25: Introduction to cython

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  

Page 26: Introduction to cython

対話コンソールでビルド

26

pyximport.install()で、pyxファイルを自動的にビルドしてイ

ンポート

#  hello.pyxをコンパイルし、  #  拡張モジュールをインポートする  

>>>  import  pyximport  >>>  pyximport.install()  (None,pyximport.  …)  

>>>  import  hello      

(コンパイル・リンクオプションを指定する場合には使えない)

Page 27: Introduction to cython

CythonはPythonより速い?

27

Pythonはインタープリタだから遅い

Cythonはコンパイルして実行するから速い

Page 28: Introduction to 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  

Page 29: Introduction to cython

パフォーマンス比較

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)

Page 30: Introduction to cython

Cython化だけでは速くならない

30

Pythonインタープリタは優秀

p  バイトコードインタープリタのオーバヘッドは確かにあるが…

Pythonオブジェクトの、動的な比較・演算APIが問題

p  PyNumber_TrueDivide、 PyObject_RichCompareなど

p  CythonもPythonも、同じAPIを使って演算を行うので、大きな差は出ない

Page 31: Introduction to cython

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  

Page 32: Introduction to cython

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版より若干遅い

Page 33: Introduction to cython

関数の呼び出しコスト

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))  

Page 34: Introduction to cython

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

Page 35: Introduction to cython

処理時間はほとんど関数呼び出し

35

Pythonの関数オブジェクトは重たい

p 引数の動的な受け渡し p フレームオブジェクトの作成

Page 36: Introduction to cython

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)  

Page 37: Introduction to cython

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

Page 38: Introduction to cython

C言語の関数

38

できるだけC/C++の関数を呼び出す

p 呼び出しコスト:低 p インライン化も可能 p Pythonからは呼び出せない

Page 39: Introduction to cython

ご清聴ありがとうございました

39