2013-08-29 2 views
3

Я только что добрался до Python, и я все еще в крутой фазе кривой обучения. Благодарим вас за любые комментарии.как распараллелить большие циклы в python

У меня есть большой цикл для запуска (большой в смысле многих итераций), например:

for i in range(10000) 
    for j in range(10000) 
     f((i,j)) 

Я, однако, что это будет общий вопрос о том, как распараллелить его, и после нескольких часов поиска по Google я пришел к решению, используя модуль «многопроцессорной», как следующее:

pool=Pool() 
x=pool.map(f,[(i,j) for i in range(10000) for j in range(10000)]) 

Это работает, когда цикл мал. Однако он очень медленный, если цикл большой, или иногда возникает ошибка памяти, если петли слишком велики. Кажется, что python сначала сгенерировал список аргументов, а затем передал список функции «f», даже используя xrange. Это верно?

Итак, это распараллеливание не работает для меня, потому что мне не нужно хранить все аргументы в списке. Есть лучший способ сделать это? Я ценю любые предложения или рекомендации. Спасибо.

+0

Вкратце: это не имеет никакого смысла, поскольку Python использует глобальную блокировку интерпретатора, которая предотвращает одновременное выполнение нескольких потоков. Вы все равно можете воспользоваться некоторыми преимуществами, если используете Jython или IronPython. http://wiki.python.org/moin/GlobalInterpreterLock –

+6

@GiulioFranco. Он использует «многопроцессорность», хотя и не «threading», поэтому GIL не играет. –

ответ

4

Кажется, что python сначала сгенерировал список аргументов, а затем передал список функции «f», даже используя xrange. Это верно?

Да, потому что вы используете понимание списка, которое явно просит его сгенерировать этот список.

(Обратите внимание, что xrange не очень уместная здесь, потому что у вас есть только два диапазона, в то время, каждый 10K долго, по сравнению с 100M списком аргументов, что нет ничего.)

Если вы хотите для генерации значений «на лету» по мере необходимости, вместо всего 100M сразу, вы хотите использовать выражение генератора вместо понимания списка. Что почти всегда только вопрос поворота скобки в скобках:

x=pool.map(f,((i,j) for i in range(10000) for j in range(10000))) 

Однако, как вы можете видеть из the source, map, в конечном счете просто составить список, если вы даете ему генератор, так что в этом случай, который ничего не решит. (Документы явно не говорят об этом, но трудно понять, как он может выбрать хороший chunksize, чтобы нарезать итерируемый, если он не имеет длины ...).

И даже если это не так, вы все равно снова столкнетесь с той же проблемой с результатами, потому что pool.map возвращает список.

Для решения обеих проблем вы можете использовать pool.imap. Он потребляет итерируемый лениво и возвращает ленивый итератор результатов.

Одна вещь, чтобы отметить, что imap не догадывается, в лучшем случае chunksize, если вы не разъехаться, но только по умолчанию 1, так что вам может понадобиться немного мысли или суда & ошибки, чтобы оптимизировать его.

Кроме того, imap по-прежнему будет стоять в очереди на некоторые результаты по мере их поступления, чтобы он мог вернуть их обратно в том же порядке, что и аргументы. В патологических случаях он может оказаться в очереди (пул-1)/объединить ваши результаты, хотя на практике это невероятно редко.Если вы хотите решить эту проблему, используйте imap_unordered. Если вам необходимо знать порядок, просто передать индексы назад и вперед с арг и результатами:

args = ((i, j) for i in range(10000) for j in range(10000)) 
def indexed_f(index, (i, j)): 
    return index, f(i, j) 
results = pool.imap_unordered(indexed_f, enumerate(args)) 

Однако я заметил, что в исходном коде, вы ничего не делать вообще с результаты f(i, j). В таком случае, зачем вообще собирать результаты? В этом случае, вы можете просто вернуться к петле:

for i in range(10000): 
    for j in range(10000): 
     map.apply_async(f, (i,j)) 

Однако imap_unordered все еще может быть стоит использовать, так как она обеспечивает очень простой способ, чтобы блокировать, пока все задачи выполняются, в то же время оставляя сам пул для последующего использования:

def consume(iterator): 
    deque(iterator, max_len=0) 
x=pool.imap_unordered(f,((i,j) for i in range(10000) for j in range(10000))) 
consume(x) 
+0

Я бы по-прежнему использовал 'xrange' только ради согласованности здесь, а в случае, когда' n' в 'range (n)' растет в какой-то более поздний момент. – Anorov

+0

@ Аноров: Последовательность с _what_? Я бы тоже использовал его ... но это не будет иметь большого значения. (Если n растет, n ** 2 будет расти еще быстрее.) – abarnert

+0

@abarnet Согласованность в том, что все выражение лениво. И вы правы в том, что это будет иметь мало значения, но я думаю, что хорошо использовать «xrange» для Python 2.x. – Anorov

Смежные вопросы