括弧が逆ゥ!
はてブ見てたらこういうの見つけた:
Python簡単実験:内包で何倍高速になるか - TIM Labs
「なんで対話型インタプリタ使ってるんや・・・・・?」という疑問は置いといて、こんなに違うのかとちょっと信じ切れなかったので、自分でも書きました。
まずは4つ
作ったのは4つで、
- 単純に for で回す
- 内包表記使う
- range をそのまま list に投げる
- ジェネレータを list に投げる
です。
import time
def time_calc(func, *arg, **kwds):
start = time.time()
func(*arg, **kwds)
end = time.time()
return end - start
def calc(func, n, *arg, **kwds):
times = [time_calc(func, *arg, **kwds) for _ in range(n)]
return sum(times) / n
def simply_for(n):
a = list()
for i in range(n):
a.append(i)
return a
def comprehension(n):
a = [x for x in range(n)]
return a
def range_case(n):
a = list(range(n))
return a
def range_(n):
for i in range(n):
yield i
def generator_case(n):
a = list(range_(n))
return a
if __name__ == '__main__':
n = 10**7
for func in (simply_for, comprehension,
range_case, generator_case):
print("{}: {}sec.".format(func.__name__, calc(func, 5, n)))
おかゆのパソコンはうんちなのですが、とりあえずこうなりました*1:
simply_for: 2.1997323036193848sec.
comprehension: 1.3124329090118407sec.
range_case: 0.550351619720459sec.
generator_case: 2.0610249996185304sec.
内包表記、うちのぱちょこんだと2倍も速くならないくらいですね。まあ速いことに変わりはありません。
それにしても、list(range(n))
が思った以上に速いですね…。
上の記事だと、単純な for より内包表記は約5倍ほど高速になってることを考えると、list(range) は10倍くらい速いことになりそう。
とはいえ、値を計算して…というのはできないですから、結局、内包表記使うのが速くていいですね。
あとは…、おかゆパソコンだと非常に微妙ですが、130msほどジェネレータつくってやるほうが速いですね。
内包表記はforが入れ子になると表記が煩わしくなるので、そういうときはジェネレータ作ってやると for よりは速いということになるのかもしれない(オーバーヘッドはあるだろうけど)。
からの入れ子3つ
というわけで、入れ子3つにしてみた:
def simply_for_2(n):
a = list()
for i in range(n):
for j in range(n):
for k in range(n):
if i+j+k % 2 == 0:
a.append(i+j+k)
return a
def comprehension_2(n):
a = [i+j+k for i in range(n) for j in range(n) for k in range(n)
if i+j+k % 2 == 0]
return a
def generator_2(n):
for i in range(n):
for j in range(n):
for k in range(n):
if i+j+k % 2 == 0:
yield i+j+k
def generator_case_2(n):
a = list(generator_2(n))
return a
if __name__ == '__main__':
n = 300
for func in (simply_for_2, comprehension_2,
generator_case_2):
print("{}: {:.3f}sec.".format(func.__name__, calc(func, 3, n)))
simply_for_2: 8.179sec.
comprehension_2: 7.936sec.
generator_case_2: 7.853sec.
び、びみょ~~~~~~~~~~~~~~~( ᕦ 。 ᕤ)
入れ子が増えると内包表記もfor並にとろくなるのかしら。そう考えると、コードの見た目の観点から普通にfor書くべきだね。
ジェネレータは安定して、「微妙に」速い。
併用や!
見た目的にも、かつ、汎用性も兼ねることを考えると、ジェネレータと内包表記を併用するのが良さそう?
def generator_3(n):
for i in range(n):
for j in range(n):
for k in range(n):
yield i, j, k
def generator_and_comprehension(n):
a = [i+j+k for i, j, k in generator_3(n) if i+j+k % 2 == 0]
return a
if __name__ == '__main__':
n = 250
for func in (simply_for_2, comprehension_2,
generator_case_2, generator_and_comprehension):
print("{}: {:.3f}sec.".format(func.__name__, calc(func, 3, n)))
simply_for_2: 4.445sec.
comprehension_2: 4.476sec.
generator_case_2: 4.380sec.
generator_and_comprehension: 7.460sec.
ひえっ( ᕦ 。 ᕤ)お、おしょい・・・!
まあ恐らく原因は、forが実質1つ増えてるというところでしょうかな……。
ジェネレータで入れ子forを分離してやるというのは、個人的には見た目すごいスッキリして好きなんだけどな…。
ちなみに、
def generator_and_for(n):
a = []
for i, j, k in generator_3(n):
if i+j+k % 2 == 0:
a.append(i+j+k)
return a
if __name__ == '__main__':
n = 250
for func in (generator_and_comprehension, generator_and_for):
print("{}: {:.3f}sec.".format(func.__name__, calc(func, 3, n)))
generator_and_comprehension: 7.505sec.
generator_and_for: 7.465sec.
なので、もうジェネレータ律速って感じがしますね。内包表記の速みはどこにいったのか……?
itertoolsや!
from itertools import product
def product_and_for(n):
a = []
for i, j, k in product(range(n), range(n), range(n)):
if i+j+k % 2 == 0:
a.append(i+j+k)
return a
def product_and_comprehension(n):
a = [i+j+k for i, j, k in product(range(n), range(n), range(n))
if i+j+k % 2 == 0]
if __name__ == '__main__':
n = 250
for func in (generator_and_comprehension, generator_and_for,
product_and_for, product_and_comprehension):
print("{}: {:.3f}sec.".format(func.__name__, calc(func, 3, n)))
いけー!ぷいきゅあがんばぇー!
generator_and_comprehension: 7.478sec.
generator_and_for: 7.412sec.
product_and_for: 4.933sec.
product_and_comprehension: 4.959sec.
!!
普通に回すよりは若干遅くなるものの、product の力が伺えますね。これが標準ライブラリ(というかC)のちからか…!
というわけで
単一 for なら内包表記で、入れ子なら itertools.product 使って単一 for で回すのがキレイにそこそこ速そう。
10.1. itertools — 効率的なループ実行のためのイテレータ生成関数 — Python 3.5.2 ドキュメント
ちなみに、product(range(n), range(n), range(n))
は product(range(n), repeat=3)
とも書けます。
ちなみに
単一 for でも、回したいシーケンスが range では上手く表現できないときは、内包表記渡すかジェネレータ式渡すか、ですが、メモリ的なこと考えるとジェネレータ式渡したほうがよさそうではある?
面倒なのでやりませんけどね!😇
てか
なんでこんなに内包表記が遅くなったんでしょ?
def if_for(n):
a = list()
for i in range(n):
if i % 2 == 0:
a.append(i)
return a
def if_comprehension(n):
a = [i for i in range(n) if i % 2 == 0]
return a
if __name__ == '__main__':
n = 10**7
for func in (if_for, if_comprehension):
print("{}: {:.3f}sec.".format(func.__name__, calc(func, 3, n)))
if_for: 2.967sec.
if_comprehension: 2.430sec.
ふむふむ。内包表記 if
いれるだけでこんなに時間が接近するとは。
もしかして、入れ子がダメなのか?
def for_for(n):
a = list()
for i in range(n):
for j in range(n):
a.append(i+j)
return a
def comp_comprehension(n):
a = [i+j for i in range(n) for j in range(n)]
return a
if __name__ == '__main__':
n = 3500
for func in (for_for, comp_comprehension):
print("{}: {:.3f}sec.".format(func.__name__, calc(func, 3, n)))
for_for: 3.454sec.
comp_comprehension: 2.177sec.
ほほう🤔約1.5倍なので、これは最初にやった単一forと変わらないですね。
内包表記、if 入れると遅くなるのか……?
結論
if はヤバイ(?)