Подтвердить что ты не робот

Большой номер итераций Python

Я написал простую программу monte-carlo π calc в Python с использованием многопроцессорного модуля. Он работает нормально, но когда я передаю 1E + 10 итераций для каждого рабочего, возникает какая-то проблема, и результат неверен. Я не могу понять, в чем проблема, потому что все в порядке на 1E + 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
4b9b3361

Ответ 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.