代码之家  ›  专栏  ›  技术社区  ›  asmaier

Python:如何将大型文本文件读入内存

  •  22
  • asmaier  · 技术社区  · 15 年前

    $ 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
    

    因此,文件中的每一行都由两个逗号分隔的整数值组成的元组组成。

    我用于读取文件和打印有关内存消耗的一些信息的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
    

    Python只读取了500MB文件的25%,就已经消耗了500MB。因此,将文件内容存储为int元组列表似乎不是非常高效的内存。 有没有更好的方法,这样我就可以把500MB的文件读入1GB的内存?

    6 回复  |  直到 15 年前
        1
  •  22
  •   Peter Hansen    15 年前

    有一种方法可以对大于RAM的文件进行排序 on this page ,但您必须根据涉及CSV格式数据的案例对其进行调整。这里还有指向其他资源的链接。

    的确,磁盘上的文件并不“比RAM大”,但内存中的表示形式很容易变得比RAM大得多 . 首先,您自己的程序不会获得整个1GB(操作系统开销等)。另一方面,即使您将其存储为纯Python的最紧凑形式(两个整数列表,假设为32位机器等),对于这3000万对整数,您也将使用934MB。

    使用numpy,您也可以完成这项工作,只需要大约250MB。以这种方式加载并不特别快,因为您必须计算行数并预分配阵列,但考虑到它在内存中,它可能是最快的实际排序:

    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()
    

    在我的机器上输出的示例文件与您显示的类似:

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

    numpy中的默认值是 Quicksort . ndarray.sort()例程(用于就地排序)也可以接受关键字参数 kind="mergesort" kind="heapsort" Record Array 顺便说一句,这是我唯一能看到的对列进行排序的方法 在一起 与默认情况相反,默认情况下会对它们进行独立排序(完全弄乱数据)。

        2
  •  8
  •   Dave Kirby    15 年前

    所有python对象在实际存储的数据之上都有内存开销。根据我的32位Ubuntu系统上的getsizeof,元组的开销为32字节,int的开销为12字节,因此文件中的每一行都需要56字节+列表中的4字节指针——我认为对于64位系统来说,这会多得多。这与您给出的数字一致,意味着您的3000万行将占用1.8GB。

    我建议不要使用python,而是使用unix排序实用程序。我不是Mac电脑的负责人,但我认为OS X排序选项与linux版本相同,因此这应该可以工作:

    sort -n -t, -k2 links.csv
    

    -k2表示第二个字段上的排序

    这将对文件进行排序,并将结果写入标准输出。您可以将其重定向到另一个文件,或者将其通过管道传送到python程序进行进一步处理。

    如果不想在运行python脚本之前对文件进行排序,可以使用子流程模块创建一个到shell sort实用程序的管道,然后从管道输出中读取排序结果。

        3
  •  4
  •   kenm    15 年前

    因为这些都只是数字,所以将它们加载到Nx2阵列中会减少一些开销。对多维数组使用NumPy。或者,您可以使用两个普通python arrays

        4
  •  4
  •   John Machin Santi    15 年前

    以下是如何进行排序并按排序顺序写入输出文件:

    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() 输出。注意:这是基于32位机器上的CPython 2.X;对于3.X和64位机器,情况都会变得更糟。总共是24N字节。您有3100万行,因此需要31*24=744 MB。。。看起来它应该会起作用;请注意,此计算不允许排序分配任何内存,但您有一个合理的安全裕度。

    旁白:以你的工资率计算,额外的GB或3 GB内存(以小时为单位)的成本是多少?

        5
  •  2
  •   John Montgomery    15 年前

    您可能想看看mmap:

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

    它将让你像对待一个大数组/字符串一样对待文件,并让操作系统处理数据进出内存的问题,以使其适合。

    因此,您可以读取csv文件,每次读取一行,然后将结果写入mmap'd文件(以合适的二进制格式),然后处理mmap'd文件。由于mmap文件只是临时文件,因此您当然可以为此创建一个tmp文件。

    
    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)
    

    不过有点粗糙。实际上,您可能希望用一个好的类来结束所有这些,这样您的edge就可以以一种使其行为类似于列表的方式进行访问(带有索引、len等)。希望它能给你一个起点。

        6
  •  0
  •   hoju    10 年前

    我使用 external merge sort : https://bitbucket.org/richardpenman/csvsort

    >>> from csvsort import csvsort
    >>> csvsort('links.csv', columns=[1], has_header=False)