列表追加和+运算符之间的时间差,我无法解释

时间:2015-05-16 14:40:23

标签: python list append concatenation

我正在尝试在Windows 7的Python 3.4中使用time.clock()来比较append+=的速度。 (这是Think Python第10章的练习。)这是我的代码:

import time
fin = open('words.txt')
print("Comparing the time it takes to make a list with append vs. the + operator")

timeConcatStart = time.clock()
wordsList = []
for line in fin:
    word = line.strip()
    wordsList += word
timeConcatEnd = time.clock()
concatElapsed = timeConcatEnd - timeConcatStart
print("+ operator took ",concatElapsed,"seconds for words.txt")

timeAppendStart = time.clock()
wordList = []
for line in fin:
    word = line.strip()
    wordList.append(word)
timeAppendEnd = time.clock()
appendElapsed = timeAppendEnd - timeAppendStart
print("Append took ",appendElapsed,"seconds for words.txt")

在我的系统上,我得知+运算符大约需要0.2秒,append需要0.002秒。

当我切换顺序以使附加代码块为第一个时,append现在需要0.2秒,而+运算符需要0.002秒。

如果我复制并粘贴代码,两者都会变快,“第三”块占用~8e-5秒,“第四”占用~4e5。然后,如果我再次复制并粘贴,则需要大约5e-5秒。更令人困惑的是,如果我删除所有复制和粘贴的代码,然后只是将变量名称wordsList的第二次出现更改为otherList,那么对于otherList实际上更快。也就是说,我得到的时间对于第一个块是0.2秒,而对于第二个块是〜5e-5。

为什么第一个循环总是花费最长的? open对象是否将words.txt文件加载到内存中,然后在下次需要时将其保留在那里?我以为有一个隐含的“关闭”。

或者它与文件I / O无关,而是与分配给列表的内存有关?即,是因为我在第一个循环中“预先分配”了wordsList,然后在第二个循环中写了它?我尝试更改变量名称以测试该假设,然后我得到的时间更短。

最后,您将如何测试/调试这些想法?我应该插入print语句来获取变量的地址吗? Obvs我是一个调试菜鸟。 请随时更正我所做的任何术语失误并感谢您的帮助

1 个答案:

答案 0 :(得分:1)

这里发生的事情是你耗尽了文件对象。文件对象使用当前文件位置从文件中读取下一个字节,循环遍历文件从文件读取直到结束。

然后,由于文件仍处于结束位置,您的第二个循环会产生无结果。您可以在第二个fin.seek(0)循环之前使用for line in fin:再次从文件开头读取,但此处还有其他问题。

您正在测试操作系统和硬件提供文件数据的速度;当基准测试总是尽可能地去除外部因素时。例如,操作系统会将读取文件数据缓存在内存中一段时间​​,这肯定会使结果产生偏差。

另一个问题是你只运行一次方法,让你的基准对其他偏见开放;计算机上运行的其他进程也需要花费时间,可能会对结果造成不公平的影响。

为了避免这种偏见,Python附带了一个名为timeit的基准测试模块,通过选择运行测试很多次(默认值为100万)来避免陷阱。最准确的操作系统计时机制,以及禁用垃圾收集系统等潜在的偏差。你应该在开始时(在内存中,而不是从文件中)给它完全相同的测试数据。

最后但并非最不重要的是,你正在比较错误的事情。 +=list.append()不同。在此,+=在功能上等同于list.extend();列表扩展,添加的序列中的每个元素分别添加,作为单个元素:

>>> lst = []
>>> lst += 'foobar'
>>> lst
['f', 'o', 'o', 'b', 'a', 'r']
>>> lst.append('foobar')
>>> lst
['f', 'o', 'o', 'b', 'a', 'r', 'foobar']

因此,list.append()会更快,因为在这种情况下,一次只添加一个元素:

>>> from timeit import timeit
>>> timeit('lst.append(value)', 'lst = []; value = "foobar"')
0.07227020798018202
>>> timeit('lst += value', 'lst = []; value = "foobar"')
0.15380392200313509

第一个测试将字符串'foobar'添加到lst一百万次,而第二个测试通过添加单个元素'f''o',{{1}来增加列表},'o''b''a'一百万次。毫不奇怪,第二次测试的速度不到一半。

如果您想使用'r'一个元素添加到列表中,则需要将该元素包装在另一个序列中;单例列表对象,例如:

+=

这仍然比使用>>> timeit('lst += [value]', 'lst = []; value = "foobar"') 0.14356198499444872 慢得多,但至少现在你实现了相同的最终结果。