为什么在一个数字上调用float()比在Python中添加0.0要慢?

use*_*234 18 python floating-point integer casting

将整数转换为float的原因是什么比在Python中向该int添加0.0要慢?

import timeit


def add_simple():
    for i in range(1000):
        a = 1 + 0.0


def cast_simple():
    for i in range(1000):
        a = float(1)


def add_total():
    total = 0
    for i in range(1000):
        total += 1 + 0.0


def cast_total():
    total = 0
    for i in range(1000):
        total += float(1)


print "Add simple timing: %s" % timeit.timeit(add_simple, number=1)
print "Cast simple timing: %s" % timeit.timeit(cast_simple, number=1)
print "Add total timing: %s" % timeit.timeit(add_total, number=1)
print "Cast total timing: %s" % timeit.timeit(cast_total, number=1)
Run Code Online (Sandbox Code Playgroud)

其输出是:

添加简单的时间:0.0001220703125

投简单时间:0.000469923019409

添加总计时:0.000164985656738

总计时间:0.00040078163147

Way*_*ner 16

如果您使用该dis模块,您可以开始了解原因:

In [11]: dis.dis(add_simple)
  2           0 SETUP_LOOP              26 (to 29)
              3 LOAD_GLOBAL              0 (range)
              6 LOAD_CONST               1 (1000)
              9 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             12 GET_ITER
        >>   13 FOR_ITER                12 (to 28)
             16 STORE_FAST               0 (i)

  3          19 LOAD_CONST               4 (1.0)
             22 STORE_FAST               1 (a)
             25 JUMP_ABSOLUTE           13
        >>   28 POP_BLOCK
        >>   29 LOAD_CONST               0 (None)
             32 RETURN_VALUE

In [12]: dis.dis(cast_simple)
  2           0 SETUP_LOOP              32 (to 35)
              3 LOAD_GLOBAL              0 (range)
              6 LOAD_CONST               1 (1000)
              9 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             12 GET_ITER
        >>   13 FOR_ITER                18 (to 34)
             16 STORE_FAST               0 (i)

  3          19 LOAD_GLOBAL              1 (float)
             22 LOAD_CONST               2 (1)
             25 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             28 STORE_FAST               1 (a)
             31 JUMP_ABSOLUTE           13
        >>   34 POP_BLOCK
        >>   35 LOAD_CONST               0 (None)
             38 RETURN_VALUE
Run Code Online (Sandbox Code Playgroud)

请注意 CALL_FUNCTION

Python中的函数调用(相对)很慢.和.查找一样.因为强制转换float需要函数调用 - 这就是它变慢的原因.

  • 根据你的问题 - 这些操作之间的差异介于"0.0001220703125"和"0.000469923019409"之间.这意味着无论您选择哪个选项,您都会看到非常小的*成本.如果你碰巧描述你的代码并且看到你称为浮动(数字)`十亿次,并且你试图让它更快,因为*直接*意味着更多的钱,那么它可能值得使用` + 0.0`,但是否则你试图记住为什么你做`+ 0.0`的可读性问题比调用`float()`函数要贵得多. (16认同)
  • 这只是故事的一部分.导致性能差异的主要因素有3个:常量折叠,内置查找和函数调用开销.这个答案只提到函数调用开销. (2认同)

use*_*ica 11

如果你看一下字节码add_simple:

>>> dis.dis(add_simple)
  2           0 SETUP_LOOP              26 (to 29)
              3 LOAD_GLOBAL              0 (range)
              6 LOAD_CONST               1 (1000)
              9 CALL_FUNCTION            1
             12 GET_ITER
        >>   13 FOR_ITER                12 (to 28)
             16 STORE_FAST               0 (i)

  3          19 LOAD_CONST               4 (1.0)
             22 STORE_FAST               1 (a)
             25 JUMP_ABSOLUTE           13
        >>   28 POP_BLOCK
        >>   29 LOAD_CONST               0 (None)
             32 RETURN_VALUE
Run Code Online (Sandbox Code Playgroud)

你会发现0.0那里实际上并不存在.它只是加载常量1.0并将其存储到a.Python在编译时计算了结果,所以你实际上并没有对添加进行计时.

如果你使用一个变量1,那么Python的原始窥孔优化器不能在编译时添加,添加0.0仍然有一个导致:

>>> timeit.timeit('float(a)', 'a=1')
0.22538208961486816
>>> timeit.timeit('a+0.0', 'a=1')
0.13347005844116211
Run Code Online (Sandbox Code Playgroud)

调用float需要两个dict查找来确定是什么float,一个在模块的全局命名空间中,另一个在内置命令中.它还具有Python函数调用开销,这比C函数调用更昂贵.

添加0.0只需要索引到函数的代码对象中co_consts以加载常量0.0,然后调用和类型的C级nb_add函数来执行添加.总体而言,这是一个较低的开销量.intfloat

  • +1,我知道常量折叠会删除添加,但是如果强制它发生的话,并没有停止考虑添加仍然比`float`调用更快. (2认同)

Ale*_*amo 7

如果您使用的是Python 3或最新版本的Python 2(2.5或更高版本),它会在字节码生成时进行常量折叠.这意味着在执行代码之前1 + 0.0替换1.0它.