【问题标题】:Python interpreted code optimisationPython解释代码优化
【发布时间】:2011-08-13 02:55:43
【问题描述】:

考虑以下代码sn-p:

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

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

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

显然,手动执行此操作没什么大不了的,但我很好奇它是否真的有必要。感谢您提供任何见解、反馈等。

【问题讨论】:

  • 这是解释器的实现细节,您不必担心。此外,这种微优化在 Python 这样的层上并不是那么有意义,尽管它是平流层。您有什么特别担心的原因吗?
  • 对我的代码的各个方面充满好奇和烦恼 :)

标签: python optimization interpreter


【解决方案1】:

您可以通过反汇编程序运行它来找出:

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 的值,所以答案似乎是否定的。

【讨论】:

  • 印象深刻,我不知道反汇编器
【解决方案2】:

仅仅通过检查代码是不可能实现这种优化的。你的名字dict 可能不是指本地字典,而是实现__setitem__ 的用户定义对象,并且该方法必须被调用三次。在运行时,一个复杂的实现可以记录名称的实际值,并进行优化,但它不能在运行前完成而不破坏一些 Python 语义。

【讨论】:

    【解决方案3】:

    不,因为那行不通,你甚至用你自己的代码证明了这一点——它实际上并不等同:

    >>> 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 风格的“引用”。您想到的仅适用于可变类型,例如list。这是 Python 的一个非常基本的属性,在编写 Python 时,您可能应该忘记 C 教给您的有关变量的所有内容。

    【讨论】:

    • 您的意思是“C++ 类引用”而不是“C 类引用”吗?
    • 我会说相反,Python 确实有“类 C++”的引用;它没有的是值类型的变量。值就是值,变量指的是值。嗯 - 它们就像 C++ 引用,因为它们始终有效且非空,并且“自动取消引用”;但就像 C++ 指针一样,它们可以重新就位,并且分配给它们可以重新就位;与 C++ 完全不同的是,它们是动态类型的。
    【解决方案4】:

    把你的两个例子改成这样:

    #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
    

    SO:为单个解释器优化代码并不好(例如我没有测试过 Jython...),但是 someone 优化解释器时很棒...

    【讨论】:

    • 我知道这一点。但问题是一样的:在 v1 中,dict[name] 为 2,在 v2 中值为 2。问题是解释器是否(大致)相同地对待这两种方式。他们不是,否则他们会(大致)同样快。
    • 我忘记了问题在阅读问题和阅读您的答案之间所做的事情:)
    猜你喜欢
    • 2019-05-03
    • 2017-03-13
    • 2017-09-03
    • 1970-01-01
    • 1970-01-01
    • 2017-11-02
    • 2014-06-24
    • 1970-01-01
    • 2011-11-02
    相关资源
    最近更新 更多