Python
Spyder
profile

Spyder 統合環境を用いたPythonスクリプトの速度の改善

Python スクリプトの速度を改善する自己流のやりかたを示します。

別バージョンの関数を実装して、それが同じ値を返していることをassert で確認しつつ、profileで実際に新しく作った別バージョンの方が処理時間が短いことを確認する。
 正しく動いていて、速くなっていないかぎり、古い版を置き換えることができません。

もちろん、以下の書籍のアドバイスは参考にした上で。
「ハイパフォーマンスPython」
English High Performance Python

Spyder 統合環境を用いたPythonスクリプトの速度の改善

  • [Run][Profile] を実行する。
    • プロファイルが簡単にできるので、どこがボトルネックになっているかを確認できる。
    • その消費時間の多い関数・メソッドが遅い理由を考える。
    • その関数の速度改善版を別の関数名で作成する。
    • 元の関数と速度改善版の関数を両方呼び出し、結果をassert で同じ値を返していることを確認する。
    • その版についてプロファイルをする。本当に速度改善版になっているかを確認する。
    • 値と速度とを確認できた後は、速度改善版の関数だけを呼び出すように書き換える。
    • このようにして、テストしつつ速度の改善できる。
# pylint: disable=C0103
"""
級数の和の計算の高速化の事例。

"""
import cv2 

def squareSum(n):
    """return square sum
    note last term  is (n-1)**2.
    """
    return sum([i**2 for i in xrange(n)])


def squareSum_ver2(n):
    """return square sum using Formula of sum of series.
    note last term  is (n-1)**2.
    """
    return (n-1)*n*(2*n-1)/6

if __name__ == "__main__":
    time0 = cv2.getTickCount()
    a = squareSum(100000)

    time1 = cv2.getTickCount()

    f = squareSum_ver2(100000)
    time2 = cv2.getTickCount()

    print a
    assert a == f

    print (time1- time0)/cv2.getTickFrequency()
    print (time2- time1)/cv2.getTickFrequency()

注:
 ここに示した例は、級数の和の公式を用いるということで、極端な違いがでる例を示しました。同じアルゴリズムでも書き方の違いでこう改善できるという例は、まずは避けました。

  • 速度の改善の余地がありそうな項目
    • numpy, pandas などでfor 文でnumpy.array、pandas.DataFrameの要素をdata[i, j]などと添え字でアクセスしている。
      • 典型的な使い方の場合だと、numpyやpandasの標準的な関数やメソッドでその操作が用意されている可能性がある。それを使うと格段に処理が速くなる。
      • OpenCVの画像データの扱いも同じ。2重のfor文を書いている部分の多くが、cv2に標準的に用意されている関数で置き換えられる場合がある。この場合も格段に処理が速くなる。
        • 画像処理で2重ループをなるべく書くな
        • どうしても2重ループを書かなくてはならない場合:
          • Cythonを使って書き換えることで高速化する。 Python言語の場合だと、明示的な型の指定がないために、各種演算の前に型を確認する操作が入ってしまう。しかもループ(および多重ループ)の中で、同じ変数に対してループの回数だけ型の確認を行ってしまうことが極端に遅くする理由となっています。
    • for 文の中で範囲判定をif 文で実行している。
      • for 文の外で範囲を決めておいて、それをfor x in items:のような利用のしかたをする。
      • python の中で if x > x0: を実行すると、xやx0の型をしらべてから、どの型での大小関係の比較を呼び出すのか判断するため、C/C++言語に比べてコストが高くつく。
    • xrange()を使わずにrange()を使ってしまっている(Python2.x)。
      • for i in range(10000):などと気軽に書いてしまうと、0から9999までの値が入ったリストを生成してしまう。(Python2.xの場合)
      • そのため、無駄にメモリを浪費してしまう。

参考とすべき書籍
一つ上のPython使いになろうにそのような書籍の紹介を書きました。