JupyterでCythonを触ってみる

はじめに

Pythonは書きやすいけど,何も工夫しない場合CとかFortranに比べると絶望的に処理速度が遅い.簡単な計算なら気になるほどではないが,大規模な計算になってくると処理速度は重要だ.

Pythonに限らず,プログラムの高速化については常に気にしていたい.今まではそこまで処理速度を意識したコードを書いてこなかったので,少し勉強しようと思ってたのでまとめる.

もちろん武器はCython!

手元にPython関連のテキストはいっぱい転がってるが,最初は触るくらいからはじめたいのでなんかいい記事ないかな~って探してたいいのがあった.

深入りしないCython入門 – Qiita

【Cython】Pythonの高速化に挑戦! – Qiita

今回は,これら記事の内容を写経して勉強させて頂く.

写経してみた

Pythonの処理を高速化させる方法としてcythonを使う.Pythonの処理が遅い原因のひとつは,変数の型をしっかり宣言していないことである.これは便利なんだけど,処理速度の観点では望ましくないらしい.
そこでcythonでの高速化のポイントとなる変数型の静的な宣言を行う.

実際に触っていく.

まずはフィボナッチ数列を題材に処理速度を比較していく.

最初は純Pythonで実装.

In [1]:
#pure Python

def fib(n):
    a, b = 0.0 , 1.0
    for i in range(n):
        a, b = a + b, a
    return
In [2]:
%timeit fib(10**7)
1 loop, best of 3: 534 ms per loop

これをベンチマークにします.次にcythonをで実装.

In [3]:
#cythonを使うために実行
%load_ext Cython
In [4]:
%%cython 
def cyfib(int n):
    a, b = 0.0, 1.0
    for i in range(n):
        a, b = a + b, a
    return a
In [5]:
%timeit cyfib(10**7)
100 loops, best of 3: 8 ms per loop

500msから8msになったので60倍くらいはやくなった!

ちなみに,%%cython -aとすると処理に時間がかかっている行を黄色で表示してくれる.
例えば,純Pythonのコードに%%cython -aをつけて実行すると以下のようになる.

In [6]:
%%cython -a
def cyfib(n):
    a, b = 0.0, 1.0
    for i in range(n):
        a, b = a + b, a
    return a
Out[6]:





Cython: _cython_magic_389ecf55cef7542db4dcf62204418fb9.pyx



Generated by Cython 0.25.2

Yellow lines hint at Python interaction.
Click on a line that starts with a “+” to see the C code that Cython generated for it.

+1: def cyfib(n):
/* Python wrapper */
static PyObject *__pyx_pw_46_cython_magic_389ecf55cef7542db4dcf62204418fb9_1cyfib(PyObject *__pyx_self, PyObject *__pyx_v_n); /*proto*/
static PyMethodDef __pyx_mdef_46_cython_magic_389ecf55cef7542db4dcf62204418fb9_1cyfib = {"cyfib", (PyCFunction)__pyx_pw_46_cython_magic_389ecf55cef7542db4dcf62204418fb9_1cyfib, METH_O, 0};
static PyObject *__pyx_pw_46_cython_magic_389ecf55cef7542db4dcf62204418fb9_1cyfib(PyObject *__pyx_self, PyObject *__pyx_v_n) {
  PyObject *__pyx_r = 0;
  __Pyx_RefNannyDeclarations
  __Pyx_RefNannySetupContext("cyfib (wrapper)", 0);
  __pyx_r = __pyx_pf_46_cython_magic_389ecf55cef7542db4dcf62204418fb9_cyfib(__pyx_self, ((PyObject *)__pyx_v_n));

  /* function exit code */
  __Pyx_RefNannyFinishContext();
  return __pyx_r;
}

static PyObject *__pyx_pf_46_cython_magic_389ecf55cef7542db4dcf62204418fb9_cyfib(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_n) {
  double __pyx_v_a;
  double __pyx_v_b;
  CYTHON_UNUSED PyObject *__pyx_v_i = NULL;
  PyObject *__pyx_r = NULL;
  __Pyx_RefNannyDeclarations
  __Pyx_RefNannySetupContext("cyfib", 0);
/* … */
  /* function exit code */
  __pyx_L1_error:;
  __Pyx_XDECREF(__pyx_t_3);
  __Pyx_XDECREF(__pyx_t_4);
  __Pyx_AddTraceback("_cython_magic_389ecf55cef7542db4dcf62204418fb9.cyfib", __pyx_clineno, __pyx_lineno, __pyx_filename);
  __pyx_r = NULL;
  __pyx_L0:;
  __Pyx_XDECREF(__pyx_v_i);
  __Pyx_XGIVEREF(__pyx_r);
  __Pyx_RefNannyFinishContext();
  return __pyx_r;
}
/* … */
  __pyx_tuple_ = PyTuple_Pack(4, __pyx_n_s_n, __pyx_n_s_a, __pyx_n_s_b, __pyx_n_s_i); if (unlikely(!__pyx_tuple_)) __PYX_ERR(0, 1, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_tuple_);
  __Pyx_GIVEREF(__pyx_tuple_);
/* … */
  __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_46_cython_magic_389ecf55cef7542db4dcf62204418fb9_1cyfib, NULL, __pyx_n_s_cython_magic_389ecf55cef7542db4); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 1, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_1);
  if (PyDict_SetItem(__pyx_d, __pyx_n_s_cyfib, __pyx_t_1) < 0) __PYX_ERR(0, 1, __pyx_L1_error)
  __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
+2:     a, b = 0.0, 1.0
  __pyx_t_1 = 0.0;
  __pyx_t_2 = 1.0;
  __pyx_v_a = __pyx_t_1;
  __pyx_v_b = __pyx_t_2;
+3:     for i in range(n):
  __pyx_t_3 = PyTuple_New(1); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 3, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_3);
  __Pyx_INCREF(__pyx_v_n);
  __Pyx_GIVEREF(__pyx_v_n);
  PyTuple_SET_ITEM(__pyx_t_3, 0, __pyx_v_n);
  __pyx_t_4 = __Pyx_PyObject_Call(__pyx_builtin_range, __pyx_t_3, NULL); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 3, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_4);
  __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
  if (likely(PyList_CheckExact(__pyx_t_4)) || PyTuple_CheckExact(__pyx_t_4)) {
    __pyx_t_3 = __pyx_t_4; __Pyx_INCREF(__pyx_t_3); __pyx_t_5 = 0;
    __pyx_t_6 = NULL;
  } else {
    __pyx_t_5 = -1; __pyx_t_3 = PyObject_GetIter(__pyx_t_4); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 3, __pyx_L1_error)
    __Pyx_GOTREF(__pyx_t_3);
    __pyx_t_6 = Py_TYPE(__pyx_t_3)->tp_iternext; if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 3, __pyx_L1_error)
  }
  __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
  for (;;) {
    if (likely(!__pyx_t_6)) {
      if (likely(PyList_CheckExact(__pyx_t_3))) {
        if (__pyx_t_5 >= PyList_GET_SIZE(__pyx_t_3)) break;
        #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
        __pyx_t_4 = PyList_GET_ITEM(__pyx_t_3, __pyx_t_5); __Pyx_INCREF(__pyx_t_4); __pyx_t_5++; if (unlikely(0 < 0)) __PYX_ERR(0, 3, __pyx_L1_error)
        #else
        __pyx_t_4 = PySequence_ITEM(__pyx_t_3, __pyx_t_5); __pyx_t_5++; if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 3, __pyx_L1_error)
        __Pyx_GOTREF(__pyx_t_4);
        #endif
      } else {
        if (__pyx_t_5 >= PyTuple_GET_SIZE(__pyx_t_3)) break;
        #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS
        __pyx_t_4 = PyTuple_GET_ITEM(__pyx_t_3, __pyx_t_5); __Pyx_INCREF(__pyx_t_4); __pyx_t_5++; if (unlikely(0 < 0)) __PYX_ERR(0, 3, __pyx_L1_error)
        #else
        __pyx_t_4 = PySequence_ITEM(__pyx_t_3, __pyx_t_5); __pyx_t_5++; if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 3, __pyx_L1_error)
        __Pyx_GOTREF(__pyx_t_4);
        #endif
      }
    } else {
      __pyx_t_4 = __pyx_t_6(__pyx_t_3);
      if (unlikely(!__pyx_t_4)) {
        PyObject* exc_type = PyErr_Occurred();
        if (exc_type) {
          if (likely(exc_type == PyExc_StopIteration || PyErr_GivenExceptionMatches(exc_type, PyExc_StopIteration))) PyErr_Clear();
          else __PYX_ERR(0, 3, __pyx_L1_error)
        }
        break;
      }
      __Pyx_GOTREF(__pyx_t_4);
    }
    __Pyx_XDECREF_SET(__pyx_v_i, __pyx_t_4);
    __pyx_t_4 = 0;
/* … */
  }
  __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
+4:         a, b = a + b, a
    __pyx_t_2 = (__pyx_v_a + __pyx_v_b);
    __pyx_t_1 = __pyx_v_a;
    __pyx_v_a = __pyx_t_2;
    __pyx_v_b = __pyx_t_1;
+5:     return a
  __Pyx_XDECREF(__pyx_r);
  __pyx_t_3 = PyFloat_FromDouble(__pyx_v_a); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 5, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_3);
  __pyx_r = __pyx_t_3;
  __pyx_t_3 = 0;
  goto __pyx_L0;

次に,Cythonで試す.

In [7]:
%%cython -a
def cyfib(int n):
    a, b = 0.0, 1.0
    for i in range(n):
        a, b = a + b, a
    return a
Out[7]:





Cython: _cython_magic_ce93c4a6ab8706cdc11e86a828dfbd19.pyx



Generated by Cython 0.25.2

Yellow lines hint at Python interaction.
Click on a line that starts with a “+” to see the C code that Cython generated for it.

+1: def cyfib(int n):
/* Python wrapper */
static PyObject *__pyx_pw_46_cython_magic_ce93c4a6ab8706cdc11e86a828dfbd19_1cyfib(PyObject *__pyx_self, PyObject *__pyx_arg_n); /*proto*/
static PyMethodDef __pyx_mdef_46_cython_magic_ce93c4a6ab8706cdc11e86a828dfbd19_1cyfib = {"cyfib", (PyCFunction)__pyx_pw_46_cython_magic_ce93c4a6ab8706cdc11e86a828dfbd19_1cyfib, METH_O, 0};
static PyObject *__pyx_pw_46_cython_magic_ce93c4a6ab8706cdc11e86a828dfbd19_1cyfib(PyObject *__pyx_self, PyObject *__pyx_arg_n) {
  int __pyx_v_n;
  PyObject *__pyx_r = 0;
  __Pyx_RefNannyDeclarations
  __Pyx_RefNannySetupContext("cyfib (wrapper)", 0);
  assert(__pyx_arg_n); {
    __pyx_v_n = __Pyx_PyInt_As_int(__pyx_arg_n); if (unlikely((__pyx_v_n == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 1, __pyx_L3_error)
  }
  goto __pyx_L4_argument_unpacking_done;
  __pyx_L3_error:;
  __Pyx_AddTraceback("_cython_magic_ce93c4a6ab8706cdc11e86a828dfbd19.cyfib", __pyx_clineno, __pyx_lineno, __pyx_filename);
  __Pyx_RefNannyFinishContext();
  return NULL;
  __pyx_L4_argument_unpacking_done:;
  __pyx_r = __pyx_pf_46_cython_magic_ce93c4a6ab8706cdc11e86a828dfbd19_cyfib(__pyx_self, ((int)__pyx_v_n));

  /* function exit code */
  __Pyx_RefNannyFinishContext();
  return __pyx_r;
}

static PyObject *__pyx_pf_46_cython_magic_ce93c4a6ab8706cdc11e86a828dfbd19_cyfib(CYTHON_UNUSED PyObject *__pyx_self, int __pyx_v_n) {
  double __pyx_v_a;
  double __pyx_v_b;
  CYTHON_UNUSED int __pyx_v_i;
  PyObject *__pyx_r = NULL;
  __Pyx_RefNannyDeclarations
  __Pyx_RefNannySetupContext("cyfib", 0);
/* … */
  /* function exit code */
  __pyx_L1_error:;
  __Pyx_XDECREF(__pyx_t_5);
  __Pyx_AddTraceback("_cython_magic_ce93c4a6ab8706cdc11e86a828dfbd19.cyfib", __pyx_clineno, __pyx_lineno, __pyx_filename);
  __pyx_r = NULL;
  __pyx_L0:;
  __Pyx_XGIVEREF(__pyx_r);
  __Pyx_RefNannyFinishContext();
  return __pyx_r;
}
/* … */
  __pyx_tuple_ = PyTuple_Pack(5, __pyx_n_s_n, __pyx_n_s_n, __pyx_n_s_a, __pyx_n_s_b, __pyx_n_s_i); if (unlikely(!__pyx_tuple_)) __PYX_ERR(0, 1, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_tuple_);
  __Pyx_GIVEREF(__pyx_tuple_);
/* … */
  __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_46_cython_magic_ce93c4a6ab8706cdc11e86a828dfbd19_1cyfib, NULL, __pyx_n_s_cython_magic_ce93c4a6ab8706cdc1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 1, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_1);
  if (PyDict_SetItem(__pyx_d, __pyx_n_s_cyfib, __pyx_t_1) < 0) __PYX_ERR(0, 1, __pyx_L1_error)
  __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
+2:     a, b = 0.0, 1.0
  __pyx_t_1 = 0.0;
  __pyx_t_2 = 1.0;
  __pyx_v_a = __pyx_t_1;
  __pyx_v_b = __pyx_t_2;
+3:     for i in range(n):
  __pyx_t_3 = __pyx_v_n;
  for (__pyx_t_4 = 0; __pyx_t_4 < __pyx_t_3; __pyx_t_4+=1) {
    __pyx_v_i = __pyx_t_4;
+4:         a, b = a + b, a
    __pyx_t_2 = (__pyx_v_a + __pyx_v_b);
    __pyx_t_1 = __pyx_v_a;
    __pyx_v_a = __pyx_t_2;
    __pyx_v_b = __pyx_t_1;
  }
+5:     return a
  __Pyx_XDECREF(__pyx_r);
  __pyx_t_5 = PyFloat_FromDouble(__pyx_v_a); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 5, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_5);
  __pyx_r = __pyx_t_5;
  __pyx_t_5 = 0;
  goto __pyx_L0;

nをint型で静的に宣言したことで3行目の黄色ラインが消えている.

最初に紹介した記事では,

まずは、何も考えずPythonコードをコピペして、黄色い行を優先的に型指定していくのが、正当なCythonの使い方だろう。

とあった.この手間だけで10倍のオーダーで高速化ができるならどんどん活用していきたい.

おわりに

とりあえず,Cythonは気になっていたので触れてよかった.やっぱりプログラムが早く動くと感動する.

今後は,詳細について勉強していこうと思う.

0