Python大迭代次数失败
Posted
技术标签:
【中文标题】Python大迭代次数失败【英文标题】:Python large iterations number fail 【发布时间】:2012-09-16 04:54:51 【问题描述】:我使用多处理模块在 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
【问题讨论】:
你得到的错误结果是什么? @AmirRachum π 就像 ~0.4 总迭代次数是正确的。 不管procs
是什么,都会发生这种情况吗? procs
使用什么样的值?
为什么 iters 是 float 而不是 int?例如,10000.0001 次迭代意味着什么?
@MattiLyra 没有除以 procs
数字。所以不应该。我稍后会测试它。 @abarnert 为指数。 1E+NUM 很有用。
【参考方案1】:
问题似乎是多处理对它可以传递给 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
来发现这个问题,甚至不用碰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(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
通常是 99999999999999991611392,而不是 100000000000000000000000。
【讨论】:
那么为什么 1000000000 次迭代可以为您提供正确的 PI 估计值,而 1410065408 次迭代却没有? @MattiLyra:作为第一个猜测,如果您正在执行 1410065408 次迭代,但认为您正在执行 1000000000 次迭代,那么您最终会得到错误的划分。但我还没有测试或真正考虑过。 在 python 2.7.3 我得到>>> pickle.dumps(xrange(int(1E10))) OverflowError: Python int too large to convert to C long
在 pyhton 3.2.3 我得到 >>> pickle.dumps(range(int(1E10)),protocol=0) b'c__builtin__\nxrange\np0\n(L0L\nL10000000000L\nL1L\ntp1\nRp2\n.'
@XavierCombelle:这不仅仅是 2.7.3 与 3.2.3 的对比;它还涉及 32 位与 64 位、不同的平台和编译器等。如果您范围内的所有数字恰好都适用于您的实现,您可以忽略该问题(除非您以后可能想要更大的范围或更多的可移植性) ;否则,你必须处理它。
这个问题被追踪到python核心开发bugs.python.org/issue16029以上是关于Python大迭代次数失败的主要内容,如果未能解决你的问题,请参考以下文章
RTSP/Onvif协议EasyNVR视频平台新增登录失败达到允许大最大次数后是否锁定账号的配置