【问题标题】:Pandas/Python memory spike while reading 3.2 GB file读取 3.2 GB 文件时 Pandas/Python 内存峰值
【发布时间】:2016-11-24 01:10:11
【问题描述】:

所以我一直在尝试使用 pandas read_csv 函数读取内存中的 3.2GB 文件,但我一直遇到某种内存泄漏,我的内存使用量会飙升 90%+

作为替代品

  1. 我尝试定义 dtype 以避免将内存中的数据作为字符串保存,但看到了类似的行为。

  2. 尝试了 numpy read csv,以为我会得到一些不同的结果,但肯定是错的。

  3. 尝试逐行阅读时遇到了同样的问题,但速度真的很慢。

  4. 我最近迁移到 python 3,所以认为那里可能存在一些错误,但在 python2 + pandas 上看到了类似的结果。

有问题的文件是来自 kaggle 比赛grupo bimbo的 train.csv 文件

系统信息:

RAM: 16GB, Processor: i7 8cores

如果您还想了解其他信息,请告诉我。

谢谢:)

编辑 1:这是一个内存峰值!不是泄漏(对不起。)

编辑 2:csv 文件示例

Semana,Agencia_ID,Canal_ID,Ruta_SAK,Cliente_ID,Producto_ID,Venta_uni_hoy,Venta_hoy,Dev_uni_proxima,Dev_proxima,Demanda_uni_equil
3,1110,7,3301,15766,1212,3,25.14,0,0.0,3
3,1110,7,3301,15766,1216,4,33.52,0,0.0,4
3,1110,7,3301,15766,1238,4,39.32,0,0.0,4
3,1110,7,3301,15766,1240,4,33.52,0,0.0,4
3,1110,7,3301,15766,1242,3,22.92,0,0.0,3

编辑3:文件中的行数74180465

还有一个简单的pd.read_csv('filename', low_memory=False)

我试过了

from numpy import genfromtxt
my_data = genfromtxt('data/train.csv', delimiter=',')

更新 下面的代码刚刚工作,但我还是想深入了解这个问题,一定有问题。

import pandas as pd
import gc
data = pd.DataFrame()
data_iterator = pd.read_csv('data/train.csv', chunksize=100000)
for sub_data in data_iterator:
    data.append(sub_data)
    gc.collect()

编辑:一段有效的代码。 感谢所有帮助人员,我通过添加 python dtypes 而不是 numpy 来搞乱我的 dtypes。一旦我修复了下面的代码就像一个魅力。

dtypes = {'Semana': pd.np.int8,
          'Agencia_ID':pd.np.int8,
          'Canal_ID':pd.np.int8,
          'Ruta_SAK':pd.np.int8,
          'Cliente_ID':pd.np.int8,
          'Producto_ID':pd.np.int8,
          'Venta_uni_hoy':pd.np.int8,
          'Venta_hoy':pd.np.float16,
          'Dev_uni_proxima':pd.np.int8,
          'Dev_proxima':pd.np.float16,
          'Demanda_uni_equil':pd.np.int8}
data = pd.read_csv('data/train.csv', dtype=dtypes)

这将内存消耗降低到略低于 4Gb

【问题讨论】:

  • 这听起来不像是内存泄漏。这听起来就像你试图一次将一个巨大的文件读入内存并导致大量的内存消耗。 (内存中的表示大于序列化形式是完全正常的。)
  • 内存泄漏 = 已分配但从未释放的内存。这不是这里的情况。峰值根本不意味着内存泄漏。
  • @limbo 感谢您的更正,但 3.2Gb 文件的 10Gb+ 使用量确实感觉不对。
  • 你能写一个数据的示例行吗?
  • @heaven00 不用担心,我们都在这里学习。我也对此感到有些困惑。你能告诉我们你使用的代码吗?

标签: python csv pandas memory


【解决方案1】:

以文本形式存储在内存中的文件不如压缩二进制格式紧凑,但它在数据方面相对紧凑。如果是简单的ascii文件,除了任何文件头信息外,每个字符只有1个字节。 Python 字符串具有类似的关系,其中有一些内部 Python 内容的开销,但每个额外的字符仅增加 1 个字节(来自使用 __sizeof__ 进行的测试)。一旦开始转换为数字类型和集合(列表、数组、数据框等),开销就会增加。例如,列表必须为每个位置存储一个类型和一个值,而字符串只存储一个值。

>>> s = '3,1110,7,3301,15766,1212,3,25.14,0,0.0,3\r\n'
>>> l = [3,1110,7,3301,15766,1212,3,25.14,0,0.0,3]
>>> s.__sizeof__()
75
>>> l.__sizeof__()
128

一点测试(假设__sizeof__ 是准确的):

import numpy as np
import pandas as pd

s = '1,2,3,4,5,6,7,8,9,10'
print ('string: '+str(s.__sizeof__())+'\n')
l = [1,2,3,4,5,6,7,8,9,10]
print ('list: '+str(l.__sizeof__())+'\n')
a = np.array([1,2,3,4,5,6,7,8,9,10])
print ('array: '+str(a.__sizeof__())+'\n')
b = np.array([1,2,3,4,5,6,7,8,9,10], dtype=np.dtype('u1'))
print ('byte array: '+str(b.__sizeof__())+'\n')
df = pd.DataFrame([1,2,3,4,5,6,7,8,9,10])
print ('dataframe: '+str(df.__sizeof__())+'\n')

返回:

string: 53

list: 120

array: 136

byte array: 106

dataframe: 152

【讨论】:

  • p = pd.DataFrame(l).__sizeof__() -- 160
  • 熊猫dtype不会在这件事上提供帮助吗? pandas.pydata.org/pandas-docs/stable/generated/…
  • 可能......我不是 pandas 的大用户,虽然我知道它在引擎盖下与 numpy 配合得很好
  • @Aaron 谢谢你们,我添加了错误的 dtypes。我使用了导致问题的python dtypes。将它们更改为 numpy dtypes 并像魅力一样工作,总使用量约为 4Gb,与文件大小几乎相同
  • @Richard 也感谢你,你的链接也有我刚刚搞砸的类似答案:)
【解决方案2】:

根据您的第二张图表,您的机器似乎在短时间内分配了额外的 4.368 GB 内存,这大约是您的 3.2 GB 数据集的大小(假设 1GB 开销,这可能是一个延伸)。

我试图找到一个可能发生这种情况的地方,但并没有非常成功。不过,如果你有动力,也许你能找到它。这是我选择的路径:

This line 读作:

def read(self, nrows=None):
    if nrows is not None:
        if self.options.get('skip_footer'):
            raise ValueError('skip_footer not supported for iteration')

    ret = self._engine.read(nrows)

这里,_engine 引用 PythonParser

这反过来又调用_get_lines()

这会调用数据source

它看起来像是从相对标准的东西(参见here)中以字符串的形式读取的,例如TextIOWrapper

所以内容被作为标准文本读取并转换,这解释了缓慢的斜坡。

尖峰呢?我认为 these lines 解释了这一点:

ret = self._engine.read(nrows)

if self.options.get('as_recarray'):
    return ret

# May alter columns / col_dict
index, columns, col_dict = self._create_index(ret)

df = DataFrame(col_dict, columns=columns, index=index)

ret 成为数据框的所有组件。

self._create_index()ret 分解为这些组件:

def _create_index(self, ret):
    index, columns, col_dict = ret
    return index, columns, col_dict

到目前为止,一切都可以通过引用来完成,而对 DataFrame() 的调用延续了这一趋势(参见 here)。

所以,如果我的理论是正确的,DataFrame() 要么在某处复制数据,要么_engine.read() 在我确定的路径上的某处这样做。

【讨论】:

  • 哇,感谢您的详细回答,我认为这主要是因为我的 dtypes 定义错误,它试图找到数据的确切 dtypes。但这仍然是一个假设。
猜你喜欢
  • 2014-08-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多