Python
Cython
パフォーマンス

続・Pythonの計算を早くするには

先日投稿した記事 Pythonの計算を早くするには の内容についてPythonのコードがC言語に比べて大分遅かった為、改善策はあるのか検証します。

最初のコード

wallis_v2.py
from numpy import *

def f(x):
    return sin(pi*x)

product_array = [0 for i in range(10)]


n = 1000000
inv_n = 1.0 / n
for k in range(n):
    x = k * inv_n
    y = f(x)
    z = y
    for m in range(10):
        product_array[m] += z
        z *= y

product = prod(list(map(lambda x: x * inv_n, product_array)))
result = 1.0/product
print(result)

初回の実行時に最適化が動くようなので、2回目以降の値を参考にします。

$ time python3 wallis_v2.py
36722.3621743
python3 wallis_v2.py  8.96s user 0.05s system 99% cpu 9.050 total

importの最適化

まずimportの部分を使用するモジュールに限定します。

from numpy import prod
from math import sin, pi
$ time python3 wallis_v2.py
36722.3621743
python3 wallis_v2.py  7.67s user 0.06s system 99% cpu 7.755 total

1秒以上早くなりました。

Cythonの使用

pyxファイルへの分割

次にCythonを導入します。その為に処理自体をpyxファイルに分割します。

wallis_v3.py
import pyximport
pyximport.install()

from integral_lib import integral

print(integral(1000000))
integral_lib.pyx
from numpy import prod
from f import f

def integral(n):
  product_array = [0 for i in range(10)]
  inv_n = 1.0 / n
  for k in range(n):
    x = k * inv_n
    y = f(x)
    z = y
    for m in range(10):
      product_array[m] += z
      z *= y

  return 1 / prod(list(map(lambda x: x * inv_n, product_array)))
f.pyx
from math import sin, pi

def f(x):
  return sin(pi * x)
$ time python3 wallis_v3.py
36722.3621743
python3 wallis_v3.py  1.05s user 0.06s system 98% cpu 1.134 total

大分早くなりました。

静的型付

f2.pyx
from math import sin, pi

def f(double x):
  cdef:
    double res = sin(pi * x)
  return res
integral_lib2.pyx
from numpy import prod
from f2 import f

def integral(int n):
  product_array = [0.0 for i in range(10)]

  cdef:
    double inv_n = 1.0 / n
    double x,y,z
    int k
  for k in range(n):
    x = k * inv_n
    y = f(x)
    z = y
    for m in range(10):
      product_array[m] += z
      z *= y

  return 1.0 / prod(list(map(lambda x: x * inv_n, product_array)))
wallis_v3.py
from integral_lib2 import integral
$ time python3 wallis_v3.py
36722.3621743
python3 wallis_v3.py  0.80s user 0.06s system 97% cpu 0.884 total

早くできました。
参考にした記事中にlibcを呼び出す方法が紹介されていたので試して見ました。一応早くなっています。

f2.pyx
from libc.math cimport sin,M_PI

def f(double x):
  cdef:
    double res = sin(M_PI * x)
  return res
$ time python3 wallis_v3.py
36722.3621743
python3 wallis_v3.py  0.72s user 0.06s system 97% cpu 0.802 total

初期のコードでは22秒だったものが、0.8秒ほどまで高速化することはできましたが、コードの保守性などを考えるとこれがベストと云うわけではありませんので、適宜選んでください。

参考

Cython : C との融合による高速化