【问题标题】:Is the sequence of opcodes (BINARY_SUBSCR, GET_ITER) somehow optimized in CPython?操作码序列(BINARY_SUBSCR,GET_ITER)是否在 CPython 中以某种方式优化?
【发布时间】:2020-03-22 04:15:03
【问题描述】:

我正在使用 Python3.5 进行一些基准测试,并且在比较以下代码示例时,我注意到 f1 的运行速度比 f2 快 45% 以上:

def f1():
    acc = 0
    a = a_
    for i in a[100:900]:
        acc += i

    return acc

def f2():
    acc = 0
    a = a_
    for i in range(100, 900):
        acc += a[i]

return acc

这对我来说有点违反直觉,因为for i in a[100:900] 看起来像是在执行不需要的数据副本。这在反汇编代码时通过BINARY_SUBSCR 操作码的存在得到了证实。这是字节码的相关部分:

  9          12 SETUP_LOOP              34 (to 49)
             15 LOAD_FAST                1 (a)
             18 LOAD_CONST               2 (100)
             21 LOAD_CONST               3 (900)
             24 BUILD_SLICE              2
             27 BINARY_SUBSCR
             28 GET_ITER
        >>   29 FOR_ITER                16 (to 48)
             32 STORE_FAST               2 (i)

您如何解释f1 的出色表现?序列BINARY_SUBSCR、GET_ITER 是否以某种方式优化以避免数据复制?


下面是完整的测试代码供参考。我尝试将列表大小增加到 1_000_000 个项目,f1 仍然表现更好。使用array.array时也是如此。

a_ = list(range(1000))

def f1():
    acc = 0
    a = a_
    for i in a[100:900]:
        acc += i

    return acc

def f2():
    acc = 0
    a = a_
    for i in range(100, 900):
        acc += a[i]

    return acc

from dis import dis
from timeit import timeit

for f in f1,f2:
    dis(f)
    print(timeit(f, number=200000))
    print()

结果:

  6           0 LOAD_CONST               1 (0)
              3 STORE_FAST               0 (acc)

  7           6 LOAD_GLOBAL              0 (a_)
              9 STORE_FAST               1 (a)

  8          12 SETUP_LOOP              34 (to 49)
             15 LOAD_FAST                1 (a)
             18 LOAD_CONST               2 (100)
             21 LOAD_CONST               3 (900)
             24 BUILD_SLICE              2
             27 BINARY_SUBSCR
             28 GET_ITER
        >>   29 FOR_ITER                16 (to 48)
             32 STORE_FAST               2 (i)

  9          35 LOAD_FAST                0 (acc)
             38 LOAD_FAST                2 (i)
             41 INPLACE_ADD
             42 STORE_FAST               0 (acc)
             45 JUMP_ABSOLUTE           29
        >>   48 POP_BLOCK

 11     >>   49 LOAD_FAST                0 (acc)
             52 RETURN_VALUE
5.18372956989333

 14           0 LOAD_CONST               1 (0)
              3 STORE_FAST               0 (acc)

 15           6 LOAD_GLOBAL              0 (a_)
              9 STORE_FAST               1 (a)

 16          12 SETUP_LOOP              37 (to 52)
             15 LOAD_GLOBAL              1 (range)
             18 LOAD_CONST               2 (100)
             21 LOAD_CONST               3 (900)
             24 CALL_FUNCTION            2 (2 positional, 0 keyword pair)
             27 GET_ITER
        >>   28 FOR_ITER                20 (to 51)
             31 STORE_FAST               2 (i)

 17          34 LOAD_FAST                0 (acc)
             37 LOAD_FAST                1 (a)
             40 LOAD_FAST                2 (i)
             43 BINARY_SUBSCR
             44 INPLACE_ADD
             45 STORE_FAST               0 (acc)
             48 JUMP_ABSOLUTE           28
        >>   51 POP_BLOCK

 19     >>   52 LOAD_FAST                0 (acc)
             55 RETURN_VALUE
8.191981540992856

【问题讨论】:

    标签: python-3.x loops optimization cpython opcode


    【解决方案1】:

    没有特殊的优化来避免数据复制(这样做是不安全的;毕竟不允许在切片之后修改源序列来更改切片的内容)。索引访问只是慢(加载序列,索引,一遍又一遍地执行BINARY_SUBSCR,每次都必须解包索引),并且制作list的浅拷贝是(相对)快(它实际上只是一个类似memcpy 的操作以及一堆引用计数增量)。

    range 上的循环还涉及每次实际制作索引包装器;一旦您超出了小的int 缓存边界,它必须每次实际分配一个int 并填充它。相比之下,一旦构建切片,切片的直接迭代只是增加引用计数和返回现有对象,没有内存开销。

    因此,您的比较不是“复制list 的一大块”与“逐个访问每个元素”,而是“将list 的一大块作为单个操作复制”与“做很多事情”构建ints 的工作量,然后一遍又一遍地解压缩它们,以逐个元素地提取list 的一大块,每个提取时都有字节码解释器开销。

    重点是,for i in range(len(seq)): ... do stuff with seq[i] ... 被认为是一种反模式是有原因的,这不仅仅是因为它更丑,而是因为它更丑。它也慢了很多。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-12
      • 1970-01-01
      • 2017-01-28
      • 1970-01-01
      相关资源
      最近更新 更多