我试图从一些巨大的文件中读取数据并将其写回来,但我意识到主要的成本来自于将数据分配到列表而不是从/向文件读取或写入数据....
rows = [None] * 1446311
begin = datetime.datetime.now()
for i in range( 1446311 ):
row = csvReader.next()
rows[i] = row
print datetime.datetime.now() - begin
上面的代码需要18秒但是5秒,如果我注释掉第5行(rows[i] = row
),我已经预先建立了列表(即保留了内存),但为什么它仍然如此慢?我能做什么让它更快?我试过row for row in csvReader
,但表现更差......
的问候, 约翰
答案 0 :(得分:6)
我得到了类似的结果,但不像你的那么戏剧化。 (注意使用timeit
模块来执行时间代码,并注意我已经考虑了列表创建,因为它对两个测试用例都是通用的。)
import csv
from timeit import Timer
def write_csv(f, n):
"""Write n records to the file named f."""
w = csv.writer(open(f, 'wb'))
for i in xrange(n):
w.writerow((i, "squared", "equals", i**2))
def test1(rows, f, n):
for i, r in enumerate(csv.reader(open(f))):
rows[i] = r
def test2(rows, f, n):
for i, r in enumerate(csv.reader(open(f))):
pass
def test(t):
return (Timer('test%d(rows, F, N)' % t,
'from __main__ import test%d, F, N; rows = [None] * N' % t)
.timeit(number=1))
>>> N = 1446311
>>> F = "test.csv"
>>> write_csv(F, N)
>>> test(1)
2.2321770191192627
>>> test(2)
1.7048690319061279
这是我对发生了什么的猜测。在两个测试中,CSV读取器从文件中读取记录,并在内存中创建表示该记录的数据结构。
在未存储记录的test2
中,数据结构会或多或少立即被删除(在循环的下一次迭代中,row
变量被更新,因此引用计数为先前的记录递减,因此回收内存)。这使得用于前一条记录的内存可以重用:这个内存已经在计算机的虚拟内存表中,并且可能仍然在缓存中,所以它(相对)快。
在存储记录的test1
中,每条记录必须在新的内存区域中分配,该区域必须由操作系统分配,并复制到缓存中,因此它(相对)慢。
因此列表分配不会占用时间,而是内存分配。
这是另外几个测试,用于说明正在发生的事情,没有csv
模块的复杂因素。在test3
中,我们为每一行创建一个新的100元素列表,并存储它。在test4
中,我们为每一行创建一个新的100元素列表,但我们不存储它,我们将它丢弃,以便下次循环时可以重用内存。
def test3(rows, f, n):
for i in xrange(n):
rows[i] = [i] * 100
def test4(rows, f, n):
for i in xrange(n):
temp = [i] * 100
rows[i] = None
>>> test(3)
9.2103338241577148
>>> test(4)
1.5666921138763428
所以我认为,如果您不需要同时将所有行存储在内存中,请不要这样做!如果可以的话,一次一个地读取它们,一次处理一个,然后忘记它们,以便Python可以解除分配它们。
答案 1 :(得分:0)
编辑:第一部分不是那么有效(见下面的评论)
你有没有试过这样的话:
rows = [None] * 1446311
for i in range( 1446311 ):
rows[i] = csvReader.next()
因为根据我在代码中看到的内容,您将数据复制两次:一次从文件到内存row = ...
,一次从row
到rows[i]
。由于你在这里有不可变的东西(字符串),我们真的在谈论数据的复制,而不是关于引用的复制。
此外,即使您之前创建了一个空列表,也会将大量数据放入其中;因为你只将None
放在开头,所以没有保留真正的内存空间。所以也许你也可以直接写一个非常简单的事情:
rows = []
for i in range( 1446311 ):
rows.append(csvReader.next())
或者甚至可以直接使用生成器语法!
rows = list(csvReader)
EDIT 在阅读Gareth的回答后,我对我的提案做了一些时间测试。顺便说一句,在从迭代器读取时要注意保护,以便在迭代器比预期更短时停止:
>>> from timeit import Timer
>>> import csv
>>> # building some timing framework:
>>> def test(n):
return min(Timer('test%d(F, N)' % t,
'from __main__ import test%d, F, N' % t)
.repeat(repeat=10, number=1))
>>> F = r"some\big\csvfile.csv"
>>> N = 200000
>>> def test1(file_in, number_of_lines):
csvReader = csv.reader(open(file_in, 'rb'))
rows = [None] * number_of_lines
for i, c in enumerate(csvReader): # using iterator syntax
if i > number_of_lines: # and limiting the number of lines
break
row = c
rows[i] = row
return rows
>>> test(1)
0.31833305864660133
>>> def test2(file_in, number_of_lines):
csvReader = csv.reader(open(file_in, 'rb'))
rows = [None] * number_of_lines
for i, c in enumerate(csvReader):
if i > number_of_lines:
break
row = c
return rows
>>> test(2)
0.25134269758603978 # remember that only last line is stored!
>>> def test3(file_in, number_of_lines):
csvReader = csv.reader(open(file_in, 'rb'))
rows = [None] * number_of_lines
for i, c in enumerate(csvReader):
if i > number_of_lines:
break
rows[i] = c
return rows
>>> test(3)
0.30860502255637812
>>> def test4(file_in, number_of_lines):
csvReader = csv.reader(open(file_in, 'rb'))
rows = []
for i, c in enumerate(csvReader):
if i > number_of_lines:
break
rows.append(c)
return rows
>>> test(4)
0.32001576256431008
>>> def test5(file_in, number_of_lines):
csvReader = csv.reader(open(file_in, 'rb'))
rows = list(csvReader)
# problem: there's no way to limit the number of lines to parse!
return rows
>>> test(5)
0.30347613834584308
我们可以看到,对于大于文档中行数的N,时间上没有太大差异。 test2
,在我的机器上,意外地只是有点不同。 test5
更优雅,但不能限制解析的行数,这可能很烦人。
所以,如果你一次需要所有行,我的建议就是采用最优雅的解决方案,即使有点长:test4
。但也许,正如Gareth所说的那样,你不需要同时拥有一切,这是获得速度和记忆的最佳方式。