列表在python中的性能是否很差?
我试图从某个大文件中读取数据并将其写回,但我意识到,主要成本来自将数据分配到列表,而不是从文件中读取或写入数据列表在python中的性能是否很差?,python,Python,我试图从某个大文件中读取数据并将其写回,但我意识到,主要成本来自将数据分配到列表,而不是从文件中读取或写入数据 rows = [None] * 1446311 begin = datetime.datetime.now() for i in range( 1446311 ): row = csvReader.next() rows[i] = row print datetime.datetime.now() - begin 上面的代码
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行(rows[I]=row
),则需要5秒,我已经提前构建了列表(即保留内存),但为什么它仍然如此缓慢?我能做些什么来加快速度?我在csvReader中尝试了逐行操作
,但它的性能更差
问候,,
约翰我得到了类似的结果,但没有你的那么戏剧性。(注意使用模块来计时代码执行,并且注意我已经考虑了列表创建,因为它对两个测试用例都是通用的。) 这是我对正在发生的事情的猜测。在这两个测试中,CSV阅读器从文件中读取一条记录,并在内存中创建一个表示该记录的数据结构 在
test2
中,如果没有存储记录,数据结构或多或少会立即被删除(在循环的下一次迭代中,行
变量会被更新,因此前一条记录的引用计数会减少,从而回收内存)。这使得用于上一条记录的内存可以重用:该内存已经在计算机的虚拟内存表中,并且可能仍在缓存中,因此它(相对)比较快
在存储记录的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可以取消分配它们。编辑:第一部分不是很有效(请参阅下面的注释) 您是否尝试过这样做:
rows = [None] * 1446311
for i in range( 1446311 ):
rows[i] = csvReader.next()
rows = []
for i in range( 1446311 ):
rows.append(csvReader.next())
因为从我在代码中看到的情况来看,您复制了两次数据:一次是使用行=…
从文件复制到内存,一次是从行
复制到行[I]
。因为这里有不可变的东西(字符串),所以我们实际上讨论的是数据的拷贝,而不是引用的拷贝
此外,即使您以前创建了一个空列表,您也会将一大块数据放入其中;由于您仅在开始时放入None
,因此未保留任何实际内存空间。也许你可以直接写一个非常简单的东西如下:
rows = [None] * 1446311
for i in range( 1446311 ):
rows[i] = csvReader.next()
rows = []
for i in range( 1446311 ):
rows.append(csvReader.next())
或者甚至可以直接使用生成器语法
rows = list(csvReader)
编辑 在阅读了加雷思的答案后,我对我的建议做了一些测试。顺便说一句,在从迭代器读取数据时,请注意采取一些保护措施,以便在迭代器比预期短时能够很好地停止:
>>> 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,时间上没有很大的差异<代码>测试2在我的机器上,毫无疑问只是有一点不同test5更优雅,但不能限制解析的行数,这可能会让人恼火
因此,如果您同时需要所有行,我的建议是使用最优雅的解决方案,即使稍微长一点:test4
。但是,正如Gareth所问,您可能不需要一次完成所有任务,这是获得速度和内存的最佳方式。我认为L5存在与不存在在运行时没有太大区别。(不过,我不得不伪造csvReader.next()调用,这可能会产生影响。)正如Gareth解释的那样,您没有为所有实际行预分配内存,而这种分配正是需要时间的。如果您不要求所有行同时在内存中,那么通过使用生成器/生成器表达式来构造代码,您可能会获得性能提高。您是否尝试过这些建议中的任何一个,以查看它们是否对运行时间产生了任何影响?我是在阅读Gareth的回答后这样做的。请看我答案中的编辑。@Gareth哦,对不起,我错过了是你要求更多的材料。很抱歉回答中的延迟,我花了时间格式化。在顶部附近仍然有几个错误:“您复制了两次数据”以及一些关于可变对象和不可变对象的混淆。否则,看起来不错。哦,我明白了:我没有想到,csv
模块会为每一行返回一个值列表。所以这是正确的,我的答案的开头有点离题。顺便问一下,test1
是否会返回一个引用列表,所有引用都指向csvReader解析的最后一个行
列表?您提到的时间是从内存分配中提取的,您是指列表内容的内存还是列表的引用(指针)?test3和test4都只为列表内容分配内存,即我认为[i]*100一次?我指的是所有行的内存。此外,测试3和4每次循环分配一个新的100元素列表。而且,您还说下一个循环可以利用我们从上一个循环分配的内存,为什么这些内存会不同于其他“野生”但可用的内存块?因为我们已经取消引用了以前分配的内存,对吗?取消分配的内存与“野生”(从未分配)内存不同!“野生”内存需要由操作系统(页表等)分配,然后缓存;最近释放的内存没有。是的,我错过了,它仍然更快,即使下一个循环所需的内存大小可能更大