pythonの速度で気にするところ

高速化に関して

  • 高速化はほんとに色々と罠が多い。意図した計測できていなかったり。(特に、python3はmapとかの返り値がジェネレータになっているので、その計測を間違っている例とかがウェブには多い。)

計測

  • まずは何よりも先に計測だ。Linuxのtimeコマンドで計測が最も単純。
  • コードの一部を計測したい場合は下記のようにして計測コードを埋め込む。
import time
if __name__ == '__main__':
    start = time.time()
    # ... some heaby procedure
    elapsed_time = time.time() - start
    print ("elapsed_time:{0}".format(elapsed_time) + "[sec]")
  • デコレータで関数毎に計測しようとするとハマる。pythonははじめからcProfileで同等のことが可能。使い方はスクリプト実行時にコマンドラインからオプションとして渡す。-sでcumtime(累積時間)でソートして出力させる。
$ python3 -m cProfile -s cumtime hoge.py

リストは連結リストではなく配列

  • Pythonのリストはリストというより可変長配列(たぶん)。arrayというのがあるけどそっちは固定長配列。
  • よって、リストの先頭要素の挿入/削除(insert/pop)とかはしない。

whileよりfor

  • 倍くらい違う(らしい)forの方がiのインクリメントと条件比較が最適化されているのかな(Byteコードを見て)?
# Bad
i=0
sum=0
while(i<N):
    sumation += i
    i+=1
# Good
for i in range(N):
    sum+=i

forを回さない

  • pythonの(というよりインタプリタ言語の)for文はfor内部を逐次呼び出すので遅い(らしい)。確かに、言われてみればインタプリタだと最適化出来ないんだ。(JITだと遅くないのかも?。forの中で使う関数をわざわざ変数に格納しているような例を見るけど、そんなのはJITでやるべきじゃないのか?)
  • 下記の例だと毎回appendを参照するコストがかかる。この場合は内包表記でやる。
# Bad
x = []
for i in range(10):
    x.append(x)
# Good
x = [x for x in range(10)]    
  • 文字列の連結も同様に気をつける。この場合はjoinを使う。
# Bad
x = ''
sep = ','
for word in words:
    x += sep + x
# Good
','.join(words)

for回すくらいならsetに変換

  • setとして扱えばfor回さなくても良い場合もある。こっちのほうが速い(らしい)
# Bad
for x in a:
  for y in b:
    if x == y:
      yield(x,y)
# Good
set(a)&set(b)

文字列のマッチングに不必要に正規表現wお使わない

  • pythonのStringはfindメソッド, inメソッドを持っている。find/inで十分なマッチングを正規表現でやると(当然)めっちゃ遅い。

不要なインポートは避ける

  • たまに関数の中でimportしていたりするけど、そのたびにimportされてしまうので注意。

その他のToBeWritten

  • Cython, JIT(NUMBA), multiprocessing, Scoop(分散)