Python大迭代次数失败

时间:2012-09-24 17:32:27

标签: python parallel-processing iteration montecarlo pi

我使用多处理模块在Python中编写了简单的monte-carlo π calculation程序。 它工作正常,但是当我为每个工作者传递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

1 个答案:

答案 0 :(得分:14)

问题似乎是多处理对可以传递给xrange内的子进程的最大int有限制。这是一个快速测试:

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,有一些小的(并没有很好记录)扩展来传递值。每当出现问题时,首先要检查的是价值是按照预期的方式到达的。

事实上,你可以通过玩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.'

即使不了解pickle协议的所有细节,显然第一种情况下的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值on(正如OP的版本一样,在计算total,因此total_in / total)。

请记住,如果你得到足够多的数字,你会遇到C双重类型的限制:1E23通常是99999999999999991611392,而不是100000000000000000000000。