главный кризис python: пул обработки идет медленнее? - PullRequest
8 голосов
/ 26 августа 2011

Так что последние несколько дней я возился с многопроцессорной библиотекой python, и мне действительно нравится пул обработки. Это легко реализовать, и я могу визуализировать множество применений. Я сделал несколько проектов, о которых я слышал прежде, чтобы познакомиться с ним, и недавно закончил программу, которая подрывает игры палача.

В любом случае, я проводил сравнение времени выполнения суммирования всех простых чисел от 1 до 2 миллионов, как однопоточных, так и через пул обработки. Теперь для обработчика Hangman размещение игр в пуле обработки позволило сократить время выполнения примерно в 8 раз (i7 с 8 ядрами), но при измельчении этих простых чисел фактически увеличило время обработки почти в два раза из 4.

Может кто-нибудь сказать мне, почему это? Вот код для тех, кто заинтересован в просмотре или тестировании:

#!/user/bin/python.exe
import math
from multiprocessing import Pool

global primes
primes = []

def log(result):
    global primes

    if result:
        primes.append(result[1])

def isPrime( n ):
    if n < 2:
        return False
    if n == 2:
        return True, n

    max = int(math.ceil(math.sqrt(n)))
    i = 2
    while i <= max:
        if n % i == 0:
            return False
        i += 1
    return True, n


def main():

   global primes

   #pool = Pool()

   for i in range(1000000, 2000000):
       #pool.apply_async(isPrime,(i,), callback = log)
       temp = isPrime(i)
       log(temp)

   #pool.close()
   #pool.join()

   print sum(primes)

   return

if __name__ == "__main__":
    main()

В настоящее время он будет выполняться в одном потоке, проходить через пул обработки, раскомментировать операторы пула и закомментировать другие строки в основном цикле for.

1 Ответ

14 голосов
/ 27 августа 2011

самый эффективный способ использования multiprocessing - это разделить работу на n блоков одинакового размера с размером n пула, который должен приблизительно равняться количеству ядер в вашей системе. Причина этого в том, что работа по запуску подпроцессов и связи между ними довольно велика. Если размер работы невелик по сравнению с количеством рабочих фрагментов, тогда издержки IPC становятся значительными.

В вашем случае вы запрашиваете многопроцессорность обрабатывать каждое простое число индивидуально. Лучший способ справиться с этой проблемой - передать каждому работнику диапазон значений (возможно, просто начальное и конечное значение) и вернуть все простые числа в найденном диапазоне.

В случае идентификации простых чисел большого числа выполненная работа увеличивается с начальным значением, и поэтому вы, вероятно, не хотите делить общий диапазон на ровно n кусков, а скорее на n * k равных кусков, с k какое-то разумное, небольшое число, скажем, 10–100. Таким образом, когда некоторые работники заканчивают работу раньше других, остается больше работы, и она может быть эффективно сбалансирована между всеми работниками.

Edit: вот улучшенный пример, чтобы показать, как это решение может выглядеть. Я изменился как можно меньше, чтобы вы могли сравнить яблоки с яблоками.

#!/user/bin/python.exe
import math
from multiprocessing import Pool

global primes
primes = set()

def log(result):
    global primes

    if result:
        # since the result is a batch of primes, we have to use 
        # update instead of add (or for a list, extend instead of append)
        primes.update(result)

def isPrime( n ):
    if n < 2:
        return False
    if n == 2:
        return True, n

    max = int(math.ceil(math.sqrt(n)))
    i = 2
    while i <= max:
        if n % i == 0:
            return False
        i += 1
    return True, n

def isPrimeWorker(start, stop):
    """
    find a batch of primes
    """
    primes = set()
    for i in xrange(start, stop):
        if isPrime(i):
            primes.add(i)

    return primes



def main():

    global primes

    pool = Pool()

    # pick an arbitrary chunk size, this will give us 100 different 
    # chunks, but another value might be optimal
    step = 10000

    # use xrange instead of range, we don't actually need a list, just
    # the values in that range.
    for i in xrange(1000000, 2000000, step):
        # call the *worker* function with start and stop values.
        pool.apply_async(isPrimeWorker,(i, i+step,), callback = log)

    pool.close()
    pool.join()

    print sum(primes)

    return

if __name__ == "__main__":
    main()
...