Python: Как считать огромный текстовый файл в память

Я использую Python 2.6 на Mac, Мини-с 1 ГБ RAM. Я хочу читать в огромном текстовом файле

$ ls -l links.csv; file links.csv; tail links.csv 
-rw-r--r--  1 user  user  469904280 30 Nov 22:42 links.csv
links.csv: ASCII text, with CRLF line terminators
4757187,59883
4757187,99822
4757187,66546
4757187,638452
4757187,4627959
4757187,312826
4757187,6143
4757187,6141
4757187,3081726
4757187,58197

Таким образом, каждая строка в файле состоит из кортежа разделенных целочисленных значений двух запятых. Я хочу читать в целом файле и отсортировать его согласно второму столбцу. Я знаю, что я мог сделать сортировку, не читая целый файл в память. Но я думал для файла 500 МБ, я должен все еще смочь сделать это в памяти, так как я имею 1 ГБ в наличии.

Однако, когда я пытаюсь читать в файле, Python, кажется, выделяет намного больше памяти, чем необходимо файлу на диске. Таким образом, даже с 1 ГБ RAM я не могу читать в файле 500 МБ в память. Мой код Python для чтения файла и печати некоторой информации о потреблении памяти:

#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys

infile=open("links.csv", "r")

edges=[]
count=0
#count the total number of lines in the file
for line in infile:
 count=count+1

total=count
print "Total number of lines: ",total

infile.seek(0)
count=0
for line in infile:
 edge=tuple(map(int,line.strip().split(",")))
 edges.append(edge)
 count=count+1
 # for every million lines print memory consumption
 if count%1000000==0:
  print "Position: ", edge
  print "Read ",float(count)/float(total)*100,"%."
  mem=sys.getsizeof(edges)
  for edge in edges:
   mem=mem+sys.getsizeof(edge)
   for node in edge:
    mem=mem+sys.getsizeof(node) 

  print "Memory (Bytes): ", mem 

Вывод, который я получил, был:

Total number of lines:  30609720
Position:  (9745, 2994)
Read  3.26693612356 %.
Memory (Bytes):  64348736
Position:  (38857, 103574)
Read  6.53387224712 %.
Memory (Bytes):  128816320
Position:  (83609, 63498)
Read  9.80080837067 %.
Memory (Bytes):  192553000
Position:  (139692, 1078610)
Read  13.0677444942 %.
Memory (Bytes):  257873392
Position:  (205067, 153705)
Read  16.3346806178 %.
Memory (Bytes):  320107588
Position:  (283371, 253064)
Read  19.6016167413 %.
Memory (Bytes):  385448716
Position:  (354601, 377328)
Read  22.8685528649 %.
Memory (Bytes):  448629828
Position:  (441109, 3024112)
Read  26.1354889885 %.
Memory (Bytes):  512208580

Уже после чтения только 25% файла 500 МБ, Python использует 500 МБ. Таким образом, кажется, что, храня содержание файла, поскольку список кортежей ints не является очень эффективной памятью. Существует ли лучший способ сделать это, так, чтобы я мог читать в своем файле 500 МБ в моего 1 ГБ памяти?

25
задан Jon Seigel 14 March 2010 в 20:29
поделиться

5 ответов

There is a recipe for sorting files larger than RAM on this page, though you'd have to adapt it for your case involving CSV-format data. There are also links to additional resources there.

Edit: True, the file on disk is not "larger than RAM", but the in-memory representation can easily become much larger than available RAM. For one thing, your own program doesn't get the entire 1GB (OS overhead etc). For another, even if you stored this in the most compact form for pure Python (two lists of integers, assuming 32-bit machine etc), you'd be using 934MB for those 30M pairs of integers.

Using numpy you can also do the job, using only about 250MB. It isn't particular fast to load this way, as you have to count the lines and pre-allocate the array, but it may be the fastest actual sort given that it's in-memory:

import time
import numpy as np
import csv

start = time.time()
def elapsed():
    return time.time() - start

# count data rows, to preallocate array
f = open('links.csv', 'rb')
def count(f):
    while 1:
        block = f.read(65536)
        if not block:
             break
        yield block.count(',')

linecount = sum(count(f))
print '\n%.3fs: file has %s rows' % (elapsed(), linecount)

# pre-allocate array and load data into array
m = np.zeros(linecount, dtype=[('a', np.uint32), ('b', np.uint32)])
f.seek(0)
f = csv.reader(open('links.csv', 'rb'))
for i, row in enumerate(f):
    m[i] = int(row[0]), int(row[1])

print '%.3fs: loaded' % elapsed()
# sort in-place
m.sort(order='b')

print '%.3fs: sorted' % elapsed()

Output on my machine with a sample file similar to what you showed:

6.139s: file has 33253213 lines
238.130s: read into memory
517.669s: sorted

The default in numpy is Quicksort. The ndarray.sort() routine (which sorts in-place) can also take keyword argument kind="mergesort" or kind="heapsort" but it appears neither of these is capable of sorting on a Record Array which, incidentally, I used as the only way I could see to sort the columns together as opposed to the default which would sort them independently (totally messing up your data).

20
ответ дан 28 November 2019 в 21:23
поделиться

Вы можете посмотреть mmap:

http: // docs. python.org/library/mmap.html

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

Итак, вы мог читать в файле csv по одной строке за раз, затем записывать результаты в файл mmap'd (в подходящем двоичном формате), а затем работать с файлом mmap'd. Поскольку файл mmap является временным, вы, конечно, можете просто создать для этой цели файл tmp.

Вот пример кода, который демонстрирует использование mmap с временным файлом для чтения данных csv и сохранения их как пары целых чисел:


import sys
import mmap
import array
from tempfile import TemporaryFile

def write_int(buffer, i):
    # convert i to 4 bytes and write into buffer
    buffer.write(array.array('i', [i]).tostring())

def read_int(buffer, pos):
    # get the 4 bytes at pos and convert to integer
    offset = 4*pos
    return array.array('i', buffer[offset:offset+4])[0]

def get_edge(edges, lineno):
    pos = lineno*2
    i, j = read_int(edges, pos), read_int(edges, pos+1)
    return i, j

infile=open("links.csv", "r")

count=0
#count the total number of lines in the file
for line in infile:
    count=count+1

total=count
print "Total number of lines: ",total

infile.seek(0)

# make mmap'd file that's long enough to contain all data
# assuming two integers (4 bytes) per line
tmp = TemporaryFile()
file_len = 2*4*count
# increase tmp file size
tmp.seek(file_len-1)
tmp.write(' ')
tmp.seek(0)
edges = mmap.mmap(tmp.fileno(), file_len)

for line in infile:
    i, j=tuple(map(int,line.strip().split(",")))
    write_int(edges, i)
    write_int(edges, j)

# now confirm we can read the ints back out ok
for i in xrange(count):
    print get_edge(edges, i)

Хотя это немного грубо. На самом деле вы, вероятно, захотите завершить все это красивым классом, чтобы ваше преимущество s могут быть доступны таким образом, чтобы они вели себя как список (с индексированием, len и т. д.). Надеюсь, это даст вам отправную точку.

2
ответ дан 28 November 2019 в 21:23
поделиться

Все объекты python имеют накладные расходы на память поверх данных, которые они фактически хранят. Согласно getsizeof в моей 32-битной системе Ubuntu, кортеж имеет накладные расходы в 32 байта, а int занимает 12 байтов, поэтому каждая строка в вашем файле занимает 56 байтов + 4-байтовый указатель в списке - я предполагаю, что это будет много больше для 64-битной системы. Это соответствует приведенным вами цифрам и означает, что ваши 30 миллионов строк займут 1,8 ГБ.

Я предлагаю вместо использования python использовать утилиту сортировки unix. Я не являюсь мастером Mac, но предполагаю, что параметры сортировки в OS X совпадают с версией Linux, так что это должно работать:

sort -n -t, -k2 links.csv

-n означает числовую сортировку

-t, означает использовать запятую в качестве разделителя полей

-k2 означает сортировку по второму полю

Это отсортирует файл и запишет результат в стандартный вывод. Вы можете перенаправить его в другой файл или передать его программе Python для дальнейшей обработки.

edit: Если вы не хотите сортировать файл перед запуском сценария python, вы можете использовать модуль подпроцесса для создания канала для утилиты сортировки оболочки, а затем прочитать отсортированные результаты из вывода канала.

8
ответ дан 28 November 2019 в 21:23
поделиться

Поскольку это всего лишь числа, их загрузка в массив Nx2 устранит некоторые накладные расходы. Используйте NumPy для многомерных массивов. В качестве альтернативы вы можете использовать два обычных массива python для представления каждого столбца.

4
ответ дан 28 November 2019 в 21:23
поделиться

Самый дешевый способ сохранить строки ввода в памяти - это элементы array.array ('i') - предполагая, что каждое число будет соответствовать 32-битному целому числу со знаком. Стоимость памяти составит 8N байт, где N - количество строк.

Вот как выполнить сортировку и записать выходной файл в отсортированном порядке:

from array import array
import csv
a = array('i')
b = array('i')
for anum, bnum in csv.reader(open('input.csv', 'rb')):
    a.append(int(anum))
    b.append(int(bnum))
wtr = csv.writer(open('output.csv', 'wb'))
for i in sorted(xrange(len(a)), key=lambda x: b[x]):
    wtr.writerow([a[i], b[i]])

К сожалению, sorted () возвращает a list, а не итератором, и этот список будет довольно большим: 4N байтов для указателей и 12N байтов для объектов типа int, т.е. 16N байтов для вывода sorted () . Примечание: это основано на CPython 2.X на 32-битной машине; ситуация ухудшается для каждой из 3.X и 64-битных машин. Всего 24N байтов. У вас 31 миллион строк, поэтому вам нужно 31 * 24 = 744 МБ ... похоже, это должно работать; обратите внимание, что этот расчет не учитывает какую-либо память, выделенную сортировкой,

4
ответ дан 28 November 2019 в 21:23
поделиться
Другие вопросы по тегам:

Похожие вопросы: