Pythonマルチプロセッシングプールのimap_unordered呼び出しの進行状況を表示しますか?


95

imap_unordered()呼び出しでマルチプロセッシングプールセットのタスクを正常に実行するスクリプトがあります。

p = multiprocessing.Pool()
rs = p.imap_unordered(do_work, xrange(num_tasks))
p.close() # No more work
p.join() # Wait for completion

しかし、私num_tasksは約250,000なので、join()メインスレッドが10秒ほどロックされます。メインプロセスがロックされていないことを示すために、コマンドラインに段階的にエコー出力できるようにしたいと考えています。何かのようなもの:

p = multiprocessing.Pool()
rs = p.imap_unordered(do_work, xrange(num_tasks))
p.close() # No more work
while (True):
  remaining = rs.tasks_remaining() # How many of the map call haven't been done yet?
  if (remaining == 0): break # Jump out of while loop
  print "Waiting for", remaining, "tasks to complete..."
  time.sleep(2)

残っているタスクの数を示す結果オブジェクトまたはプール自体のメソッドはありますか?multiprocessing.Valueオブジェクトをカウンターとして使用しようとしましたが(タスクの実行後にアクションをdo_work呼び出しcounter.value += 1ます)、カウンターは増分を停止する前に合計値の〜85%にしか到達しません。

回答:


80

結果セットのプライベート属性にアクセスする必要はありません:

from __future__ import division
import sys

for i, _ in enumerate(p.imap_unordered(do_work, xrange(num_tasks)), 1):
    sys.stderr.write('\rdone {0:%}'.format(i/num_tasks))

7
(すべての反復ではなく)コードの終了後にのみ出力が表示されます。提案はありますか?
Hanan Shteingart 2014年

@HananShteingart:私のシステム(Ubuntu)では、Python 2と3の両方で正常に動作しdef do_word(*a): time.sleep(.1)ます。例として使用しました。それがうまくいかない場合は、問題を示す完全な最小限のコード例を作成します。何が起こると予想され、何が起こるかを言葉で説明し、Pythonスクリプトの実行方法、OS、Pythonのバージョンを説明しますそして新しい質問として投稿
jfs 14

13
私は@HananShteingartと同じ問題を抱えていました。それは、私がを使おうとしていたためPool.map()です。私は実現しなかっただけ imap()imap_unordered()、このように作品を-ドキュメントがちょうど本当に「彼らが来るようイテレータは結果を返す基礎となる」という意味と言う「()は、マップのlazierバージョン」。
simonmacmullen 2015年

@simonmacmullen:質問と私の回答の両方を使用しますimap_unordered()。ハナンの問題はおそらくsys.stderr.write('\r..')同じです(進行状況を示すために同じ行を上書きします)。
jfs 2015年

2
また可能!私は主に私が作った愚かな仮定を文書化したかった-これを読んでいる他の誰かもそれを作った場合に備えて。
simonmacmullen 2015年

94

私の個人的なお気に入り-物事が並行して実行およびコミットされている間、素敵な小さな進行状況バーと完了ETAを提供します。

from multiprocessing import Pool
import tqdm

pool = Pool(processes=8)
for _ in tqdm.tqdm(pool.imap_unordered(do_work, tasks), total=len(tasks)):
    pass

64
プールが値を返す場合はどうなりますか?
Nickpick 2017

11
ループの前にresultと呼ばれる空のリストを作成してから、ループ内でresult.append(x)を実行します。私はこれを2つのプロセスで試し、mapの代わりにimapを使用しましたが、@ nickpickに必要なすべてが機能しました
bs7280

2
だから私のプログレスバーはインプレースで進行するのではなく新しい行に反復しています、なぜこれがそうなのか考えていますか?
オースティン

2
忘れないでくださいpip install tqdm
T氏

3
@ bs7280 result.append(x)とは、result.append(_)ですか?Xとは?
ジェイソン

27

作業の進行状況を確認しようとした時点で、作業はすでに完了していることがわかりました。これはtqdmを使用して私のために働いたものです

pip install tqdm

from multiprocessing import Pool
from tqdm import tqdm

tasks = range(5)
pool = Pool()
pbar = tqdm(total=len(tasks))

def do_work(x):
    # do something with x
    pbar.update(1)

pool.imap_unordered(do_work, tasks)
pool.close()
pool.join()
pbar.close()

これは、ブロックされているかどうかにかかわらず、すべての種類のマルチプロセッシングで機能するはずです。


4
スレッドの束を作成し、各スレッドが独立してカウントしていると思います
nburn42

1
関数内に、酸洗いエラーが発生する関数があります。
ojunk

21

さらに掘り下げて、自分自身で答えを見つけました。結果オブジェクトの__dict__を見ると、タスクの完了ごとに増加imap_unorderedする_index属性があることがわかりました。したがって、これはロギングに機能し、whileループにラップされます:

p = multiprocessing.Pool()
rs = p.imap_unordered(do_work, xrange(num_tasks))
p.close() # No more work
while (True):
  completed = rs._index
  if (completed == num_tasks): break
  print "Waiting for", num_tasks-completed, "tasks to complete..."
  time.sleep(2)

ただし、結果オブジェクトは少し異なりますが、をと交換するimap_unorderedと、map_async実行がはるかに高速になることがわかりました。代わりに、からの結果オブジェクトmap_asyncには_number_left属性とready()メソッドがあります。

p = multiprocessing.Pool()
rs = p.map_async(do_work, xrange(num_tasks))
p.close() # No more work
while (True):
  if (rs.ready()): break
  remaining = rs._number_left
  print "Waiting for", remaining, "tasks to complete..."
  time.sleep(0.5)

3
私はこれをPython 2.7.6でテストしました。rs._number_leftは、残っているチャンクの数のようです。したがって、rs._chunksizeが1でない場合、rs._number_leftは残りのリストアイテムの数にはなりません。
アレン

このコードはどこに置くべきですか?これはrs既知のコンテンツが実行されるまで実行されず、少し遅れているかどうかを意味します。
和漢短歌

@WakanTanka:余分なスレッドをスピンオフした後、メインスクリプトに入ります。私の最初の例では、それは「while」ループに入り、rsすでに他のスレッドを起動しています。
MidnightLightning

1
質問や回答を編集して、最低限の実例を示してください。私はrsループで見ていません、私は初心者を多重処理しています、そしてこれは助けになるでしょう。どうもありがとうございました。
和漢短歌

1
少なくともでpython 3.5は、を使用_number_leftしたソリューションは機能しません。_number_left処理されずに残っているチャンクを表します。たとえば、50個の要素を並列に関数に渡したい場合、3つのプロセス_map_async()を持つスレッドプールの場合、それぞれ5つの要素を持つ10個のチャンクが作成されます。_number_left次に、これらのチャンクのうちいくつが完了したかを表します。
mSSM 2016年

9

これはかなり古い質問であることはわかっていますが、Pythonでタスクのプールの進行状況を追跡したい場合は、次のようにします。

from progressbar import ProgressBar, SimpleProgress
import multiprocessing as mp
from time import sleep

def my_function(letter):
    sleep(2)
    return letter+letter

dummy_args = ["A", "B", "C", "D"]
pool = mp.Pool(processes=2)

results = []

pbar = ProgressBar(widgets=[SimpleProgress()], maxval=len(dummy_args)).start()

r = [pool.apply_async(my_function, (x,), callback=results.append) for x in dummy_args]

while len(results) != len(dummy_args):
    pbar.update(len(results))
    sleep(0.5)
pbar.finish()

print results

基本的に、callbakでapply_asyncを使用します(この場合、戻り値をリストに追加するため)ので、他のことをするのを待つ必要はありません。次に、whileループ内で、作業の進行状況を確認します。この場合、見栄えを良くするためにウィジェットを追加しました。

出力:

4 of 4                                                                         
['AA', 'BB', 'CC', 'DD']

それが役に立てば幸い。


変更が必要:[pool.apply_async(my_function, (x,), callback=results.append) for x in dummy_args]for(pool.apply_async(my_function, (x,), callback=results.append) for x in dummy_args)
デビッドPrzybilla

それは真実ではない。ここではジェネレーターオブジェクトは機能しません。チェックしました。
スワガタム

9

Timの提案どおり、とを使用tqdmimapてこの問題を解決できます。imap_unorderedマッピングの結果にアクセスできるように、この問題を見つけて解決策を調整しました。仕組みは次のとおりです。

from multiprocessing import Pool
import tqdm

pool = multiprocessing.Pool(processes=4)
mapped_values = list(tqdm.tqdm(pool.imap_unordered(do_work, range(num_tasks)), total=len(values)))

ジョブから返された値を気にしない場合は、リストを変数に割り当てる必要はありません。


4

で動作する簡単なソリューションを探している人のためにPool.apply_async()

from multiprocessing import Pool
from tqdm import tqdm
from time import sleep


def work(x):
    sleep(0.5)
    return x**2

n = 10

p = Pool(4)
pbar = tqdm(total=n)
res = [p.apply_async(work, args=(
    i,), callback=lambda _: pbar.update(1)) for i in range(n)]
results = [p.get() for p in res]

3

進行状況の印刷出力を作成するカスタムクラスを作成しました。マビーこれは役立ちます:

from multiprocessing import Pool, cpu_count


class ParallelSim(object):
    def __init__(self, processes=cpu_count()):
        self.pool = Pool(processes=processes)
        self.total_processes = 0
        self.completed_processes = 0
        self.results = []

    def add(self, func, args):
        self.pool.apply_async(func=func, args=args, callback=self.complete)
        self.total_processes += 1

    def complete(self, result):
        self.results.extend(result)
        self.completed_processes += 1
        print('Progress: {:.2f}%'.format((self.completed_processes/self.total_processes)*100))

    def run(self):
        self.pool.close()
        self.pool.join()

    def get_results(self):
        return self.results

1

この単純なキューベースのアプローチを試してください。これは、プーリングでも使用できます。プログレスバーの開始後に何かを印刷すると、少なくともこの特定のプログレスバーの場合、移動されることに注意してください。(PyPIの進捗状況1.5)

import time
from progress.bar import Bar

def status_bar( queue_stat, n_groups, n ):

    bar = Bar('progress', max = n)  

    finished = 0
    while finished < n_groups:

        while queue_stat.empty():
            time.sleep(0.01)

        gotten = queue_stat.get()
        if gotten == 'finished':
            finished += 1
        else:
            bar.next()
    bar.finish()


def process_data( queue_data, queue_stat, group):

    for i in group:

        ... do stuff resulting in new_data

        queue_stat.put(1)

    queue_stat.put('finished')  
    queue_data.put(new_data)

def multiprocess():

    new_data = []

    groups = [[1,2,3],[4,5,6],[7,8,9]]
    combined = sum(groups,[])

    queue_data = multiprocessing.Queue()
    queue_stat = multiprocessing.Queue()

    for i, group in enumerate(groups): 

        if i == 0:

            p = multiprocessing.Process(target = status_bar,
                args=(queue_stat,len(groups),len(combined)))
                processes.append(p)
                p.start()

        p = multiprocessing.Process(target = process_data,
        args=(queue_data, queue_stat, group))
        processes.append(p)
        p.start()

    for i in range(len(groups)):
        data = queue_data.get() 
        new_data += data

    for p in processes:
        p.join()
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.