前置き
データ解析や機械学習においてpythonは、もはや手放すことのできない言語であります。
しかし、相手となるデータの容量はすさまじいものがあります。
今回のアドベントカレンダーで、
Pythonでcsvをいじくりまわす①
Pythonでcsvをいじくりまわす②
と記事を書いてきましたが、「できないことをできるようにする」だけでなく、
「一応できていることをもっと速く、快適にできるようにする」という意識も必要となってきます。
なぜ処理速度にこだわるか
最近上司からの説教にもありました。
「大量データ処理において、1つの処理の所要時間を50ミリ秒→45ミリ秒に減らせると、それのイテレーション分だけ全体の所要時間を短くできる」と。
WEBアプリケーションなんかでは、ページ遷移の速度が5ミリ秒改善しても「ふーん」って感じかもしれませんが、データ解析等においては事情が変わってきます。
単純な話、2時間かかっていた処理を30分で終わらせられるようになると、これまでの4倍も動作確認にトライできるようになります。
それってすごい話ですよね。
というわけで、↑の過去記事で紹介していたサンプルのDataFrame(10列10行)を、巨大化(10列1,000,000行)させて
- 生Python
- csvモジュール
- Pandas
の3パターンで実行時間、メモリ消費量をはかってみました。(memory_profilerを使用)
処理の内容は、巨大csvを読み込んで、別ファイルにそっくりそのまま中身を移す処理です。
先に言っておくと、じっくり考察できなかったので結果だけです。すみません。
これはアドベントが終わった後にでも時間とって考えてみたいと思います。
生Python
from datetime import datetime
from tqdm import tqdm
@profile
def main():
with open('data/sample.csv', mode='r') as f:
with open('data/normal_output.csv', mode='w') as output:
for line in tqdm(f):
output.write(line)
if __name__ == '__main__':
start = datetime.now()
main()
print ("elapsed_time:{0}".format(datetime.now() - start))
"""
10000001it [31:12, 5340.53it/s]
elapsed_time:0:31:12.492195
Filename: normal.py
Line # Mem usage Increment Line Contents
================================================
4 13.340 MiB 13.340 MiB @profile
5 def main():
6 13.340 MiB 0.000 MiB with open('data/sample.csv', mode='r') as f:
7 13.340 MiB 0.000 MiB with open('data/output.csv', mode='w') as output:
8 13.695 MiB 0.355 MiB for line in tqdm(f):
9 13.695 MiB 0.000 MiB output.write(line)
"""
処理時間31分。
memory_profilerを使うとやや処理速度が落ちるそうですが、目安にはなるかなと。
では次。
csvモジュール
from datetime import datetime
from tqdm import tqdm
import csv
@profile
def main():
with open('data/sample.csv', mode='r') as f:
with open('data/csv_module_output.csv', mode='w') as output:
reader = csv.reader(f)
for row in tqdm(reader):
i = 0
for element in row:
output.write(element)
i += 1
if i != len(row):
output.write(',')
output.write('\n')
if __name__ == '__main__':
start = datetime.now()
main()
print ("elapsed_time:{0}".format(datetime.now() - start))
"""
10000001it [22:57:55, 120.96it/s]
elapsed_time:22:57:55.272260
Filename: csv_module.py
Line # Mem usage Increment Line Contents
================================================
5 13.695 MiB 13.695 MiB @profile
6 def main():
7 13.695 MiB 0.000 MiB with open('data/sample.csv', mode='r') as f:
8 13.695 MiB 0.000 MiB with open('data/csv_module_output.csv', mode='w') as output:
9 13.695 MiB 0.000 MiB reader = csv.reader(f)
10 16.090 MiB 0.082 MiB for row in tqdm(reader):
11 16.090 MiB 0.000 MiB i = 0
12 16.090 MiB 0.305 MiB for element in row:
13 16.090 MiB 1.477 MiB output.write(element)
14 16.090 MiB 0.531 MiB i += 1
15 16.090 MiB 0.000 MiB if i != len(row):
16 16.090 MiB 0.000 MiB output.write(',')
17 16.090 MiB 0.000 MiB output.write('\n')
forが入れ子になっている部分でややメモリ消費量が増えている。
…っていうか処理時間がすごい。14時間。なんなの。
夜寝るまえに処理走らせたのに朝終わってなかった恐怖。
これは考察対象にしよう。このままでは、csvモジュールが役立たずみたいになってしまう。
もし変な処理になっていたら突っ込みください。おねげーしますだ。
Pandas
最後。
from datetime import datetime
from tqdm import tqdm
import pandas as pd
@profile
def main():
reader = pd.read_csv('data/sample.csv', chunksize=1000)
header = True
for row_chunk in tqdm(reader):
row_chunk.to_csv('data/pandas_output.csv', mode='a', header=header, index=False)
if header:
header = False
if __name__ == '__main__':
start = datetime.now()
main()
print ("elapsed_time:{0}".format(datetime.now() - start))
chunksizeを1000にして実行。
pandasのみ、chunksizeを1000、5000、10000それぞれ変えた状態で実行しました。
ログはファイル出力してあるので記事上では割愛。
今回のソースやログはここにコミットしてあるので自由に覗いていってください。
こいつらもいずれkwsk考察してみようと思います。
お疲れ俺。
## えっもう終わり?
ごめんなさいごめんなさいごめんなさい。
本腰据えて比較しようとすると思った以上にやることが増えてしまったので、一回整理してちゃんと記事に上げようと思います。
それでは。