Показати хід виклику багатопроцесорного пулу 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
Я бачу роздруківку лише після виходу коду (не кожної ітерації). У вас є пропозиція?
Ханан Штейнгарт,

@HananShteingart: Це чудово працює в моїй системі (Ubuntu) як з Python 2, так і з 3. Я використовував def do_word(*a): time.sleep(.1)як приклад. Якщо у вас це не працює, створіть повний приклад мінімального коду, який демонструє вашу проблему: опишіть за допомогою слів, що ви очікуєте, і що станеться замість цього, згадайте, як ви запускаєте скрипт Python, яка ваша ОС, версія Python і опублікуйте це як нове запитання .
jfs

13
У мене була та ж проблема, що і @HananShteingart: це тому, що я намагався використовувати Pool.map(). Я не розумів , що тільки imap() і imap_unordered()працювати таким чином - документація просто говорить «версія ледачіше карти ()» , але на самому ділі означає «лежачий в основі итератор повертає результати , як вони приходять в».
simonmacmullen

@simonmacmullen: і питання, і моя відповідь використовуються imap_unordered(). Проблема Ханана, мабуть, пов’язана з sys.stderr.write('\r..')(переписуванням того самого рядка, щоб показати прогрес).
jfs

2
Також можливо! В основному я хотів задокументувати дурне припущення, яке я зробив - на випадок, якщо хтось, хто читає це, теж зробив це.
simonmacmullen

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

11
Я створив порожній список з назвою result перед циклом, тоді всередині циклу просто виконую результат.append (x). Я спробував це з 2 процесами і використовував imap замість map, і все працювало так, як я хотів, щоб @nickpick
bs7280

2
отже, мій індикатор прогресу виконує ітерацію до нових рядків, а не прогресує на місці.
Остін,

2
не забудьтеpip install tqdm
містер Т

3
@ bs7280 Під result.append (x) ви мали на увазі result.append (_)? Що таке х?
jason

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()створюється 10 фрагментів з 5 елементами в кожному. _number_leftпотім представляє, скільки з цих фрагментів було виконано.
mSSM

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

В основному, ви використовуєте apply_async із callbak (у цьому випадку це додавання поверненого значення до списку), тому вам не доведеться чекати, щоб зробити щось інше. Потім, протягом циклу while, ви перевіряєте хід роботи. У цьому випадку я додав віджет, щоб він виглядав приємніше.

Вихід:

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

Сподіваюся, це допомагає.


треба змінити: [pool.apply_async(my_function, (x,), callback=results.append) for x in dummy_args]за(pool.apply_async(my_function, (x,), callback=results.append) for x in dummy_args)
Девід Пшибілла

Це не правда. Об'єкт генератора тут працювати не буде. Перевірили.
swagatam

9

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