web-dev-qa-db-ja.com

Pythonマルチプロセッシングを使用した通信でのOSXとLinux間のパフォーマンスの不一致

私はPythonのmultiprocessingモジュールについてもっと学び、プロセス間の通信のためのさまざまな手法を評価しようとしています。 Pipe配列を転送するためのQueueArray、およびmultiprocessing(すべてnumpyから)のパフォーマンスを比較するベンチマークを作成しましたプロセス。完全なベンチマークは ここ で見つけることができます。 Queueのテストの抜粋は次のとおりです。

def process_with_queue(input_queue, output_queue):
    source = input_queue.get()
    dest = source**2
    output_queue.put(dest)


def test_with_queue(size):

    source = np.random.random(size)

    input_queue = Queue()
    output_queue = Queue()

    p = Process(target=process_with_queue, args=(input_queue, output_queue))
    start = timer()
    p.start()
    input_queue.put(source)
    result = output_queue.get()
    end = timer()

    np.testing.assert_allclose(source**2, result)

    return end - start

Linuxラップトップでこのテストを実行したところ、配列サイズが1000000の場合に次の結果が得られました。

Using mp.Array: time for 20 iters: total=2.4869s, avg=0.12435s
Using mp.Queue: time for 20 iters: total=0.6583s, avg=0.032915s
Using mp.Pipe:  time for 20 iters: total=0.63691s, avg=0.031845s

Arrayは共有メモリを使用し、おそらくピクルスを必要としないため、パフォーマンスが非常に悪いのを見て少し驚きましたが、numpyには制御できないコピーがあるはずだと思います。

ただし、Macbookで同じテスト(配列サイズ1000000に対して)を実行したところ、次の結果が得られました。

Using mp.Array: time for 20 iters: total=1.6917s, avg=0.084587s
Using mp.Queue: time for 20 iters: total=2.3478s, avg=0.11739s
Using mp.Pipe:  time for 20 iters: total=8.7709s, avg=0.43855s

もちろん、システムが異なればパフォーマンスも異なるため、実際のタイミングの違いはそれほど驚くべきことではありません。 isが驚くべきことは、相対的なタイミングの違いです。

これを説明できるのは何ですか?これは私にとってかなり驚くべき結果です。 LinuxとWindowsの間、またはOSXとWindowsの間でこのような大きな違いが見られても驚くことではありませんが、これらはOSXとLinuxの間で非常によく似た動作をするだろうと思いました。

この質問 WindowsとOSXのパフォーマンスの違いに対処します。これはより期待されているようです。

26
ddavella

TL; DR:LinuxではCライブラリを呼び出すと配列の速度が低下するため、OSXは配列の方が高速です

Arrayからmultiprocessingを使用すると、 CタイプPythonライブラリ を使用してC呼び出しを行い、配列のメモリを設定します。これは、OSXよりもLinuxの方が比較的時間がかかります。 pypyを使用してOSXでこれを観察することもできます。メモリの設定は、OSXでpython3を使用する(Clangを使用する)よりも、pypy(およびGCCとLLVM)を使用する方がはるかに時間がかかります。

TL; DR:WindowsとOSXの違いは、マルチプロセッシングが新しいプロセスを開始する方法にあります

主な違いは、multiprocessingの実装にあります。これは、OSXではWindowsとは異なる動作をします。最も重要な違いは、multiprocessingが新しいプロセスを開始する方法です。これを行うには、spawnfork、またはforkserverの3つの方法があります。 Windowsでのデフォルト(およびサポートされている)の方法はspawnです。 * nix(OSXを含む)でのデフォルトの方法はforkです。これは、multiprocessingドキュメントの コンテキストと開始メソッド セクションに記載されています。

結果が異なるもう1つの理由は、反復回数が少ないことです。

反復回数を増やし、時間単位ごとに処理される関数呼び出しの数を計算すると、3つのメソッド間で比較的一貫した結果が得られます。

さらなる分析:cProfileで関数呼び出しを見てください

timeitタイマー関数を削除し、コードをcProfileプロファイラーでラップしました。

このラッパー関数を追加しました:

_def run_test(iters, size, func):
    for _ in range(iters):
        func(size)
_

そして、main()のループを次のように置き換えました。

_for func in [test_with_array, test_with_pipe, test_with_queue]:
    print(f"*** Running {func.__name__} ***")
    pr = cProfile.Profile()
    pr.enable()
    run_test(args.iters, args.size, func)
    pr.disable()
    ps = pstats.Stats(pr, stream=sys.stdout)
    ps.strip_dirs().sort_stats('cumtime').print_stats()
_

OSXの分析-Linuxとアレイの違い

私が見ているのは、キューはパイプよりも高速であり、パイプは配列よりも高速です。プラットフォーム(OSX/Linux/Windows)に関係なく、キューはパイプより2〜3倍高速です。 OSXとWindowsでは、PipeはArrayよりも約1.2倍と1.5倍高速です。しかし、Linuxでは、PipeはArrayよりも約3.6倍高速です。言い換えると、Linuxでは、ArrayはWindowsやOSXよりも比較的低速です。これはおかしい。

CProfileデータを使用して、OSXとLinuxのパフォーマンス比を比較しました。 _sharedctypes.py_のArrayRawArrayの2つの関数呼び出しに時間がかかります。これらの関数は、配列シナリオでのみ呼び出されます(パイプまたはキューでは呼び出されません)。 Linuxでは、これらの呼び出しはほぼ70%の時間かかりますが、OSXでは42%の時間しかかかりません。したがって、これが大きな要因です。

コードに ズームインすると、Array(84行目)がRawArrayを呼び出し、RawArray(54行目)は特別なことを何もしないことがわかります。 、_ctypes.memset_( ドキュメント )の呼び出しを除く。容疑者がいます。それをテストしましょう。

次のコードは、timeitを使用して、1MBのメモリバッファを「A」に設定するパフォーマンスをテストします。

_import timeit
cmds = """\
import ctypes
s=ctypes.create_string_buffer(1024*1024)
ctypes.memset(ctypes.addressof(s), 65, ctypes.sizeof(s))"""
timeit.timeit(cmds, number=100000)
_

これをMacBookProとLinuxサーバーで実行すると、LinuxではOSXよりも実行速度が大幅に低下することが確認されます。 pypy[〜#〜] gcc [〜#〜] とApples [〜#〜] llvm [〜#〜]を使用してコンパイルされたOSX上にあることを知っている 、これは、 Clang に対して直接コンパイルされたOSX上にあるPythonよりもLinuxの世界に似ています。通常、PythonプログラムはCPythonよりもpypyで高速に実行されますが、上記のコードはpypyで6.4倍遅く実行されます(同じハードウェア上で!)。

私のCツールチェーンとCライブラリの知識は限られているので、深く掘り下げることはできません。したがって、私の結論は次のとおりです。LinuxではCライブラリへのメモリ呼び出しが配列の速度を低下させるため、OSXとWindowsは配列の方が高速です

OSXの分析-Windowsのパフォーマンスの違い

次に、OSXとWindowsのデュアルブートMacBookProでこれを実行しました。利点は、基盤となるハードウェアが同じであるということです。 OSだけが違います。反復回数を1000に、サイズを10.000に増やしました。

結果は次のとおりです。

  • OSX:
    • 配列:10.895秒で225668呼び出し
    • パイプ:6.894秒で209552コール
    • キュー:7.892秒で728173の呼び出し
  • ウィンドウズ:
    • 配列:296.050秒で354076呼び出し
    • パイプ:234.996秒で374229コール
    • キュー:250.966秒で903705コール

私たちはそれを見ることができます:

  1. Windowsの実装(spawnを使用)は、OSX(forkを使用)よりも多くの呼び出しを必要とします。
  2. Windowsの実装は、OSXよりも呼び出しごとにはるかに長い時間がかかります。

すぐにはわかりませんが、注意すべき点は、呼び出しあたりの平均時間を見ると、3つのマルチプロセッシングメソッド(配列、キュー、パイプ)間の相対パターンが同じ(下のグラフを参照)。言い換えると、OSXとWindowsのアレイ、キュー、パイプのパフォーマンスの違いは、次の2つの要因によって完全に説明できます。1。Python2つのプラットフォーム間のパフォーマンス。 2.両方のプラットフォームがマルチプロセッシングを処理するさまざまな方法。

言い換えると、呼び出し数の違いは、multiprocessingドキュメントの コンテキストと開始メソッド セクションで説明されています。実行時間の違いは、OSXとWindowsのPythonのパフォーマンスの違いで説明されています。以下のグラフに示すように、これら2つのコンポーネントを除外すると、アレイ、キュー、パイプの相対的なパフォーマンスは、OSXとWindowsで(多かれ少なかれ)同等です。

Performance differences of Array, Queue and Pipe between OSX and Windows

4
agtoever