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 估计值,而 141006540​​8 次迭代却没有? @MattiLyra:作为第一个猜测,如果您正在执行 141006540​​8 次迭代,但认为您正在执行 1000000000 次迭代,那么您最终会得到错误的划分。但我还没有测试或真正考虑过。 在 python 2.7.3 我得到 &gt;&gt;&gt; pickle.dumps(xrange(int(1E10))) OverflowError: Python int too large to convert to C long 在 pyhton 3.2.3 我得到 &gt;&gt;&gt; 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视频平台新增登录失败达到允许大最大次数后是否锁定账号的配置

使用Python请求http/https时设置失败重试次数

如何计算 Chapel forall 循环中的迭代次数

subprocess.run() 在第二次迭代中失败

有没有办法跟踪或获取 JPA 在由于 BatchUpdateException 而失败之前完成的批迭代总数?

第一章:性能测试的常见指标