问题 在Python中加载大文件


我正在使用在Ubuntu 9.04上运行的Python 2.6.2 [GCC 4.3.3]。我需要使用Python脚本逐行读取大数据文件(~1GB,> 300万行)。

我尝试了下面的方法,我发现它使用了非常大的内存空间(~3GB)

for line in open('datafile','r').readlines():
   process(line)

要么,

for line in file(datafile):
   process(line)

比方说,是否有更好的方法逐行加载大文件

  • a)明确提到文件在内存中任何时候都可以加载的最大行数?要么
  • b)通过大小的块(例如1024字节)加载它,只要所述块的最后一行完全加载而不被截断?

几个建议给了我上面提到的方法并且已经尝试过,我试图看看是否有更好的方法来处理这个问题。到目前为止,我的搜索效果不佳。我感谢您的帮助。

p / s我已经使用了一些内存分析 Heapy 并发现我正在使用的Python代码中没有内存泄漏。

2012年8月20日更新,16:41(GMT + 1)

尝试了J.F.Sebastian,mgilson和IamChuckB建议的两种方法,(数据文件是一个变量)

with open(datafile) as f:
    for line in f:
        process(line)

也,

import fileinput
for line in fileinput.input([datafile]):
    process(line)

奇怪的是他们两个都使用了〜3GB的内存,我在这个测试中的数据文件大小是765.2MB,由21,181,079行组成。我看到内存在时间上增加(大约40-80MB步长),然后稳定在3GB。

一个基本的疑问, 使用后是否需要冲洗线?

我使用Heapy进行了内存分析,以便更好地理解这一点。

1级分析

Partition of a set of 36043 objects. Total size = 5307704 bytes.
 Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
     0  15934  44  1301016  25   1301016  25 str
     1     50   0   628400  12   1929416  36 dict of __main__.NodeStatistics
     2   7584  21   620936  12   2550352  48 tuple
     3    781   2   590776  11   3141128  59 dict (no owner)
     4     90   0   278640   5   3419768  64 dict of module
     5   2132   6   255840   5   3675608  69 types.CodeType
     6   2059   6   247080   5   3922688  74 function
     7   1716   5   245408   5   4168096  79 list
     8    244   1   218512   4   4386608  83 type
     9    224   1   213632   4   4600240  87 dict of type
<104 more rows. Type e.g. '_.more' to view.>

================================================== ==========

1级指数的2级分析 - 指数0

Partition of a set of 15934 objects. Total size = 1301016 bytes.
 Index  Count   %     Size   % Cumulative  % Referred Via:
     0   2132  13   274232  21    274232  21 '.co_code'
     1   2132  13   189832  15    464064  36 '.co_filename'
     2   2024  13   114120   9    578184  44 '.co_lnotab'
     3    247   2   110672   9    688856  53 "['__doc__']"
     4    347   2    92456   7    781312  60 '.func_doc', '[0]'
     5    448   3    27152   2    808464  62 '[1]'
     6    260   2    15040   1    823504  63 '[2]'
     7    201   1    11696   1    835200  64 '[3]'
     8    188   1    11080   1    846280  65 '[0]'
     9    157   1     8904   1    855184  66 '[4]'
<4717 more rows. Type e.g. '_.more' to view.>

1级 - 指数1的2级分析

Partition of a set of 50 objects. Total size = 628400 bytes.
 Index  Count   %     Size   % Cumulative  % Referred Via:
     0     50 100   628400 100    628400 100 '.__dict__'

1级指数2的2级分析

Partition of a set of 7584 objects. Total size = 620936 bytes.
 Index  Count   %     Size   % Cumulative  % Referred Via:
     0   1995  26   188160  30    188160  30 '.co_names'
     1   2096  28   171072  28    359232  58 '.co_varnames'
     2   2078  27   157608  25    516840  83 '.co_consts'
     3    261   3    21616   3    538456  87 '.__mro__'
     4    331   4    21488   3    559944  90 '.__bases__'
     5    296   4    20216   3    580160  93 '.func_defaults'
     6     55   1     3952   1    584112  94 '.co_freevars'
     7     47   1     3456   1    587568  95 '.co_cellvars'
     8     35   0     2560   0    590128  95 '[0]'
     9     27   0     1952   0    592080  95 '.keys()[0]'
<189 more rows. Type e.g. '_.more' to view.>

1级 - 指数3的2级分析

Partition of a set of 781 objects. Total size = 590776 bytes.
 Index  Count   %     Size   % Cumulative  % Referred Via:
     0      1   0    98584  17     98584  17 "['locale_alias']"
     1     29   4    35768   6    134352  23 '[180]'
     2     28   4    34720   6    169072  29 '[90]'
     3     30   4    34512   6    203584  34 '[270]'
     4     27   3    33672   6    237256  40 '[0]'
     5     25   3    26968   5    264224  45 "['data']"
     6      1   0    24856   4    289080  49 "['windows_locale']"
     7     64   8    20224   3    309304  52 "['inters']"
     8     64   8    17920   3    327224  55 "['galog']"
     9     64   8    17920   3    345144  58 "['salog']"
<84 more rows. Type e.g. '_.more' to view.>

================================================== ==========

等级2的等级3分析 - 指数0,等级1 - 指数0

Partition of a set of 2132 objects. Total size = 274232 bytes.
 Index  Count   %     Size   % Cumulative  % Referred Via:
     0   2132 100   274232 100    274232 100 '.co_code'

2级指数0级,1级 - 指数1的3级分析

Partition of a set of 50 objects. Total size = 628400 bytes.
 Index  Count   %     Size   % Cumulative  % Referred Via:
     0     50 100   628400 100    628400 100 '.__dict__'

2级指数0级,1级 - 指数2的3级分析

Partition of a set of 1995 objects. Total size = 188160 bytes.
 Index  Count   %     Size   % Cumulative  % Referred Via:
     0   1995 100   188160 100    188160 100 '.co_names'

2级指数0级,1级 - 指数3的3级分析

Partition of a set of 1 object. Total size = 98584 bytes.
 Index  Count   %     Size   % Cumulative  % Referred Via:
     0      1 100    98584 100     98584 100 "['locale_alias']"

还在排除故障。

如果您以前遇到过这种情况,请与我分享。

谢谢你的帮助。

更新于2012年8月21日,01:55(GMT + 1)

  1. mgilson,进程函数用于后处理网络模拟器2(NS2)跟踪文件。 tracefile中的一些行如下所示。我在python脚本中使用了大量的对象,计数器,元组和字典来了解无线网络的执行情况。
s 1.231932886 _25_ AGT  --- 0 exp 10 [0 0 0 0 Y Y] ------- [25:0 0:0 32 0 0] 
s 1.232087886 _25_ MAC  --- 0 ARP 86 [0 ffffffff 67 806 Y Y] ------- [REQUEST 103/25 0/0]
r 1.232776108 _42_ MAC  --- 0 ARP 28 [0 ffffffff 67 806 Y Y] ------- [REQUEST 103/25 0/0]
r 1.232776625 _34_ MAC  --- 0 ARP 28 [0 ffffffff 67 806 Y Y] ------- [REQUEST 103/25 0/0]
r 1.232776633 _9_ MAC  --- 0 ARP 28 [0 ffffffff 67 806 Y Y] ------- [REQUEST 103/25 0/0]
r 1.232776658 _0_ MAC  --- 0 ARP 28 [0 ffffffff 67 806 Y Y] ------- [REQUEST 103/25 0/0]
r 1.232856942 _35_ MAC  --- 0 ARP 28 [0 ffffffff 64 806 Y Y] ------- [REQUEST 100/25 0/0]
s 1.232871658 _0_ MAC  --- 0 ARP 86 [13a 67 1 806 Y Y] ------- [REPLY 1/0 103/25]
r 1.233096712 _29_ MAC  --- 0 ARP 28 [0 ffffffff 66 806 Y Y] ------- [REQUEST 102/25 0/0]
r 1.233097047 _4_ MAC  --- 0 ARP 28 [0 ffffffff 66 806 Y Y] ------- [REQUEST 102/25 0/0]
r 1.233097050 _26_ MAC  --- 0 ARP 28 [0 ffffffff 66 806 Y Y] ------- [REQUEST 102/25 0/0]
r 1.233097051 _1_ MAC  --- 0 ARP 28 [0 ffffffff 66 806 Y Y] ------- [REQUEST 102/25 0/0]
r 1.233109522 _25_ MAC  --- 0 ARP 28 [13a 67 1 806 Y Y] ------- [REPLY 1/0 103/25]
s 1.233119522 _25_ MAC  --- 0 ACK 38 [0 1 67 0 Y Y] 
r 1.233236204 _17_ MAC  --- 0 ARP 28 [0 ffffffff 65 806 Y Y] ------- [REQUEST 101/25 0/0]
r 1.233236463 _20_ MAC  --- 0 ARP 28 [0 ffffffff 65 806 Y Y] ------- [REQUEST 101/25 0/0]
D 1.233236694 _18_ MAC  COL 0 ARP 86 [0 ffffffff 65 806 67 1] ------- [REQUEST 101/25 0/0]
  1. 使用Heapy进行3级分析的目的是帮助我缩小哪些对象占用大部分内存。正如你所看到的,遗憾的是我无法看到哪一个特别需要调整,因为它过于通用。例子我虽然知道“dict of 主要.NodeStatistics“在36043(0.1%)对象中只有50个对象,但它占用了运行脚本的总内存的12%,我无法找到我需要查看的特定字典。

  2. 我尝试实施David Eyk的建议如下(片段),尝试每500,000行手动垃圾收集,

import gc
  for i,line in enumerate(file(datafile)):
    if (i%500000==0):
      print '-----------This is line number', i
      collected = gc.collect()
      print "Garbage collector: collected %d objects." % (collected)

不幸的是,内存使用量仍为3GB,输出(片段)如下所示,

-----------This is line number 0
Garbage collector: collected 0 objects.
-----------This is line number 500000
Garbage collector: collected 0 objects.
  1. 实施了martineau的建议,我看到内存使用量现在比之前的3GB早了22MB!我期待实现的东西。奇怪的是下面的,

我做了与以前相同的内存分析,

1级分析

Partition of a set of 35474 objects. Total size = 5273376 bytes.
 Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
     0  15889  45  1283640  24   1283640  24 str
     1     50   0   628400  12   1912040  36 dict of __main__.NodeStatistics
     2   7559  21   617496  12   2529536  48 tuple
     3    781   2   589240  11   3118776  59 dict (no owner)
     4     90   0   278640   5   3397416  64 dict of module
     5   2132   6   255840   5   3653256  69 types.CodeType
     6   2059   6   247080   5   3900336  74 function
     7   1716   5   245408   5   4145744  79 list
     8    244   1   218512   4   4364256  83 type
     9    224   1   213632   4   4577888  87 dict of type
<104 more rows. Type e.g. '_.more' to view.>

将前面的内存分析输出与上面的内容进行比较,str减少了45个对象(17376个字节),元组减少了25个对象(3440个字节)和dict(没有所有者),尽管没有对象更改,它减少了1536个字节的内存大小。所有其他对象都是相同的,包括dict 主要.NodeStatistics。对象总数为35474.对象的小幅减少(0.2%)产生99.3%的内存节省(从3GB减少22MB)。很奇怪。

如果你意识到,虽然我知道记忆饥饿正在发生的地方,但我还能够缩小导致出血的那个。

将继续调查此事。

感谢所有指针,利用这个机会学习python,因为我不是专家。感谢您花时间来帮助我。

更新于2012年8月23日00:01(GMT + 1) - 已解决

  1. 我继续使用每个martineau建议的简约代码进行调试。我开始在过程函数中添加代码并观察内存流失。

  2. 当我添加如下所示的类时,我发现内存开始流血,

class PacketStatistics(object):
    def __init__(self):
        self.event_id = 0
        self.event_source = 0
        self.event_dest = 0
        ...

我正在使用3个类,136个计数器。

  1. 与我的朋友Gustavo Carneiro讨论了这个问题,他建议使用 插槽 取代字典。

  2. 我把课程改成如下,

class PacketStatistics(object):
    __slots__ = ('event_id', 'event_source', 'event_dest',...)
    def __init__(self):
        self.event_id = 0
        self.event_source = 0
        self.event_dest = 0
        ...
  1. 当我转换所有3个类时,之前3GB的内存使用量变为504MB。 高达80%的内存使用量节省!!

  2. 以下是dict之后的内存分析 插槽 皈依。

Partition of a set of 36157 objects. Total size = 4758960 bytes.
 Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
     0  15966  44  1304424  27   1304424  27 str
     1   7592  21   624776  13   1929200  41 tuple
     2    780   2   587424  12   2516624  53 dict (no owner)
     3     90   0   278640   6   2795264  59 dict of module
     4   2132   6   255840   5   3051104  64 types.CodeType
     5   2059   6   247080   5   3298184  69 function
     6   1715   5   245336   5   3543520  74 list
     7    225   1   232344   5   3775864  79 dict of type
     8    244   1   223952   5   3999816  84 type
     9    166   0   190096   4   4189912  88 dict of class
<101 more rows. Type e.g. '_.more' to view.>

dict of __main__.NodeStatistics 不再是前十名了。

我对结果很满意,很高兴关闭这个问题。

感谢您的所有指导。真的很感激。

RGDS Saravanan K.


6643
2017-08-20 14:08


起源

docs.python.org/library/mmap - tMC
它看起来像你的内心 process 功能而不是你如何阅读文件。我注意到NodeStatistics似乎占用了少量对象的大量空间。内部python属性似乎也出现在很多内存信息中。 - Winston Ewert
什么是 process 功能实际上呢? - mgilson
使用假人 process() 功能和观察内存使用情况,以便您确定是否正在优化正确的代码。函数中的某些东西可能会持有 line 参数的数据。 - martineau
它是什么格式的文件?! JSON,XML,CSV,基因组学或其他一些众所周知的格式?大多数都有专门的读者,在C中进行了优化。尽可能避免在本机Python中进行解析。 - smci


答案:


with open('datafile') as f:
    for line in f:
        process(line)

这是有效的,因为文件是迭代器,每次产生1行,直到没有更多的行产生。


12
2017-08-20 14:10



@ J.FSebastian ::谢谢。 - mgilson
这也可以作为file in open('datafile')进行文件中的行:process(line)然而,mgilson的建议更好,因为它确保文件关闭,即使在此期间引发了一些错误 process(line)。 - HerrKaputt
我错误地按了ENTER而不是SHIFT-ENTER。我的完整评论现在已经发布了。你快,不是吗? :) - HerrKaputt
@HerrKaputt - 我很容易注意到小红点:) - mgilson
也许跑步 gc.collect() 每个~100000行左右(根据您的情况调整)会有帮助吗? - David Eyk


fileinput 模块将允许您逐行读取它而不将整个文件加载到内存中。 pydocs

import fileinput
for line in fileinput.input(['myfile']):
do_something(line)

代码示例取自 yak.net


3
2017-08-20 14:12



谢谢你的建议。我试过这个并将我的发现作为原始文章的更新分享 - Saravanan K


@mgilson的回答是对的。简单的解决方案虽然有官方提及(@HerrKaputt在评论中提到这一点)

file = open('datafile')
for line in file:
    process(line)
file.close()

这很简单,pythonic,可以理解。如果你不明白怎么做 with 工作只是使用这个。

正如另一张海报所提到的,这并没有创建像file.readlines()这样的大型列表。相反,它以传统的unix文件/管道方式一次拉出一行。


0
2017-08-21 01:41



谢谢你的指针。正如@Winston Ewert正确地指出我的调查显示巨大的内存使用量不是由于加载大文件而是如何管理对象。我在原始文章的更新中分享了我的发现。谢谢你的帮助 - Saravanan K


如果文件是JSON,XML,CSV,基因组学或任何其他众所周知的格式,那么有专门的读者直接使用C代码,并且比在本机Python中解析更加优化速度和内存 - 避免在可能的情况下本地解析它。

但总的来说,我的经验提示:

  • Python的  package非常适合管理子进程,当子进程结束时,所有内存泄漏都会消失。
  • 运行reader子进程作为 multiprocessing.Process 并使用一个 multiprocessing.Pipe(duplex=True) 沟通(发送文件名和任何其他args,然后读取它的标准输出)
  • 读取小块(但不是很小)块,比如64Kb-1Mb。更好地用于内存,也用于其他正在运行的进程/子进程的响应性

0
2018-05-18 17:58