1. hoto17296
Changes in body
Source | HTML | Preview
@@ -1,43 +1,96 @@
些細なことでも [multiprocessing](https://docs.python.org/ja/3/library/multiprocessing.html) 使うと便利だよ、という小ネタ。
## やりたいこと
``` python
from glob import glob
files = glob('data/*.csv')
-len(files) # 40000
+len(files) # 10000
```
-この 4万件の CSV ファイルを Pandas DataFrame として読み込みたい。
+この 1万件の CSV ファイルを Pandas DataFrame として読み込みたい。
+
+ちなみに検証用のデータは以下のようにして生成した。
+
-## 普通に読み込む
``` python
+import numpy as np
import pandas as pd
-arr = [pd.read_csv(f) for f in files]
+row_n = 10000
+col_n = 3
+
+columns = [f'col{i}' for i in range(col_n)]
+for i in range(10000):
+ df = pd.DataFrame(np.random.randn(row_n, col_n), columns=columns)
+ df.to_csv(f'data/{i:04}.csv', index=False)
```
-**約 59 秒** かかった。
+## 並列化の手法いくつか
+今回は
+
+- 並列化なし (シングルスレッド)
+- マルチスレッド
+- マルチプロセス
-## 並列読み込む
-[multiprocessing](https://docs.python.org/ja/3/library/multiprocessing.html) を使ってやってみる
+比較した
-`p.map` には `pd.read_csv` を直接渡したり `lambda` を渡したりすることはできないっぽいので、 `read_csv` という関数を定義している
+ちなみに asyncio (イベントループ) については、ファイルの非同期読み込みが標準モジュールだけではできなさそうだったので試さなかった。 aiofiles などの外部モジュールを使えばできると思うので、誰か試してみてほしい。
+
+### 並列化なし (シングルスレッド)
+``` python
+import pandas as pd
+
+arr = [pd.read_csv(f) for f in files]
+```
+
+### マルチプロセス
+[multiprocessing.Pool](https://docs.python.org/3/library/multiprocessing.html#multiprocessing.pool.Pool) を使う
``` python
import pandas as pd
from multiprocessing import Pool
def read_csv(f):
return pd.read_csv(f)
with Pool() as p:
arr = p.map(read_csv, files)
```
-**約 15 秒** で終わった
+`p.map` には `pd.read_csv` を直接渡したり `lambda` を渡したりすることはできないので、 `read_csv` という関数を定義している。
+
+`Pool()` で並列数を指定しなかった場合は CPU のコア数と同じ並列数で実行される。
+
+### マルチスレッド
+[multiprocessing.dummy.Pool](https://docs.python.org/3/library/multiprocessing.html#module-multiprocessing.dummy) は threading を使っているとのことなので、これを使ってみる。
+
+``` python
+import pandas as pd
+from multiprocessing.dummy import Pool
+
+with Pool() as p:
+ arr = p.map(pd.read_csv, files)
+```
+
+マルチスレッドの場合は `pd.read_csv` をそのまま渡せる。
+
+## 結果
+4コア CPU のマシンで計測した結果が以下。
+
+|| 読み込み速度 |
+|---|---|
+| 並列化なし (シングルスレッド) | 82 sec |
+| マルチスレッド | 28 sec |
+| マルチプロセス | 22 sec |
+
+マルチプロセスが一番速かった。
+
+4並列で読み込んで4倍速くなるとまではいかなかったが、並列化の恩恵を十分に感じられる程度には速くなった。
+
+### マルチスレッドの注意点
+今回はマルチスレッドもマルチプロセスとほぼ同じくらいには速くなったので、手軽さを重視してマルチスレッドの方を採用してもいいケースもあるかもしれない
-`Pool` は並列数を指定しなければ CPU のコア数で並列処理してくれるはずだけど、12 コア積んだマシンで実行しても 12 倍速くなるわけではなかった
+ただし Python (CPython) のマルチスレッドは [GIL (Global Interpreter Lock)](https://wiki.python.org/moin/GlobalInterpreterLock) の制約があり、複数のスレッドが同時に Python バイトコードを実行することができない
-しかし 4 倍速くなっただけでも十分ありがたい
+そのため、今回のファイル読み込みのような I/O バウンドな処理は高速化できるかもしれないが、例えば「読み込んだ各データに対してなんらかの重い処理を行う」などの CPU バウンドな処理を併せて実行した場合はおそらく今回ほど速くならない。そのようなケースではマルチプロセスで並列化した方が高速化できると思われる