Ошибка большого числа итераций Python
Я написал простой Монте-Карло вычисление π программа на Python, используя модуль многопроцессорной обработки. Он работает отлично, но когда я передаю 1e+10 итераций для каждого работника, возникает некоторая проблема, и результат неправильный. Я не могу понять в чем проблема, потому что все хорошо на 1е+9 повторов!
import sys
from multiprocessing import Pool
from random import random
def calculate_pi(iters):
""" Worker function """
points = 0 # points inside circle
for i in iters:
x = random()
y = random()
if x ** 2 + y ** 2 <= 1:
points += 1
return points
if __name__ == "__main__":
if len(sys.argv) != 3:
print "Usage: python pi.py workers_number iterations_per_worker"
exit()
procs = int(sys.argv[1])
iters = float(sys.argv[2]) # 1E+8 is cool
p = Pool(processes=procs)
total = iters * procs
total_in = 0
for points in p.map(calculate_pi, [xrange(int(iters))] * procs):
total_in += points
print "Total: ", total, "In: ", total_in
print "Pi: ", 4.0 * total_in / total
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 = float(sys.argv[2])
и затем с помощью int(iters)
позже, чтобы избежать случайного использования поплавка iters
значение позже (как и версия OP, в computing total
и поэтому total_in / total
).
и имейте в виду, что если вы доберетесь до достаточно больших чисел, вы столкнетесь с пределами типа C double:1E23
обычно 99999999999999991611392, а не 100000000000000000000000.