不久前,我用Python为IRC编写了一个Markov chain文本生成器。在运行一两个月后,它会消耗掉我VPS的所有空闲内存,我需要清除它的数据并重新开始。现在我正在重写它,我想尽可能优雅地处理内存问题。在
我需要保留的数据通常是一个将字符串映射到字符串列表的字典。更具体地说,消息中的每个单词都映射到所有可能的后续单词。这仍然是一个过于简单化,但它已经足够将我的问题具体化。在
目前,我正在努力解决的解决方案是管理“桶”数据。它会跟踪每个bucket的表观大小,当bucket达到一定的大小时,“archive”一个bucket并转移到一个新的bucket,大约5个bucket之后,它会在每次创建新bucket时删除最旧的“archived”bucket。这有一个简单的优点:删除一个完整的bucket不会创建任何死胡同或无法访问的单词,因为每个消息中的单词都进入同一个bucket中。在
问题是“跟踪每个桶的表观尺寸”说起来容易做起来难。在
我第一次尝试使用sys.getsizeof
,但很快发现,确定对象在内存中的实际大小是不切实际的。我还研究了guppy/heapy/各种其他内存使用模块,但它们似乎都没有达到我所期望的效果(即,对单个对象进行基准测试)。目前我正在试验较低级别的psutil模块。以下是应用程序当前状态的摘录:
class Markov(object):
# (constants declared here)
def __init__(self):
self.proc = psutil.Process(os.getpid())
self.buckets = []
self._newbucket()
def _newbucket(self):
self.buckets.append(copy.deepcopy(self.EMPTY_BUCKET))
def _checkmemory(f):
def checkmemory(self):
# Check memory usage of the process and the entire system
if (self.proc.get_memory_percent() > self.MAX_MEMORY
or psutil.virtual_memory().percent > self.MAX_TOTAL_MEMORY):
self.buckets.pop(0)
# If we just removed the last bucket, add a new one
if not self.buckets:
self._newbucket()
return f()
return checkmemory
@_checkmemory
def process(self, msg):
# generally, this adds the words in msg to self.buckets[-1]
@_checkmemory
def generate(self, keywords):
# generally, this uses the words in all the buckets to create a sentence
这里的问题是,这只会使bucket过期;我不知道何时“归档”当前的bucket,因为Python的开销内存使我无法准确地确定距离到达self.MAX_MEMORY
有多远。更不用说,Markov
类实际上是由无头IRC客户机管理的许多“插件”之一(为了简洁起见,我省略了另一个细节),因此开销不仅存在,而且不可预测。在
简而言之:有没有一种方法可以精确地对单个Python对象进行基准测试?或者,如果您能想出比我的基于桶的解决方案更好的“过期”旧数据的方法,我会洗耳恭听。在
这可能是一个有点老套的解决方案,但是如果bucket对象是可pickle的(听起来像是这样),那么可以对它们进行pickle并测量pickled对象字符串的字节长度。它可能不是内存中解压对象的大小,但它应该随着对象的增长而线性增长,并使您对对象之间的相对大小有一个相当好的了解。在
为了避免必须对非常大的对象进行pickle,您可以测量添加到bucket中的每个条目的大小,方法是单独对其进行酸洗,并将其bytelength添加到bucket的total bytelength属性中。 不过,请记住,如果这样做,条目和bucket的内部绑定中将使用一些开销内存,这些内存不会由条目本身的独立大小反映出来,但是您可以运行一些测试来分析这一点,并计算出超出实际大小的每个新条目的内存开销百分比。在
相关问题 更多 >
编程相关推荐