我写的很简单monte-carlo π calculation Python 程序,使用多处理模块。 它工作得很好,但是当我为每个 worker 传递 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
最佳答案
问题似乎是多处理对它可以传递给 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 pickling,以及一些次要的(没有很好记录的)扩展来传递值。每当出现问题时,首先要检查的是值是否按照您预期的方式到达。
事实上,您可以通过玩 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 协议(protocol)的所有细节,也应该很明显,第一种情况下的 I1000000000
是 1E9 作为 int,而下一种情况的等效 block 约为 1.41E9 ,而不是 1E10,作为一个整数。你可以试试
一个显而易见的解决方案是传递 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)
是为了避免以后意外使用浮点 iters
值(就像 OP 的版本一样,在计算 total
因此 total_in/total
)。
请记住,如果数字足够大,就会遇到 C double 类型的限制:1E23
通常是 99999999999999991611392,而不是 100000000000000000000000。
关于Python 大迭代次数失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12569977/