2
2

More than 1 year has passed since last update.

[Python3] 2重loopをitertools.productで置き換えて、速度が遅くならないか確かめた

Last updated at Posted at 2022-07-26

経緯

  1. 2重ループを廃止して可読性を上げたい
    ※ループがネストしていると単体テストを書きにくい、という問題もある
  2. しかし、itertools.product()は遅いという情報を入手した

「2.」の情報元は以下の記事

リファクタリングしたせいで処理が遅くなったら困るので、どのくらい遅くなるのか、自分で確かめることにした。

環境

測定は、お手軽に試せる Google Colaboratory 上で行いました。

colaboratory
!python --version
Python 3.7.13

調査 part.1 (2重ループ)

比較対象

  1. 2重forループ
  2. itertools.productに対するループ

ソースコード

1. ループ対象

まず、ループ対象になる配列を生成しておく。
これをループさせる。(今回はrangeとした)

colaboratory
ran1 = range(1, 10000)
ran2 = range(1, 500)

実際の運用で想定されるのよりも、ちょっと多めのloop数にしておいた。

2. 2重ループ

colaboratory
%%timeit -r 5 -n 20

cnt = 0
for i in ran1:
    for j in ran2:
        cnt += i * j

3. itertools.product()のループ

colaboratory
%%timeit -r 5 -n 20

import itertools

cnt = 0
iterator = itertools.product(ran1, ran2)
for i, j in iterator:
    cnt += i * j

結果

ループ種別 測定結果 測定結果詳細
2重ループ 505 ms 20 loops, best of 5: 505 ms per loop
itertools.product()ループ 556 ms 20 loops, best of 5: 556 ms per loop

んーーーー10%の遅延??
大差ないじゃんかーーーもー(´^ω^`)

stackoverflowにビビらされただけでした。

調査 part.2 (5重ループ)

...で、終わったら記事として物足りないので、多重ループさせた場合の比較もしてみることにした。
stackoverflowは2重じゃなくて多重ループを取り上げてましたしね。

別に目の前の仕事では必要なかったんですけど、、、いつか使うかもしれないし。。。いや、使いたくないけど(。ŏ﹏ŏ)

比較対象

  1. 5重forループ
  2. itertools.productに対するループ

ソースコード

1. ループ対象

colaboratory
ran1 = range(1, 10)
ran2 = range(1, 5)
ran3 = range(1, 21)
ran4 = range(1, 18)
ran5 = range(1, 6)

ループ数は適当

2. 5重ループ

colaboratory
%%timeit -r 5 -n 20
cnt = 0
for i in ran1:
    for j in ran2:
        for k in ran3:
            for l in ran4:
                for m in ran5:
                    cnt += i + j + k + l + m

3. itertools.product()のループ

colaboratory
%%timeit -r 5 -n 20
import itertools

cnt = 0
iterator = itertools.product(ran1, ran2, ran3, ran4, ran5)
for i, j, k, l, m in iterator:
    cnt += i + j + k + l + m

結果

ループ種別 測定結果 測定結果詳細
2重ループ 52.5 ms 20 loops, best of 5: 52.5 ms per loop
itertools.product()ループ 63.7 ms 20 loops, best of 5: 63.7 ms per loop

ループのネスト数が増えたら、確かに遅延割合も大きくなった。
2重のネスト程度なら10%遅延で済むけど、5重ネストだと20%程度遅延することもあるみたい。

考察

itertools.productの導入に当たっては、ループ数とループのネスト数に注意する必要があるのではないかと思う。

たとえばループ内の処理がそもそも重い処理(ファイル出力とか画像読み込みとか?)で、ループ数もネスト数も少ない場合は、この遅延はそんなに気にならないはず

理由としては、重い処理自体が更に遅くなるわけではないからである。
あくまでも、1ループごとに余計なオフセットがうっすら上乗せされるだけなので、loopのネストをitertools.productに置き換えたところで、ループ数もネスト数も少なければ、誤差程度の遅延が発生するだけだろう。
たとえば、2時間かかってた処理が2時間3分になるとかその程度だと思われる。

逆に、ループ内の処理ではなくて、ループ数やネスト数が膨大なために処理時間がかかっているような場合は、今回の調査結果のように10%や20%の遅延が発生するので、itertools.productに置き換えるべきかどうかは落ち着いて考えた方が良いかもしれない。

また、5重以上のループの場合は当然20%以上の遅延が発生すると思われるので、注意が必要だろう。
stackoverflowに掲載されていた8重ループの例では、60%ほどの遅延が発生していた。

補足

前記stackoverflowの回答のうち、この人の回答

に従えば遅延を防げるのかもしれないけど、今回の当初の目的は可読性を上げることだったので、なかなか理解しにくいこの実装は候補から除外した。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2