在Python2.*分区中,哪个分区的开销更大



假设我想在Python 2.*中划分两个被视为整数的变量(主要是6和7)。例如:

a, b = 3, 2
print a/b
# Prints "1"

现在,据我所知,至少有两种(非冗余)方法可以使该除法成为正常的浮点除法(不运行from __future__ import division)。它们是:

print a*1.0/b      # Of course you could multiply b by 1.0 also 

print float(a)/b   # Here you could also have cast b as a float

这些方法中的一种(在速度上)比另一种有优势吗?一个的开销比另一个多吗?

>>> timeit.timeit(stmt="a*1.0/b",setup="a,b=3,2",number=100)
4.669614510532938e-05
>>> timeit.timeit(stmt="float(a)/b",setup="a,b=3,2",number=100)
7.18402232422477e-05

从上面可以看出,简单地使用a*1.0/b要比使用float(a)快得多。这是因为在Python中调用函数的成本非常高。话虽如此,你可以做一些类似的事情:

a,b=float(3),2
print a/b

你会有一个基准:

>>> timeit.timeit(stmt="a/b",setup="a,b=float(3),2",number=100)
2.5144078108496615e-05

这是因为您只调用float()一次,而那是在分配a时。这反过来又不需要考虑1.0*a,从而得到更快的结果。

使用dis模块进一步分解,您可以在一个循环中看到对此的实际调用:

分割期间浮动

def floatmethod():
a,b=3,2
while True:
print float(a)/b

分区期间浮动显示结果

dis.dis(floatmethod)
2           0 LOAD_CONST               3 ((3, 2))
3 UNPACK_SEQUENCE          2
6 STORE_FAST               0 (a)
9 STORE_FAST               1 (b)
3          12 SETUP_LOOP              25 (to 40)
>>   15 LOAD_GLOBAL              0 (True)
18 POP_JUMP_IF_FALSE       39
4          21 LOAD_GLOBAL              1 (float)
24 LOAD_FAST                0 (a)
27 CALL_FUNCTION            1
30 LOAD_FAST                1 (b)
33 BINARY_DIVIDE       
34 PRINT_ITEM          
35 PRINT_NEWLINE       
36 JUMP_ABSOLUTE           15
>>   39 POP_BLOCK           
>>   40 LOAD_CONST               0 (None)
43 RETURN_VALUE        

速度下降的原因

这个方法慢得多的原因是,它必须首先获取LOAD_GLOBAL: float,然后获取a的值(LOAD_FAST: a),然后调用float(a)(CALL_FUNCTION)。然后,它最终执行除法(BINARY_DIVIDE),所有这些都在循环中一遍又一遍地完成。

分配时浮动

def initfloatmethod():
a,b=float(3),2
while True:
print a/b

分配结果浮动

dis.dis(initfloatmethod)
2           0 LOAD_GLOBAL              0 (float)
3 LOAD_CONST               1 (3)
6 CALL_FUNCTION            1
9 LOAD_CONST               2 (2)
12 ROT_TWO             
13 STORE_FAST               0 (a)
16 STORE_FAST               1 (b)
3          19 SETUP_LOOP              19 (to 41)
>>   22 LOAD_GLOBAL              1 (True)
25 POP_JUMP_IF_FALSE       40
4          28 LOAD_FAST                0 (a)
31 LOAD_FAST                1 (b)
34 BINARY_DIVIDE       
35 PRINT_ITEM          
36 PRINT_NEWLINE       
37 JUMP_ABSOLUTE           22
>>   40 POP_BLOCK           
>>   41 LOAD_CONST               0 (None)
44 RETURN_VALUE      

速度增加的原因

您可以看到,在执行除法的行上,它不再需要调用float函数,从而允许立即执行除法。它只是调用LOAD_GLOBAL: float并调用CALL_FUNCTION一次,这是在赋值时,而不是在循环中。这意味着它可以直接跳到BINARY_DIVIDE调用。

用于此基准的统计数据:

Python 2.7.3 (default, Apr 10 2012, 23:31:26) [MSC v.1500 32 bit (Intel)] on win32

使用Python 2.7.3:

In [7]: %timeit a*1.0/b
10000000 loops, best of 3: 165 ns per loop
In [8]: %timeit float(a)/b
1000000 loops, best of 3: 228 ns per loop

因此,第一种方法看起来稍微快一点。

也就是说,在进行微观优化之前,对代码进行概要分析总是值得的。

最新更新