我试图在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()
慢得多,但至少现在您获得了相同的最终结果。