はじめに
どうもMizokeiです。
最近Pythonにハマっており、Python中心の生活を送っているMizokeiです。
Pythonのコーディングに関する本を読んでいますと、「リスト内包表記」で書くと処理が速くなりエレガントであるという内容の記載がありました。
その日読んだ本に限らず、その他の本やネットでも、同様な話がありますが、単純に、
…本当に速いの?
と疑問になりました。几帳面な人が
1ナノ秒くらい速いだけで言ってるんじゃないの?
とも思いました。そりゃまあ、速いのは速いですけど・・・。みたいな。factベースですけど。みたいな。
そこで検証してみることにしました。本記事の実行環境は以下です。
実行環境
- Windows10 64bit
- Python 3.7
リスト内包表記とは?
検証に入る前に、「リスト内包表記」の説明を極めてザックリしておきます。初めて知ったり、分からなかったり、気になったりした人は、コーディング周りの難しそうな本を手にとって見るとよいかと思います(本当に中身が難しいかはわかりませんが)。
「リスト内包表記」を単純に説明すると、以下のようなループ文があったとき、
0 1 2 3 4 5 6 7 8 9 |
max_num = 10 normal_list = [] i = 0 while i < max_num: if i % 3 == 0: normal_list.append(i) i += 1 print(normal_list) |
以下のような表現で書くと、早く動くよ、といったものになります。
0 1 2 3 4 |
max_num = 10 expart_list = [i for i in range(max_num) if i % 3 == 0] print(expart_list) |
理由としては、構文で行っていた処理が、インタプリタ内部で実行できるようになるため、とのことです。
ちなみに、上記の2パターンのコードは、いずれも以下のリスト内容になります。
0 1 2 |
[0, 3, 6, 9] |
比較用関数を用意
リストにひたすら要素の数だけ追加する関数です。数を増やしながら確認したいため、引数にループの最大数を与えます。これを便宜的に「ノーマル表記」と名付けます。
ノーマル表記
0 1 2 3 4 5 6 |
#ノーマルループ def normal_loop(max_num): normal_list = [] for i in range(max_num): normal_list.append(i) |
リスト内包表記で記載したループ関数です。こちらも同様、ループの最大数を与えます。こちらも便宜的に「リスト内包表記」と名付けます。
リスト内包表記
0 1 2 3 4 |
#リスト内包表現ループ def expart_loop(max_num): expart_list = [i for i in range(max_num)] |
以下のコードを参考に処理時間を測定します。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#処理時間の測定 import time #処理時間の測定開始 start = time.time() # ここに時間を測定したい処理 #処理時間の測定終了 end = time.time() #処理時間計算 elapsed_time = end - start |
処理時間を測定
では、さっそく2パターンの関数での経過時間を測定してみます。あまりに少ないループ回数だと差が出ないと思われるため、10,000,000回のループを、1倍~10倍までの計10回で試してみました。
すると・・・
項目 | 平均値 |
---|---|
ノーマル表記 | 5.978 秒 |
リスト内包表記 | 4.352 秒 |
速度向上率 | 1.358 倍 |
試行回数10回とした場合の速度向上率の平均値は、
1.358倍!!
普通にループを行うよりは速いですね。
それぞれの実行時間を表にまとめてみます。
どうも、見ていると、試行回数が多くなっても、実行時間が同程度のものもあるようです。マシンのCPUやメモリの影響もあるかもしれません。
ノーマル表記
試行回数※ | 実行時間 |
---|---|
1回目 | 1.044 秒 |
2回目 | 2.071 秒 |
3回目 | 3.177 秒 |
4回目 | 4.065 秒 |
5回目 | 5.124 秒 |
6回目 | 6.260 秒 |
7回目 | 7.268 秒 |
8回目 | 9.254 秒 |
9回目 | 9.994 秒 |
10回目 | 11.527 秒 |
リスト内包表記
試行回数※ | 実行時間 |
---|---|
1回目 | 0.760 秒 |
2回目 | 1.632 秒 |
3回目 | 2.430 秒 |
4回目 | 3.013 秒 |
5回目 | 3.793 秒 |
6回目 | 4.674 秒 |
7回目 | 5.356 秒 |
8回目 | 6.375 秒 |
9回目 | 7.780 秒 |
10回目 | 7.707 秒 |
※試行回数1回目は10,000,000回のループ、それから試行回数分掛け算し、10回目には、100,000,000回のループを実行しています。
今回使用したソースコード全文
今回使用した、ソースコードの全文を記載しておきます。きれいなコードではないですが、参考となれば幸いです。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# -*- coding: utf-8 -*- ''' リスト内包表記について考える ''' # # 標準ライブラリ # #処理時間の測定 import time #最大ループ倍率 LOOP_MAX_NUM = 10 #最大ループ回数 LIST_MAX_NUM = 10000000 #実行時間の測定 def calc_processing_time(): #平均用 pattern_1_average_list = [] pattern_2_average_list = [] result_average_list = [] #10倍まで for max_num in range(1, LOOP_MAX_NUM + 1): # # パターン1 # #処理時間の測定開始 pattern_1_start = time.time() #パターン① 通常ループ normal_loop(LIST_MAX_NUM * max_num) #処理時間の測定終了 pattern_1_end = time.time() #処理時間計算 pattern_1_elapsed_time = pattern_1_end - pattern_1_start #ログ出力 output(max_num, (LIST_MAX_NUM * max_num), pattern_1_elapsed_time, 'ノーマル表記') #平均算出用 pattern_1_average_list.append(pattern_1_elapsed_time) # # パターン2 # #処理時間の測定開始 pattern_2_start = time.time() #パターン② 内包表現ループ expart_loop(LIST_MAX_NUM * max_num) #処理時間の測定終了 pattern_2_end = time.time() #処理時間計算 pattern_2_elapsed_time = pattern_2_end - pattern_2_start #ログ出力 output(max_num, (LIST_MAX_NUM * max_num), pattern_2_elapsed_time, 'リスト内包表現') #平均算出用 pattern_2_average_list.append(pattern_2_elapsed_time) #速度向上率 print('[{:2.0f}'.format(max_num) + ' 回目] [{:,}'.format(LIST_MAX_NUM * max_num)+'件のループ] 速度向上率 : \t\t\t{:2.3f}'.format(pattern_1_elapsed_time/pattern_2_elapsed_time) + ' 倍') #平均算出用 result_average_list.append(pattern_1_elapsed_time/pattern_2_elapsed_time) #改行 print() #平均値 #実行時間 print(' ~~~~~~ 結果発表! ~~~~~~ ') print() print('\tノーマル表記の平均\t{:2.3f}'.format(get_average(pattern_1_average_list)) + ' 秒') print('\tリスト内包表記の平均\t{:2.3f}'.format(get_average(pattern_2_average_list)) + ' 秒') #速度向上率 print('\t速度向上率の平均\t{:2.3f}'.format(get_average(result_average_list)) + ' 倍') print() print(' ~~~~~~ 結果発表! ~~~~~~ ') #ノーマルループ def normal_loop(max_num): normal_list = [] for i in range(max_num): normal_list.append(i) #リスト内包表現ループ def expart_loop(max_num): expart_list = [i for i in range(max_num)] #秒の出力 def output(num, loop_num, sec_time, message): #ログ出力 print ('[{:2.0f}'.format(num) + ' 回目] [{:,}'.format(loop_num) + '件のループ] ' + message + "(実行時間) : \t{:.3f}".format(sec_time) + " 秒") #平均値算出 def get_average(average_list): return sum(average_list) / len(average_list) #メイン処理 if __name__ == '__main__': #処理時間の測定 calc_processing_time() |
以下、実行時の出力ログです。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
[ 1 回目] [10,000,000件のループ] ノーマル表記(実行時間) : 1.044 秒 [ 1 回目] [10,000,000件のループ] リスト内包表現(実行時間) : 0.760 秒 [ 1 回目] [10,000,000件のループ] 速度向上率 : 1.374 倍 [ 2 回目] [20,000,000件のループ] ノーマル表記(実行時間) : 2.071 秒 [ 2 回目] [20,000,000件のループ] リスト内包表現(実行時間) : 1.632 秒 [ 2 回目] [20,000,000件のループ] 速度向上率 : 1.269 倍 [ 3 回目] [30,000,000件のループ] ノーマル表記(実行時間) : 3.177 秒 [ 3 回目] [30,000,000件のループ] リスト内包表現(実行時間) : 2.430 秒 [ 3 回目] [30,000,000件のループ] 速度向上率 : 1.308 倍 [ 4 回目] [40,000,000件のループ] ノーマル表記(実行時間) : 4.065 秒 [ 4 回目] [40,000,000件のループ] リスト内包表現(実行時間) : 3.013 秒 [ 4 回目] [40,000,000件のループ] 速度向上率 : 1.349 倍 [ 5 回目] [50,000,000件のループ] ノーマル表記(実行時間) : 5.124 秒 [ 5 回目] [50,000,000件のループ] リスト内包表現(実行時間) : 3.793 秒 [ 5 回目] [50,000,000件のループ] 速度向上率 : 1.351 倍 [ 6 回目] [60,000,000件のループ] ノーマル表記(実行時間) : 6.260 秒 [ 6 回目] [60,000,000件のループ] リスト内包表現(実行時間) : 4.674 秒 [ 6 回目] [60,000,000件のループ] 速度向上率 : 1.340 倍 [ 7 回目] [70,000,000件のループ] ノーマル表記(実行時間) : 7.268 秒 [ 7 回目] [70,000,000件のループ] リスト内包表現(実行時間) : 5.356 秒 [ 7 回目] [70,000,000件のループ] 速度向上率 : 1.357 倍 [ 8 回目] [80,000,000件のループ] ノーマル表記(実行時間) : 9.254 秒 [ 8 回目] [80,000,000件のループ] リスト内包表現(実行時間) : 6.375 秒 [ 8 回目] [80,000,000件のループ] 速度向上率 : 1.452 倍 [ 9 回目] [90,000,000件のループ] ノーマル表記(実行時間) : 9.994 秒 [ 9 回目] [90,000,000件のループ] リスト内包表現(実行時間) : 7.780 秒 [ 9 回目] [90,000,000件のループ] 速度向上率 : 1.285 倍 [10 回目] [100,000,000件のループ] ノーマル表記(実行時間) : 11.527 秒 [10 回目] [100,000,000件のループ] リスト内包表現(実行時間) : 7.707 秒 [10 回目] [100,000,000件のループ] 速度向上率 : 1.496 倍 ~~~~~~ 結果発表! ~~~~~~ ノーマル表記の平均 5.978 秒 リスト内包表記の平均 4.352 秒 速度向上率の平均 1.358 倍 ~~~~~~ 結果発表! ~~~~~~ |
まとめ
正直、1.01倍とかなんじゃないかと思っていましたが、結構速度向上が期待できそうですね。繰り返しますが、結果は、
1.358倍
でした。
この数値を速いと見るか、たいして変わらないと見るかは自由ですが、相当な件数をループする場合には効果がありそうです。
念の為、件数を減らして実行してみましたが、以下が結果でした。
100,000~1,000,000件のループの場合
項目 | 平均値 |
---|---|
ノーマル表記 | 0.057 秒 |
リスト内包表 | 0.041 秒 |
速度向上率 | 1.428 倍 |
減らした場合の速度向上率は平均で、
1.428倍
パフォーマンスとしては上々ですね。
ただ実際のところは、0.057秒かかったプログラムが、0.041秒になるくらいの速度だと、一般的なプログラムを書く程度には影響はしなさそうです。体感でわかりませんものね。早さを意識したいときには、使ってみるといいのではないでしょうか。
と、結論付ける結果でした。(チャンチャン
この記事が誰かの参考となれば幸いです。それでは。
関連記事
【Python】ピクセル値を操作して画像を作成してみよう【画像処理】
【Python×業務自動化】Excelを起動する【RPA】
【Python超入門】クラスを作成してみよう