Numpy/Python中基本数学运算的速度:为什么整数除法最慢?

2024-05-14 16:20:49 发布

您现在位置:Python中文网/ 问答频道 /正文

EDIT2:正如@ShadowRanger指出的,这是一种Numpy现象,而不是Python。但是,当在Python中使用列表理解进行计算时(因此x+y变成{}),那么所有的算术运算仍然需要同样长的时间(尽管比Numpy长100倍以上)。然而,当我在实际模拟中使用整数除法时,它们运行得更快。 因此,主要问题仍然存在:即使在Python中,为什么这些测试表明整数除法并不比正则除法快?

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

你知道为什么会这样吗?为什么整数除法不快?在


Tags: numpynumber列表stringnpsetup整数算术
1条回答
网友
1楼 · 发布于 2024-05-14 16:20:49

简而言之:浮点除法在硬件级别比整数除法便宜。而且至少在一种常见的体系结构上,浮点除法可以矢量化,而整数除法则不能,因此代码中最昂贵的运算必须执行更多次,整数运算的每次运算成本更高,而浮点运算的运算次数更少,每次运算的成本更低。在

{1}可用时,{回答:^ 1}使用。它只为整数提供矢量化乘法(通过^{}系列指令),但同时为浮点提供乘法(^{} family)和除法(^{} family)。在

当您使用/进行真正的除法时,结果类型是float64,而不是{},numpy可以通过单个压缩加载和转换来执行操作(使用the ^{} family of operations,然后是压缩除法,然后是压缩移回内存(^{} family)。在

在配备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 tableIDIV的开销为24-90个周期,延迟为42-95;所有512位寄存器的VDIVPD的开销为16个周期,延迟为24个周期。VDIVPD不仅仅是8倍的工作,它(最多)在IDIV所需周期的三分之二(我不知道为什么IDIV的循环计数范围如此之大,但是VDIVPD甚至比{}的最佳数字还要好)。对于普通AVX操作(每个VDIVPD只有四个除法),每个操作的周期被减半(到八个),而每个指令中两个除法的普通DIVPD只有四个周期,所以除法本身的速度基本相同,无论您使用的是SSE2、AVX还是AVX512指令(AVX512只是在延迟方面节省了一点时间,以及装载/存储开销)。即使从未使用过矢量化指令,普通FDIV也只是一个4-5个周期的指令(二进制浮点除法比整数除法更容易,去算数),所以你可以看到浮点数学做得很好。在

要点是,在硬件级别上,分割大量64位浮点值比分割大量64位整数值要便宜,因此使用/进行真正的除法本质上比使用//的地板除法快。在

在我自己的机器上(我已经验证了它只使用了基线SSE2DIVPD,因此它只对每个指令执行两次除法),我尝试复制您的结果,并且我的计时有点不一致。每次操作需要485μs的真除法,而楼层除法好吧,每次手术1.05毫秒;楼层划分只稍微长了2倍多,对你来说几乎是3倍。您的numpy的副本是在支持AVX或AVX512的情况下编译的,因此,您在truedivision中压缩了更多的性能。在


至于为什么非numpyPythonint层划分比真正的除法耗时更长,这是一个类似的原因,但有几个复杂的因素:

  1. (帮助int / int,伤害int // int)来自numpyapply;IDIV的相同硬件指令开销问题比FDIV慢。在
  2. 解释程序执行总性能差异的时间越大(隐藏的开销越大)
  3. (通常增加整数运算的成本)整数除法在Python int上甚至更为昂贵;在CPython上,int被实现为15位或30位肢体的数组,其中一个ssize_t同时定义了符号性和肢体数。CPython的float只是一个普通C double的普通对象包装器,没有任何特殊功能。在
  4. (增加int // int的成本)Python保证floor除法,但是C只提供截断整数除法,因此即使在小的int的快速路径中,Python也必须检查不匹配的符号并调整操作,以确保结果是floor,而不是简单的截断。在
  5. (增加int / int操作的开销,特别是对于大输入)CPython的int / int操作不仅将两个操作数都转换为float(Cdouble)并执行浮点除法。当操作数足够小时,它会尝试这样做,但如果操作数太大,它将使用复杂的回退算法来实现最佳的正确性。在
  6. (减少重复的int / int的成本,如果结果是短时间丢弃的)由于Pythonfloat是固定大小的,所以它们实现了一个小的性能优化,为它们使用一个空闲列表;当您重复创建和删除{}时,新创建的不需要到内存分配器,它们只是从空闲列表中提取,当引用计数降到零时释放到空闲列表。CPython的int是可变长度的,不使用空闲列表。在

总的来说,这都稍微有利于int / int(至少对于小的ints;大的int情况会变得更复杂,但是对于{}情况可能更糟,因为基于数组的除法算法非常复杂/昂贵),因此看到Python内置类型的类似行为并不意外。在

相关问题 更多 >

    热门问题