Ответ 1
Проблема заключается в том, что многопроцессорность имеет предел наибольшему int, который он может передать подпроцессам внутри xrange. Вот быстрый тест:
import sys
from multiprocessing import Pool
def doit(n):
print n
if __name__ == "__main__":
procs = int(sys.argv[1])
iters = int(float(sys.argv[2]))
p = Pool(processes=procs)
for points in p.map(doit, [xrange(int(iters))] * procs):
pass
Сейчас:
$ ./multitest.py 2 1E8
xrange(100000000)
xrange(100000000)
$ ./multitest.py 2 1E9
xrange(1000000000)
xrange(1000000000)
$ ./multitest.py 2 1E10
xrange(1410065408)
xrange(1410065408)
Это часть более общей проблемы с многопроцессорной обработкой: она опирается на стандартную травировку Python, а некоторые незначительные (и недостаточно хорошо документированные) расширения передают значения. Всякий раз, когда что-то идет не так, первое, что нужно проверить, это то, что значения поступают так, как вы ожидали.
Фактически вы можете увидеть эту проблему, играя с помощью pickle
, даже не касаясь multiprocessing
(что не всегда происходит из-за этих незначительных расширений, но часто бывает):
>>> pickle.dumps(xrange(int(1E9)))
'c__builtin__\nxrange\np0\n(I0\nI1000000000\nI1\ntp1\nRp2\n.'
>>> pickle.dumps(xrange(int(1E10)))
'c__builtin__\nxrange\np0\n(I0\nI1410065408\nI1\ntp1\nRp2\n.'
Даже не изучая все детали протокола рассола, должно быть очевидно, что I1000000000
в первом случае равен 1E9 как int, тогда как эквивалентный кусок следующего случая составляет около 1.41E9, а не 1E10, как int. Вы можете экспериментировать
Одно очевидное решение - передать int(iters)
вместо xrange(int(iters))
и calculate_pi
создать xrange
из его аргумента. (Примечание: в некоторых случаях очевидная трансформация, подобная этой, может повредить производительность, может быть, и плохо. Но в этом случае она, вероятно, немного лучше, если что-либо - более простой объект для передачи, и вы распараллеливаете конструкцию xrange
- и, конечно же, разница настолько крошечная, что, вероятно, не будет иметь значения. Просто подумайте, прежде чем слепо трансформировать.)
И быстрый тест показывает, что теперь это работает:
import sys
from multiprocessing import Pool
def doit(n):
print xrange(n)
if __name__ == "__main__":
procs = int(sys.argv[1])
iters = int(float(sys.argv[2]))
p = Pool(processes=procs)
for points in p.map(doit, [iters] * procs):
pass
Тогда:
$ ./multitest.py 2 1E10
xrange(10000000000)
xrange(10000000000)
Однако вы все равно столкнетесь с большим лимитом:
$ ./multitest.py 2 1E100
OverflowError: Python int too large to convert to C long
Опять же, это та же самая основная проблема. Один из способов решения этой проблемы - передать аргумент arg вниз как строку и сделать int (float (a)) внутри подпроцессов.
В качестве побочного примечания: причина, по которой я делаю iters = int(float(sys.argv[2]))
вместо просто iters = float(sys.argv[2])
, а затем использовать int(iters)
позже, чтобы избежать случайного использования значения float iters
позже (как это делает версия OP, в вычислении total
и, следовательно, total_in / total
).
И имейте в виду, что если вы доберетесь до достаточно больших чисел, вы столкнетесь с ограничениями двойного типа C: 1E23
обычно 9999999999999999161611392, а не 100000000000000000000000.