为什么在dis.dis下反汇编这两个函数具有相同的字节码?

马修·莫森(Matthew Moisen):

接下来是四个具有相同输出的函数,但是使用列表理解或紧密循环以及对vs内联条件的函数调用编写。

有趣的是,a并且b在反汇编时具有相同的字节码,但是b比快得多a

此外,d使用没有函数调用的紧密循环的,比a使用带有函数调用的列表理解的速度要快

为什么函数a和b具有相同的字节码,为什么b的性能要比给定的相同字节码好得多?

import dis

def my_filter(n):
    return n < 5

def a():
    # list comprehension with function call
    return [i for i in range(10) if my_filter(i)]

def b():
    # list comprehension without function call
    return [i for i in range(10) if i < 5]

def c():
    # tight loop with function call
    values = []
    for i in range(10):
        if my_filter(i):
            values.append(i)
    return values

def d():
    # tight loop without function call
    values = []
    for i in range(10):
        if i < 5:
            values.append(i)
    return values

assert a() == b() == c() == d()

import sys
>>> sys.version_info[:]
(3, 6, 5, 'final', 0)

# list comprehension with function call
>>> dis.dis(a)
  2           0 LOAD_CONST               1 (<code object <listcomp> at 0x00000211CBE8B300, file "<stdin>", line 2>)
              2 LOAD_CONST               2 ('a.<locals>.<listcomp>')
              4 MAKE_FUNCTION            0
              6 LOAD_GLOBAL              0 (range)
              8 LOAD_CONST               3 (10)
             10 CALL_FUNCTION            1
             12 GET_ITER
             14 CALL_FUNCTION            1
             16 RETURN_VALUE

# list comprehension without function call
>>> dis.dis(b)
  2           0 LOAD_CONST               1 (<code object <listcomp> at 0x00000211CBB64270, file "<stdin>", line 2>)
              2 LOAD_CONST               2 ('b.<locals>.<listcomp>')
              4 MAKE_FUNCTION            0
              6 LOAD_GLOBAL              0 (range)
              8 LOAD_CONST               3 (10)
             10 CALL_FUNCTION            1
             12 GET_ITER
             14 CALL_FUNCTION            1
             16 RETURN_VALUE

# a and b have the same byte code?
# Why doesn't a have a LOAD_GLOBAL (my_filter) and CALL_FUNCTION?
# c below has both of these    

# tight loop with function call
>>> dis.dis(c)
  2           0 BUILD_LIST               0
              2 STORE_FAST               0 (values)

  3           4 SETUP_LOOP              34 (to 40)
              6 LOAD_GLOBAL              0 (range)
              8 LOAD_CONST               1 (10)
             10 CALL_FUNCTION            1
             12 GET_ITER
        >>   14 FOR_ITER                22 (to 38)
             16 STORE_FAST               1 (i)

  4          18 LOAD_GLOBAL              1 (my_filter)
             20 LOAD_FAST                1 (i)
             22 CALL_FUNCTION            1
             24 POP_JUMP_IF_FALSE       14

  5          26 LOAD_FAST                0 (values)
             28 LOAD_ATTR                2 (append)
             30 LOAD_FAST                1 (i)
             32 CALL_FUNCTION            1
             34 POP_TOP
             36 JUMP_ABSOLUTE           14
        >>   38 POP_BLOCK

  6     >>   40 LOAD_FAST                0 (values)
             42 RETURN_VALUE

# tight loop without function call
>>> dis.dis(d)
  2           0 BUILD_LIST               0
              2 STORE_FAST               0 (values)

  3           4 SETUP_LOOP              34 (to 40)
              6 LOAD_GLOBAL              0 (range)
              8 LOAD_CONST               1 (10)
             10 CALL_FUNCTION            1
             12 GET_ITER
        >>   14 FOR_ITER                22 (to 38)
             16 STORE_FAST               1 (i)

  4          18 LOAD_FAST                1 (i)
             20 LOAD_CONST               2 (5)
             22 COMPARE_OP               0 (<)
             24 POP_JUMP_IF_FALSE       14

  5          26 LOAD_FAST                0 (values)
             28 LOAD_ATTR                1 (append)
             30 LOAD_FAST                1 (i)
             32 CALL_FUNCTION            1
             34 POP_TOP
             36 JUMP_ABSOLUTE           14
        >>   38 POP_BLOCK

  6     >>   40 LOAD_FAST                0 (values)
             42 RETURN_VALUE

import timeit

>>> timeit.timeit(a)  # list comprehension with my_filter
1.2435139456834463
>>> timeit.timeit(b)  # list comprehension without my_filter
0.6717423789164627
>>> timeit.timeit(c)  # no list comprehension with my_filter
1.326850592144865
>>> timeit.timeit(d)  # no list comprehension no my_filter
0.7743895521070954

为什么ab在拆卸时具有相同的字节码?我本来希望b有更好看的字节码。值得注意的是,我会认为a需要a LOAD_GLOBAL ? (my_filter)和a CALL FUNCTION例如,与列表理解c相同a但没有列表理解,它在地址18和22上使用这些字节码。

但是,即使使用相同的字节码,其b性能也比更好a这里发生了什么?

甚至更有趣的是d,它使用紧密循环但没有调用my_filter,它比b使用列表推导但具有的调用要my_filter看起来使用函数的开销超过了紧密循环的开销。

我的目标是尝试弄清楚是否可以将列表理解的条件分解为一个函数,以使列表理解更易于阅读。

乔治:

请注意这两个字节码ab只运行<listcomp>对象别处定义。

2           0 LOAD_CONST               1 (<code object <listcomp> at 0x00000211CBE8B300, file "<stdin>", line 2>)

由于包装函数ab相同,因此它们的字节码相同,仅listcomps的地址不同。

在python 3.7中,dis模块还打印listcomps,这是完整的代码和输出:

import sys
import dis

def my_filter(n):
    return n < 5

def a():
    # list comprehension with function call
    return [i for i in range(10) if my_filter(i)]

def b():
    # list comprehension without function call
    return [i for i in range(10) if i < 5]

print(sys.version)
print('-' * 70)
dis.dis(a)
print('-' * 70)
dis.dis(b)

-

3.7.3 (default, May 19 2019, 21:16:26) 
[Clang 10.0.1 (clang-1001.0.46.4)]
----------------------------------------------------------------------
  9           0 LOAD_CONST               1 (<code object <listcomp> at 0x1065c61e0, file "/w/test/x.py", line 9>)
              2 LOAD_CONST               2 ('a.<locals>.<listcomp>')
              4 MAKE_FUNCTION            0
              6 LOAD_GLOBAL              0 (range)
              8 LOAD_CONST               3 (10)
             10 CALL_FUNCTION            1
             12 GET_ITER
             14 CALL_FUNCTION            1
             16 RETURN_VALUE

Disassembly of <code object <listcomp> at 0x1065c61e0, file "/w/test/x.py", line 9>:
  9           0 BUILD_LIST               0
              2 LOAD_FAST                0 (.0)
        >>    4 FOR_ITER                16 (to 22)
              6 STORE_FAST               1 (i)
              8 LOAD_GLOBAL              0 (my_filter)
             10 LOAD_FAST                1 (i)
             12 CALL_FUNCTION            1
             14 POP_JUMP_IF_FALSE        4
             16 LOAD_FAST                1 (i)
             18 LIST_APPEND              2
             20 JUMP_ABSOLUTE            4
        >>   22 RETURN_VALUE
----------------------------------------------------------------------
 13           0 LOAD_CONST               1 (<code object <listcomp> at 0x1066188a0, file "/w/test/x.py", line 13>)
              2 LOAD_CONST               2 ('b.<locals>.<listcomp>')
              4 MAKE_FUNCTION            0
              6 LOAD_GLOBAL              0 (range)
              8 LOAD_CONST               3 (10)
             10 CALL_FUNCTION            1
             12 GET_ITER
             14 CALL_FUNCTION            1
             16 RETURN_VALUE

Disassembly of <code object <listcomp> at 0x1066188a0, file "/w/test/x.py", line 13>:
 13           0 BUILD_LIST               0
              2 LOAD_FAST                0 (.0)
        >>    4 FOR_ITER                16 (to 22)
              6 STORE_FAST               1 (i)
              8 LOAD_FAST                1 (i)
             10 LOAD_CONST               0 (5)
             12 COMPARE_OP               0 (<)
             14 POP_JUMP_IF_FALSE        4
             16 LOAD_FAST                1 (i)
             18 LIST_APPEND              2
             20 JUMP_ABSOLUTE            4
        >>   22 RETURN_VALUE

对于python <3.7。参见Python:使用Dis分析列表理解

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

为什么 Python dis 模块不能反汇编这个 .pyc 文件?

dis.findlabels()有什么作用?

为什么这两个代码片段具有相同的效果?

为什么这两个函数没有给我相同的答案?

为什么这两个用于打印整数的二进制表示形式的函数具有相同的输出?

python dis输出中的>>是什么?

为什么这两个查询具有相同的“已处理的GB”(并因此产生了成本)?

具有两个可执行文件(.dis)的两个线程Limbo(编程语言)

为什么这两个对象的 id 相同?

为什么这两个代码给出的结果相同?

为什么这两个指针访问相同的元素?

为什么反汇编一个python类定义会显示两个相同的LOAD_CONST,它们的类名是什么?

为什么G ++编译器不以相同的方式对待这两个函数?

为什么这两个SQL日期函数不能提供相同的答案

为什么这两个数组具有相同的形状?

为什么两个函数给出具有相同方法的不同输出?

为什么这两个回溯算法有相同的输出?

为什么2个不同的python lambda具有相同的字节码?

为什么这两个DOMDocument函数的行为不同?

为什么这两个函数的类型不同?

为什么这两个模板函数的输出不同?

为什么这两个函数的结果不同?

为什么这两个float64具有不同的值?

为什么这两个结构在内存中具有不同的大小?

为什么这两个右值引用示例具有不同的行为?

这两个程序有多少结果,为什么?

这两个JS函数有什么区别?

这两个递归函数有什么区别?

这两个函数声明有什么区别?