我不确定这是否与其他PyPy记忆问题重复,但是在这里我将提供一个具体示例。
from __future__ import division
def mul_inv(a, m):
"""Modular multiplicative inverse, a^-1 mod m. Credit: rosettacode.org"""
m0 = m
x0, x1 = 0, 1
if m == 1: return 1
while a > 1:
assert m != 0, "a and m must be coprime"
q = a // m
a, m = m, a%m
x0, x1 = x1 - q * x0, x0
if x1 < 0: x1 += m0
return x1
M = 1000000009
L = 10**8
bin2 = [0] * L
bin2[0] = 1
for n in range(L-1):
bin2[n+1] = (bin2[n] * (4*n + 2) * mul_inv(n+1, M)) % M
if n % 10**5 == 0: print(n, bin2[n])
print(bin2[:20])
使用python 3.6时,该程序最多使用3-4 GB并运行到完成为止(Armin Rigo的列表更改并未对此做出重大更改)。使用运行PyPy 5.10.0的python 2.7.13,程序会迅速达到8 GB(我有多少RAM)并冻结。即使gc.collect()
被调用,当n
约为3.5 * 10 ^ 7时,程序也会用尽内存。
此内存使用量来自哪里?唯一大的内存使用情况应该是将bin2
初始化为10 ^ 8 int列表。假设mul_inv
中的所有局部变量都是垃圾回收的,那么没有什么可以增加内存使用了。
答案 0 :(得分:1)
AFAICT这里的问题是,您正在为数组分配long数,尽管有了模数,PyPy似乎并未注意到该数字仍然适合机器字。
我可以想到两种解决方法:
bin2[n+1]
传递分配给int()
的值。array.array()
。前者仅影响PyPy2,在我的Mac上似乎可以稳定地占用约800MB的内存,而后者无论在PyPy2还是PyPy3中运行,都可以稳定在约1.4GB。
>不过,我还没有完全运行程序,所以YMMV…
答案 1 :(得分:0)
糟糕,这是整数列表优化的糟糕情况。问题是,这始于int列表:
bin2 = [0] * L
这在内部存储为一个整数数组。它通常要紧凑得多,即使在这种情况下它也不会改变任何东西-因为在CPython上,它是一个包含L
个相同对象0
的副本的列表。
但是问题是很快,我们在列表中存储了long
。此时,我们需要将整个列表转换为可以存储任何内容的通用类型。但!问题是我们看到1亿个零,因此我们创建了1亿个0
对象。除了列表本身有800MB的存储空间外,这会立即造成3GB的存储压力。
如果我们像这样初始化列表,我们可以检查是否不会出现问题,以便它实际上包含同一对象的1亿倍:
bin2 = [0L] * L # Python 2.x
bin2[0] = 1
也就是说,在您的示例中,您不需要列表首先包含1亿个元素。您可以将其初始化为:
bin2 = [1]
,然后使用bin2.append()
。这样一来,程序的启动速度将大大提高,并且开始时不会占用大量内存。
请注意,PyPy3仍比CPython3使用更多的内存。