python效率和内存中的大对象

tip*_*ipu 8 python garbage-collection memory-management

我有一个多个进程,每个进程处理有40000元组的列表.这几乎可以最大化机器上的可用内存.如果我这样做:

        while len(collection) > 0:
            row = collection.pop(0)
            row_count = row_count + 1
            new_row = []
            for value in row:
                if value is not None:
                    in_chars = str(value)
                else:
                    in_chars = ""

                #escape any naughty characters
                new_row.append("".join(["\\" + c if c in redshift_escape_chars else c for c in in_chars]))
            new_row = "\t".join(new_row)
            rows += "\n"+new_row
            if row_count % 5000 == 0:
                gc.collect()
Run Code Online (Sandbox Code Playgroud)

这会释放更多的记忆吗?

Fra*_*ila 7

由于collection正以rows不断增长的速度收缩,因此您的内存使用率将保持稳定.这个gc.collect()电话不会产生太大的影响.

CPython中的内存管理很微妙.仅仅因为删除引用并运行收集周期并不一定意味着内存将返回到OS.有关详情,请参阅此答案.

要真正节省内存,您应该围绕生成器和迭代器而不是大型项列表来构造此代码.我很惊讶你说你有连接超时,因为获取所有行不应该花费比一次取一行并执行你正在进行的简单处理花费更多的时间.也许我们应该看看你的db-fetching代码?

如果一次一行的处理确实不可能,那么至少将您的数据保存为不可变的deque并使用生成器和迭代器对其执行所有处理.

我将概述这些不同的方法.

首先,一些常见的功能:

# if you don't need random-access to elements in a sequence
# a deque uses less memory and has faster appends and deletes
# from both the front and the back.
from collections import deque
from itertools import izip, repeat, islice, chain
import re

re_redshift_chars = re.compile(r'[abcdefg]')

def istrjoin(sep, seq):
    """Return a generator that acts like sep.join(seq), but lazily

    The separator will be yielded separately
    """
    return islice(chain.from_iterable(izip(repeat(sep), seq)), 1, None)

def escape_redshift(s):
    return re_redshift_chars.sub(r'\\\g<0>', s)

def tabulate(row):
    return "\t".join(escape_redshift(str(v)) if v is not None else '' for v in row)
Run Code Online (Sandbox Code Playgroud)

现在理想的是一次一行的处理,如下所示:

cursor = db.cursor()
cursor.execute("""SELECT * FROM bigtable""")
rowstrings = (tabulate(row) for row in cursor.fetchall())
lines = istrjoin("\n", rowstrings)
file_like_obj.writelines(lines)
cursor.close()
Run Code Online (Sandbox Code Playgroud)

这将占用尽可能少的内存 - 一次只有一行.

如果您确实需要存储整个结果集,可以稍微修改一下代码:

cursor = db.cursor()
cursor.execute("SELECT * FROM bigtable")
collection = deque(cursor.fetchall())
cursor.close()
rowstrings = (tabulate(row) for row in collection)
lines = istrjoin("\n", rowstrings)
file_like_obj.writelines(lines)
Run Code Online (Sandbox Code Playgroud)

现在我们将所有结果收集到collection第一个,它完全保留在整个程序运行的内存中.

但是,我们也可以复制您在使用时删除收集项的方法.我们可以通过创建一个生成器来保持相同的"代码形状",该生成器在其工作时清空其源集合.它看起来像这样:

def drain(coll):
    """Return an iterable that deletes items from coll as it yields them.

    coll must support `coll.pop(0)` or `del coll[0]`. A deque is recommended!
    """
    if hasattr(coll, 'pop'):
        def pop(coll):
            try:
                return coll.pop(0)
            except IndexError:
                raise StopIteration
    else:
        def pop(coll):
            try:
                item = coll[0]
            except IndexError:
                raise StopIteration
            del coll[0]
            return item
    while True:
        yield pop(coll)
Run Code Online (Sandbox Code Playgroud)

现在,你可以很容易地替换drain(collection)collection当你想为你去释放内存.之后drain(collection)耗尽时,collection对象将是空的.