pythonマルチスレッド、マルチプロセスをベンチマーキングして見た
pythonの並列処理について
pythonの並列処理について勉強しました、どうやらCPythonのGIL(Global Interpreter Lock)がボトルネックになる場合があります。David Beazley氏がこの問題について詳しく説明してくれました。今回はベンチマーキングを通して実際の動作を体験して見たいです。
再現したい問題
ざっくり:マルチスレッドまたはマルチプロセスで同じワークロード(ある量の計算)を分散させたら、シリアル(1スレッド)よりスピードが遅い
pythonのコードはデフォルトでシングルスレッドのpythonインタープリタープロセス内で実行されます。Threadingモジュールを使ってマルチスレッド実行の場合、スレッドスケジューリングする時GILを用います。GILを獲得できたスレッドのみが実行できます。スレッドが一定期間走ったら、GIL待ちの他のスレッドの存在をチェックし、あったら譲ります、なかったら続行します。python3.2以前の実装だと、GILの移譲にすれ違いが多く、シングルスレッドよりパーフォマンスが悪いことが目立ちます。python3.2以後GILが改良されました。また、複数cpuを並列して利用できるmultiprocessingモジュールもありますので、合わせてテストしてみたいです。
ベンチマークプラン
同じワークロードに対して
1. python2でシリアルで実行する
2. python2でマルチスレッドで実行する
3. python2でマルチプロセスで実行する
4. python3でシリアルで実行する
5. python3でマルチスレッドで実行する
6. python3でマルチプロセスで実行する
各プランでの実行時間を比較します。
環境:
開発環境 | 設定 |
---|---|
Mac OS: | Sierra |
Processer | 2.4 GHz Intel Core i5(4cores) |
Memory | 16 GB 1600 MHz DDR3 |
python2 | 2.7.10 |
python3 | 3.6.2 |
使用するモジュール:
from threading import Thread
from multiprocessing import Pool
from timeit import default_timer
準備に入ります
まずはワークロードを用意します。
def workload():
"""
provide CPU pound workload
"""
counter = 1000000
while counter > 0:
counter -= 1
次にテストしたい並列度を用意します
settings = [2 ** i for i in range(9)]
>>> [1, 2, 4, 8, 16, 32, 64, 128, 256]
同じワークロードをシリアルあるいは並列処理に振り分ける時、例えばsetting=16の時、シリアルプランでは連続16回workload()を実行し、総実行時間を指標に取ります。マルチプランでは16スレッドあるいは16プロセスに振り分け、振り分け開始からワーク完了までの実行時間を指標に取ります。
シリアル実行関数:
def serializing(n):
"""
workloading one by one/serially
"""
start = default_timer()
for _ in range(n):
workload()
return (default_timer() - start)
マルチスレッド実行関数
def threading(n):
"""
dispatch workload to n thread
"""
start = default_timer()
thread_pool = []
for _ in range(n):
t = Thread(target=workload, args=())
t.start()
thread_pool.append(t)
for t in thread_pool:
t.join()
return (default_timer() - start)
マルチプロセス実行関数
def multiprocessing(n):
"""
dispatch workload to n process
"""
start = default_timer()
process_pool = Pool(processes=n, )
process_pool.apply(workload)
process_pool.close()
process_pool.join()
return (default_timer() - start)
実行に入ります
python2、python3で3プランずつ実行し、実行時間(秒単位)を集めてチャートにして見ました。
python2のベンチマーキング結果
驚くことに(予想通りではありますが)、マルチスレッド実行する場合、実行時間がシリアル実行よりもずっと遅いとわかりました。マルチプロセス実行の場合うまくワークロードを分散できて、シリアル実行よりずっと早いとわかりました。
python3のベンチマーキング結果
python3(3.2以後)のGILが改善され、python2のようにマルチスレッド実行の方がシリアル実行より何倍も遅いという現象は解消されました。それでもシリアル実行より早いことはありません(困ったもの)。
マルチプロセス実行に至っては相変わらず優秀です。
python2とpython3の結果を比較してみた
なんてこと、シリアル実行(普通にコードを実行)する場合、python3の方が逆に遅いです。それも安定してpython2より倍遅いです。プロダクションの場合どうしましょうか。
まとめ
今回のベンチマーキングをとしてわかったこと(上記設定のワークロードに対して)。
- シリアル実行: python3がpython2より倍遅い
- マルチスレッド実行: python2ではシリアル実行より数倍遅いですが、 python3ではシリアル実行に近い性能を出せます。
- マルチプロセス実行: 全体的優秀 (ワークロードが少ない時にシリアル実行より遅い場合もありえます)
追記:今回のワークロードは計算しかないのでマルチスレッドを使うほどダメになりますが、IO処理が入った場合マルチスレッド実行の方が早いこともあります。
Author And Source
この問題について(pythonマルチスレッド、マルチプロセスをベンチマーキングして見た), 我々は、より多くの情報をここで見つけました https://qiita.com/pumbaacave/items/942f86269b2c56313c15著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .