【问题标题】:Huge cost of Memory in PythonPython中巨大的内存成本
【发布时间】:2016-06-04 16:28:14
【问题描述】:

我正在编写一段在 Python 中使用对象的代码。我有 1.1GB 的文件需要解析并转换为对象。

但是,有 1.1GB 的文件,它消耗 超过 7GB 的内存(我停止了它,因为它可以走得更远......),这是挺大。我使用内存分析器检查并查看发生了什么,并且...有我得到的结果示例:

Line #    Mem usage    Increment   Line Contents
================================================
78   30.352 MiB    0.000 MiB   @profile
79                             def getInfos(listExch):
80
81   30.352 MiB    0.000 MiB    SizeTot = 0
82   30.352 MiB    0.000 MiB    upListExch = set()
83
84 5325.996 MiB 5295.645 MiB    for exch in listExch:
85
86
87 5325.996 MiB    0.000 MiB        symbExch = exch.symb
88 5325.996 MiB    0.000 MiB        nameExch = exch.name
89 5325.996 MiB    0.000 MiB        stList = exch.getStList()
90 5325.996 MiB    0.000 MiB        upExch = Exch(symbExch,nameExch)
91
92 7572.309 MiB 2246.312 MiB        for st in stList:
93
94 7572.309 MiB    0.000 MiB            unexpected = False
95 7572.309 MiB    0.000 MiB            symbSt = st.symb
96
97 7572.309 MiB    0.000 MiB            filepath = '{0}/{1}.csv'.format(download_path,symbSt)
98
99 7572.309 MiB    0.000 MiB            upSt = parseQ(st,filepath)
100 7572.309 MiB    0.000 MiB               upExch.addSt(upSt)
101 5325.996 MiB -2246.312 MiB      upListExch.add(upExch)
102
103                                 return upListExch

下面还有我写的对象模型:

Exch 是一个包含listSt 的对象,每个St 包含一个listQ 的对象。

class Exch:
    def __init__(self,symb,name):
        self.symb = symb
        self.name = name
        self.listSt = set()

    def addSt(self,st):
        self.listSt.add(st)

    def setStList(self,listSt):
        self.listSt = listSt

    def getStList(self):
        return self.listSt

class St:
    def __init__(self,symb,name):
        self.symb = symb
        self.name = name
        self.listQ = set()

    def getQList(self):
        return self.listQ

    def addQ(self,q):
        self.listQ.add(q)

class Q:

    def __init__(self,date,dataH,dataM,dataL):

            self.date = date
            self.dataH = dataH
            self.dataM = dataM
            self.dataL = dataL

我在这里做错了吗?还是 Python 不适应这种数据量?

编辑:

输入listExch包含Exch对象列表,每个stlistSt包含一个空listQ

输出将与输入相同,除了每个st 对象中的每个listQ 将被添加。

解析器已经制作完成:

def parseQ(st,filepath):

    loc_date,loc_dataH,loc_dataM,loc_dataL = 0,0,0,0

    with open (filepath, 'rt') as csvfile:
            reader = csv.reader (csvfile,delimiter=',')
            row1 = next(reader)
            unexpected = False

            for idx,el in enumerate(row1):
                    if (el == 'Date'):
                            loc_date = idx
                    elif (el == 'Number High'):
                            loc_dataH = idx
                    elif (el == 'Number Medium'):
                            loc_dataM = idx
                    elif (el == 'Number Low'):
                            loc_dataL = idx
                    else:
                            log.error('Unexpected format on file {}. Skip the file'.format(filepath))
                            unexpected = True
                            break
            if (unexpected):
                    log.error('The file "{}" is not properly set'.format(filepath))
                    return False
            else:
                    next(reader)
                    for row in reader:
                            try:
                                st.addQ(Q(row[loc_date],row[loc_dataH],row[loc_dataM],row[loc_dataL]))
    return st

【问题讨论】:

  • s/Go/GB/g?还是“围棋”是我不知道的计量单位?
  • @user161778 gigaoctet
  • @AnttiHaapala,这清除了它,谢谢!
  • 您能否添加一些细节(或告诉我在哪里,例如它已经显示的分析)您对 csv 数据的实际操作。也许是驱动转换的代码,所以我们可以检查它是否保持“更长”的时间等等?
  • 为什么必须一次将所有文件保存在内存中?如果您正在处理 csv,请重写您的代码,使其与生成器一起使用。然后流式传输 csv。

标签: python memory-management out-of-memory


【解决方案1】:

我一点也不惊讶。

读取 CSV 会生成一个行列表,每个行都指向一个元素列表。

现在,每个都是PyObject,这意味着它有一个typeref,我认为通常使用size_t,并且包含它的列表必须包含它的id(巧合的是,它只是一个指向 PyObject) 的指针,所以这是两个 size_t,即指针类型的大小,只是因为存在 一个元素。这甚至没有考虑到元素的“有效负载”也需要一些内存这一事实!

在 64 位机器上,这将是 128 位的纯结构开销每个元素。我不知道你的元素是什么样子的,但很可能这不仅仅是实际内容。

一般情况下,不要这样做。如果您的数据是表格的,请使用 numpy 加载它,它不会有 python 列表列表,而只是分配一大块内存来转储原始值并在访问它们时计算单个元素的地址,而不是走 Python 路线从指针跳到指针。这样一来,您也将获得很大的速度。

我还要提一下,CSV 对于存储大量数据来说是一种特别糟糕的格式。该语言没有正式的定义(这就是为什么 python 的 CSV 阅读器有“方言”的概念),它在存储浮点数方面效率极低(并且可能不精确),如果不阅读 all 就没有机会访问第 N 行 N-1 前行,它取决于字符串解析,不能用于就地修改值,除非这不会改变字符串的长度......总而言之:你做得很好如果你读过这些文件一次,并将它们转换成一些实际存储方式的表格格式。

“但 CSV 是纯文本,我可以用我的文本编辑器读取它”的论点并不重要——没有人能够“快速点击”1GB 的 CSV。因此,请尝试摆脱 CSV 文件。 Numpy,再次具有本机存储格式,可能适合您的目的,或者使用 HDF 或任何其他标准格式 - 或者如果 CSV 的所有元素都是相同类型,您也可以将它们保存为原始字节图像数据——这将是最快、最节省空间的存储方法(但您必须“从外部”记住数据的结构),除了稀疏性。

编辑: 正如 OP 指出的那样, 正是他的计划:读取 CSV,验证其内容,然后将其存储在数据库中!风格不错。

现在,读取可以按行进行,因此您可以读取一行(或几行),将数据存储在数据库中,忘记行,获取下一行,等等。可以对存储在数据库中的数据进行验证,可能在数据库的单独“暂存”部分中。

【讨论】:

  • 这正是我想要对 CSV 文件执行的操作:我需要读取它们一次,检查是否有问题,然后将其推送到数据库中。
  • 那么,读取一行,将该行推送到数据库,然后忘记该行。阅读下一行,...
  • 我认为继续的方式是:读取 CSV 文件 > 转换为对象 > 使用 python 检查这些对象是否有任何错误数据 > 将所有数据推送到数据库中。
  • 我想我可以逐步拆分检查部分,而不是一次读取和转换任何内容。感谢您提供这些有用的信息!
  • @toshiro92 仅适用于将所有行作为单个数据库事务插入的情况。在大多数应用程序中,记录非验证行并单独调查它们并在以后处理它们会更有意义。
猜你喜欢
  • 1970-01-01
  • 2010-12-26
  • 2016-12-12
  • 1970-01-01
  • 1970-01-01
  • 2017-12-20
  • 2010-12-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多