Багатопроцесорна обробка: використовуйте tqdm для відображення індикатора прогресу


103

Щоб зробити мій код більш "пітонічним" та швидшим, я використовую "багатопроцесорну обробку" та функцію map, щоб надіслати його a) функцію та b) діапазон ітерацій.

Імплантоване рішення (тобто виклик tqdm безпосередньо в діапазоні tqdm.tqdm (діапазон (0, 30)) не працює з багатопроцесорною обробкою (як сформульовано в коді нижче).

Рядок прогресу відображається від 0 до 100% (коли python читає код?), Але це не вказує на фактичний прогрес функції карти.

Як відобразити індикатор прогресу, який вказує, на якому кроці знаходиться функція 'map'?

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

Будь-яка допомога чи пропозиції вітаються ...


Чи можете ви опублікувати фрагмент коду на панелі виконання?
Олексій

2
Для людей, які шукають рішення за допомогою .starmap(): Ось патч для Poolдодавання .istarmap(), з яким також можна працювати tqdm.
Дарконаут

Відповіді:


136

Використовуйте imap замість map, який повертає ітератор оброблених значень.

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

14
Оператор, що включає list (), чекає закінчення ітератора. total = також потрібно, оскільки tqdm не знає, як довго триватиме ітерація,
hkyi

15
Чи існує подібне рішення для starmap()?
тарашипка

2
for i in tqdm.tqdm(...): pass може бути більш прямолінійним, цеlist(tqdm.tqdm)
savfod

1
Це працює, але чи хтось інший постійно друкував індикатор виконання на новому рядку для кожної ітерації?
Денніс Субачов,

3
Поведінка пов'язана, коли специфічно chunk_sizeдля p.imap. Чи можете tqdmоновити кожну ітерацію замість кожного шматка?
huangbiubiu

54

Знайдене рішення: Будьте обережні! Через багатопроцесорність час оцінки (ітерація на цикл, загальний час тощо) може бути нестабільним, але індикатор виконання працює бездоганно.

Примітка: Контекстний менеджер для пулу доступний лише з версії Python 3.3

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()не потрібно, він буде автоматично закритий після припиненняwith
Сагар Кар

5
Чи tqdmпотрібен тут другий / внутрішній дзвінок?
shadowtalker

7
а як щодо виводу _foo (my_number), який повертається як "r", про який йде мова?
Likak

3
Чи існує подібне рішення для starmap()?
тарашипка

2
@shadowtalker - здається, працює і без;). У будь-якому випадку - imap_unorderedтут ключове значення, воно дає найкращі показники та найкращі оцінки рівня прогресу.
Томаш

21

Ви можете використовувати p_tqdmзамість цього.

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
Це працює надзвичайно добре, і це було дуже просто pip install. Це замінює tqdm для більшості моїх потреб
crypdick


p_tqdmобмежено multiprocessing.Pool, недоступне для тем
pateheo

19

Вибачте за запізнення, але якщо вам потрібна лише паралельна карта, то в останню версію ( tqdm>=4.42.0) тепер вбудовано:

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)

Посилання: https://tqdm.github.io/docs/contrib.concurrent/ та https://github.com/tqdm/tqdm/blob/master/examples/parallel_bars.py


1
Дякую за це. Працює легко, набагато краще, ніж будь-яке інше рішення, яке я пробував.
user3340499

Круто (+1), але кидає HBox(children=(FloatProgress(value=0.0, max=30.0), HTML(value='')))Юпітера
Ебе Ісаак


Я бачу проблему з обговоренням злому tqdm_notebook, однак не можу відпрацювати рішення для вирішення для tqdm.contrib.concurrent.
Ебе Ісаак

8

на основі відповіді Хаві Мартінеса я написав функцію imap_unordered_bar. Його можна використовувати так само, як і imap_unorderedз тією лише різницею, що показаний бар обробки.

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
Це перекроє смугу на кожному кроці нового рядка. Як оновити той самий рядок?
misantroop

Рішення в моєму випадку (Windows / Powershell): Colorama.
misantroop

"pbar.close () не потрібен, він буде автоматично закритий після припинення з", як коментар Сагара до відповіді @ scipy
Шетті

1

Ось моя думка про те, коли вам потрібно повернути результати від ваших паралельних виконуючих функцій. Ця функція робить декілька речей (є ще одна моя публікація, яка пояснює це далі), але ключовим моментом є те, що існують завдання, які очікують на розгляд, і черга, яка завершена. Коли працівники закінчують кожне завдання в черзі, що очікує, вони додають результати в чергу, виконану із завданнями. Ви можете обернути перевірку до черги завдань, виконаної за допомогою панелі прогресу tqdm. Я не ставлю тут реалізацію функції do_work (), це не актуально, оскільки тут повідомляється про моніторинг завершеної черги завдань та оновлення індикатора виконання кожного разу, коли надходить результат.

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

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

-2

Цей підхід простий і працює.

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()
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.