【问题标题】:Understanding why theses opcodes from different codes are the same理解为什么来自不同代码的这些操作码是相同的
【发布时间】:2018-08-17 09:04:46
【问题描述】:

我想深入理解为什么下面这两个生成的操作码是相同的(除了加载/存储的值)。

特别是如何将这个 'BINARY_MULTIPLY' 用于 str 和 int ? C (CPython) 是否在后台进行类型检查并应用正确的函数,无论值是字符串还是整数?

我们可以说这种机制与鸭子类型有关吗?

>>> def tata():
...     a = 1
...     b = 1
...     c = a * b
... 
>>> dis.dis(tata)
  2           0 LOAD_CONST               1 (1)
              3 STORE_FAST               0 (a)

  3           6 LOAD_CONST               1 (1)
              9 STORE_FAST               1 (b)

  4          12 LOAD_FAST                0 (a)
             15 LOAD_FAST                1 (b)
             18 BINARY_MULTIPLY     
             19 STORE_FAST               2 (c)
             22 LOAD_CONST               0 (None)
             25 RETURN_VALUE      

>>> def toto():
...     a = "1"
...     b = "1"
...     c = a * b
... 
>>> dis.dis(toto)
  2           0 LOAD_CONST               1 ('1')
              3 STORE_FAST               0 (a)

  3           6 LOAD_CONST               1 ('1')
              9 STORE_FAST               1 (b)

  4          12 LOAD_FAST                0 (a)
             15 LOAD_FAST                1 (b)
             18 BINARY_MULTIPLY     
             19 STORE_FAST               2 (c)
             22 LOAD_CONST               0 (None)
             25 RETURN_VALUE      

【问题讨论】:

    标签: python python-3.x cpython opcode duck-typing


    【解决方案1】:

    Python 字节码是非常高级的,并且考虑到该语言极其动态的语义,它不能做太多不同的事情。 BINARY_MULTIPLY 在源代码中指定 * 时会发出,无论操作数的类型如何。具体做什么是在运行时确定的。

    事后看来这很明显:在 Python 一般中,类型仅在运行时才知道,并且考虑到它允许的灵活性(例如通过猴子补丁),您可以仅在非常执行的时刻。不出所料,这就是 CPython 如此缓慢的原因之一。

    在特定情况下,例如您的示例中显示的情况,编译器可以执行类型推断并在编译时执行计算,或者至少发出一些(想象的)更具体的操作码。不幸的是,这会使解释器复杂化并且在一般情况下不会有太大帮助,因为通常您的计算涉及来自外部的参数,例如:

    def square(x):
        return x*x
    

    这里的x 可以是任何类型,因此编译时智能没有用处。

    def times5(x):
        return x * 5
    

    即使知道这里的 5,times5 也会根据 x 的类型做完全不同的事情("a" -> "aaaaa"; 2 -> 10; 4.5 -> 22.5; 一些自定义类类型 -> 它取决于运算符重载,仅在运行时知道)。

    您可以采用 asm.js 的方式并找到提供类型提示的间接方式,但 Python (PyPy) 的高性能实现只是使用跟踪 JIT 方法自行推断常用的参数类型 (运行代码一段时间后)并为观察到的情况生成优化的机器代码。

    【讨论】:

      【解决方案2】:

      第一个问题的答案是肯定的。 Python(CPython 实现)在内部检查操作数的类型并应用正确的函数,无论值是字符串还是整数。这种行为的原因,虽然与实现有关,但通常是因为它更优化(C 显然比 Python 快)并且在某些意义上更整洁地在确定操作后推迟类型检查。造成这种情况的一个原因可能是因为 1) 操作数的数量多于操作。 2) 类型检查(至少在 CPython 实现中)可以在内部进程中轻松正确地完成。

      第二个问题的答案是否定的,因为我们不会根据代码/方程式/等的其他属性来确定这些对象的类型。我们只是以较低的优先级来做。

      还请注意,关于方程式中字节码顺序的另一个重要点是,执行字节码的顺序与由相应解析器创建的最终解析树有关。考虑以下示例:

      In [4]: dis.dis("a, b, c, d = 4, 5, 7, 0; a + b * c - d")
        1           0 LOAD_CONST               5 ((4, 5, 7, 0))
                    3 UNPACK_SEQUENCE          4
                    6 STORE_NAME               0 (a)
                    9 STORE_NAME               1 (b)
                   12 STORE_NAME               2 (c)
                   15 STORE_NAME               3 (d)
                   18 LOAD_NAME                0 (a)
                   21 LOAD_NAME                1 (b)
                   24 LOAD_NAME                2 (c)
                   27 BINARY_MULTIPLY
                   28 BINARY_ADD
                   29 LOAD_NAME                3 (d)
                   32 BINARY_SUBTRACT
                   33 POP_TOP
                   34 LOAD_CONST               4 (None)
                   37 RETURN_VALUE
      

      【讨论】:

        【解决方案3】:

        这确实与鸭子类型有关,后者将类型检查或方法有效性/存在性检查延迟到调用之前。 python BINARY_MULTIPLY 与 python 表达式 lambda x, y: x * y 完全一样。只要支持__mul__ 协议,它就不会与任何类型显式相关。

        如果你想知道这在 C 中是如何工作的,python 将操作码委托给 PyNumber_Multiply,如果可能,它会从 __mul__ 槽中获取方法(或者如果对象是 @987654325,它会退回到重复@),其中此方法是特定于类型的。换句话说,int、float、str、list 的 __mul__s 可能不同。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2015-04-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-02-26
          • 1970-01-01
          相关资源
          最近更新 更多