EDIT2:正如@ShadowRanger指出的,这是一种Numpy现象,而不是Python。但是,当在Python中使用列表理解进行计算时(因此x+y
变成{
EDIT1:版本:Python 3.5.5,Numpy 1.15.0。在
似乎在Python的Numpy中,整数除法比常规除法(整数的除法)更昂贵,这是违反直觉的。测试时,我得到这样的结论:
setup_string = 'import numpy as np;\
N=int(1e5);\
x=np.arange(1,N+1, dtype=int);\
y=np.arange(N, dtype=int);'
加法(+)~0.1s
^{pr2}$减法(-)~0.1s
timeit("x-y", setup=setup_string, number=int(1e3))
0.09425603999989107
乘法(*)~0.1s
timeit("x*y", setup=setup_string, number=int(1e3))
0.09888673899695277
除法(/)~0.35s
timeit("x/y", setup=setup_string, number=int(1e3))
0.3574664070038125
整数除法(//)~1s(!)
timeit("x//y", setup=setup_string, number=int(1e3))
1.006298642983893
你知道为什么会这样吗?为什么整数除法不快?在
简而言之:浮点除法在硬件级别比整数除法便宜。而且至少在一种常见的体系结构上,浮点除法可以矢量化,而整数除法则不能,因此代码中最昂贵的运算必须执行更多次,整数运算的每次运算成本更高,而浮点运算的运算次数更少,每次运算的成本更低。在
{1}可用时,{回答:^ 1}使用。它只为整数提供矢量化乘法(通过^{} 系列指令),但同时为浮点提供乘法(^{} family )和除法(^{} family )。在
当您使用},} family of operations ,然后是压缩除法,然后是压缩移回内存(^{} family )。在
/
进行真正的除法时,结果类型是float64
,而不是{numpy
可以通过单个压缩加载和转换来执行操作(使用the ^{在配备AVX512的最现代x86-64芯片上(Xeon Phi x200+和Skylake-X及更高版本,后者自2017年底开始在桌面市场上销售),每一个这样的矢量化指令可以同时执行8个操作(2011年后的旧体系结构可以用AVX执行4个操作,而在此之前,您可以使用SSE2执行两个操作)。对于
/
,这意味着您只需要为每八个要执行的除法发出两个VCVTQQ2PD
(每个源数组一个)、一个VDIVPD
和一个VMOVAPD
(所有EVEX
前缀为512位操作)。相反,对于//
要执行相同的八个除法,它需要从内存中发出八个MOV
(加载左数组操作数)、八个CQO
(将左数组操作数符号扩展到128位值IDIV
所需)、八个IDIV
(至少从右侧数组加载),八个MOV
回到记忆中。在我不知道
numpy
是否充分利用了这一点(我自己的副本显然是为所有x86-64机器提供的SSE2基线编译的,所以它一次只做两个除法,而不是八个除法),但有可能的是,根本没有办法将等价的整数运算矢量化。在虽然整数大小写的单个指令通常要便宜一点,但它们基本上总是比组合的等效指令更昂贵。对于整数除法,单个操作的实际上比压缩操作的浮点除法更糟糕;每Agner Fog's Skylake-X table每}的最佳数字还要好)。对于普通AVX操作(每个
IDIV
的开销为24-90个周期,延迟为42-95;所有512位寄存器的VDIVPD
的开销为16个周期,延迟为24个周期。VDIVPD
不仅仅是8倍的工作,它(最多)在IDIV
所需周期的三分之二(我不知道为什么IDIV
的循环计数范围如此之大,但是VDIVPD
甚至比{VDIVPD
只有四个除法),每个操作的周期被减半(到八个),而每个指令中两个除法的普通DIVPD
只有四个周期,所以除法本身的速度基本相同,无论您使用的是SSE2、AVX还是AVX512指令(AVX512只是在延迟方面节省了一点时间,以及装载/存储开销)。即使从未使用过矢量化指令,普通FDIV
也只是一个4-5个周期的指令(二进制浮点除法比整数除法更容易,去算数),所以你可以看到浮点数学做得很好。在要点是,在硬件级别上,分割大量64位浮点值比分割大量64位整数值要便宜,因此使用
/
进行真正的除法本质上比使用//
的地板除法快。在在我自己的机器上(我已经验证了它只使用了基线SSE2
DIVPD
,因此它只对每个指令执行两次除法),我尝试复制您的结果,并且我的计时有点不一致。每次操作需要485μs的真除法,而楼层除法好吧,每次手术1.05毫秒;楼层划分只稍微长了2倍多,对你来说几乎是3倍。您的numpy
的副本是在支持AVX或AVX512的情况下编译的,因此,您在truedivision中压缩了更多的性能。在至于为什么非
numpy
Pythonint
层划分比真正的除法耗时更长,这是一个类似的原因,但有几个复杂的因素:int / int
,伤害int // int
)来自numpy
apply;IDIV
的相同硬件指令开销问题比FDIV
慢。在int
上甚至更为昂贵;在CPython上,int
被实现为15位或30位肢体的数组,其中一个ssize_t
同时定义了符号性和肢体数。CPython的float
只是一个普通Cdouble
的普通对象包装器,没有任何特殊功能。在int // int
的成本)Python保证floor除法,但是C只提供截断整数除法,因此即使在小的int
的快速路径中,Python也必须检查不匹配的符号并调整操作,以确保结果是floor,而不是简单的截断。在int / int
操作的开销,特别是对于大输入)CPython的int / int
操作不仅将两个操作数都转换为float
(Cdouble
)并执行浮点除法。当操作数足够小时,它会尝试这样做,但如果操作数太大,它将使用复杂的回退算法来实现最佳的正确性。在int / int
的成本,如果结果是短时间丢弃的)由于Pythonfloat
是固定大小的,所以它们实现了一个小的性能优化,为它们使用一个空闲列表;当您重复创建和删除{int
是可变长度的,不使用空闲列表。在总的来说,这都稍微有利于}情况可能更糟,因为基于数组的除法算法非常复杂/昂贵),因此看到Python内置类型的类似行为并不意外。在
int / int
(至少对于小的int
s;大的int
情况会变得更复杂,但是对于{相关问题 更多 >
编程相关推荐