Joblib
前回、tempfileモジュールで一時ファイル、一時ディレクトリを作成する方法を紹介しました。
今回はJoblibライブラリを使って簡単な並列処理を行う方法を紹介します。
並列処理に関してはこれまでMultiprocessingやThreading、Concurrentといったライブラリを使ってきました。
multiprocessingは一つの処理を複数のCPUに分散させる「マルチプロセス」で、Threadingは複数の処理を複数のCPUで同時に行わせる「マルチスレッド」でした。
concurrent.futuresはProcessPoolExecutorを使うと「マルチプロセス」、ThreadPoolExecutorを使うと「マルチスレッド」になるライブラリでした。
私の場合は複数の別の処理を同時に行わせることが多いので、concurrent.futuresのThreadPoolExecutorを使うことが多いです。
ただ最近Joblibというライブラリでもっと簡単に並列処理が行えるということだったので、とりあえず使ってみようというのが今回の発端です。
ちなみにJoblibで並列処理を行う場合のデフォルトは「マルチプロセス」のようです。
それでは始めていきましょう。
Joblibのインストール
まずはJoblibライブラリをpipを使ってインストールします。
pip install joblib
Joblibの使い方
Joblibを使うには、Joblibライブラリから「Parallel」と「delayed」をインポートします。
そして「Parallel(n_jobs=使用するCPU数)([delayed(関数)(要素) for 要素 in リスト])」とします。
これでリストの中から一つずつ要素を取り出し、関数に渡す処理が並列化されます。
出力は「変数 = Parallel(n_jobs=使用するCPU数)([delayed(関数)(要素) for 要素 in リスト])」とすることで変数に格納されます。
from joblib import Parallel, delayed
def square(i):
return i*i
data = Parallel(n_jobs=1)([delayed(square)(i) for i in range(100)])
print(data)
実行結果
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324,
361, 400, 441, 484, 529, 576, 625, 676, 729, 784, 841, 900, 961, 1024, 1089, 1156,
1225, 1296, 1369, 1444, 1521, 1600, 1681, 1764, 1849, 1936, 2025, 2116, 2209, 2304,
2401, 2500, 2601, 2704, 2809, 2916, 3025, 3136, 3249, 3364, 3481, 3600, 3721, 3844,
3969, 4096, 4225, 4356, 4489, 4624, 4761, 4900, 5041, 5184, 5329, 5476, 5625, 5776,
5929, 6084, 6241, 6400, 6561, 6724, 6889, 7056, 7225, 7396, 7569, 7744, 7921, 8100,
8281, 8464, 8649, 8836, 9025, 9216, 9409, 9604, 9801]
上記の例では関数は自分で作成していますが、他のライブラリの関数を使うこともできます。
from joblib import Parallel, delayed
import numpy as np
data = Parallel(n_jobs=1)([delayed(np.square)(i) for i in range(100)])
print(data)
実行結果
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324,
361, 400, 441, 484, 529, 576, 625, 676, 729, 784, 841, 900, 961, 1024, 1089, 1156,
1225, 1296, 1369, 1444, 1521, 1600, 1681, 1764, 1849, 1936, 2025, 2116, 2209, 2304,
2401, 2500, 2601, 2704, 2809, 2916, 3025, 3136, 3249, 3364, 3481, 3600, 3721, 3844,
3969, 4096, 4225, 4356, 4489, 4624, 4761, 4900, 5041, 5184, 5329, 5476, 5625, 5776,
5929, 6084, 6241, 6400, 6561, 6724, 6889, 7056, 7225, 7396, 7569, 7744, 7921, 8100,
8281, 8464, 8649, 8836, 9025, 9216, 9409, 9604, 9801]
処理速度の変化
この並列化によりどれくらい処理が速くなったのかをJupyter notebookのマジックコマンド「%%timeit」で処理時間を測定してみました。
使用するCPU数は1から3を検討しますが、私のMacBook Airはデュアルコアのため、2までしか高速化されないと考えられます。
%%timeit
from joblib import Parallel, delayed
def square(i):
return i*i
time_start = time.time()
data = Parallel(n_jobs=1)([delayed(square)(i) for i in range(10000)])
実行結果
294 ms ± 20.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%%timeit
from joblib import Parallel, delayed
def square(i):
return i*i
time_start = time.time()
data = Parallel(n_jobs=2)([delayed(square)(i) for i in range(10000)])
実行結果
64.4 ms ± 1.76 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%%timeit
from joblib import Parallel, delayed
def square(i):
return i*i
time_start = time.time()
data = Parallel(n_jobs=3)([delayed(square)(i) for i in range(10000)])
実行結果
71.2 ms ± 2.98 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
結果としてはCPU1つが294ms、2つが64.4ms、3つが71.2msとなり、CPU2つになると処理時間が短縮されますが、実際に搭載されているCPU数よりも多い3を指定した場合はCPU2つとほぼ同じ処理時間になりました。
ただCPU1つから2つへの変化が単純に速度が2倍となっているわけではない理由はよく分かりません。
とりあえずいつもの処理に少し加えるだけで処理時間が短縮できるので、要所要所で使っていくのはありかなと思います。
次回は日本地図を表示するjapanmapというライブラリを紹介します。
ではでは今回はこんな感じで。
コメント