はじめに

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.405245
multiThreadはあまり伸びないですね。。
JITはおそらく初回にコンパイルが走るため若干遅いが、最速ですね
Cythonは、コンパイルオプションとかいじればもっと早くなるのかな?

ただ、JITはnopythonモードじゃないと糞遅くなるので、注意です。結構はまりました


参考資料

https://www.sejuku.net/blog/89458