Python:islice 的性能问题

Posted

技术标签:

【中文标题】Python:islice 的性能问题【英文标题】:Python: performance issues with islice 【发布时间】:2015-07-04 23:01:52 【问题描述】:

使用以下代码,我发现执行时间越来越长,因为我增加了 islice 中的起始行。例如,4 的 start_row 将在 1 秒内执行,但 500004 的 start_row 将需要 11 秒。为什么会发生这种情况,是否有更快的方法来做到这一点?我希望能够遍历大型 CSV 文件(几 GB)中的多个行范围并进行一些计算。

import csv
import itertools
from collections import deque
import time

my_queue = deque()

start_row = 500004
stop_row = start_row + 50000

with open('test.csv', 'rb') as fin:
    #load into csv's reader
    csv_f = csv.reader(fin)

    #start logging time for performance
    start = time.time()

    for row in itertools.islice(csv_f, start_row, stop_row):
        my_queue.append(float(row[4])*float(row[10]))

    #stop logging time
    end = time.time()
    #display performance
    print "Initial queue populating time: %.2f" % (end-start)

【问题讨论】:

相关:***.com/questions/620367/…(虽然,呃,如果你在这里尝试使用它,接受的答案会惨败) 所以不要使用公认的答案,使用基于Rosenfield's的东西。 【参考方案1】:

例如,start_row 为 4 将在 1s 内执行,但 start_row 为 500004 需要 11 秒

这就是 islice 是聪明的。或者懒惰,这取决于你喜欢哪个术语。

问题是,文件“只是”硬盘上的字节字符串。他们没有任何内部组织。 \n 只是那个长长的字符串中的另一组字节。 如果不查看之前的所有信息,就无法访​​问任何特定的行(除非您的行长度完全相同,在这种情况下您可以使用file.seek)。

第 4 行?查找第 4 行很快,您的计算机只需要查找 3 \n。第 50004 行?您的计算机必须通读该文件,直到找到 500003 \n。没有办法,如果有人告诉你其他情况,他们要么有某种其他类型的量子计算机,要么他们的计算机正在读取文件,就像世界上所有其他计算机一样,就在他们背后。

至于你能做些什么:在尝试抓取线条进行迭代时要聪明一点。聪明,又懒惰。安排您的请求,以便您只遍历文件一次,并在提取所需数据后立即关闭文件。 (顺便说一句,islice 完成了所有这些工作。)

在python中

lines_I_want = [(start1, stop1), (start2, stop2),...]
with f as open(filename):
     for i,j in enumerate(f):
          if i >= lines_I_want[0][0]:
              if i >= lines_I_want[0][1]:
                   lines_I_want.pop(0)
                   if not lines_I_want: #list is empty
                         break
              else:
                   #j is a line I want. Do something

如果您对制作该文件有任何控制权,请将每一行设置为相同的长度,以便您可以seek。或者使用数据库。

【讨论】:

【参考方案2】:

使用islice() 进行操作的问题在于,在返回任何内容之前,它会遍历您想要的第一行之前的所有行。显然,起始行越大,所需的时间就越长。另一个是您使用csv.reader 来读取这些行,这可能会产生不必要的开销,因为 csv 文件的一行通常是其中的一行。唯一不正确的情况是 csv 文件中的字符串字段包含嵌入的换行符——根据我的经验,这种情况并不常见。

如果这是对您的数据的有效假设,那么首先索引文件并构建一个 (filename, offset, number-of-rows) 元组表可能会更快文件中大约相同大小的行/行逻辑块。这样,您可以通过首先寻找起始偏移量然后从该点读取指定数量的 csv 行来相对快速地处理它们。

这种方法的另一个优点是它允许您并行处理块,我怀疑这是您根据您的previous question 尝试解决的真正问题。因此,即使您在这里没有提到多处理,如果是这样的话,下面的内容已经被编写为与这样做兼容。

import csv
from itertools import islice
import os
import sys

def open_binary_mode(filename, mode='r'):
    """ Open a file proper way (depends on Python verion). """
    kwargs = (dict(mode=mode+'b') if sys.version_info[0] == 2 else
              dict(mode=mode, newline=''))
    return open(filename, **kwargs)

def split(infilename, num_chunks):
    infile_size = os.path.getsize(infilename)
    chunk_size = infile_size // num_chunks
    offset = 0
    num_rows = 0
    bytes_read = 0
    chunks = []
    with open_binary_mode(infilename, 'r') as infile:
        for _ in range(num_chunks):
            while bytes_read < chunk_size:
                try:
                    bytes_read += len(next(infile))
                    num_rows += 1
                except StopIteration:  # end of infile
                    break
            chunks.append((infilename, offset, num_rows))
            offset += bytes_read
            num_rows = 0
            bytes_read = 0
    return chunks

chunks = split('sample_simple.csv', num_chunks=4)
for filename, offset, rows in chunks:
    print('processing:  rows starting at offset '.format(rows, offset))
    with open_binary_mode(filename, 'r') as fin:
        fin.seek(offset)
        for row in islice(csv.reader(fin), rows):
            print(row)

【讨论】:

嗨,martineau,我已经用 4.5GB 的 csv 文件尝试了您的代码,拆分功能大约需要 42 秒。我还使用了 NightShadeQueen 在上面的链接中发布的“索引每一行”方法。它也需要 42 秒。似乎总时间取决于我们必须读取文件中的每一行这一事实,而不是我们存储的偏移量。 索引每一行需要更多的内存,即使不需要更长的时间。最好的情况是,您可以将文件分成大约相同行数的块,而不管它们的长度如何,这与一起表示大约相同字节数的行块不同。

以上是关于Python:islice 的性能问题的主要内容,如果未能解决你的问题,请参考以下文章

与 functools.partial 一起使用时,itertools.islice 会引发 ValueError

yield列表反转 islice切片(2.6)

itertools 的 islice(count()) vs range()

如何在不包含列/行标题的制表符分隔文件中使用 itertools.islice()

Python 请求与 PyCurl 性能

性能:Python 3.x 与 Python 2.x [关闭]