forループとリスト内包表記の実行時間の差を計測
Python forループとリスト内包表記の実行時間の差を計測 - 複雑系スパゲティソース(はてな版)
で、内包表記の方が、forループより10倍速い
という結果が出ていたのですが、
疑問のコメントが出ていたので、自分でも測ってみました。
あとprintをすると、forループと内包表記の純粋な比較にはならない上、
まず、forループと内包表記で値が異なっていたので、統一。
画面が流れてしまって測定結果が見づらいので、printは無くします。
でも、やっぱり、forループの方が遅かったのですが、
上手い書き方をすればもっとマシになるか、と、色々いじってみました。
#encoding:shift-jis from __future__ import with_statement, division, print_function import datetime from timeit import Timer N = 20 def test_for_loop(): #forループ listall = [] for first_rc in range(N): for second_rc in range(N): for third_rc in range(N): listall = listall + [third_rc] return listall def test_comprehension(): #リスト内包表記 return [third_rc for first_rc in range(N) for second_rc in range(N) for third_rc in range(N)] def test_for_loop_append(): #forループ 加算ではなくappendを使用 listall = [] for first_rc in range(N): for second_rc in range(N): for third_rc in range(N): listall.append(third_rc) return listall def test_for_loop_append2(): #forループ appendをローカル変数に代入 listall = [] listall_append = listall.append for first_rc in range(N): for second_rc in range(N): for third_rc in range(N): listall_append(third_rc) return listall def test_for_loop_extend(): #forループ appendをextendに変更 listall = [] listall_extend = listall.extend for first_rc in range(N): for second_rc in range(N): listall_extend(range(N)) return listall def test_for_loop_xrange(): #forループ rangeをxrangeに変更 listall = [] listall_extend = listall.extend for first_rc in xrange(N): for second_rc in xrange(N): listall_extend(xrange(N)) return listall def test_comprehension_xrange(): #リスト内包表記 rangeをxrangeに変更 return [third_rc for first_rc in xrange(N) for second_rc in xrange(N) for third_rc in xrange(N)] def test_times(): #素直にかけ算! return range(N)*(N*N) test_functions = [ test_for_loop, test_comprehension, test_for_loop_append, test_for_loop_append2, test_for_loop_extend, test_for_loop_xrange, test_comprehension_xrange, test_times, ] for f in test_functions: assert f() == test_for_loop(), str(f) count = 100 for f in test_functions: print(f.__name__, Timer(f).timeit(count))
結果
forループ | test_for_loop | 390.356245556 |
リスト内包表記 | test_comprehension | 1.23270108352 |
forループ 加算ではなくappendを使用 | test_for_loop_append | 2.46575807819 |
forループ appendをローカル変数に代入 | test_for_loop_append2 | 1.70131902239 |
forループ appendをextendに変更 | test_for_loop_extend | 0.550672222308 |
forループ rangeをxrangeに変更 | test_for_loop_xrange | 0.487926258784 |
リスト内包表記 rangeをxrangeに変更 | test_comprehension_xrange | 1.03056813087 |
素直にかけ算! | test_times | 0.0899770780923 |
まず、オリジナルのforループがおっそろしく遅いです。
一々、listall = listall + [third_rc]
で、
新しくリストを作っているので。
普通、追加にはappendを使うので、そのようにしたら、
比較できる程度には早くなりました。
次に、毎回、appendメソッドを属性一覧の中から探してくるのは時間がかかるので、
ローカル変数に代入して、時間を稼ぎます。
また、今回は3番目のループは、中身をそのまま追加しているだけなので、
extendに変更します。
そして、巨大なリストを生成してメモリを消費するrangeを、
リストを生成しないxrangeに変更・・・って、これはextendとか、
メソッドを変数に代入する前にやるべきですよね(笑)。
これは内包表記でも使える方法です。
改良の結果、なんと内包表記よりforループの方が速くなりました!
変数を評価する回数が減ったからでしょうかね?
しかし、もっとも速かったのは素直にかけ算!
いや、もちろん、実際的なコードでは使えないでしょうけど、
ヘタに凝ったコードを書くよりも、シンプルな方が良い事もあるんですね。