我在timeit上获得了非常令人惊讶的结果,有人可以告诉我我做错了什么吗?我正在使用Python 2.7。
这是文件speedtest_init.py的内容:
import random to_count = [random.randint(0, 100) for r in range(60)]
这些是speedtest.py的内容:
__author__ = 'BlueTrin' import timeit def test_init1(): print(timeit.timeit('import speedtest_init')) def test_counter1(): s = """\ d = defaultdict(int); for i in speedtest_init.to_count: d[i] += 1 """ print(timeit.timeit(s, 'from collections import defaultdict; import speedtest_init;')) def test_counter2(): print(timeit.timeit('d = Counter(speedtest_init.to_count);', 'from collections import Counter; import speedtest_init;')) if __name__ == "__main__": test_init1() test_counter1() test_counter2()
控制台输出为:
C:\Python27\python.exe C:/Dev/codility/chlorum2014/speedtest.py 2.71501962931 65.7090444503 91.2953839048 Process finished with exit code 0
我认为默认情况下timeit()运行代码的1000000倍,因此我需要将时间除以1000000,但是令人惊讶的是Counter的速度比defaultdict()慢。
那是预期的吗?
编辑:
同样,使用dict比defaultdict(int)更快:
def test_counter3(): s = """\ d = {}; for i in speedtest_init.to_count: if i not in d: d[i] = 1 else: d[i] += 1 """ print(timeit.timeit(stmt=s, setup='from collections import defaultdict; import speedtest_init;')
最后一个版本比defaultdict(int)快,这意味着除非您更关心可读性,否则应使用dict()而不是defaultdict()。
是的,这是预期的;该Counter() 构造 的用途Counter.update(),它使用self.get()加载初始值,而不是依靠__missing__。
Counter()
Counter.update()
self.get()
__missing__
此外,defaultdict __missing__工厂完全由C代码处理,尤其是在使用其他类似的类型int()(本身由C实现)时。Counter源是纯Python,因此该Counter.__missing__方法需要Python框架才能执行。
defaultdict
int()
Counter
Counter.__missing__
由于dict.get()仍在C中处理,因此对于,构造器方法是更快的方法Counter(),只要您使用相同的技巧Counter.update()和别名self.get作为本地优先项即可:
dict.get()
self.get
>>> import timeit >>> import random >>> import sys >>> sys.version_info sys.version_info(major=2, minor=7, micro=9, releaselevel='final', serial=0) >>> to_count = [random.randint(0, 100) for r in range(60)] >>> timeit.timeit('for i in to_count: c[i] += 1', ... 'from collections import Counter; from __main__ import to_count; c = Counter()', ... number=10000) 0.2510359287261963 >>> timeit.timeit('for i in to_count: c[i] = c_get(i, 0) + 1', ... 'from collections import Counter; from __main__ import to_count; c = Counter(); c_get = c.get', ... number=10000) 0.20978617668151855
两者defaultdict并Counter为他们的功能,而不是他们的表现内置有用的类; 不依赖__missing__钩子可以更快:
>>> timeit.timeit('for i in to_count: d[i] = d_get(i, 0) + 1', ... 'from __main__ import to_count; d = {}; d_get = d.get', ... number=10000) 0.11437392234802246
这是使用别名dict.get()方法以实现最大速度的常规词典。但是随后,您还必须重新实现Counter或Counter.most_common()方法的bag行为。该defaultdict用例去的方式数不胜数。
Counter.most_common()
在Python 3.2中,Counter()通过添加一个处理这种情况的C库来更新速度。参见问题10667。在Python 3.4上进行测试,Counter()构造函数现在击败了别名dict.get情况:
dict.get
>>> timeit.timeit('Counter(to_count)', ... 'from collections import Counter; from __main__ import to_count', ... number=100000) 0.8332311600097455 >>> timeit.timeit('for i in to_count: d[i] = d_get(i, 0) + 1', ... 'from __main__ import to_count; d = {}; d_get = d.get', ... number=100000) 0.961191965994658 >>> import sys >>> sys.version_info sys.version_info(major=3, minor=4, micro=2, releaselevel='final', serial=0)
(注意:要获得有意义的计时结果,迭代次数从10k增加到100k;因此,如果将上述dict.get()情况与上述情况进行比较,则需要在计时中乘以10倍,即1.144秒)。