Python解释器代码优化

20

考虑以下代码片段:

dict [name] = 0
dict [name] += 1
dict [name] += 1

Python解释器是否自动识别对字典值的重复引用并使用缓存的本地引用,有点类似于C/C++的别名优化,变成了这样:

value = dict [name]
value = 0
value += 1
value += 1

显然,手动完成这项任务并不是什么大问题,但我很好奇是否真的有必要这样做。欢迎任何见解、反馈等。


那是解释器的实现细节,你不必担心。此外,在 Python 这样的层面上进行微型优化并没有太多意义,因为它已经非常高了。你有什么特别的理由担心这个问题吗? - Santa
5
好奇心和对代码的每个方面都过度担忧的倾向 :) - Gearoid Murphy
4个回答

28
你可以通过反汇编器运行它以查找信息:
import dis

def test():
    name = 'test'
    tdict = {}
    tdict[name] = 0
    tdict[name] += 1
    tdict[name] += 1

dis.dis(test)

运行它,我们得到:

 13           0 LOAD_CONST               1 ('test')
              3 STORE_FAST               0 (name)

 14           6 BUILD_MAP                0
              9 STORE_FAST               1 (tdict)

 15          12 LOAD_CONST               2 (0)
             15 LOAD_FAST                1 (tdict)
             18 LOAD_FAST                0 (name)
             21 STORE_SUBSCR        

 16          22 LOAD_FAST                1 (tdict)
             25 LOAD_FAST                0 (name)
             28 DUP_TOPX                 2
             31 BINARY_SUBSCR       
             32 LOAD_CONST               3 (1)
             35 INPLACE_ADD         
             36 ROT_THREE           
             37 STORE_SUBSCR        

 17          38 LOAD_FAST                1 (tdict)
             41 LOAD_FAST                0 (name)
             44 DUP_TOPX                 2
             47 BINARY_SUBSCR       
             48 LOAD_CONST               3 (1)
             51 INPLACE_ADD         
             52 ROT_THREE           
             53 STORE_SUBSCR        
             54 LOAD_CONST               0 (None)
             57 RETURN_VALUE        

看起来,在这种情况下,LOAD_FAST每次尝试访问以执行递增操作时都会加载tdictname的值,因此答案似乎是否定的。


1
令人印象深刻,我之前不知道反汇编器。 - Gearoid Murphy

9

仅通过检查代码是无法进行这种类型的优化的。你的名称dict可能不是指本地字典,而是指实现了__setitem__的用户定义对象,并且必须调用该方法三次。在运行时,一个复杂的实现可以注意到名称的实际值,并进行优化,但是在运行之前无法进行优化,否则会破坏一些Python语义。


1
不行,因为那样做是行不通的,而且你甚至用自己的代码证明了这一点 - 它实际上并不等同。
>>> a = {}
>>> name = 'x'
>>> a[name] = 0
>>> a[name] += 1
>>> a[name] += 1
>>> a[name] # ok no suprises so far
2
>>> a = {}
>>> a[name] = 0
>>> x = a[name] # x is now literally `0`, not some sort of reference to a[name]
>>> x
0
>>> x += 1
>>> x += 1
>>> a[name] # so this never changed
0
>>>

Python 没有类似 C 的“引用(reference)”概念。你在想的那个用法只能适用于可变类型,比如 list。这是 Python 很基础的一点,当你使用 Python 编程时,应该忘掉 C 传授给你的关于变量的所有知识。


2
你是不是指的是“类似于C ++的引用”而不是“类似于C的引用”? - brandizzi
我认为相反,Python确实有类似于"C++"的引用;它没有的是值类型变量。值是值,而变量是指向这些值的。嗯 - 它们就像C++ 引用一样始终有效且非空,并且“自动解除引用”;但是像C++ 指针一样,它们可以被重新分配,并且对它们的赋值会重新分配它们;与C++ 完全不同的是,它们是动态类型的。 - Karl Knechtel

1

将你的两个例子改成类似于这样:

#v1.py
di = {}
name = "hallo"
di[name] = 0
for i in range(2000000):
    di[name] += 1

#v2.py
di = {}
name = "hallo"
di[name] = 0
value = di[name]
for i in range(2000000):
    value += 1

你可以看到以下的测试结果,v2更快,但pypy要快得多 :-)
$ time python2.7 v1.py
real    0m0.788s
user    0m0.700s
sys     0m0.080s

$ time python2.7 v2.py
real    0m0.586s
user    0m0.490s
sys     0m0.090s

$ time pypy v1.py
real    0m0.203s
user    0m0.210s
sys     0m0.000s

$ time pypy v2.py
real    0m0.117s
user    0m0.080s
sys     0m0.030s

所以,为单个解释器优化代码并不好(例如我没有测试过Jython...),但当有人优化解释器时,这是很棒的...


我知道这一点。但问题是相同的:在v1中,dict[name]为2,在v2中值为2。问题是解释器是否以(大致)相同的方式处理两种方法。但它们并不相同,否则它们将(大致)同样快。 - xubuntix
在阅读问题和阅读您的答案之间,我忘记了那是问题的内容 :) - agf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接