【问题标题】:Segmentation Fault Error while doing big calculations in python在 python 中进行大计算时出现分段错误错误
【发布时间】:2021-05-29 18:16:17
【问题描述】:

我想计算一些非常大的数字的 collat​​z 序列,但我猜 python 无法处理这么大的数字,我不知道如何让它处理。

这是我的程序:

def collatz(n):
    if (n == -1 or n == 1 or n == -17 or n == -17 -2**4096):
        print('break found',n)
        return
    if str(n)[-1] in ['1','3','5','7','9']:
        #print(n)
        return collatz(3*n + 1)
    else:
        return collatz(n//2)

我想使用n = 2**4096 范围数字。我通过sys.setrecursionlimit 函数增加了递归限制。但现在我面临分段错误错误。

>>> sys.setrecursionlimit(10**9)
>>> collatz(2**1000 + 1)
break found: 1
>>> collatz(2**4000 + 1)
Segmentation fault (core dumped)

请就我需要修改的内容给我一些建议以实现大输入支持..

【问题讨论】:

  • 不要使用递归(你没有足够大的堆栈)。此外,您有一个不返回值的分支...
  • 另外,使用n % 2 == 1 代替str(n)[-1] in ['1','3','5','7','9'] - 将大整数转换为字符串可能会很慢(尽管2**4096 不是特别大。

标签: python segmentation-fault collatz


【解决方案1】:

使它成为非递归的,太深的递归会溢出堆栈,堆栈通常只有几兆字节。堆栈溢出后,您的程序会因分段错误而崩溃。

您的代码已修改为非递归(不会崩溃):

Try it online!

def collatz(n):
    while True:
        if (n == -1 or n == 1 or n == -17 or n == -17 -2**4096):
            print('break found', n)
            return
        if str(n)[-1] in ['1','3','5','7','9']:
            #print(n)
            n = 3 * n + 1
        else:
            n = n // 2


collatz(2**4000 + 1)

输出:

break found 1

顺便说一句,经典的 Collat​​z 问题可以用更短、更快的代码来解决,例如:

Try it online!

def collatz(n):
    for i in range(1 << 50):
        if n == 1:
            return i
        n = 3 * n + 1 if n & 1 else n >> 1

print('Collatz chain length:', collatz(2**4000 + 1))

输出:

Collatz chain length: 29400

另外,我想提一下 Python 库 GMPY2,它基于著名的基于 C 的 GMP。它有非常优化的长整数算术代码,如果你真的需要速度,它可以用来提升你的代码。

在 Windows 上 gmpy2 可以通过下载from here 并通过pip install gmpy2‑2.0.8‑cp39‑cp39‑win_amd64.whl 安装来安装。在Linux上可以通过sudo apt install python3-gmpy2安装。

安装后你可以很简单地使用gmpy2,比如下面的函数collatz_gmpy()

Try it online!

def collatz_py(n):
    for i in range(1 << 50):
        if n == 1:
            return i
        n = 3 * n + 1 if n & 1 else n >> 1

def collatz_gmpy(n):
    from gmpy2 import mpz
    n = mpz(n)
    for i in range(1 << 50):
        if n == 1:
            return i
        n = 3 * n + 1 if n & 1 else n >> 1

def test():
    import timeit
    n = 2 ** 100000 + 1
    for i, f in enumerate([collatz_py, collatz_gmpy]):
        print(f.__name__, round(timeit.timeit(lambda: f(n), number = 1), 3), 'secs')

test()

输出:

collatz_py 7.477 secs
collatz_gmpy 2.916 secs

正如我们所见,与常规 Python 的变体相比,GMPY2 变体提供了 2.56x 倍的加速。

【讨论】:

  • 我对这里的数学一无所知...为什么两个答案不同(OP算法不正确)?
  • FWIW,第二个版本在我的电脑上运行了 1.518 秒,在计算 collatz(2**40000-1) 时——虽然结果不同。
  • @thebjorn 答案不同,因为我在第二个代码中输出了不同的内容。在我的第二个代码中,我解决了一个经典的Collatz problem 示例,因为它通常被很多人解决。在我的第二个代码中,我返回循环迭代的次数,这个数字等于 Collat​​z 链的长度,因为算法总是产生一个链,不同输入的长度不同。 OP的算法总是输出1,它输出的不是链的长度,而是链的最后一个元素,最后总是1(所有其他负值,如-17永远不会发生)
  • @Arty for i in range(1 &lt;&lt; 50): 做什么?是为了得到一个非常大的限制吗?
  • @Vicrobot 是的,1import itertools,我只是想通过随机大限制来简化事情。
【解决方案2】:

我会这样写:

def collatz(n):
    tmp = -17 - 2**4096            # precompute constant value outside loop
    while 1:                       # in general, iteration is better than recursion in Python for these kinds of functions
        if n in (-1, 1, -17, tmp): # less typing than lots of or clauses
            return n               # return values rather than printing them
        elif n % 2 == 1:           # faster than converting to string and checking last digit
            n = 3*n + 1
        else:
            n //= 2

用例如调用它:

print(collatz(2**4000 + 1))

性能:在我的 PC 上,collatz(2**5000-1) 使用上述代码需要 0.069 秒。将代码更改为 elif str(n)[-1] in '13579': 需要 1.606 秒,即慢 23 倍。

collatz(2**40000-1) 上,上面的代码使用了 3.822 秒。改变

elif n % 2 == 1:

到任一

elif n % 2:

elif n & 1:

将时间减少到 1.988 秒(即没有差异)。

n //= 2 更改为 n &gt;&gt;= 1 将时间进一步减少到 1.506 秒。

【讨论】:

  • 顺便说一句,不确定 Python 是否进行了除法优化,因此可以将 n % 2 写为 n &amp; 1n //= 2 写为 n &gt;&gt;= 1
  • Python 没有:-)
  • 那么为了让代码真正优化得更好,最好将n % 2写成n &amp; 1n //= 2写成n &gt;&gt;= 1。只是一个注释,不需要修改你的代码。
  • @Arty 我添加了性能比较。 str 删除效果最大,但所有其他建议都具有可衡量的性能优势,至少在我测试过的单个数字上 ;-)
  • 另一个微小的优化:3n+1 是偶数 => 下一步将是 //2,您可以将这两个步骤合并为一个 (3n+1)//2n+(n+1)//2。或者干脆省略else: 每次都进行除法。
猜你喜欢
  • 2021-08-31
  • 1970-01-01
  • 2016-07-11
  • 2013-06-30
  • 1970-01-01
  • 2021-04-26
  • 1970-01-01
  • 1970-01-01
  • 2018-12-19
相关资源
最近更新 更多