繁体   English   中英

python脚本的内存管理

[英]Memory management for python scripts

因此,我正在尝试解决python中Euler项目中的一些问题。 我目前正在研究问题92 (方形数字链)。 基本上,这个想法是,如果您采用任何整数,然后对它的组成位数进行平方(例如42 = 4 2 + 2 2 = 20,然后2 2 + 0 2 = 4,依此类推),则您总是以1或2结束。 89。

我正在尝试编写一个程序,该程序可以计算在1到10 K范围内将有多少个数字以89结尾,有多少将以1结尾。许多。 目标是能够做到最大可能的K。 (对于那些好奇的人来说,这是Hackerrank的挑战)。

为了在我的一生中做大量工作,我需要使用缓存。 但这是缓存(最终会占用大量RAM)和计算时间之间的平衡。

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

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

我的问题:

有没有一种方法来检查什么是占用了我的内存? 是否有一些工具(或脚本)可以让我从根本上监视程序中变量的内存使用? 或者假设我的RAM用完了一定是错误的,这一定是因为程序中的某些变量太大了吗? 我必须承认,我对程序中内存使用的详细细节还不是很清楚。

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

我建议测试各种缓存大小,以查看拥有尽可能大的缓存是否真正有益。

如果采用任何10位数字并计算其数字的平方和,则总和最多为10 * 9 * 9 =810。因此,如果将结果缓存为1到810,那么您应该能够处理所有4到10位之间的数字而无需递归。

这样,我在大约6分钟内处理了前10个8 ^数字,而内存使用量则保持在大约10 MB的恒定值。

这是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

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


我将从约翰·科尔曼的答案开始。

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)

一个可以加快速度的小事情是,通过将cache初始化为{1:some_value, 89:some_other_value}来避免ifhelper(n) 明显的初始化是{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) 摆脱递归会不必要地创建一个空列表,需要对其进行垃圾回收。

约翰·科尔曼(John Coleman)的答案最大的问题是,通过sum(int(d)**2 for d in str(n))计算数字平方sum(int(d)**2 for d in str(n)) 虽然非常pythonic,但这非常昂贵。 我将从将helperf的变量ss更改为一个函数开始:

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

仅此一项就无法提高性能。 实际上,这会损害性能。 函数调用在python中很昂贵。 通过将此功能设为函数,我们可以通过用整数算术替换字符串操作来做一些非Python的事情:

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

这里的加速非常重要。 我的计算时间减少了30%。 那不是很好 还有一个问题,就是使用幂运算符。 除了Fortran和Matlab以外,几乎所有语言都使用d*dd**2快得多。 在python中肯定是这种情况。 这项简单的更改将执行时间减少了30%,而执行时间却减少了近一半。

将所有这些放在一起产生

cache = {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)))


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

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))


上面的内容(在我的计算机上)大约需要16.5秒才能计算出我的计算机上1(含)和10000000(不含)之间的数字比率。 这几乎比初始版本(44.7秒)快三倍。 以上计算需要花费三分钟多的时间来计算1(含)和1e8(不含)之间的数字比率。


事实证明我还没有完成。 程序仅对12345678进行计算时,就无需按位数计算(例如)12345679的数字平方和。缩短十个用例中九个用例的计算时间的捷径便得到了回报。 函数ss(n)变得更加复杂:

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

这样,计算(不包括)1e7之前的数字的比率需要6.6秒,对于不包括1e8的数字要计算68秒。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM