在处理器中,整数除法的成本通常是整数乘法的几倍:
- 流水线式的组合乘法器通常在不到10个周期内完成操作;
- 而对于整数除法则没有硬件支持,或者使用的迭代除法器比乘法器慢几倍。
表 1.1 比较了一些处理器上乘法和除法的时间。这张表展示了乘法和除法时间差距的增长趋势。
因此, Division by Invariant Integers using Multiplication 中提出了使用整数乘法进行任意非零整数常数和运行时不变量之间除法的算法。
文档 4. Instruction tables 中记录了更广泛的处理指令性能,其中 Intel IceLake 处理器的乘除法指令延迟和吞吐倒数如下表所示:
可以看出,在现代 CPU 处理器上除法开销大的情况并未发生改变。NVIDIA 和 AMD GPU 均不支持整数除法指令,CUDA C++ Programming Guide 5.4.1. Arithmetic Instructions 中指出整数除法和取模运算可能会编译出多达20条指令,建议开发者在某些情况下使用位运算来替代。很多深度学习框架和加速库中均采用论文所提出的算法来加速常量除法运算。
2 Mathematical notations
设 x x x 是一个实数。那么
- ⌊ x ⌋ \lfloor x\rfloor ⌊x⌋ 表示不超过 x x x 的最大整数,
- ⌈ x ⌉ \lceil x \rceil ⌈x⌉ 表示不小于 x x x 的最小整数。
令 T R U N C ( x ) \mathrm{TRUNC}(x) TRUNC(x) 表示 x x x 的整数部分,向零取整。形式上,
- 如果 x ≥ 0 x \ge 0 x≥0,则 T R U N C ( x ) = ⌊ x ⌋ \mathrm{TRUNC}(x) = \lfloor x\rfloor TRUNC(x)=⌊x⌋;
- 如果 x ≤ 0 x \le 0 x≤0,则 T R U N C ( x ) = ⌈ x ⌉ \mathrm{TRUNC}(x) = \lceil x \rceil TRUNC(x)=⌈x⌉。
x x x 的绝对值是 ∣ x ∣ |x| ∣x∣。对于 x > 0 x > 0 x>0, x x x 以 2 为(实数)底的对数为 log 2 x \log_2 x log2x。
乘法写为 x ∗ y x ∗ y x∗y。
如果 x x x、 y y y 和 n n n 是整数且 n ≠ 0 n \neq 0 n=0,则 x ≡ y ( m o d n ) x \equiv y (\mathrm{mod}\, n) x≡y(modn) 意味着 x − y x − y x−y 是 n n n 的倍数。
在语言定义中常见两种余数运算符。有时余数带有被除数的符号,有时带有除数的符号。我们使用 Ada 符号。
n r e m d = n − d ∗ T R U N C ( n / d ) ( sign of dividend ) , n m o d d = n − d ∗ ⌊ n / d ⌋ ( sign of divisor ) . \begin{aligned} n \enspace\mathrm{rem}\enspace d &= n − d ∗ \mathrm{TRUNC}(n/d) \quad &(\text{sign of dividend}),\\ n \enspace mod \enspace d &= n − d ∗ \lfloor n/d \rfloor &(\text{sign of divisor}). \end{aligned} nremdnmodd=n−d∗TRUNC(n/d)=n−d∗⌊n/d⌋(sign of dividend),(sign of divisor).
Fortran 90 中的名称是 MOD 和 MODULO。在 C 语言中,对余数的定义取决于具体的实现(许多 C 实现将有符号商向零舍入并使用 r e m \mathrm{rem} rem 余数)。还有其他一些建议的定义[6, 7]。
如果 n n n 是一个 udword 或 sdword,那么 H I G H ( n ) \mathrm{HIGH}(n) HIGH(n) 和 L O W ( n ) \mathrm{LOW}(n) LOW(n) 分别表示 n n n 的最高有效半部和最低有效半部:
- L O W ( n ) \mathrm{LOW}(n) LOW(n) 是一个 udword;
- 如果 n n n 是 udword, H I G H ( n ) \mathrm{HIGH}(n) HIGH(n) 是一个 udword ;如果 n n n 是 sdword 则 H I G H ( n ) \mathrm{HIGH}(n) HIGH(n) 是 sword。
在这两种情况下, n = 2 N ∗ H I G H ( n ) + L O W ( n ) n = 2^N ∗ \mathrm{HIGH}(n) + \mathrm{LOW}(n) n=2N∗HIGH(n)+LOW(n)。
3 Assumed instructions
建议的代码假设在 N 位机器上执行表 3.1 中的操作。某些原语,如加载常量和操作数,在符号中是隐含的,并且不包含在操作计数中。
第8节中的算法要求能够对两个双字进行加减运算,得到一个双字结果;这通常会扩展为2-4条指令。
处理常数除数的算法需要在编译时对 udword 进行算术运算。
处理运行时不变除数的算法需要取一个正整数以2为底的对数(有时向上取整,有时向下取整),并需要将一个 udword 除以一个 uword。如果这些算法只针对常数除数,那么这些操作只需要在编译时进行。如果该架构有一个前导零计数(LDZ)指令,那么可以从以下方式中找到这些对数
⌈ log 2 x ⌉ = N − L D Z ( x − 1 ) , ⌊ log 2 x ⌋ = N − 1 − L D Z ( x ) ( 1 ≤ x ≤ 2 N − 1 ) . \begin{aligned} \lceil \log_2 x \rceil &= N − \mathrm{LDZ}(x − 1), \\ \lfloor \log_2 x \rfloor &= N − 1 − \mathrm{LDZ}(x)\qquad (1 ≤ x ≤ 2N − 1). \end{aligned} ⌈log2x⌉⌊log2x⌋=N−LDZ(x−1),=N−1−LDZ(x)(1≤x≤2N−1).
某些算法可能会产生诸如 S R L ( x , 0 ) \mathrm{SRL}(x, 0) SRL(x,0) 或 − ( x − y ) −(x − y) −(x−y) 等表达式;优化器应该进行显而易见的简化。一些描述显示了 2 N 2^N 2N 的加法或减法,这实际上是一个无操作(no-op)。
如果一个架构缺少算术右移,则可以根据下面的等式来计算它:
S R A ( x , ℓ ) = S R L ( x + 2 N − 1 , ℓ ) − 2 N − 1 − ℓ whenever 0 ≤ n < 2 N \mathrm{SRA}(x, \ell) = \mathrm{SRL}(x + 2^{N −1}, \ell) − 2^{N −1−\ell} \quad \text{whenever } 0 \le n < 2^N SRA(x,ℓ)=SRL(x+2N−1,ℓ)−2N−1−ℓwhenever 0≤n<2N
如果一个架构只有 M U L S H \mathrm{MULSH} MULSH 和 M U L U H \mathrm{MULUH} MULUH 中的一个,那么另一个可以通过以下方式计算得出:
M U L U H ( x , y ) = M U L S H ( x , y ) + A N D ( x , X S I G N ( y ) ) + A N D ( y , X S I G N ( x ) ) \mathrm{MULUH}(x, y) = \mathrm{MULSH}(x, y) + \mathrm{AND}(x, \mathrm{XSIGN}(y))+ \mathrm{AND}(y, \mathrm{XSIGN}(x)) MULUH(x,y)=MULSH(x,y)+AND(x,XSIGN(y))+AND(y,XSIGN(x))
对于任意 N 位模式的 x x x, y y y(在 M U L U H \mathrm{MULUH} MULUH 中被解释为 uword,在 M U L S H \mathrm{MULSH} MULSH 中被解释为 sword)。
4 Unsigned division
假设我们想要编译一个无符号除法 q = ⌊ n / d ⌋ q = \lfloor n/d\rfloor q=⌊n/d⌋,其中 0 < d < 2 N 0 < d < 2^N 0<d<2N 是一个常数或运行时不变量, 0 ≤ n < 2 N 0 \le n < 2^N 0≤n<2N 是变量。
让我们尝试找到 1 / d 1/d 1/d 的一个有理近似 m / 2 N + ℓ m/2^{N +\ell} m/2N+ℓ,使得
⌊ n d ⌋ = ⌊ m ∗ n 2 N + ℓ ⌋ whenever 0 ≤ n < 2 N \lfloor \frac{n}{d}\rfloor = \lfloor \frac{m*n}{2^{N +\ell}}\rfloor \quad \text{whenever } 0 \le n < 2^N ⌊dn⌋=⌊2N+ℓm∗n⌋whenever 0≤n<2N
将 n = d n = d n=d 代入上式中显示我们需要 2 N + ℓ ≤ m ∗ d 2^{N +\ell} \le m * d 2N+ℓ≤m∗d。设置 n = q ∗ d − 1 n = q * d - 1 n=q∗d−1 显示 2 N + ℓ ∗ q > m ∗ ( q ∗ d − 1 ) 2^{N +\ell} * q > m * (q * d - 1) 2N+ℓ∗q>m∗(q∗d−1)。乘以 d d d 得出 ( m ∗ d − 2 N + ℓ ) ∗ ( q ∗ d − 1 ) < 2 N + ℓ (m * d - 2^{N +\ell}) * (q * d - 1) < 2^{N +\ell} (m∗d−2N+ℓ)∗(q∗d−1)<2N+ℓ。 如果 m ∗ d − 2 N + ℓ ≤ 2 m * d - 2^{N +\ell} \le 2 m∗d−2N+ℓ≤2,则对于所有小于 2 N 2^N 2N 的 q ∗ d − 1 q * d - 1 q∗d−1 的值,这个不等式都成立。下面的定理 4.2 声明这些条件是充分的,因为当 n < 2 N n < 2^N n<2N 时,最大相对误差( 2 N 2^N 2N分之一)太小,不足以影响商。
定理 4.2 假设 m m m、 d d d、 ℓ \ell ℓ 是非负整数,使得 d ≠ 0 d \neq 0 d=0 且
2 N + ℓ ≤ m ∗ d ≤ 2 N + ℓ + 2 ℓ (4.3) 2^{N+\ell} \le m * d \le 2^{N+\ell} + 2^\ell \qquad\text{(4.3)} 2N+ℓ≤m∗d≤2N+ℓ+2ℓ(4.3)
那么对于每一个整数 n n n 且 0 ≤ n < 2 N 0 \le n < 2^N 0≤n<2N 有 ⌊ n d ⌋ = ⌊ m ∗ n / 2 N + ℓ ⌋ \lfloor \frac{n}{d}\rfloor = \lfloor m*n / 2^{N +\ell}\rfloor ⌊dn⌋=⌊m∗n/2N+ℓ⌋。
证明: 定义 k = m ∗ d − 2 N + ℓ k = m * d - 2^{N +\ell} k=m∗d−2N+ℓ。那么根据假设有 0 ≤ k ≤ 2 ℓ 0 \le k \le 2^\ell 0≤k≤2ℓ。给定 0 ≤ n < 2 N 0 \le n < 2^N 0≤n<2N 的 n n n,写作 n = q ∗ d + r n = q * d + r n=q∗d+r,其中 q = ⌊ n / d ⌋ q = \lfloor n/d \rfloor q=⌊n/d⌋ 且 0 ≤ r ≤ d − 1 0 \le r \le d - 1 0≤r≤d−1。我们必须证明 q = ⌊ m ∗ n / 2 N + ℓ ⌋ q = \lfloor m * n/ 2^{N +\ell}\rfloor q=⌊m∗n/2N+ℓ⌋。通过计算得出
m ∗ n 2 N + ℓ − q = m ∗ d d ∗ n 2 N + ℓ − q = k + 2 N + ℓ d ∗ n 2 N + ℓ − q = k ∗ n d ∗ 2 N + ℓ + n d − n − r d = k 2 ℓ ∗ n 2 N ∗ 1 d + r d \begin{aligned} \frac{m ∗ n}{2^{N +\ell}} - q &= \frac{m * d}{d}*\frac{n}{2^{N +\ell}}- q \\ &= \frac{k + 2^{N +\ell}}{d}*\frac{n}{2^{N +\ell}}- q \\ &= \frac{k ∗ n}{d*2^{N +\ell}} + \frac{n}{d} - \frac{n-r}{d}\\ &= \frac{k}{2^\ell}*\frac{n}{2^N}*\frac{1}{d} + \frac{r}{d} \end{aligned} 2N+ℓm∗n−q=dm∗d∗2N+ℓn−q=dk+2N+ℓ∗2N+ℓn−q=d∗2N+ℓk∗n+dn−dn−r=2ℓk∗2Nn∗d1+dr
因为 0 ≤ k ≤ 2 ℓ 0 \le k \le 2^\ell 0≤k≤2ℓ, 0 ≤ n < 2 N 0 \le n < 2^N 0≤n<2N, 0 ≤ r ≤ d − 1 0 \le r \le d - 1 0≤r≤d−1,
0 2 ℓ ∗ 0 2 N ∗ 1 d + 0 d ≤ k 2 ℓ ∗ n 2 N ∗ 1 d + r d ≤ 2 ℓ 2 ℓ ∗ 2 N − 1 2 N ∗ 1 d + d − 1 d 0 ≤ k 2 ℓ ∗ n 2 N ∗ 1 d + r d ≤ 2 N − 1 2 N ∗ 1 d + d − 1 d 0 ≤ k 2 ℓ ∗ n 2 N ∗ 1 d + r d ≤ 1 − 1 2 N ∗ d 0 ≤ k 2 ℓ ∗ n 2 N ∗ 1 d + r d < 1 \begin{aligned} \frac{0}{2^\ell}*\frac{0}{2^N}*\frac{1}{d} + \frac{0}{d} \le &\frac{k}{2^\ell}*\frac{n}{2^N}*\frac{1}{d} + \frac{r}{d}\le \frac{2^\ell}{2^\ell}*\frac{2^N -1}{2^N}*\frac{1}{d} + \frac{d - 1}{d}\\ 0 \le &\frac{k}{2^\ell}*\frac{n}{2^N}*\frac{1}{d} + \frac{r}{d}\le \frac{2^N -1}{2^N}*\frac{1}{d} + \frac{d - 1}{d}\\ 0 \le &\frac{k}{2^\ell}*\frac{n}{2^N}*\frac{1}{d} + \frac{r}{d}\le 1 − \frac{1}{ 2^N ∗ d} \\ 0 \le &\frac{k}{2^\ell}*\frac{n}{2^N}*\frac{1}{d} + \frac{r}{d} < 1 \end{aligned} 2ℓ0∗2N0∗d1+d0≤0≤0≤0≤2ℓk∗2Nn∗d1+dr≤2ℓ2ℓ∗2N2N−1∗d1+dd−12ℓk∗2Nn∗d1+dr≤2N2N−1∗d1+dd−12ℓk∗2Nn∗d1+dr≤1−2N∗d12ℓk∗2Nn∗d1+dr<1
这个差值是非负的,并且不超过 1。
如果不等式(4.3)成立,定理 4.2 允许用 m ∗ n / 2 N + ℓ m * n/ 2^{N +\ell} m∗n/2N+ℓ 的乘法来替代除以 d d d。一般来说,我们要求 2 ℓ ≥ d − 1 2^{\ell} \ge d-1 2ℓ≥d−1 以确保在区间 [ 2 N + ℓ , 2 N + ℓ + 2 ℓ ] [2^{N +\ell}, 2^{N +\ell} + 2^\ell] [2N+ℓ,2N+ℓ+2ℓ] 中存在 d d d 的适当倍数。为了与有符号除法的算法(第5节和第6节)兼容,选择 m ∗ d > 2 N + ℓ m * d > 2^{N +\ell} m∗d>2N+ℓ,尽管定理 4.2 允许相等。由于 m m m 几乎可以大到 2 N + 1 2^{N +1} 2N+1,我们不直接乘以 m m m,而是分别乘以 2 N 2^N 2N 和 m − 2 N m - 2^N m−2N。这引出了图 4.1 中的代码。在计算仅依赖于除数的常数之后,每个商的成本为1次乘法、2次加法/减法以及2次移位。
图 4.1 的解释:
- 如果 d = 1 d = 1 d=1,那么 ℓ = 0 \ell = 0 ℓ=0,所以 m ′ = 1 m' = 1 m′=1 且 s h 1 = s h 2 = 0 sh_1 = sh_2 = 0 sh1=sh2=0。代码计算 t 1 = ⌊ 1 ∗ n / 2 N ⌋ = 0 t_1 = \lfloor 1 ∗ n/2^N \rfloor = 0 t1=⌊1∗n/2N⌋=0 和 q = n q = n q=n。
- 若 d > 1 d > 1 d>1,则 ℓ ≥ 1 \ell≥1 ℓ≥1,故 s h 1 = 1 sh_1 = 1 sh1=1 且 s h 2 = ℓ − 1 sh_2 =\ell −1 sh2=ℓ−1。由于 m ′ ≤ 2 N ∗ ( 2 ℓ − d ) d + 1 ≤ 2 N ∗ ( d − 1 ) d + 1 < 2 N m' \le \frac{2^N ∗ (2^\ell − d)}{d} + 1 \le \frac{2^N ∗ (d − 1)}{d} + 1 < 2^N m′≤d2N∗(2ℓ−d)+1≤d2N∗(d−1)+1<2N, m ′ m' m′ 的值可以置于一个 uword 中。由于 0 ≤ t 1 ≤ n 0 \le t_1 \le n 0≤t1≤n, q q q 的公式简化为
q = S R L ( t 1 + S R L ( n − t 1 , 1 ) , ℓ − 1 ) = ⌊ t 1 + ⌊ ( n − t 1 ) 2 ⌋ 2 ℓ − 1 ⌋ = ⌊ ⌊ 2 ∗ t 1 2 + ( n − t 1 ) 2 ⌋ 2 ℓ − 1 ⌋ (4.5) = ⌊ ⌊ ( t 1 + n ) / 2 ⌋ 2 ℓ − 1 ⌋ = ⌊ t 1 + n 2 ℓ ⌋ \begin{aligned} q &= \mathrm{SRL}(t_1 + \mathrm{SRL}(n − t_1, 1), \ell− 1)\\ &=\lfloor \frac{t_1 + \lfloor \frac{(n − t_1)}{2} \rfloor}{2^{\ell− 1}}\rfloor\\ &=\lfloor \frac{\lfloor \frac{2*t_1}{2} + \frac{(n − t_1)}{2} \rfloor}{2^{\ell− 1}}\rfloor \qquad\text{(4.5)}\\ &=\lfloor \frac{\lfloor(t_1 + n)/2\rfloor}{2^{\ell− 1}} \rfloor\\ &=\lfloor \frac{t_1 + n}{2^{\ell}} \rfloor \end{aligned} q=SRL(t1+SRL(n−t1,1),ℓ−1)=⌊2ℓ−1t1+⌊2(n−t1)⌋⌋=⌊2ℓ−1⌊22∗t1+2(n−t1)⌋⌋(4.5)=⌊2ℓ−1⌊(t1+n)/2⌋⌋=⌊2ℓt1+n⌋
而 t 1 + n = ⌊ m ′ ∗ n / 2 N ⌋ + n = ⌊ ( m ′ + 2 N ) ∗ n / 2 N ⌋ t_1 + n = \lfloor m' ∗ n/2^N \rfloor + n = \lfloor (m' + 2^N)∗ n/2^N \rfloor t1+n=⌊m′∗n/2N⌋+n=⌊(m′+2N)∗n/2N⌋。设 m = m ′ + 2 N = ⌊ 2 N + ℓ / d ⌋ + 1 m = m' + 2^N = \lfloor 2^{N+\ell}/d\rfloor + 1 m=m′+2N=⌊2N+ℓ/d⌋+1。由于 2 N + ℓ < m ∗ d ≤ 2 N + ℓ + 2 ℓ 2^{N +\ell} < m ∗ d ≤ 2^{N +\ell} + 2^\ell 2N+ℓ<m∗d≤2N+ℓ+2ℓ,且 d ≤ 2 ℓ d \le 2^{\ell} d≤2ℓ,
2 N + ℓ / d − 1 ≤ m − 1 ≤ 2 N + ℓ / d 2 N + ℓ − d ≤ m ∗ d − d ≤ 2 N + ℓ 2 N + ℓ ≤ m ∗ d ≤ 2 N + ℓ + d 2 N + ℓ ≤ m ∗ d ≤ 2 N + ℓ + 2 ℓ \begin{aligned} 2^{N+\ell}/d -1 &\le m - 1 \le 2^{N+\ell}/d\\ 2^{N+\ell} -d &\le m*d - d \le 2^{N+\ell}\\ 2^{N+\ell} &\le m*d \le 2^{N+\ell} + d\\ 2^{N+\ell} &\le m*d \le 2^{N+\ell} + 2^{\ell} \end{aligned} 2N+ℓ/d−12N+ℓ−d2N+ℓ2N+ℓ≤m−1≤2N+ℓ/d≤m∗d−d≤2N+ℓ≤m∗d≤2N+ℓ+d≤m∗d≤2N+ℓ+2ℓ
因此满足定理 4.2 的假设。
注意: 从概念上讲, q q q 是 S R L ( n + t 1 , ℓ ) \mathrm{SRL}(n + t_1, \ell) SRL(n+t1,ℓ),就像在 (4.5) 中一样。不要这样计算 q q q,因为 n + t 1 n+t_1 n+t1 可能会溢出 N N N 位,且移位计数可能会越界。
改进: 如果 d d d 是常数且为2的幂,则用移位替换除法。
改进: 如果 d d d 是常数且 m = m ′ + 2 N m = m' + 2^N m=m′+2N 是偶数,则将 m / 2 ℓ m/2^\ell m/2ℓ 约简到最低项。原始乘数约简后在 N N N 位内。在极少数情况下(例如,在 32 位机器上 d = 641 d = 641 d=641,在 64 位机器上 d = 274177 d = 274177 d=274177),最终的移位为零。
改进。 如果 d d d 是常数且为偶数,则对某个 e > 0 e > 0 e>0 重写 ⌊ n d ⌋ = ⌊ ⌊ n / 2 e ⌋ d / 2 e ⌋ \lfloor \frac{n}{d}\rfloor=\lfloor \frac{\lfloor n/2^e\rfloor}{d/2^e}\rfloor ⌊dn⌋=⌊d/2e⌊n/2e⌋⌋。然后可以使用 S R L \mathrm{SRL} SRL 计算 ⌊ n / 2 e ⌋ \lfloor n/2^e\rfloor ⌊n/2e⌋。由于 n / 2 e < 2 N − e n/2^e < 2^{N−e} n/2e<2N−e,乘数所需的精度比之前少。
这些思想体现在图 4.2 中,它生成了 n / d n/d n/d 的代码,其中 n n n 是无符号的, d d d 是常数。
以下三个示例阐述了图 4.2 中的情况。所有示例都采用无符号 32 位算术。
示例: q = ⌊ n / 10 ⌋ q = \lfloor n/10 \rfloor q=⌊n/10⌋。CHOOSE MULTIPLIER 找到 m l o w = ( 2 36 − 6 ) / 10 m_\mathrm{low} = (2^{36} − 6)/10 mlow=(236−6)/10 和 m h i g h = ( 2 36 + 14 ) / 10 m_\mathrm{high} = (2^{36} + 14)/10 mhigh=(236+14)/10。 经过一轮除以2的操作后,它返回 ( m , 3 , 4 ) (m, 3, 4) (m,3,4),其中 m = ( 2 34 + 1 ) / 5 m = (2^{34} + 1)/5 m=(234+1)/5。推荐的代码 q = S R L ( M U L U H ( ( 2 34 + 1 ) / 5 , n ) , 3 ) q = \mathrm{SRL}(\mathrm{MULUH}((2^{34} + 1)/5, n), 3) q=SRL(MULUH((234+1)/5,n),3) 消除了预先的0位移。见表 11.1。
示例: q = ⌊ n / 7 ⌋ q = \lfloor n/7 \rfloor q=⌊n/7⌋。这里 m = ( 2 35 + 3 ) / 7 > 2 32 m = (2^{35} + 3)/7 > 2^{32} m=(235+3)/7>232。本示例使用图 4.1 中较长的序列。
示例: q = ⌊ n / 14 ⌋ q = \lfloor n/14\rfloor q=⌊n/14⌋。CHOOSE MULTIPLIER 首先返回当 d = 7 d = 7 d=7 时相同的乘数。 推荐的代码使用2和7的独立除法:
q = S R L ( M U L U H ( ( 2 34 + 5 ) / 7 , S R L ( n , 1 ) ) , 2 ) . q = \mathrm{SRL}(\mathrm{MULUH}((2^{34} + 5)/7, \mathrm{SRL}(n, 1)), 2). q=SRL(MULUH((234+5)/7,SRL(n,1)),2).
本算法和后续算法共享的程序 CHOOSE MULTIPLIER 如图 6.2 所示。
因为 N + s h p o s t ≤ ℓ + p r e c N + sh_{post} ≤ \ell + prec N+shpost≤ℓ+prec,所以 N + s h p o s t − p r e c ≤ ℓ N + sh_{post}-prec ≤ \ell N+shpost−prec≤ℓ。
2 N + s h p o s t ∗ ( 1 + 2 − p r e c ) ≤ 2 N + s h p o s t + 2 ℓ 2^{N +sh_{post}} ∗ (1 + 2^{−prec}) \le 2^{N +sh_{post}} + 2^\ell 2N+shpost∗(1+2−prec)≤2N+shpost+2ℓ
又因为 0 ≤ s h p o s t ≤ ℓ 0 \le sh_{post} ≤ \ell 0≤shpost≤ℓ,所以
2 N + s h p o s t ∗ ( 1 + 2 − p r e c ) ≤ 2 N + ℓ + 2 ℓ 2^{N +sh_{post}} ∗ (1 + 2^{−prec}) \le 2^{N +\ell} + 2^\ell 2N+shpost∗(1+2−prec)≤2N+ℓ+2ℓ
不等式(4.3)的上界成立。
因为 2 ℓ − 1 < d 2^{\ell−1} < d 2ℓ−1<d 且 d ≤ 2 p r e c d \le 2^{prec} d≤2prec, 所以 ℓ − 1 < p r e c \ell-1 < prec ℓ−1<prec,所以 2 − p r e c < 2 1 − ℓ 2^{−prec}< 2^{1−\ell} 2−prec<21−ℓ。
m < 2 N + s h p o s t ∗ ( 1 + 2 1 − ℓ ) / d m < 2^{N +sh_{post}} ∗ (1 + 2^{1−\ell})/d m<2N+shpost∗(1+21−ℓ)/d
因为 2 ℓ − 1 < d ≤ 2 ℓ 2^{\ell−1} < d \le 2^\ell 2ℓ−1<d≤2ℓ 且 2 1 − ℓ ≤ 1 2^{1−\ell} \le 1 21−ℓ≤1,所以 1 + 2 1 − ℓ ≤ 2 1 + 2^{1−\ell} \le 2 1+21−ℓ≤2。可得
2 N + s h p o s t ∗ ( 1 + 2 1 − ℓ ) / d ≤ 2 N + s h p o s t ∗ 2 / d 2^{N +sh_{post}} ∗ (1 + 2^{1−\ell})/d \le 2^{N +sh_{post}} ∗ 2/d 2N+shpost∗(1+21−ℓ)/d≤2N+shpost∗2/d
又因为 d ≤ 2 ℓ d \le 2^{\ell} d≤2ℓ,所以
2 N + s h p o s t + 1 / d ≤ 2 N + s h p o s t − ℓ + 1 2^{N +sh_{post}+1}/d \le 2^{N +sh_{post}−\ell+1} 2N+shpost+1/d≤2N+shpost−ℓ+1
因此, 2 N + s h p o s t ∗ ( 1 + 2 1 − ℓ ) / d ≤ 2 N + s h p o s t − ℓ + 1 2^{N +sh_{post}} ∗ (1 + 2^{1−\ell})/d \le 2^{N +sh_{post}−\ell+1} 2N+shpost∗(1+21−ℓ)/d≤2N+shpost−ℓ+1。
注意:略去5到9节中商向零取整的有符号除法、商向负无穷大取整的有符号除法、udword 除以运行时不变的 uword,以及先验已知余数为零的除法。
10 Implementation in GCC
作者在可自由使用的 GCC 编译器[21]中实现了常数除数的算法,扩展了其与机器和语言无关的内部代码生成。 并且还对一些机器描述符或 md 文件进行了轻微的机器依赖的修改,以获得最优的代码。GCC 支持的所有语言和几乎所有处理器都将受益。 相关更改计划包含在 GCC 2.6中。
为了生成 N 位数除法的代码,CHOOSE MULTIPLIER 函数需要执行 ( 2 N 2N 2N) 位算术运算。这使得该程序比图 6.2中显示的更复杂。
根据操作的位大小选择最佳指令是一个棘手的问题,作者花了相当长的时间:
- 对于某些架构,选择具有最小可用精度的乘法指令很重要。
- 在其他架构上,可以通过一系列加法、减法和移位更快地执行乘法。
作者还没有实现任何针对运行时不变除数的算法。只有少数架构(AMD 29050、英特尔 x86、摩托罗拉 68k 和88110,以及在某种程度上的 IBM POWER)具有足够的硬件支持来使这种实现可行,即可以用于整数对数计算的指令,以及 ( 2 N 2N 2N) 位/ N N N 位除法指令。即使有硬件支持,也必须小心,确保转换真正改进了代码;例如,可能需要执行很多次循环才能确保较快的循环体超过循环头中乘数的计算成本。
11 Results
图 11.1 提供了一个编译时间常数除法器的例子,它在所有最新的处理器实现上都获得了显著的加速。该程序将二进制数转换为十进制字符串。它为每个输出数字计算一个商和一个余数。
表 11.1 展示了为 Alpha、MIPS、POWER 和 SPARC 生成的汇编代码。这里没有显式的除法。尽管最初是单独计算的,但商和余数计算已合并(通过 GCC 的公共子表达式消除过程)。
unsigned int 数据类型在所有四种架构上均为32位,但 Alpha 是一种64位架构。Alpha 的代码比其他的长,因为它使用
4 ∗ [ ( 2 16 + 1 ) ∗ ( 2 8 + 1 ) ∗ ( 4 ∗ [ 4 ∗ ( 4 ∗ x − x ) + x ] − x ) ] + x 4 * [(2^{16} + 1) * (2^8 + 1) * (4 * [4 * (4 * x - x) + x] - x)] + x 4∗[(216+1)∗(28+1)∗(4∗[4∗(4∗x−x)+x]−x)]+x
将 ( 2 34 + 1 ) / 5 (2^{34} + 1)/5 (234+1)/5 乘以 x x x,而不是更慢的、需要 23 个周期的mulq
。这说明这些算法所需的乘法有时可以通过一系列的移位、加法和减法快速计算[5],因为小常数除数的乘数在二进制模式上有规律。
表 11.2 比较了有除法消除算法和没有除法消除算法的基转换例程在一些处理器实现上的时间。转换的数字是一个完整的32位数,足以隐藏测量中的程序调用开销。
作者还运行了 SPEC’92 的整数基准测试。对于大多数程序来说,改进微不足道;观察到的最佳改进仅为3%左右。一些涉及哈希的基准测试显示出高达约30%的提升。作者预计在一些数论代码上会有显著的提升。
参考资料:
- Division by Invariant Integers using Multiplication
- Improved division by invariant integers
- C++干货系列——从编译期常量谈到constexpr(二)
- Integer Division by Constants: Optimal Bounds
- lemire/fast_division
- Thorius/fast_division
- Why doesn’t gcc or clang on ARM use “Division by Invariant Integers using Multiplication” trick?
- Perform integer division using multiplication [duplicate]
- paddle/phi/kernels/funcs/fast_divmod.h
- paddle/phi/kernels/primitive/datamover_primitives.h
- onnxruntime/core/providers/cuda/shared_inc/fast_divmod.h
- intel/xetla/include/common/utils/fastmath.hpp
- How to find magic multipliers for divisions by constant on a GPU?
- [原创]加减乘除反汇编笔记与MagicNumber探究记录
- Java 源码 - java.lang.Integer
- 除以二
- C/C++除数为整数常量(但不是2的幂次)时,优化得div指令都没了是什么原理?
- 编译技术-除常数优化
- 即,令,当,这些数学用语在英文论文中如何表示?
- 英语分数的表达,特别是分母比较大的怎么表达 比如: 2/37 2/115 3/1112
- What has a better performance: multiplication or division?
- 4. Instruction tables
- 『扩展欧几里得算法 Extended Euclid』
- 擴展歐基里德算法 (Extended Euclidean algorithm)
- The integer division algorithm of Intel’s x86 processors
- x86汇编_MUL/IMUL乘法指令_笔记52
- integer division and modulo
- Quick ALU Performance multiply/division investigation
- Most efficient way of dividing by power of two
- Fast integer division and modulo with a const runtime divisor
- Floating Point and Integer Arithmetic Benchmark
- Achieved IOPs
- Integer division problem
- 5.4.1. Arithmetic Instructions