はじめに
Pythonは基本的にスクリプト言語のため、処理速度はJavaやC言語などのコンパイルされたプログラムを実行する言語に比べると遅い
しかし、PythonはC言語で書かれたライブラリを読み込んだり、並列処理をしたり、
JITコンパイラでコンパイルするなどして、高速化することができる
実行環境
Python 3.6.5高速化方法
高速化の方法はいくつか存在しまが、ここでは下記の方法を紹介します今回は足し算を下記方法で比べてみます
- 並列化(マルチプロセス、マルチスレッド)
- C言語のライブラリ利用
- JITコンパイラを利用
- Cpythonの利用
普通の計算
まずは普通の足し算def calc_normal(values): """ Normal Sum """ start_time = time.time() result = 0 for i in values: result += i elapsed_time = time.time() - start_time return result, elapsed_time
並列化
並列化は、複数のCPUコアを同時に利用することで、処理速度を上げるものです。ここではマルチスレッドによる処理を実装してみます
def calc_multi_thread(values, workers=2): """ Use Multithreading """ start_time = time.time() result = 0 x_start = 0 x_delta = int(len(values)/workers) if x_delta < 1: x_delta = 1 x_end = x_delta th_value = [] executor = concurrent.futures.ThreadPoolExecutor(max_workers=workers) for _ in range(workers): if x_start >= len(values): # 処理を中止 break elif x_start < len(values) and x_end > len(values): x_end = len(values) th_value.append(values[x_start:x_end]) # 更新 x_start = x_end x_end += x_delta executor = concurrent.futures.ThreadPoolExecutor(max_workers=workers) th_result = executor.map(calc_normal, th_value) for i in list(th_result): result += i[0] elapsed_time = time.time() - start_time return result, elapsed_time
C言語のライブラリ
ここでは、例としてNumpyを取り上げます。Numpyは行列計算などのライブラリで、中身はC言語で実装されていますhttps://github.com/numpy/numpy
def calc_numpy(values): """ Use C Library """ start_time = time.time() result = np.array(values).sum() elapsed_time = time.time() - start_time return result, elapsed_time
JITコンパイラ
JIT(Just In Time)コンパイラであるNumbaを利用することで、実行前にコンパイル処理しますnopythonモードで動作させないと、遅くなるため、デコレータでオプションを指定します
このときNumbaで利用できる型が決まっているので、注意が必要です。
あとコンパイルするので、サイズがわからないループはできないです。なので、ループ回数を引数に渡してます。(けっこうはまった。。。)
timeの型もないし、Stringも使えないので、ほぼ計算用途の高速化ですね
def calc_normal_jit(values): @jit('i8(i8, i8[:])', nopython=True) def func(N, values): result = 0 for i in range(N): result += values[i] return result
""" main """ start_time = float(time.time()) result = func(len(values), values) elapsed_time = time.time() - start_time return result, elapsed_time
デコレータで指定している引数や戻り値の型
# Aliases to Numpy type names b1 = bool_ i1 = int8 i2 = int16 i4 = int32 i8 = int64 u1 = uint8 u2 = uint16 u4 = uint32 u8 = uint64 f4 = float32 f8 = float64 c8 = complex64 c16 = complex128 float_ = float32 double = float64 void = none
Cython
Cythonは、CやJava言語と同じようにコンパイルさせます。特徴としては、Pythonには不要だった、型宣言が必要になります
http://omake.accense.com/static/doc-ja/cython/src/reference/compilation.html
https://qiita.com/R-Imai/items/20c1a0a74824b6158fd4
.pyx ファイル
import sys import time def calc_normal(list values): """ Normal Sum """ cdef: double result, i start_time = time.time() result = 0 for i in values: result += i elapsed_time = time.time() - start_time #print (sys._getframe().f_code.co_name + " : elapsed_time:{0}".format(elapsed_time) + "[sec]") return result, elapsed_time
setup.py
from distutils.core import setup, Extension from Cython.Build import cythonize ext = Extension("lib_cython", sources=["lib_cython.pyx"], include_dirs=['.']) setup(name="lib_cython", ext_modules=cythonize([ext]))
コンパイル
python3.6 setup.py build_ext --inplace
結果
sizeは足した数字の数です。size normal jit numpy multi_thread cython 0 10 0.000027 0.000433 0.000090 0.001095 0.000002 1 100 0.000042 0.000014 0.000072 0.000970 0.000006 2 1000 0.000356 0.000015 0.000070 0.001309 0.000037 3 10000 0.002886 0.000057 0.000095 0.003864 0.000348 4 100000 0.029310 0.000144 0.000677 0.030184 0.003948 5 1000000 0.321735 0.001395 0.010521 0.299840 0.022091 6 10000000 2.369445 0.012043 0.099299 2.031897 0.405245multiThreadはあまり伸びないですね。。
JITはおそらく初回にコンパイルが走るため若干遅いが、最速ですね
Cythonは、コンパイルオプションとかいじればもっと早くなるのかな?
ただ、JITはnopythonモードじゃないと糞遅くなるので、注意です。結構はまりました
コメント