我一直使用n = int(n)
将float
转换为int
。
最近,我发现了另一种做法:
n = n // 1
哪种方法效率更高?为什么?
我一直使用n = int(n)
将float
转换为int
。
最近,我发现了另一种做法:
n = n // 1
哪种方法效率更高?为什么?
使用timeit
测试一下:
$ bin/python -mtimeit -n10000000 -s 'n = 1.345' 'int(n)'
10000000 loops, best of 3: 0.234 usec per loop
$ bin/python -mtimeit -n10000000 -s 'n = 1.345' 'n // 1'
10000000 loops, best of 3: 0.218 usec per loop
因此,整数除法只比普通除法快一点点。请注意,这些值非常接近,而且我不得不增加循环重复次数来消除计算机上的随机影响。即使进行了如此高的重复次数,您仍需要多次重复实验以查看数字变化了多少,并且在大多数情况下哪种方式更快。
这是合理的,因为int()
需要全局查找和函数调用(因此状态被推送和弹出):
>>> import dis
>>> def use_int(n):
... return int(n)
...
>>> def use_floordiv(n):
... return n // 1
...
>>> dis.dis(use_int)
2 0 LOAD_GLOBAL 0 (int)
3 LOAD_FAST 0 (n)
6 CALL_FUNCTION 1
9 RETURN_VALUE
>>> dis.dis(use_floordiv)
2 0 LOAD_FAST 0 (n)
3 LOAD_CONST 1 (1)
6 BINARY_FLOOR_DIVIDE
7 RETURN_VALUE
LOAD_GLOBAL
和CALL_FUNCTION
指令比LOAD_CONST
和BINARY_FLOOR_DIVIDE
指令慢; LOAD_CONST
是简单的数组查找,LOAD_GLOBAL
需要进行字典查找。
将int()
绑定到本地名称可能会产生微小的差异,再次给予它优势(因为它需要比// 1
整数除法做更少的工作):
$ bin/python -mtimeit -n10000000 -s 'n = 1.345' 'int(n)'
10000000 loops, best of 3: 0.233 usec per loop
$ bin/python -mtimeit -n10000000 -s 'n = 1.345; int_=int' 'int_(n)'
10000000 loops, best of 3: 0.195 usec per loop
$ bin/python -mtimeit -n10000000 -s 'n = 1.345' 'n // 1'
10000000 loops, best of 3: 0.225 usec per loop
需要再次运行10百万次以始终看到差异。
尽管如此,int(n)
更加明确,除非您在时间关键循环中执行此操作,否则在可读性方面,int(n)
胜过n // 1
。 时间差异太小,无法使人们花费认知成本去解释// 1
在这里的作用。
n = some_float
而不是 n = some_int
的情况下计时,对吗? - Inbar Rosea//1
会比函数调用多做一点工作。 - Kosint(n)
以提高可读性。 - Inbar Rose实际上,int
似乎比除法更快。慢的部分是在全局范围内查找函数。
如果我们避免这种情况,以下是我的数字:
$ python -mtimeit -s 'i=int; a=123.456' 'i(a)'
10000000 loops, best of 3: 0.122 usec per loop
$ python -mtimeit -s 'i=int; a=123.456' 'a//1'
10000000 loops, best of 3: 0.145 usec per loop
i=int
? - Inbar Roseint(n)
以提高可读性。 - Inbar Rose使用float.__trunc__()
比builtins.int()
快30%
@MartijnPieters的绑定builtins.int
技巧确实很有趣,它让我想起了一个优化轶事。然而,调用builtins.int
并不是最高效的方法。
让我们来看一下这个例子:
python -m timeit -n10000000 -s "n = 1.345" "int(n)"
10000000 loops, best of 5: 48.5 nsec per loop
python -m timeit -n10000000 -s "n = 1.345" "n.__trunc__()"
10000000 loops, best of 5: 33.1 nsec per loop
这是一个30%的增益!发生了什么?
原来所有builtins.int
所做的就是调用以下方法链:
1.345.__int__
,则返回1.345.__int__()
,否则:1.345.__index__
,则返回1.345.__index__()
,否则:1.345.__trunc__
,则返回1.345.__trunc__()
1.345.__int__
未定义1 - 1.345.__index__
也未定义。因此,直接调用1.345.__trunc__()
可以跳过所有不必要的方法调用 - 这相对昂贵。
float.__trunc__
本质上只是一个实例方法,我们可以将 1.345
作为 self
参数传递。python -m timeit -n10000000 -s "n = 1.345; f=int" "f(n)"
10000000 loops, best of 5: 43 nsec per loop
python -m timeit -n10000000 -s "n = 1.345; f=float.__trunc__" "f(n)"
10000000 loops, best of 5: 27.4 nsec per loop
两种方法的表现都如预期所料2,并且它们保持大致相同的比例!
1 我对此并不完全确定 - 如果有人知道请纠正我。
2 这让我感到惊讶,因为我原以为 float.__trunc__
在实例创建时绑定到了 1.345
。如果有人能够友善地解释一下这个问题那就太好了。
还有这个方法builtins.float.__floor__
,文档中没有提到 - 它比builtins.int
快但比buitlins.float.__trunc__
慢。
python -m timeit -n10000000 -s "n = 1.345; f=float.__floor__" "f(n)"
10000000 loops, best of 5: 32.4 nsec per loop
//
,因为它返回的是 float
而不是 int
。 - Leonardus Chenn=n//1
与以下内容完全相同:
n.__floordiv__(1)
n.__divmod__(1)[0]
这两个函数返回浮点数而不是整数。在CPython的__divmod__
函数内,分母和分子必须从PyObject转换为double。因此,在这种情况下,使用floor
函数比使用//
运算符更快,因为只需要进行一次转换。
from cmath import floor
n=floor(n)
只是一个统计测试,让我们开心一下 - 将timeit测试更改为您喜欢的任何内容:
import timeit
from scipy import mean, std, stats, sqrt
# Parameters:
reps = 100000
dups = 50
signif = 0.01
timeit_setup1 = 'i=int; a=123.456'
timeit_test1 = 'i(a)'
timeit_setup2 = 'i=int; a=123.456'
timeit_test2 = 'a//1'
#Some vars
t1_data = []
t2_data = []
frmt = '{:.3f}'
testformat = '{:<'+ str(max([len(timeit_test1), len(timeit_test2)]))+ '}'
def reportdata(mylist):
string = 'mean = ' + frmt.format(mean(mylist)) + ' seconds, st.dev. = ' + \
frmt.format(std(mylist))
return string
for i in range(dups):
t1_data.append(timeit.timeit(timeit_test1, setup = timeit_setup1,
number = reps))
t2_data.append(timeit.timeit(timeit_test2, setup = timeit_setup2,
number = reps))
print testformat.format(timeit_test1) + ':', reportdata(t1_data)
print testformat.format(timeit_test2) + ':', reportdata(t2_data)
ttest = stats.ttest_ind(t1_data, t2_data)
print 't-test: the t value is ' + frmt.format(float(ttest[0])) + \
' and the p-value is ' + frmt.format(float(ttest[1]))
isit = ''
if float(ttest[1]) > signif:
isit = "not "
print 'The difference of ' + \
'{:.2%}'.format(abs((mean(t1_data)-mean(t2_data))/mean(t1_data))) + \
' +/- ' + \
'{:.2%}'.format(3*sqrt((std(t1_data)**2 + std(t2_data)**2)/dups)) + \
' is ' + isit + 'significative.'
pandas
和 ipython notebook
:-) - Kospandas
是什么,因为这里每个人都会问关于pandas的问题!它在待办列表上 :) 谢谢! - Roberto
//
始终表示整数除法,而/
则根据上下文(在Python 2中)或实际除法(在Python 3或在2中使用from __future__ import division
)进行计算。 - Kos//
仍然返回一个浮点数,只不过是向下取整。 - Wooble//
总是执行地板除法,但其返回类型取决于输入类型。例如,3.0 // 2
返回1.0
。 - user395760