Загрузка большого файла в Python

Я использую Python 2.6.2 [GCC 4.3.3], работающий на Ubuntu 9.04. Мне нужно прочитать большой файл данных (~ 1 ГБ, > 3 миллиона строк), строка за строкой, используя Python script.

Я попробовал методы ниже, я считаю, что он использует очень большое пространство памяти (~ 3 ГБ)

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

или,

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

Есть ли лучший способ загрузить большой файл по строкам, скажем

  • a), явно указывая максимальное количество строк, которые файл может загружать в любой момент времени в памяти? Или
  • b) путем загрузки его кусками размера, скажем, 1024 байта, если последняя строка упомянутого куска загружается полностью без усечения?

Несколько предложений дали методы, о которых я упоминал выше, и уже попробовали, я пытаюсь понять, есть ли лучший способ справиться с этим. До сих пор мой поиск не был плодотворным. Я ценю вашу помощь.

p/s Я провел некоторое профилирование памяти с помощью Heapy и не обнаружил утечек памяти в используемом мной коде Python.

Обновление 20 августа 2012, 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)

Странно, что оба из них используют ~ 3 ГБ памяти, размер моего файла данных в этом тесте составляет 765,2 МБ, состоящий из 21 181 079 строк. Я вижу, что память увеличивается по времени (около 40-80 МБ шагов) до стабилизации на 3 ГБ.

Простейшее сомнение, Нужно ли очищать линию после использования?

Я использовал профилирование памяти с помощью 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.>

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

Профилирование уровня 2 для уровня 1-индекс 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.>

Уровень 2 Профилирование для уровня 1-Индекс 1

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 для уровня 1-индекс 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.>

Уровень 2 Профилирование для уровня 1-Индекс 3

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.>

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

Уровень 3 Профилирование для уровня 2-индекс 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'

Уровень 3 Профилирование для уровня 2-индекс 0, уровень 1-индекс 1

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__'

Уровень 3 Профилирование для уровня 2-индекс 0, уровень 1-индекс 2

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'

Уровень 3 Профилирование для уровня 2-индекс 0, уровень 1-индекс 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']"

По-прежнему устраняйте эту проблему.

Поделитесь со мной, если вы столкнулись с этим раньше.

Спасибо за вашу помощь.

Обновление 21 августа 2012, 01:55 (GMT + 1)

  • mgilson, функция процесса используется для публикации файла трассировки сетевого симулятора 2 (NS2). Некоторые из строк в файле трассировки разделяются, как показано ниже. Я использую множество объектов, счетчиков, кортежей и словарей в python script, чтобы узнать, как работает беспроводная сеть.
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]
  • Цель профилирования 3 уровня с использованием Heapy - помочь мне сузить, какой объект поглощает большую часть памяти. Как вы можете видеть, к сожалению, я не мог понять, какой из них нуждается в настройке, поскольку он слишком общий. Например, я знаю, что "dict of main.NodeStatistics" имеет только 50 объектов из 36043 (0,1%) объектов, но он занимает 12% от общей памяти, используемой для запуска script, я я не могу найти, какой конкретный словарь мне нужно изучить.

  • Я попытался реализовать предложение Дэвида Эйка, как показано ниже (фрагмент), пытаясь собрать мусор вручную на каждые 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)

К сожалению, использование памяти по-прежнему составляет 3 ГБ, а вывод (фрагмент) - ниже,

-----------This is line number 0
Garbage collector: collected 0 objects.
-----------This is line number 500000
Garbage collector: collected 0 objects.
  • Реализованное предложение martineau, я вижу, что использование памяти теперь составляет 22 МБ от более ранних 3 ГБ! Что-то, чего я ожидал достичь. Странная вещь ниже,

Я делал то же самое профилирование памяти, что и раньше,

Уровень 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 main.NodeStatistics. Общее количество объектов - 35474. Небольшое сокращение объекта (0,2%) обеспечило 99,3% экономии памяти (22 МБ от 3 ГБ). Очень странно.

Если вы понимаете, хотя я знаю место, где происходит голод памяти, я все же могу сузить, какой из них вызывает кровотечение.

Будет продолжать исследовать это.

Благодаря всем указателям, используя эту возможность многому научиться на python, поскольку я не эксперт. Цените свое время, чтобы помочь мне.

Обновление 23 августа 2012, 00:01 (GMT + 1) - РЕШЕНИЕ

  • Я продолжал отладку, используя минималистичный код на предложение по мартини. Я начал добавлять коды в функцию процесса и наблюдать за кровотечением памяти.

  • Я нахожу, что память начинает истекать кровью, когда я добавляю класс, как показано ниже,

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

Я использую 3 класса с 136 счетчиками.

  • Обсудив эту проблему с моим другом Густаво Карнейро, он предложил использовать слот для замены dict.

  • Я преобразовал класс, как показано ниже,

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
        ...
  • Когда я преобразовал все 3 класса, использование памяти 3 ГБ ранее стало 504 МБ. Огромное 80% экономии памяти!

  • Ниже приведено профилирование памяти после преобразования в слот.

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 Сараванан К

Ответ 1

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

Это работает, потому что файлы являются итераторами, дающими 1 строку за раз, пока не будет больше строк.

Ответ 2

Модуль fileinput позволит вам читать строки за строкой, не загружая весь файл в память. pydocs

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

Пример кода, взятый из yak.net

Ответ 3

Ответ на

@mgilson правильный. Простое решение имеет официальное упоминание, хотя (@HerrKaputt упомянул об этом в комментарии)

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

Это просто, питово и понятно. Если вы не понимаете, как работает with, просто используйте это.

Как упоминал другой плакат, это не создает большой список, например file.readlines(). Скорее, он оттягивает одну строку за раз в традиционном для unix файлах/трубах.

Ответ 4

Если файл JSON, XML, CSV, genomics или любой другой известный формат, есть специализированные читатели, которые используют C-код напрямую и гораздо более оптимизированы как для скорости, так и для памяти, чем для парсинга в родном Python - избегайте его синтаксического анализа по возможности.

Но в общем, советы по моему опыту:

  • Python multiprocessing пакет является фантастическим для управления подпроцессами, все утечки памяти исчезают, когда заканчивается подпроцесс.
  • запустите подпроцесс читателя как multiprocessing.Process и используйте multiprocessing.Pipe(duplex=True) для связи (отправьте имя файла и любые другие аргументы, затем прочитайте его stdout)
  • читайте в маленьких (но не крошечных) кусках, скажем, 64Kb-1Mb. Лучше для использования памяти, а также для реагирования на другие запущенные процессы/подпроцессы.