Pythonシリアル演算、パラレル演算、マルチスレッド、マルチプロセス比較実験

3641 ワード

Pythonはマルチコアプロセッサの性能を発揮できない(GILに制限されているそうで、ロックされているのは1つのCPUコアしか使えない、これについてはここに記事がある)が、Pythonのmultiprocessing(マルチプロセス)モジュールやパラレル演算モジュール(例えばpprocess)でマルチコアに使用できる.
テストコードは以下の通りで、プログラムはシリアル演算、パラレル演算、マルチスレッドとマルチプロセスが同じ関数を実行するのにかかる時間をそれぞれテストした.
#! /usr/local/bin/python2.7
# test.py

import time
import pprocess #       linux   
import threading
from multiprocessing import Process 

def takeuptime(n):
    chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
    s = chars * 1000
    for i in range(10*n):
        for c in chars:
            s.count(c)

if __name__ == '__main__':
    list_of_args = [1000, 1000, 1000, 1000]

    # Serial computation
    start = time.time()
    serial_results = [takeuptime(args) for args in list_of_args]
    print "%f s for traditional, serial computation." % (time.time() - start)

    # Parallel computation
    nproc = 4 # maximum number of simultaneous processes desired
    results = pprocess.Map(limit=nproc, reuse=1)
    parallel_function = results.manage(pprocess.MakeReusable(takeuptime))
    start = time.time()
    # Start computing things
    for args in list_of_args:
        parallel_function(args)
    parallel_results = results[:]
    print "%f s for parallel computation." % (time.time() - start)

    # Multithreading computation
    nthead = 4 # number of threads
    threads = [threading.Thread(target=takeuptime, args=(list_of_args[i],)) for i in range(nthead)]
    start = time.time()
    # Start threads one by one
    for thread in threads:
        thread.start()
    # Wait for all threads to finish
    for thread in threads:
        thread.join()
    print "%f s for multithreading computation." % (time.time() - start)


    # Multiprocessing computation
    process = []
    nprocess = 4 # number of processes
    for i in range(nprocess):
        process.append(Process(target=takeuptime, args=(list_of_args[i],)))
    start = time.time()
    # Start processes one by one
    for p in process:
        p.start()
    # Wait for all processed to finish
    for i in process:
        p.join()
    print "%f s for multiprocessing computation." % (time.time() - start)

実行結果は次のとおりです.
[root@localhost test]# python test.py
62.452934 s for traditional, serial computation.
20.665276 s for parallel computation.
64.835923 s for multithreading computation.
18.392281 s for multiprocessing computation.
テスト結果から,並列演算とマルチプロセス計算の速度はシリアル計算とマルチスレッド計算よりも著しく速いことが明らかになった.
 
ここで質問ですが、なぜマルチスレッドの時間がシリアル単一スレッドより少ないのでしょうか(64.873760>62.452934).
私たちの通常の経験によると、マルチスレッドは単一スレッドよりも速いに違いありません.なぜテスト結果はそうではありませんか.
前述したように、Pythonは1つのCPUコアしか使用できないため、マルチスレッドであっても同じ時間にCPUは1つのスレッド演算しか処理できず、複数のスレッドは並列に動作せず、交代で切り替えて実行される.
したがって、マルチスレッドは、スレッドにデータダウンロードがあり、データの戻りを待つ間にスレッドがブロックされるなど、スレッドにブロックが発生する場合にのみ意味があり、CPUは他のスレッドの演算を処理することができる.
上記のテストプログラムのtakeuptime()関数はブロックされず、演算を続けているので、マルチスレッドとシングルスレッドの効果は同じです(スレッド切り替えにも時間がかかるので、このときマルチスレッドがかかる場合はシングルスレッドよりも多くなります).
並列演算とマルチプロセス演算が速いのは,複数のCPUコアを同時に利用し,複数のデータ演算を同時に行うことができるからである.
takeuptime()関数をブロックに変更し、テストします.
def takeuptime(n):
    def download(url):
        # simulate downloading
        time.sleep(2)
    for i in range(5):
        html = download('http://www.redicecn.com/page%d.html' % i)

新しい実行結果は次のとおりです.
[root@localhost test]# python test.py
39.996438 s for traditional, serial computation.
10.003863 s for parallel computation.
10.003480 s for multithreading computation.
10.008936 s for multiprocessing computation.
ブロックされたデータ処理中にマルチスレッドの役割が顕著であることがわかる.