Tampilkan kemajuan panggilan imap_unordered pool multiprosesing Python?

96

Saya memiliki skrip yang berhasil melakukan kumpulan tugas multiprosesing dengan imap_unordered()panggilan:

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

Namun, saya num_taskssekitar 250.000, jadi join()utas utama terkunci selama 10 detik atau lebih, dan saya ingin dapat menggema ke baris perintah secara bertahap untuk menunjukkan bahwa proses utama tidak terkunci. Sesuatu seperti:

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)

Apakah ada metode untuk objek hasil atau kumpulan itu sendiri yang menunjukkan jumlah tugas yang tersisa? Saya mencoba menggunakan multiprocessing.Valueobjek sebagai penghitung ( do_workmemanggil counter.value += 1tindakan setelah melakukan tugasnya), tetapi penghitung hanya mencapai ~ 85% dari total nilai sebelum berhenti menambah.

MidnightLightning
sumber

Jawaban:

80

Tidak perlu mengakses atribut pribadi dari kumpulan hasil:

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))
jfs
sumber
7
Saya melihat hasil cetak hanya setelah kode keluar (tidak setiap iterasi). Apakah anda punya saran?
Hanan Shteingart
@HananShteingart: Ini berfungsi dengan baik di sistem saya (Ubuntu) dengan Python 2 dan 3. Saya telah menggunakan def do_word(*a): time.sleep(.1)sebagai contoh. Jika tidak berhasil untuk Anda, buat contoh kode minimal lengkap yang mendemonstrasikan masalah Anda: jelaskan menggunakan kata-kata apa yang Anda harapkan akan terjadi dan apa yang terjadi, sebutkan bagaimana Anda menjalankan skrip Python Anda, apa OS Anda, versi Python dan posting sebagai pertanyaan baru .
jfs
14
Saya memiliki masalah yang sama dengan @HananShteingart: itu karena saya mencoba menggunakan Pool.map(). Saya tidak menyadarinya hanya imap() dan imap_unordered()bekerja dengan cara ini - dokumentasinya hanya mengatakan "Versi lazier dari map ()" tetapi sebenarnya berarti "iterator yang mendasari mengembalikan hasil saat mereka masuk".
simonmacmullen
@simonmacmullen: baik pertanyaan dan jawaban saya digunakan imap_unordered(). Masalah Hanan mungkin karena sys.stderr.write('\r..')(menimpa baris yang sama untuk menunjukkan kemajuan).
jfs
2
Mungkin juga! Saya terutama ingin mendokumentasikan asumsi bodoh yang saya buat - kalau-kalau ada orang lain yang membaca ini juga melakukannya.
simonmacmullen
94

Favorit pribadi saya - memberi Anda bilah kemajuan kecil yang bagus dan penyelesaian ETA sementara semuanya berjalan dan berjalan secara paralel.

from multiprocessing import Pool
import tqdm

pool = Pool(processes=8)
for _ in tqdm.tqdm(pool.imap_unordered(do_work, tasks), total=len(tasks)):
    pass
Tim
sumber
64
bagaimana jika kumpulan mengembalikan nilai?
Nickpick
11
Saya membuat daftar kosong yang disebut result before loop lalu di dalam loop lakukan result.append (x). Saya mencoba ini dengan 2 proses dan menggunakan imap alih-alih peta dan semuanya berfungsi seperti yang saya inginkan ke @nickpick
bs7280
2
jadi bilah kemajuan saya beralih ke baris baru alih-alih maju di tempat, tahu mengapa ini mungkin terjadi?
Austin
2
jangan lupa untukpip install tqdm
Mr. T
3
@ bs7280 Dengan result.append (x) maksud Anda result.append (_)? Apa x?
jason
27

Saya menemukan bahwa pekerjaan tersebut sudah selesai pada saat saya mencoba untuk memeriksa kemajuannya. Inilah yang berhasil untuk saya menggunakan 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()

Ini harus bekerja dengan semua rasa multiprosesing, apakah mereka memblokir atau tidak.

reubano
sumber
4
Saya pikir membuat banyak utas, dan setiap utas menghitung secara independen
nburn42
1
Saya memiliki fungsi di dalam fungsi yang menghasilkan kesalahan pengawetan.
ojunk
21

Menemukan jawaban sendiri dengan beberapa lebih menggali: Mengambil melihat pada __dict__satu imap_unorderedobjek hasil, saya menemukan ia memiliki _indexatribut yang bertahap dengan masing-masing penyelesaian tugas. Jadi ini berfungsi untuk logging, dibungkus dalam whileloop:

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)

Namun, saya menemukan bahwa menukar imap_unordereduntuk map_asyncmenghasilkan eksekusi yang jauh lebih cepat, meskipun objek hasilnya sedikit berbeda. Sebagai gantinya, objek hasil dari map_asyncmemiliki _number_leftatribut, dan ready()metode:

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)
MidnightLightning
sumber
3
Saya menguji ini untuk Python 2.7.6 dan rs._number_left tampaknya jumlah potongan yang tersisa. Jadi jika rs._chunksize bukan 1 maka rs._number_left tidak akan menjadi jumlah item daftar yang tersisa.
Allen
Di mana saya harus meletakkan kode ini? Maksud saya ini tidak dijalankan sampai konten rsdiketahui dan agak terlambat atau tidak?
Wakan Tanka
@ WakanTanka: Ini masuk ke skrip utama setelah memutar utas tambahan. Dalam contoh asli saya, ini masuk dalam loop "while", di mana rstelah meluncurkan utas lainnya.
MidnightLightning
1
Bisakah Anda mengedit pertanyaan dan / atau jawaban Anda untuk menunjukkan contoh kerja minimum. Saya tidak melihat rsdalam lingkaran mana pun, saya pemula multiprocessing dan ini akan membantu. Terima kasih banyak.
Wakan Tanka
1
Setidaknya dalam python 3.5, solusi yang menggunakan _number_lefttidak berhasil. _number_leftmewakili bongkahan yang tersisa untuk diproses. Misalnya, jika saya ingin 50 elemen diteruskan ke fungsi saya secara paralel, maka untuk kumpulan utas dengan 3 proses _map_async()membuat 10 potongan dengan masing-masing 5 elemen. _number_leftkemudian mewakili berapa banyak dari potongan ini yang telah diselesaikan.
mSSM
9

Saya tahu bahwa ini adalah pertanyaan yang agak lama, tetapi inilah yang saya lakukan ketika saya ingin melacak perkembangan kumpulan tugas dengan 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

Pada dasarnya, Anda menggunakan apply_async dengan callbak (dalam hal ini, ini untuk menambahkan nilai yang dikembalikan ke daftar), jadi Anda tidak perlu menunggu untuk melakukan sesuatu yang lain. Kemudian, dalam loop sementara, Anda memeriksa perkembangan pekerjaan. Dalam hal ini, saya menambahkan widget agar terlihat lebih bagus.

Hasil:

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

Semoga membantu.

Julien Tourille
sumber
harus berubah: [pool.apply_async(my_function, (x,), callback=results.append) for x in dummy_args]untuk(pool.apply_async(my_function, (x,), callback=results.append) for x in dummy_args)
David Przybilla
Itu tidak benar. Objek generator tidak akan berfungsi di sini. Dicentang.
swagatam
9

Seperti yang disarankan oleh Tim, Anda dapat menggunakan tqdmdan imapuntuk memecahkan masalah ini. Saya baru saja menemukan masalah ini dan mengubah imap_unorderedsolusinya, sehingga saya dapat mengakses hasil pemetaan. Begini cara kerjanya:

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)))

Jika Anda tidak peduli dengan nilai yang dikembalikan dari pekerjaan Anda, Anda tidak perlu menetapkan daftar ke variabel apa pun.

mrapacz.dll
sumber
4

untuk siapa saja yang mencari solusi sederhana yang bekerja dengan 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]
zeawoas
sumber
3

Saya membuat kelas khusus untuk membuat cetakan kemajuan. Maby ini membantu:

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
Aronstef
sumber
1

Coba pendekatan berbasis Antrean sederhana ini, yang juga dapat digunakan dengan penggabungan. Perhatikan bahwa mencetak apa pun setelah dimulainya bilah kemajuan akan menyebabkannya dipindahkan, setidaknya untuk bilah kemajuan khusus ini. (Kemajuan 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()
Mott The Tuple
sumber