web-dev-qa-db-ja.com

`multiprocessing.Queue.get`がとても遅いのはなぜですか?

_multiprocessing.Queue_を理解するのに助けが必要です。私が直面している問題は、queue.get(...)からの結果の取得が、queue.put(...)およびキューのバッファ(両端キュー)の呼び出しと比較して、陽気に遅れていることです。

このリークされた抽象化により、私はキューの内部を調査することになりました。その単純な ソースコードdeque実装 を指し示しているだけであり、それは私が見ている動作を説明するためにそれを使用できないほど単純に思えます。また、Queueがパイプを使用していることを読みましたが、ソースコードでそれを見つけることができないようです。

問題を再現する最小限の例に要約し、その下に可能な出力を指定します。

_import threading
import multiprocessing
import queue

q = None
def enqueue(item):
    global q
    if q is None:
        q = multiprocessing.Queue()
        process = threading.Thread(target=worker, args=(q,))  # or multiprocessing.Process Doesn't matter
        process.start()
    q.put(item)
    print(f'len putted item: {len(item)}. qsize: {q.qsize()}. buffer len: {len(q._buffer)}')


def worker(local_queue):
    while True:
        try:
            while True:  # get all items
                item = local_queue.get(block=False)
                print(f'len got item: {len(item)}. qsize: {q.qsize()}. buffer len: {len(q._buffer)}')
        except queue.Empty:
            print('empty')


if __name__ == '__main__':
    for i in range(1, 100000, 1000):
        enqueue(list(range(i)))
_

出力:

_empty
empty
empty
len putted item: 1. qsize: 1. buffer len: 1
len putted item: 1001. qsize: 2. buffer len: 2
len putted item: 2001. qsize: 3. buffer len: 1
len putted item: 3001. qsize: 4. buffer len: 2
len putted item: 4001. qsize: 5. buffer len: 3
len putted item: 5001. qsize: 6. buffer len: 4
len putted item: 6001. qsize: 7. buffer len: 5
len putted item: 7001. qsize: 8. buffer len: 6
len putted item: 8001. qsize: 9. buffer len: 7
len putted item: 9001. qsize: 10. buffer len: 8
len putted item: 10001. qsize: 11. buffer len: 9
len putted item: 11001. qsize: 12. buffer len: 10
len putted item: 12001. qsize: 13. buffer len: 11
len putted item: 13001. qsize: 14. buffer len: 12
len putted item: 14001. qsize: 15. buffer len: 13
len putted item: 15001. qsize: 16. buffer len: 14
len got item: 1. qsize: 15. buffer len: 14
len putted item: 16001. qsize: 16. buffer len: 15
len putted item: 17001. qsize: 17. buffer len: 16
len putted item: 18001. qsize: 18. buffer len: 17
len putted item: 19001. qsize: 19. buffer len: 18
len putted item: 20001. qsize: 20. buffer len: 19
len putted item: 21001. qsize: 21. buffer len: 20
len putted item: 22001. qsize: 22. buffer len: 21
len putted item: 23001. qsize: 23. buffer len: 22
len putted item: 24001. qsize: 24. buffer len: 23
len putted item: 25001. qsize: 25. buffer len: 24
len putted item: 26001. qsize: 26. buffer len: 25
len putted item: 27001. qsize: 27. buffer len: 26
len putted item: 28001. qsize: 28. buffer len: 27
len got item: 1001. qsize: 27. buffer len: 27
empty
len putted item: 29001. qsize: 28. buffer len: 28
empty
empty
empty
len got item: 2001. qsize: 27. buffer len: 27
empty
len putted item: 30001. qsize: 28. buffer len: 28
_

結果について次の点に注意してください。要素28001を挿入した後、ワーカーはキューに要素が残っていないことを検出しましたが、さらに数十個あります。同期のため、一部を除いてすべてを取得しても問題ありません。しかし、それはtwoを見つけることしかできません!

そして、このパターンは続きます。

これは、私がキューに入れたオブジェクトのサイズに関係しているようです。小さなオブジェクトの場合、たとえばlist(range(i))ではなくiの場合、この問題は発生しません。しかし、話していたオブジェクトのサイズはまだキロバイトであり、そのような重大な遅延を威厳のあるものにするのに十分な大きさではありません(私の現実の非最小の例では、これは簡単に数分かかりました)

私の質問は具体的には:Pythonのプロセス間で大量のデータを(そうではない)共有するにはどうすればよいですか?さらに、キューの内部実装のどこからこの停滞が発生しているのか知りたいです

13
JBSnorro

私もこの問題に遭遇しました。大きなnumpy配列(〜300MB)を送信していましたが、mp.queue.get()で非常に低速でした。

Mp.Queueのpython2.7ソースコードを調べたところ、(unixライクなシステムで)最も遅い部分は socket_connection.c_conn_recvall()であることがわかりましたが、私は深く見ていません。

この問題を回避するために、実験的なパッケージを作成します [〜#〜] fmq [〜#〜]

このプロジェクトは、multiprocessing.Queue(mp.Queue)の使用に触発されています。 mp.Queueは、パイプの速度制限のため(Unixライクなシステムの場合)、大きなデータ項目では低速です。

Mp.Queueがプロセス間転送を処理することで、FMQはスティーラースレッドを実装します。このスレッドは、アイテムが利用可能になるとmp.Queueからアイテムを盗み、それをQueue.Queueに入れます。次に、コンシューマープロセスはQueue.Queueからデータをすぐにフェッチできます。

スピードアップは、プロデューサープロセスとコンシューマープロセスの両方が計算集約型であり(したがってマルチプロセッシングが必要)、データが大きい(たとえば、> 50 227x227イメージ)という仮定に基づいています。それ以外の場合は、マルチプロセッシングを使用したmp.Queueまたはスレッドを使用したQueue.Queueで十分です。

fmq.Queueは、mp.Queueのように簡単に使用できます。

このプロジェクトは初期段階にあるため、まだいくつかの 既知の問題 があることに注意してください。

6
weitang114

将来の読者のために、以下を使用してみることもできます。

q = multiprocessing.Manager().Queue()

ただの代わりに

q = multiprocessing.Queue()

私はまだ完全に蒸留してこの動作の背後にあるメカニズムを理解していませんが、1つ ソース 私はそれが約であると主張しました:「大きなアイテムをキューにプッシュすると、アイテムは基本的にバッファリングされますが、キューのput関数の即時復帰。」

著者はそれと修正方法についてさらに説明し続けますが、私にとっては、マネージャーを追加することで簡単でクリーンなトリックができました。

受け入れられた回答で言及されているFMQもPython2専用です。これが、この回答がいつかより多くの人々に役立つ可能性があると私が感じた理由の1つです。

1
Vinícius M