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



我试图在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秒。如果我再复制粘贴一次,两者都需要5 -5秒。更令人困惑的是,如果我去掉所有复制和粘贴的代码,然后仅仅将变量名wordsList的第二次出现更改为otherList,它实际上对于otherList来说更快。也就是说,第一个区块的时间是0.2秒,而第二个区块的时间是~5e-5。

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

或者它与文件I/O无关,而是与分配给列表的内存有关?也就是说,这是因为我在第一个循环中"预分配"了wordsList,然后在第二个循环中重写了它吗?我试着改变变量名来检验这个假设,然后我得到了更短的时间。

最后,你将如何测试/调试这些想法?我应该插入打印语句来获取变量的地址或其他东西吗?显然,我是一个调试新手。请随时纠正我所犯的任何术语错误,感谢任何帮助

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

由于文件仍处于结束位置,因此第二个循环将不产生结果。您可以在第二个for line in fin:循环之前使用fin.seek(0)再次从文件的开头开始读取,但是这里存在其他问题。

你更多的是测试操作系统和硬件传输文件数据的速度;在进行基准测试时,总是尽可能多地去除外部因素。例如,操作系统会在内存中缓存读取的文件数据一段时间,这肯定会影响结果。

另一个问题是,每种方法只运行一次,使基准对其他偏差敞开了大门;计算机上运行的其他进程也需要时间,并且可能不公平地影响结果。

为了避免这种偏差,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 100万次,而第二个测试通过将单个元素'f''o''o''b''a''r'添加100万次来增加列表。不出所料,第二次测试的运行速度不到它的一半。

如果你想使用+=添加一个元素到列表中,你需要在另一个序列中包装该元素;单例列表对象,例如:

>>> timeit('lst += [value]', 'lst = []; value = "foobar"')
0.14356198499444872

这仍然比使用list.append()慢得多,但至少现在您获得了相同的最终结果。

最新更新