はじめに
お疲れ様です。新卒1年目のNodaです。
無事配属も決まり、いろいろ落ち着いてきました(たぶん)
そんな中、Pythonで文字列のリストを整数型に変換するというシンプルな処理に、色々なやり方があることに気づきました。
「結局どれが一番いいのか」と3秒くらい悩んだ結果
早ければ正義では?
という身も蓋もない結論に至ったので、今回はそのスピードを比較してみることにしました。
ついでにPythonのアウトプットとして、この記事にまとめておこうと思います。
比較する3つの方法
今回は、文字列のリストを整数型に変換するという処理を以下の3つの方法を用いて計算時間を計測していこうと思います。
- for文
空のリストを用意して、appendを用いて文字列リストを整数型に変更したものを代入していく方法 - 内包表記
Python独自の記法。空のリストを用意しなくても実行することが可能。
知らない方は以下のサイトを参考にしてください
https://aiacademy.jp/media/?p=1252 - map表記
関数型っぽい書き方。配列の全要素に関数を適用することが可能
知らない方は以下のサイトをご参考にしてください
https://www.akkodis.co.jp/candidate/insight/column_127
実験方法
今回は、進捗可視化のためのtqdm、実行時間計測のtime、平均を求めるstatisticsを用いて行います。
from tqdm import tqdm
import time
import statistics
手順は以下の通りです。
- 要素数がNの文字列リストを用意する
- 1から3の処理をそれぞれX回行い、それぞれの平均実行時間を計測する
実際に利用したコードは以下のようになっています。
def run_all():
str_list = [str(i) for i in range(10000000)] # 100万件の文字列
REPEAT = 50
# for文
for_times = []
for _ in range(REPEAT):
start = time.time()
int_list = []
for s in tqdm(str_list, desc="for文", leave=False):
int_list.append(int(s))
for_times.append(time.time() - start)
# 内包表記
comp_times = []
for _ in range(REPEAT):
start = time.time()
int_list = [int(s) for s in tqdm(str_list, desc="内包表記", leave=False)]
comp_times.append(time.time() - start)
# map関数
map_times = []
for _ in range(REPEAT):
start = time.time()
int_list = list(tqdm(map(int, str_list), total=len(str_list), desc="map関数", leave=False))
map_times.append(time.time() - start)
# 結果出力
print("\n--- 平均処理時間 ---")
print(f"for文: {statistics.mean(for_times):.4f}秒")
print(f"内包表記: {statistics.mean(comp_times):.4f}秒")
print(f"map関数: {statistics.mean(map_times):.4f}秒")
if __name__ == "__main__":
run_all()
結果発表
ではでは、気になる結果発表です。
① N(文字列リストの大きさ)を変化させた場合(X = 50)
まずは、文字列のリストの大きさを徐々に変化させて、それぞれの実行時間を比較してみました。
N | 10 | 100 | 1000 | 5000 | 10⁴ | 10⁵ | 10⁶ | 10⁷ |
---|---|---|---|---|---|---|---|---|
for | 0.0004 | 0.0005 | 0.0008 | 0.0014 | 0.0025 | 0.0207 | 0.1981 | 1.9356 |
内包 | 0.0002 | 0.0002 | 0.0004 | 0.0011 | 0.0025 | 0.0193 | 0.1861 | 1.8646 |
map | 0.0002 | 0.0002 | 0.0005 | 0.0011 | 0.0022 | 0.0170 | 0.1626 | 1.5028 |
傾向まとめ
- サイズが大きくなるほど差が顕著に
- for < 内包 < map の順で高速化
実際、リストサイズが100万件を超えるあたりから、mapの速度が頭ひとつ抜け出してきました。
② X(実行回数)を変化させた場合(N = 10⁵)
X | 5 | 8 | 10 | 15 | 50 | 100 | 500 | 1000 |
---|---|---|---|---|---|---|---|---|
for | 0.2023 | 0.2026 | 0.2035 | 0.1990 | 0.1975 | 0.1957 | 0.1945 | 0.1982 |
内包 | 0.1898 | 0.1913 | 0.1887 | 0.1888 | 0.1872 | 0.1846 | 0.1886 | 0.1874 |
map | 0.1622 | 0.1627 | 0.1608 | 0.1604 | 0.1754 | 0.1589 | 0.1632 | 0.1613 |
傾向まとめ
- 実行回数が増えると、全体的に時間が安定してくる
- for < 内包 < map の順で高速化
まとめ
結果的に実行速度は以下のように早くなっていきます。
for文 < 内包表記 < map
今回の調査でリストの操作は、実行時間の観点ではfor文よりも内包表記やmapを使えばいいのではと感じました。
しかし、実際に調べた結果そう簡単な問題ではないようです。
1. for文と比較して内包表記では可読性が落ちる
Pythonに慣れていない人にとっては一行でまとめられる内包表記よりfor文の方が読みやすい
2. 内包表記ではfor文のインデントに惑わされることがなくなる
Pythonはインデントの違いで全く違う動作をすることがあるので、for文が深くなった際ミスが多くなる
3. mapが常に最速とは限らない
複雑な条件分岐を行う際は、内包表記の方が早くなることもある(ChatGPT)
おわりに
今回の記事はまるで大学のレポートを書いているような気分になりました。
普段なんとなく書いているコードも色々検証・調査してみると発見があるんだなと感じるいい機会になりました。まだまだPython初心者ですが、これからもPythonに肩までゆっくり浸かっていこうと思います。
この記事が、同じような環境でハマっている方の助けになれば幸いです。
ご覧いただきありがとうございました。
参考サイトまとめ