python脚本的内存管理

时间:2015-10-18 13:49:13

标签: python memory-management ram

所以我试图解决python中Euler项目的一些问题。我目前正在研究Problem 92方形数字链。基本上这个想法是,如果你取任何整数,并递归地对其组成数字求平方(例如42 = 4 2 + 2 2 = 20,然后2 2 + 0 2 = 4等),你总是以1或89结束。

我正在尝试编写一个程序,可以计算出1到10个 K 范围内的数字,最终会有89个,最后会有多少个数字结束于1.我不是在尝试存储哪些整数最终到达哪里,只有多少。目标是能够为最大的K可能做到这一点。 (这是Hackerrank对那些好奇的挑战)。

在我生命中为了大量的事情,我需要使用缓存。但是,这是缓存(最终占用大量RAM)和计算时间之间的平衡行为。

我的问题是我最终耗尽了内存。所以我试图限制我正在使用的缓存的长度。但是,我仍然内存不足。我似乎无法找到导致我内存不足的原因。

我在ubuntu 14.04 LTS上的pycharm上运行它。

我的问题:

有没有办法检查什么占用我的内存?是否有一些工具(或脚本)可以让我基本上监视程序中变量的内存使用情况?或者错误地假设如果我用完RAM,那一定是因为我程序中的某个变量太大了?我不得不承认我对程序中内存使用的精细细节并不是那么清楚....

编辑:当K = 8时,我用完了mem,所以对于10 8 的整数,这不是那么大。此外,我在10 8 之前进行了测试(因此10 7 ,终止但需要一些时间并且使用比较小计算更多的内存)。而且似乎没有限制我的缓存大小变量产生差异.....

3 个答案:

答案 0 :(得分:3)

I would suggest testing various cache sizes to see if it is actually beneficial to have as large a cache as possible.

If you take any 10-digit number and compute the sum of squares of its digits, the sum will be at most 10*9*9 = 810. Thus, if you cache the result for numbers 1 to 810, then you should be able to process all numbers with between 4 and 10 digits without recursion.

In this way, I have processed the first 10^8 numbers in around 6 minutes with memory usage staying constant at roughly 10 MB.

答案 1 :(得分:2)

这是Mathias Rav的一个很好的想法,但保留了使用带有记忆的递归函数的想法。我们的想法是使用辅助函数来完成繁重的工作,并使主函数只进行迭代的第一步。第一步是将问题大小减小到一个缓存有用的大小。缓存仍然很小。我能够在大约10分钟内完成所有数字达到10 ** 8(由于递归导致的开销使得此解决方案的效率低于Mathias'解决方案):

cache = {}

def helper(n):
    if n == 1 or n == 89:
        return n
    elif n in cache:
        return cache[n]
    else:
        ss = sum(int(d)**2 for d in str(n))
        v = helper(ss)
        cache[n] = v
        return v

def f(n):
    ss = sum(int(d)**2 for d in str(n))
    return helper(ss)

def freq89(n):
    total = 0
    for i in range(1,n+1):
        if f(i) == 89: total += 1
    return total/n

答案 2 :(得分:1)

这是Mathias Rav和John Coleman对答案的扩展评论。我打算把它作为社区维基的答案。约翰科尔曼说不要这样做,所以我不是。


我将从John Coleman的回答开始。

cache = {}

def helper(n):
    if n == 1 or n == 89:
        return n
    elif n in cache:
        return cache[n]
    else:
        ss = sum(int(d)**2 for d in str(n))
        v = helper(ss)
        cache[n] = v
        return v

def f(n):
    ss = sum(int(d)**2 for d in str(n))
    return helper(ss)

通过将if初始化为helper(n),一件可以加快速度的小事就是避免cache中的{1:some_value, 89:some_other_value} {1:1, 89:89}。明显的初始化是{1:False, 89:True}。不太明显但最终更快的初始化是if f(i) == 89: total += 1。这样可以将if f(i): total += 1更改为def helper(n): l = [] while n not in cache : l.append(n) n = sum(int(d)**2 for d in str(n)) v = cache[n] for k in l : cache[k] = v return v

另一件可能有用的小事就是摆脱递归。情况并非如此。为了摆脱递归,我们必须按照

的方式做一些事情
f(n)

问题在于helper遇到的几乎所有数字都已经在缓存中,这要归功于从f(n)调用sum(int(d)**2 for d in str(n))的方式。摆脱递归不必要地创建一个需要进行垃圾收集的空列表。

John Coleman的答案最大的问题是通过ss计算数字的平方和。虽然非常pythonic,这是非常昂贵的。我首先将helperf中的变量def ss(n): return sum(int(d)**2 for d in str(n)) 更改为函数:

def ss(n):
    s = 0
    while n != 0:
        d = n % 10
        n = n // 10
        s += d**2
    return s

仅此一点对性能无效。事实上,它会伤害性能。函数调用在python中很昂贵。通过使它成为一个函数,我们可以通过用整数运算替换字符串运算来做一些非pythonic的事情:

d*d

这里的加速非常重要;我的计算时间减少了30%。那不是很好。还有另一个问题,使用取幂运算符。几乎任何语言,但Fortran和Matlab,使用d**2cache = {1:False, 89:True} def ss (n): s = 0 while n != 0: d = n % 10 n = n // 10 s += d*d return s def helper(n): if n in cache: return cache[n] else: v = helper(ss(n)) cache[n] = v return v def f(n): return helper(ss(n)) def freq89(n): total = 0 for i in range(1,n+1): if f(i): total += 1 return total/n print (freq89(int(1e7))) 快得多。这在python中肯定是这样的。这个简单的改变几乎将执行时间减少了30%。

将所有这些放在一起产生

N = int(1e7)
cache = {1:False, 89:True}

def ss(n):
    s = 0
    while n != 0:
        d = n % 10
        n //= 10
        s += d*d
    return s

def initialize_cache(maxsum):
    for n in range(1,maxsum+1):
        l = []
        while n not in cache:
            l.append(n)
            n = ss(n)
        v = cache[n]
        for k in l:
            cache[k] = v

def freq89(n):
    total = 0
    for i in range(1,n):
        if cache[ss(i)]:
            total += 1
    return total/n

maxsum = 81*len(str(N-1))
initialize_cache(maxsum)
print (freq89(N))


我还没有利用Mathias Rav的答案。在这种情况下,摆脱递归是有意义的。它还有助于将循环嵌入到初始化缓存的函数内部的初始范围内(函数调用在python中很昂贵)。

ss(n)


上面大约需要16.5秒(在我的电脑上)来计算我的电脑上1(含)和10000(不包括)之间数字的比率。这几乎是初始版本(44.7秒)的三倍。上面计算1(包括)和1e8(不包括)之间的数字的比率需要花费三分多于3分钟。


事实证明我没有完成。当程序刚刚为12345678执行此操作时,无需计算(例如)12345679的数字的平方和。一个减少十分之九用例的计算时间的快捷方式得到回报。函数prevn = 0 prevd = 0 prevs = 0 def ss(n): global prevn, prevd, prevs d = n % 10 if (n == prevn+1) and (d == prevd+1): s = prevs + 2*prevd + 1 prevs = s prevn = n prevd = d return s s = 0 prevn = n prevd = d while n != 0: d = n % 10 n //= 10 s += d*d prevs = s return s 变得有点复杂:

setTimeout

有了这个,计算最多(但不包括)1e7的数字的比率需要6.6秒,最多但不包括1e8的数字为68秒。