Mostrare lo stato di avanzamento di una chiamata imap_unordered del pool multiprocessing Python?


95

Ho uno script che esegue con successo un set di attività Pool multiprocessing con una imap_unordered()chiamata:

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

Tuttavia, my num_tasksè di circa 250.000, quindi join()blocca il thread principale per circa 10 secondi e mi piacerebbe essere in grado di eseguire l'eco sulla riga di comando in modo incrementale per mostrare che il processo principale non è bloccato. Qualcosa di simile a:

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)

Esiste un metodo per l'oggetto risultato o per il pool stesso che indica il numero di attività rimanenti? Ho provato a utilizzare un multiprocessing.Valueoggetto come contatore ( do_workrichiama counter.value += 1un'azione dopo aver svolto il suo compito), ma il contatore arriva solo a ~ 85% del valore totale prima di interrompere l'incremento.

Risposte:


80

Non è necessario accedere agli attributi privati ​​del set di risultati:

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
Vedo la stampa solo dopo l'uscita del codice (non tutte le iterazioni). Hai un suggerimento?
Hanan Shteingart

@ HananShteingart: Funziona bene sul mio sistema (Ubuntu) sia con Python 2 che 3. L'ho usato def do_word(*a): time.sleep(.1)come esempio. Se non funziona per te, crea un esempio di codice minimo completo che dimostri il tuo problema: descrivi usando le parole cosa ti aspetti che accada e cosa succede invece, menziona come esegui il tuo script Python, qual è il tuo sistema operativo, versione Python e pubblicalo come nuova domanda .
jfs

13
Ho avuto lo stesso problema di @HananShteingart: è perché stavo cercando di usare Pool.map(). Non me ne rendevo conto solo imap() e imap_unordered()lavoro in questo modo - la documentazione dice solo "Una versione più pigra di map ()" ma in realtà significa "l'iteratore sottostante restituisce i risultati non appena arrivano".
simonmacmullen

@simonmacmullen: sia la domanda che la mia risposta usano imap_unordered(). Il problema di Hanan è probabilmente dovuto a sys.stderr.write('\r..')(sovrascrivere la stessa riga per mostrare l'avanzamento).
jfs

2
Possibile anche! Volevo principalmente documentare un'ipotesi stupida che avevo fatto - nel caso in cui qualcun altro lo leggesse lo avesse fatto anche lui.
simonmacmullen

94

Il mio preferito: ti offre una piccola barra di avanzamento e l'ETA di completamento mentre le cose funzionano e si impegnano in parallelo.

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
cosa succede se pool restituisce un valore?
Nickpick

11
Ho creato un elenco vuoto chiamato risultato prima del ciclo, quindi all'interno del ciclo esegui semplicemente result.append (x). Ho provato questo con 2 processi e ho usato imap invece di map e tutto ha funzionato come volevo per @nickpick
bs7280

2
quindi la mia barra di avanzamento sta iterando su nuove linee invece di progredire sul posto, qualche idea del perché potrebbe essere?
Austin

2
non dimenticare dipip install tqdm
Sig. T

3
@ bs7280 Con result.append (x) intendevi result.append (_)? Cos'è x?
jason

27

Ho scoperto che il lavoro era già terminato quando ho provato a controllarne lo stato di avanzamento. Questo è ciò che ha funzionato per me usando 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()

Questo dovrebbe funzionare con tutti i tipi di multiprocessing, che si blocchino o meno.


4
Penso che crei un mucchio di thread e ogni thread conta in modo indipendente
nburn42

1
Ho funzioni all'interno di funzioni che si traducono in un errore di decapaggio.
ojunk

21

Trovato una risposta me stesso con un po 'di scavo: Dando uno sguardo al __dict__di imap_unorderedoggetto risultato, ho trovato che ha un _indexattributo che incrementi con ogni completamento compito. Quindi questo funziona per la registrazione, avvolto nel whileciclo:

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)

Tuttavia, ho scoperto che lo scambio imap_unordereddi con a ha map_asyncportato a un'esecuzione molto più veloce, sebbene l'oggetto risultato sia leggermente diverso. Invece, l'oggetto risultato di map_asyncha un _number_leftattributo e un ready()metodo:

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
Ho provato questo per Python 2.7.6 e rs._number_left sembra essere il numero di blocchi rimanenti. Quindi, se rs._chunksize non è 1, rs._number_left non sarà il numero di elementi dell'elenco rimanenti.
Allen

Dove devo mettere questo codice? Voglio dire che questo non viene eseguito fino a quando il contenuto di rsè noto ed è un po 'tardi o no?
Wakan Tanka

@ WakanTanka: va nello script principale dopo aver eliminato i thread extra. Nel mio esempio originale, va nel ciclo "while", dove rsha già avviato gli altri thread.
MidnightLightning

1
Potresti modificare la tua domanda e / o risposta per mostrare un esempio minimo di lavoro. Non vedo rsnessun loop, sto multiprocessing newbie e questo aiuterebbe. Grazie mille.
Wakan Tanka

1
Almeno in python 3.5, la soluzione che utilizza _number_leftnon funziona. _number_leftrappresenta i blocchi che rimangono da elaborare. Ad esempio, se voglio che 50 elementi vengano passati alla mia funzione in parallelo, per un pool di thread con 3 processi vengono _map_async()creati 10 blocchi con 5 elementi ciascuno. _number_leftquindi rappresenta quanti di questi blocchi sono stati completati.
mSSM

9

So che questa è una domanda piuttosto vecchia, ma ecco cosa faccio quando voglio monitorare la progressione di un pool di attività in 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

Fondamentalmente, usi apply_async con un callbak (in questo caso, è per aggiungere il valore restituito a una lista), quindi non devi aspettare per fare qualcos'altro. Quindi, in un ciclo di tempo, controlli la progressione del lavoro. In questo caso, ho aggiunto un widget per renderlo più bello.

Il risultato:

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

Spero che sia d'aiuto.


devo cambiare: [pool.apply_async(my_function, (x,), callback=results.append) for x in dummy_args]per(pool.apply_async(my_function, (x,), callback=results.append) for x in dummy_args)
David Przybilla

Non è vero. Un oggetto generatore non funzionerà qui. Controllato.
swagatam

9

Come suggerito da Tim, puoi usare tqdme imapper risolvere questo problema. Mi sono appena imbattuto in questo problema e ho ottimizzato la imap_unorderedsoluzione, in modo da poter accedere ai risultati della mappatura. Ecco come funziona:

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

Se non ti interessano i valori restituiti dai tuoi lavori, non è necessario assegnare l'elenco a nessuna variabile.


4

per chiunque cerchi una soluzione semplice che lavori con 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

Ho creato una classe personalizzata per creare una stampa dell'avanzamento. Maby questo aiuta:

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

Prova questo semplice approccio basato sulla coda, che può essere utilizzato anche con il pool. Tieni presente che la stampa di qualsiasi cosa dopo l'avvio della barra di avanzamento ne causerà lo spostamento, almeno per questa particolare barra di avanzamento. (Progressi di 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()
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.