我无法解释的列表追加和 + 运算符之间的时间差异
Difference in time between list append and + operator that I can't explain
我正在尝试在 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 秒。
如果我复制并粘贴代码,两者都会变得更快,"third" 块耗时约 8e-5 秒,"fourth" 耗时约 4e5。然后,如果我再次复制和粘贴,都需要大约 5e-5 秒。更令人困惑的是,如果我去掉所有复制和粘贴的代码,然后只是将变量名 wordList 的第二次出现更改为 otherList,则 otherList 实际上 faster。也就是说,第一个块我得到的时间是 0.2 秒,第二个块是 ~5e-5。
为什么第一个循环总是耗时最长? open
对象是否将 words.txt
文件加载到内存中,然后将其保存在那里以备下次需要时使用?我以为有一个隐含的 "close".
或者它与文件无关I/O而是与分配给列表的内存有关?也就是说,是不是因为我在第一个循环中 "pre-allocated" wordsList 然后在第二个循环中写了它?我尝试更改变量名称来检验该假设,然后我得到了更短的时间。
最后,您test/debug这些想法如何?我应该插入打印语句来获取变量的地址还是什么? Obvs 我是调试菜鸟。
请随时纠正我所做的任何术语失误,感谢您的帮助
这里发生的事情是您耗尽了文件对象。文件对象使用当前文件位置从文件中读取下一个字节,并循环读取文件直到文件结束。
你的第二个循环然后产生无结果,因为文件仍然在结束位置。您可以在第二个 for line in fin:
循环之前使用 fin.seek(0)
再次从文件开头开始读取,但这里还有其他问题。
您正在测试 OS 和您的硬件传输文件数据的速度;进行基准测试时,请始终尽可能多地消除外部因素。例如,OS 会将读取的文件数据缓存在内存中一段时间,这肯定会使结果发生偏差。
另一个问题是你 运行 每种方法只用一次,让你的基准对其他偏见敞开大门;您计算机上的其他进程 运行 也需要时间,并且可能会对结果产生不公平的影响。
为了避免这种偏差,Python 附带了一个名为 timeit
的基准测试模块,它通过 运行 多次测试 来避免陷阱 (默认值为 100 万),为您的 OS 选择最准确的计时机制,并禁用垃圾收集系统等潜在偏差。你应该在开始时给它完全相同的测试数据(在内存中,而不是从文件中)。
最后但同样重要的是,你比较的是错误的东西。 +=
与 list.append()
不同。这里,+=
在功能上等同于 list.extend()
;列表扩展为单独添加的添加序列中的每个元素,not 作为单个元素:
>>> 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'
、[ =24=、'b'
、'a'
和 'r'
一百万次。毫不奇怪,第二次测试的运行速度不到一半。
如果要使用+=
将一个元素添加到列表中,您需要将该元素包装在另一个序列中;单例列表对象,例如:
>>> timeit('lst += [value]', 'lst = []; value = "foobar"')
0.14356198499444872
这仍然比使用 list.append()
慢得多,但至少现在您获得了相同的最终结果。
我正在尝试在 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 秒。
如果我复制并粘贴代码,两者都会变得更快,"third" 块耗时约 8e-5 秒,"fourth" 耗时约 4e5。然后,如果我再次复制和粘贴,都需要大约 5e-5 秒。更令人困惑的是,如果我去掉所有复制和粘贴的代码,然后只是将变量名 wordList 的第二次出现更改为 otherList,则 otherList 实际上 faster。也就是说,第一个块我得到的时间是 0.2 秒,第二个块是 ~5e-5。
为什么第一个循环总是耗时最长? open
对象是否将 words.txt
文件加载到内存中,然后将其保存在那里以备下次需要时使用?我以为有一个隐含的 "close".
或者它与文件无关I/O而是与分配给列表的内存有关?也就是说,是不是因为我在第一个循环中 "pre-allocated" wordsList 然后在第二个循环中写了它?我尝试更改变量名称来检验该假设,然后我得到了更短的时间。
最后,您test/debug这些想法如何?我应该插入打印语句来获取变量的地址还是什么? Obvs 我是调试菜鸟。 请随时纠正我所做的任何术语失误,感谢您的帮助
这里发生的事情是您耗尽了文件对象。文件对象使用当前文件位置从文件中读取下一个字节,并循环读取文件直到文件结束。
你的第二个循环然后产生无结果,因为文件仍然在结束位置。您可以在第二个 for line in fin:
循环之前使用 fin.seek(0)
再次从文件开头开始读取,但这里还有其他问题。
您正在测试 OS 和您的硬件传输文件数据的速度;进行基准测试时,请始终尽可能多地消除外部因素。例如,OS 会将读取的文件数据缓存在内存中一段时间,这肯定会使结果发生偏差。
另一个问题是你 运行 每种方法只用一次,让你的基准对其他偏见敞开大门;您计算机上的其他进程 运行 也需要时间,并且可能会对结果产生不公平的影响。
为了避免这种偏差,Python 附带了一个名为 timeit
的基准测试模块,它通过 运行 多次测试 来避免陷阱 (默认值为 100 万),为您的 OS 选择最准确的计时机制,并禁用垃圾收集系统等潜在偏差。你应该在开始时给它完全相同的测试数据(在内存中,而不是从文件中)。
最后但同样重要的是,你比较的是错误的东西。 +=
与 list.append()
不同。这里,+=
在功能上等同于 list.extend()
;列表扩展为单独添加的添加序列中的每个元素,not 作为单个元素:
>>> 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'
、[ =24=、'b'
、'a'
和 'r'
一百万次。毫不奇怪,第二次测试的运行速度不到一半。
如果要使用+=
将一个元素添加到列表中,您需要将该元素包装在另一个序列中;单例列表对象,例如:
>>> timeit('lst += [value]', 'lst = []; value = "foobar"')
0.14356198499444872
这仍然比使用 list.append()
慢得多,但至少现在您获得了相同的最终结果。