Dan*_*Dan 12 python benchmarking profiling
据我所知,括号只不过是一个包装器__getitem__.以下是我对此进行基准测试的方法:
首先,我生成了一个半大字典.
items = {}
for i in range(1000000):
items[i] = 1
Run Code Online (Sandbox Code Playgroud)
然后,我使用cProfile测试以下三个函数:
def get2(items):
for k in items.iterkeys():
items.get(k)
def magic3(items):
for k in items.iterkeys():
items.__getitem__(k)
def brackets1(items):
for k in items.iterkeys():
items[k]
Run Code Online (Sandbox Code Playgroud)
结果如下:
1000004 function calls in 3.779 CPU seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 3.779 3.779 <string>:1(<module>)
1 2.135 2.135 3.778 3.778 dict_get_items.py:15(get2)
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
1000000 1.644 0.000 1.644 0.000 {method 'get' of 'dict' objects}
1 0.000 0.000 0.000 0.000 {method 'iterkeys' of 'dict' objects}
1000004 function calls in 3.679 CPU seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 3.679 3.679 <string>:1(<module>)
1 2.083 2.083 3.679 3.679 dict_get_items.py:19(magic3)
1000000 1.596 0.000 1.596 0.000 {method '__getitem__' of 'dict' objects}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
1 0.000 0.000 0.000 0.000 {method 'iterkeys' of 'dict' objects}
4 function calls in 0.136 CPU seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.136 0.136 <string>:1(<module>)
1 0.136 0.136 0.136 0.136 dict_get_items.py:11(brackets1)
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
1 0.000 0.000 0.000 0.000 {method 'iterkeys' of 'dict' objects}
Run Code Online (Sandbox Code Playgroud)
这个问题是我的基准测试方式吗?我尝试用简单的"通道"替换括号访问,以确保实际访问数据,并发现"通过"运行得更快.我对此的解释是确实正在访问数据.我也尝试添加一个新列表,它给出了类似的结果.
首先,Not_a_Golfer发布的反汇编:
>>> d = {1:2}
>>> dis.dis(lambda: d[1])
1 0 LOAD_GLOBAL 0 (d)
3 LOAD_CONST 1 (1)
6 BINARY_SUBSCR
7 RETURN_VALUE
>>> dis.dis(lambda: d.get(1))
1 0 LOAD_GLOBAL 0 (d)
3 LOAD_ATTR 1 (get)
6 LOAD_CONST 1 (1)
9 CALL_FUNCTION 1
12 RETURN_VALUE
>>> dis.dis(lambda: d.__getitem__(1))
1 0 LOAD_GLOBAL 0 (d)
3 LOAD_ATTR 1 (__getitem__)
6 LOAD_CONST 1 (1)
9 CALL_FUNCTION 1
12 RETURN_VALUE
Run Code Online (Sandbox Code Playgroud)
现在,获得正确的基准测试对于阅读结果中的任何内容显然非常重要,而且我不知道在那里有多少帮助.但假设确实存在差异(这对我来说很有意义),这是我猜测的原因:
dict.get只是"做得更多"; 它必须检查密钥是否存在,如果不是,则返回其第二个参数(默认为None).这意味着存在某种形式的条件或异常捕获,因此我完全不会惊讶于这将与检索与键相关联的值的更基本操作具有不同的时序特性.
Python具有用于"订阅"操作的特定字节码(如反汇编中所示).内置类型,包括dict,主要在C中实现,它们的实现不一定遵循普通的Python规则(只需要它们的接口,甚至还有很多极端情况).所以我的猜测是BINARY_SUBSCR操作码的实现或多或少直接与支持此操作的内置类型的底层C实现相关.对于这些类型,我希望它实际上__getitem__是作为包装C实现的Python级方法而存在,而不是括号语法调用Python级方法.
这可能是有趣的基准thing.__getitem__(key)对thing[key]用于实现自定义的类的实例__getitem__; 你实际上可能会看到相反的结果,因为BINARY_SUBSCR操作码在内部必须回退到做同样的工作来查找方法并调用它.
| 归档时间: |
|
| 查看次数: |
3198 次 |
| 最近记录: |