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

Posted

技术标签:

【中文标题】Numpy/Python 中基本数学运算的速度:为啥整数除法最慢?【英文标题】:speed of elementary mathematical operations in Numpy/Python: why is integer division slowest?Numpy/Python 中基本数学运算的速度:为什么整数除法最慢? 【发布时间】:2019-08-02 11:15:45 【问题描述】:

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

EDIT1:版本:Python 3.5.5、Numpy 1.15.0。

似乎在 PythonNumpy 中,整数除法更多比常规除法(整数)昂贵,这是违反直觉的。测试时,我得到了这个:

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

timeit("x+y", setup=setup_string, number=int(1e3))
0.09872294100932777

减法 (-) ~ 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

任何想法为什么会发生这种情况?为什么整数除法不快?

【问题讨论】:

这不是关于 Python 的 整数除法,而是关于 numpy 的。一种重要的区别。 你能指定你使用的是哪个版本的Python和numpy吗? Python 3.5.5, Numpy 1.15.0 另外,@ShadowRanger 你可能会在这里找到一些东西。在我的模拟中,我使用列表推导(因为我需要舍入整数除法)然后转换回 numpy 数组,所以 Python 和整数除法的 Numpy 实现之间可能存在(很大)差异。我会尽快测试。 @ShadowRanger 即使在 Python 中执行此操作,整数除法似乎也不会更快(请参阅更新的问题) 另外,setup_string 不是一个有效的字符串。 【参考方案1】:

简答:在硬件层面,浮点除法比整数除法更便宜。并且在至少一种通用架构上,浮点除法可以向量化,而整数除法不能,因此代码中最昂贵的运算必须执行更多次,整数运算的每次运算成本更高,而浮点运算执行的次数更少次,每次操作的成本更低。

长答案:numpy 在可用时使用矢量化数学,the x86-64 architecture (which I'm guessing you're using) doesn't provide a SIMD instruction for integer division。它只为整数提供向量化乘法(通过PMULUDQ 系列指令),但为浮点提供乘法(MULPD family)和除法(DIVPD family)。

当您使用/ 进行真正的除法时,结果类型是float64,而不是int64,并且numpy 可以通过单个打包加载和转换来执行操作(使用the VCVTQQ2PD family of operations,后跟一个打包除法,然后打包回内存 (MOVAPD family)。

在采用 AVX512 的最现代 x86-64 芯片(至强融核 x200+ 和 Skylake-X 及更高版本,后者自 2017 年底起可用于桌面市场)上,每条此类矢量化指令可以同时执行八项操作(旧架构从 2011 年后可以用 AVX 做四个,在此之前你可以用 SSE2 做两个)。对于/,这意味着您只需为每八个分区发出两个VCVTQQ2PDs(每个源阵列一个),一个VDIVPD 和一个VMOVAPD(所有EVEX 前缀为512 位操作)到被执行。相比之下,// 要执行相同的八次除法,它需要从内存发出八个MOVs(加载左侧数组操作数),八次CQOs(将左侧数组操作数符号扩展至 128 位)值IDIV 需要),八个IDIVs(至少为您从右侧数组加载)和八个MOVs 回到内存。

我不知道numpy 是否充分利用了这一点(我自己的副本显然是针对所有 x86-64 机器提供的 SSE2 基线编译的,所以它一次只进行两个分区,而不是八个分区),但它是可能,但根本无法对等价的整数运算进行矢量化。

虽然整数情况下的单独指令通常便宜一些,但它们基本上总是比组合的等效指令更昂贵。而对于整数除法,单个操作实际上比压缩操作的浮点除法更糟糕;每Agner Fog's Skylake-X table,每IDIV的成本为24-90个周期,延迟为42-95; VDIVPD 与所有 512 位寄存器的成本为 16 个周期,延迟为 24 个周期。 VDIVPD 不只是做八倍的工作,它(最多)在 IDIV 所需的三分之二的周期内完成(我不知道为什么 IDIV 的周期计数范围如此之大,但是VDIVPD 甚至超过了 IDIV 的最佳数字)。对于普通的 AVX 操作(每个 VDIVPD 只有四个除法),每个操作的周期减少了一半(到八个),而在每条指令两个除法上的普通 DIVPD 只有四个周期,所以除法本身基本相同无论您使用 SSE2、AVX 还是 AVX512 指令(AVX512 只是为您节省一点延迟和加载/存储开销),都可以提高速度。即使从未使用过向量化指令,普通的FDIV 也只是一个 4-5 个周期的指令(二进制浮点除法通常比整数除法更容易,看图),所以你会期望看到浮点数学不错。

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

在我自己的机器上(我已经验证它仅使用基线 SSE2 DIVPD,因此它每条指令只执行两个除法),我尝试复制您的结果,并且我的时间差异较小。真正的除法,每次操作耗时 485 μs,而地板除法每次操作耗时 1.05 ms;地板分割只长了 2 倍多一点,对你来说几乎长了 3 倍。猜测一下,您的 numpy 副本是在支持 AVX 或 AVX512 的情况下编译的,因此您从真正的除法中获得了更多的性能。


至于为什么非numpyPythonint地板除法比真除法时间长,原因类似,但有一些复杂因素:

    (帮助int / int,伤害int // int)同样适用于numpy 的硬件指令开销问题; IDIVFDIV 慢。 (隐藏性能差异)执行单个除法的解释器开销占总时间的更大百分比(减少相对性能差异,因为更多时间花在开销上) (通常会增加整数运算的成本)在 Python ints 上,整数除法的成本甚至更多;在 CPython 上,int 实现为 15 位或 30 位肢体数组,ssize_t 定义了签名和肢体数量。 CPython 的 float 只是普通 C double 的普通对象包装器,没有特殊功能。 (增加int // int的成本)Python保证取整除法,但C只提供截断整数除法,所以即使在小ints的快速路径中,Python也必须检查不匹配的符号并将操作调整为确保结果是地板,而不是简单的截断。 (增加int / int 运算的成本,专门针对大输入)CPython 的int / int 运算不仅将两个操作数都转换为float(C double)并执行浮点除法。当操作数足够小时,它会尝试这样做,但如果它们太大,它会使用复杂的后备算法来实现最佳的正确性。 (如果结果在短时间内被丢弃,则减少重复int / int 的成本)由于 Python floats 是固定大小的,因此他们实现了较小的性能优化以使用空闲列表;当您反复创建和删除floats 时,新创建的不需要进入内存分配器,它们只需从空闲列表中拉出,当引用计数降至零时释放到空闲列表。 CPython 的ints 是可变长度的,不使用空闲列表。

总的来说,这一切都略微支持int / int(至少对于小型ints;大型int 情况变得更加复杂,但是对于int // int 来说可能也更糟,因为数组基于-的除法算法非常复杂/昂贵),因此使用 Python 内置类型看到类似的行为并不意外。

【讨论】:

以上是关于Numpy/Python 中基本数学运算的速度:为啥整数除法最慢?的主要内容,如果未能解决你的问题,请参考以下文章

Numpy

numpy

如何提高MATLAB的运算速度

如何提高MATLAB的运算速度

归纳数学常识提高运算速度

按日期分隔数据框并计算数学模型 Numpy Python