/dev/null

脳みそのL1キャッシュ

multiprocessingとthreadingライブラリのベンチマーク

Pythonにはマルチプロセスを実現するためのmultiprocessingライブラリ,マルチスレッドを実現するためのthreadingライブラリがある.今回はこれらのライブラリを使って,CPU boundな処理とI/O boundな処理を実行した際の処理時間を計測した.

計測プログラム

以下の計測プログラムを書いた.I/O boundな処理として,1秒間sleepする関数を用意し,CPU boundな処理として,1から与えられた数までの和を計算する関数を用意した.

from multiprocessing import Process
from threading import Thread
from datetime import datetime
from random import randint
import sys
import time
import os

N = 3628800

def io(d):
    # ignore d
    time.sleep(1)

def cpu(d):
    n = 0
    while d > 0:
        n += d
        d -= 1

def bench(thpr, work, njob):
    f = cpu if work == "cpu" else io
    js = [thpr(target=f, args=(N//njob, )) for _ in range(njob)]

    s = datetime.now()
    for j in js:
        j.start()
    for j in js:
        j.join()
    e = datetime.now()
    d = e - s
    return d.total_seconds()

def main():
    if len(sys.argv) != 5:
        print(f"Usage: pythnon {sys.argv[0]} [thread|process] [cpu|io] NJOB NTRY")
        return

    thpr = sys.argv[1]
    work = sys.argv[2]
    njob = int(sys.argv[3])
    ntry = int(sys.argv[4])

    if thpr not in ["thread", "process"]:
        print("Only thread or process is allowed")
        return

    if work not in ["cpu", "io"]:
        print("Only cpu or io is allowed")
        return

    if njob <= 0:
        print("NJOB should be > 0")
        return

    if ntry <= 0:
        print("NTRY should be > 0")
        return

    thpr = Process if thpr == "process" else Thread
    result = sum([bench(thpr, work, njob) for _ in range(ntry)]) / ntry

    print(f"{result:.5f}")

if __name__ == "__main__":
    main()

以下のように使います.

$ python bench.py thread cpu 5 2

これは,5スレッドでCPU boundな処理を2回実行した際の処理時間の平均を出力してくれます.

測定結果

前述のプログラムを使って,以下の4つの場合について処理時間を測定した.

  • multiprocessing, I/O bound
  • multiprocessing, CPU bound
  • threading, I/O bound
  • threading, CPU bound

各ケースについて,プロセス数/スレッド数を1から10に変動させて,それぞれ20回測定した.測定結果は以下の通り.

multiprocessing, I/O bound

f:id:d2v:20200428012429p:plain

まあ,妥当ではという感想.逐次的に10回実行するなら10秒かかるところ,マルチプロセス(10プロセス)にすることで,約1秒で全タスクが終わる.

multiprocessing, CPU bound

f:id:d2v:20200428012449p:plain

1プロセスのときは0.6秒前後だったのが,2プロセスのときは0.3秒前後と約半分になった.しかし,そこで頭打ちを食らい,以降,プロセス数を増やしても性能向上せず.使っているマシンのCPUが2コアだから,こちらもまあ妥当では.

threading, I/O bound

f:id:d2v:20200428012500p:plain

multiprocessing, I/O boundとだいたい似た傾向.multiprocessing, I/O boundに比べて,threadingの場合はスレッド数を増やしても,処理時間が上がらず,比較的安定している.スレッドの方がコンテキストスィッチが軽量だから?

threading, CPU bound

f:id:d2v:20200428012511p:plain

なぜこうなった.PythonがGILのせいで,CPU boundな処理をマルチスレッドにしても性能向上が見込めないことはどこかで耳にしたことがあるが,性能劣化するとは思わなかった.やはり,GIL絡みなのか?要調査.

GILに関する良さげな文献1を見つけたのであとで読む.

参考文献