Multiprocessing: usa tqdm per visualizzare una barra di avanzamento


97

Per rendere il mio codice più "pitonico" e più veloce, utilizzo "multiprocessing" e una funzione map per inviarlo a) la funzione eb) l'intervallo di iterazioni.

La soluzione impiantata (ovvero, chiamare tqdm direttamente nell'intervallo tqdm.tqdm (intervallo (0, 30)) non funziona con il multiprocessing (come formulato nel codice seguente).

La barra di avanzamento viene visualizzata da 0 a 100% (quando Python legge il codice?) Ma non indica l'effettivo progresso della funzione mappa.

Come visualizzare una barra di avanzamento che indica in quale fase si trova la funzione "mappa"?

from multiprocessing import Pool
import tqdm
import time

def _foo(my_number):
   square = my_number * my_number
   time.sleep(1)
   return square 

if __name__ == '__main__':
   p = Pool(2)
   r = p.map(_foo, tqdm.tqdm(range(0, 30)))
   p.close()
   p.join()

Qualsiasi aiuto o suggerimento è il benvenuto ...


Puoi pubblicare lo snippet di codice della barra di avanzamento?
Alex

1
Per le persone alla ricerca di una soluzione con .starmap(): Ecco una patch per l' Poolaggiunta .istarmap(), che funzionerà anche con tqdm.
Darkonaut

Risposte:


127

Usa imap invece di map, che restituisce un iteratore di valori elaborati.

from multiprocessing import Pool
import tqdm
import time

def _foo(my_number):
   square = my_number * my_number
   time.sleep(1)
   return square 

if __name__ == '__main__':
   with Pool(2) as p:
      r = list(tqdm.tqdm(p.imap(_foo, range(30)), total=30))

13
Un'istruzione list () che lo racchiude attende che l'iteratore termini. total = è anche richiesto poiché tqdm non sa per quanto tempo sarà l'iterazione,
hkyi

13
Esiste una soluzione simile per starmap()?
tarashypka

1
for i in tqdm.tqdm(...): pass potrebbe essere più semplice, quellolist(tqdm.tqdm)
savfod

1
Funziona, ma qualcun altro ha fatto stampare continuamente la barra di avanzamento su una nuova riga per ogni iterazione?
Dennis Subachev

3
Il comportamento è cablato quando specifico chunk_sizedi p.imap. Può tqdmaggiornare ogni iterazione invece di ogni blocco?
huangbiubiu

49

Soluzione trovata: fai attenzione! A causa del multiprocessing, il tempo di stima (iterazione per ciclo, tempo totale, ecc.) Potrebbe essere instabile, ma la barra di avanzamento funziona perfettamente.

Nota: Context manager per Pool è disponibile solo dalla versione 3.3 di Python

from multiprocessing import Pool
import time
from tqdm import *

def _foo(my_number):
   square = my_number * my_number
   time.sleep(1)
   return square 

if __name__ == '__main__':
    with Pool(processes=2) as p:
        max_ = 30
        with tqdm(total=max_) as pbar:
            for i, _ in enumerate(p.imap_unordered(_foo, range(0, max_))):
                pbar.update()

2
pbar.close()non richiesto, verrà chiuso automaticamente alla fine delwith
Sagar Kar

5
La seconda tqdmchiamata / interiore è necessaria qui?
shadowtalker

5
che dire dell'output di _foo (my_number) restituito come "r" in questione?
Likak

3
Esiste una soluzione simile per starmap()?
tarashypka

2
@shadowtalker - sembra funzionare senza;). Comunque - imap_unorderedè fondamentale qui, fornisce le migliori prestazioni e le migliori stime sulla barra di avanzamento.
Tomasz Gandor

19

Puoi usare p_tqdminvece.

https://github.com/swansonk14/p_tqdm

from p_tqdm import p_map
import time

def _foo(my_number):
   square = my_number * my_number
   time.sleep(1)
   return square 

if __name__ == '__main__':
   r = p_map(_foo, list(range(0, 30)))

1
Funziona molto bene ed è stato molto facile pip install. Questo sostituisce tqdm per la maggior parte delle mie esigenze
crypdick

Merci Victor;)
Gabriel Romon

p_tqdmè limitato a multiprocessing.Pool, non disponibile per i thread
pateheo

17

Ci scusiamo per il ritardo, ma se tutto ciò di cui hai bisogno è una mappa simultanea, l'ultima versione ( tqdm>=4.42.0) ora ha questo integrato:

from tqdm.contrib.concurrent import process_map  # or thread_map
import time

def _foo(my_number):
   square = my_number * my_number
   time.sleep(1)
   return square 

if __name__ == '__main__':
   r = process_map(_foo, range(0, 30), max_workers=2)

Riferimenti: https://tqdm.github.io/docs/contrib.concurrent/ e https://github.com/tqdm/tqdm/blob/master/examples/parallel_bars.py


Grazie per questo. Funziona facilmente, molto meglio di qualsiasi altra soluzione che ho provato.
user3340499

Cool (+1), ma lancia HBox(children=(FloatProgress(value=0.0, max=30.0), HTML(value='')))in Jupyter
Ébe Isaac il


Vedo un problema con la discussione sull'hacking di tqdm_notebook, tuttavia, non riesco a trovare una soluzione da risolvere per tqdm.contrib.concurrent.
Ébe Isaac,

8

sulla base della risposta di Xavi Martínez ho scritto la funzione imap_unordered_bar. Può essere utilizzato nello stesso modo imap_unorderedcon la sola differenza che viene mostrata una barra di elaborazione.

from multiprocessing import Pool
import time
from tqdm import *

def imap_unordered_bar(func, args, n_processes = 2):
    p = Pool(n_processes)
    res_list = []
    with tqdm(total = len(args)) as pbar:
        for i, res in tqdm(enumerate(p.imap_unordered(func, args))):
            pbar.update()
            res_list.append(res)
    pbar.close()
    p.close()
    p.join()
    return res_list

def _foo(my_number):
    square = my_number * my_number
    time.sleep(1)
    return square 

if __name__ == '__main__':
    result = imap_unordered_bar(_foo, range(5))

3
Questo ridisegnerà la barra ad ogni passaggio su una nuova riga. Come aggiornare la stessa linea?
misantroop

Soluzione nel mio caso (Windows / Powershell): Colorama.
misantroop

"pbar.close () non richiesto, verrà chiuso automaticamente alla fine del con" come il commento che Sagar ha fatto alla risposta di @ scipy
Tejas Shetty,

0
import multiprocessing as mp
import tqdm


some_iterable = ...

def some_func():
    # your logic
    ...


if __name__ == '__main__':
    with mp.Pool(mp.cpu_count()-2) as p:
        list(tqdm.tqdm(p.imap(some_func, iterable), total=len(iterable)))

0

Ecco la mia opinione per quando hai bisogno di ottenere risultati dalle tue funzioni di esecuzione parallela. Questa funzione fa alcune cose (c'è un altro mio post che lo spiega ulteriormente) ma il punto chiave è che c'è una coda di attività in sospeso e una coda di attività completate. Man mano che i lavoratori hanno terminato ogni attività nella coda in sospeso, aggiungono i risultati nella coda delle attività completate. È possibile includere il controllo nella coda delle attività completate con la barra di avanzamento tqdm. Non sto inserendo l'implementazione della funzione do_work () qui, non è rilevante, poiché il messaggio qui è per monitorare la coda delle attività completate e aggiornare la barra di avanzamento ogni volta che viene visualizzato un risultato.

def par_proc(job_list, num_cpus=None, verbose=False):

# Get the number of cores
if not num_cpus:
    num_cpus = psutil.cpu_count(logical=False)

print('* Parallel processing')
print('* Running on {} cores'.format(num_cpus))

# Set-up the queues for sending and receiving data to/from the workers
tasks_pending = mp.Queue()
tasks_completed = mp.Queue()

# Gather processes and results here
processes = []
results = []

# Count tasks
num_tasks = 0

# Add the tasks to the queue
for job in job_list:
    for task in job['tasks']:
        expanded_job = {}
        num_tasks = num_tasks + 1
        expanded_job.update({'func': pickle.dumps(job['func'])})
        expanded_job.update({'task': task})
        tasks_pending.put(expanded_job)

# Set the number of workers here
num_workers = min(num_cpus, num_tasks)

# We need as many sentinels as there are worker processes so that ALL processes exit when there is no more
# work left to be done.
for c in range(num_workers):
    tasks_pending.put(SENTINEL)

print('* Number of tasks: {}'.format(num_tasks))

# Set-up and start the workers
for c in range(num_workers):
    p = mp.Process(target=do_work, args=(tasks_pending, tasks_completed, verbose))
    p.name = 'worker' + str(c)
    processes.append(p)
    p.start()

# Gather the results
completed_tasks_counter = 0

with tqdm(total=num_tasks) as bar:
    while completed_tasks_counter < num_tasks:
        results.append(tasks_completed.get())
        completed_tasks_counter = completed_tasks_counter + 1
        bar.update(completed_tasks_counter)

for p in processes:
    p.join()

return results

-2

Questo approccio è semplice e funziona.

from multiprocessing.pool import ThreadPool
import time
from tqdm import tqdm

def job():
    time.sleep(1)
    pbar.update()

pool = ThreadPool(5)
with tqdm(total=100) as pbar:
    for i in range(100):
        pool.apply_async(job)
    pool.close()
    pool.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.