【问题标题】:Memory over when reading a CSV file by python通过 python 读取 CSV 文件时内存溢出
【发布时间】:2022-01-07 01:03:12
【问题描述】:

问题 1。

我尝试读取大小约为 1GB 的 CSV 文件,如下所示

import csv

res = []
with open("my_csv.csv", "r") as f:
    reader = csv.reader(f)
    for row in reader:
        res.append(row)

我认为 1GB 足够小,可以将其作为列表加载到我的内存中。但实际上,代码冻结了,内存使用率为 100%。在我运行代码之前,我检查了额外的内存只有几 GB。

This answer 说,

“您正在将所有行读入一个列表,然后处理该列表。 不要那样做。"

但我想知道为什么?为什么列表的内存比文件大得多?


问题 2。

有没有什么方法可以将 CSV 解析为 dict 而不会出现内存问题?

例如,

CSV

apple,1,2,a    
apple,4,5,b    
banana,AAA,0,3    
kiwi,g1,g2,g3

字典

{"apple" : [[1, 2, a], [4, 5, b]],
 "banana": [[AAA, 0, 3]],
 "kiwi"  : [[g1, g2, g3]]}

【问题讨论】:

  • 您运行的是 32 位 Python 吗?你有多少内存?
  • 此信息来自 CSV 的事实无关紧要。您只是在问保存数百万个字符串列表需要多少内存。
  • 对。每行由一个列表对象和 4 个字符串对象加上字符串数据组成。这确实需要更多内存,尤其是在字段很小的情况下,例如您的示例。
  • @TimRoberts 64 位和 16GB。所以额外的至少是 10GB。
  • 请说明您要对数据进行的处理。在读取它的同时一次执行此操作是可能的。例如如果你只是在数东西。这样文件可以是任意大小而不会导致内存问题。

标签: python csv


【解决方案1】:

在这样的循环中将数百万个元素附加到列表中可能效率低下,因为列表会周期性地超出其当前分配,并且必须将其复制到新的内存区域会增加其大小。这会随着更大的列表一遍又一遍地发生,因此它成为一个指数过程。

使用list() 函数可能会更好,它可能会更有效。

with open("my_csv.csv", "r") as f:
    reader = csv.reader(f)
    res = list(reader)

即使它仍然存在相同的内存问题,它也会更快,因为循环是在优化的 C 代码中而不是在 Python 中解释的。

所有列表本身也有开销。在内部,列表具有一些标题信息,然后指向每个列表元素的数据。也可以分配多余的空间以允许增长而无需重新分配,但我怀疑csv 模块能够避免这种情况(附加到从 CSV 读取的列表中并不常见)。这种开销通常并不显着,但如果您有很多列表并且元素很小,则开销可能会接近所需内存的两倍。

对于您的第二个问题,您应该注意所链接问题中的建议。一次处理一条记录,随时添加到字典中。

result = {}
with open("my_csv.csv", "r") as f:
    reader = csv.reader(f)
    for row in reader:
        result.setdefault(row[0], []).append(row[1:])

【讨论】:

  • "列表超出其当前分配,必须复制到新的内存区域增加其大小"这是否意味着当我将元素附加到列表时,会在内存中创建一个全新的列表空间?然后我猜想旧列表(在附加之前)不会立即从内存中删除。 (例如,如果我附加一个从 0 到 5 的范围,那么内存会保留所有列表 []、[0]、[0, 1]、... 和 [0, 1, 2, 3, 4, 5] ) 我理解正确吗?
  • 不是每次都这样。当它重新分配时,它会增加额外的空间以允许增长。因此,如果您有一个包含 10 个元素的列表并附加到它,它可能会为 20 个元素分配空间。然后你可以在它需要重新分配和复制之前再添加 9 个元素。
  • 垃圾回收器立即回收旧内存。
  • 啊,好吧。我混淆了你所说的“指数过程”。大致拥有两次。我应该检查列表如何使用内存。感谢您的详细解释。
  • 它仍然是指数级的,因为它必须在达到 20 时再次增长,然后是 40,以此类推。
【解决方案2】:

回答你第二个问题:

有没有什么方法可以将 CSV 解析成字典而不会出现内存问题?

您并不是说“内存问题”是什么,但如果您在 Python 中将 CSV 解析为 dict,那么您将使用比 CSV 本身更多的内存。

我创建了一个脚本来生成“大”CSV,然后使用@Barmar 的代码监控时间和峰值内存消耗以构建result dict,并注意到平均而言,该代码使用的内存比 CSV 的大小多 10 倍。

下面是我处理 3 个“大”文件的结果,一个有 100K 行,一个有 1M 行,一个有 10M 行。

每个文件的 csv-to-dict 过程的统计数据显示在下面的 3 个块中:

  • 第一行来自ls -h <CSV-FILE>
  • 接下来的两行来自/usr/bin/time -l <CSV-FILE>
715M Jan  6 19:44 gen_10000000x10.csv
55.98 real        49.54 user         4.33 sys
7.46G  peak memory footprint
---
72M  Jan  6 19:47 gen_1000000x10.csv
4.66 real         4.49 user         0.15 sys
753M  peak memory footprint
---
7.2M Jan  6 19:44 gen_100000x10.csv
0.35 real         0.32 user         0.02 sys
79M  peak memory footprint

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-07-10
    • 2021-11-01
    • 2020-01-29
    • 1970-01-01
    • 2012-04-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多