字节码优化

m9_*_*psy 10 python

这里有两个简单的例子.在第一个示例中,append方法在循环内生成LOAD_ATTR指令,在第二个示例中,它只生成一次,结果保存在变量中(即缓存).提醒:我记得,extend这个任务的方法要快得多

setup = \
"""LIST = []
ANOTHER_LIST = [i for i in range(10**7)]

def appender(list, anohter_list):
    for elem in anohter_list:
        list.append(elem)

def appender_optimized(list, anohter_list):
    append_method = list.append
    for elem in anohter_list:
        append_method(elem)"""


import timeit

print(timeit.timeit("appender(LIST, ANOTHER_LIST)", setup=setup, number=10))
print(timeit.timeit("appender_optimized(LIST, ANOTHER_LIST)", setup=setup, number=10))
Run Code Online (Sandbox Code Playgroud)

结果:

11.92684596051036
7.384205785584728
Run Code Online (Sandbox Code Playgroud)

4.6秒差异(即使是这么大的名单)也不是开玩笑 - 我认为这种差异不能算作"微优化".为什么Python不为我做这件事?因为字节码必须是源代码的精确反映?编译器甚至优化任何东西?例如,

def te():
    a = 2
    a += 1
    a += 1
    a += 1
    a += 1
Run Code Online (Sandbox Code Playgroud)

产生

LOAD_FAST                0 (a)
LOAD_CONST               2 (1)
INPLACE_ADD
STORE_FAST               0 (a)
Run Code Online (Sandbox Code Playgroud)

4次而不是优化成+ = 4.或者它是否优化了一些着名的东西,如产生位移而不是乘以2?我是否误解了基本语言概念?

mgi*_*son 9

Python是一种动态语言.这意味着您在编写代码方面有很多自由.由于python暴露了大量内省(这是非常有用的BTW),许多优化根本无法执行.例如,在你的第一个例子中,python无法知道list调用它时将是什么数据类型.我可以创建一个非常奇怪的类:

class CrazyList(object):
    def append(self, value):
        def new_append(value):
            print "Hello world"

        self.append = new_append
Run Code Online (Sandbox Code Playgroud)

显然这没用,但我可以写这个,它有效的python.如果我将此类型传递给上面的函数,则代码将与您"缓存" append函数的版本不同.

我们可以写一个类似的例子+=(它可能有副作用,如果"编译器"优化了它就不会执行).

为了有效地优化,python必须知道你的类型......对于绝大多数代码,它没有(万无一失)获取类型数据的方法,因此它甚至不会尝试进行大多数优化.


请注意,这一个微优化(并且记录良好).它在某些情况下很有用,但在大多数情况下,如果你编写惯用的python则没有必要.例如,您的list示例最好使用.extend您在帖子中注明的方法编写.大多数情况下,如果你的循环足够紧密,方法的查找时间足以在整个程序运行时中起作用,那么你应该找到一种方法来重写该循环以提高效率甚至推动计算更快的语言(例如C).有些图书馆非常擅长这个(numpy).


话虽如此,有一些优化可以通过称为"窥孔优化器"的阶段中的"编译器"安全地完成.例如,它将为您做一些简单的常量折叠:

>>> import dis
>>> def foo():
...     a = 5 * 6
... 
>>> dis.dis(foo)
  2           0 LOAD_CONST               3 (30)
              3 STORE_FAST               0 (a)
              6 LOAD_CONST               0 (None)
              9 RETURN_VALUE        
Run Code Online (Sandbox Code Playgroud)

在某些情况下,它会缓存值供以后使用,或者将一种类型的对象转换为另一种:

>>> def translate_tuple(a):
...   return a in [1, 3]
... 
>>> import dis
>>> dis.dis(translate_tuple)
  2           0 LOAD_FAST                0 (a)
              3 LOAD_CONST               3 ((1, 3))
              6 COMPARE_OP               6 (in)
              9 RETURN_VALUE
Run Code Online (Sandbox Code Playgroud)

(注意列表变成了一个tuple和缓存 - 在python3.2 + set文字也可以变成frozenset和缓存).