【发布时间】:2016-06-02 08:19:51
【问题描述】:
最近的question,是否允许编译器用浮点乘法代替浮点除法,启发了我提出这个问题。
在严格要求下,代码转换后的结果应与实际除法运算按位相同,
很容易看出,对于二进制 IEEE-754 算术,这对于作为 2 的幂的除数是可能的。只要倒数
除数是可表示的,乘以除数的倒数会得到与除数相同的结果。例如,乘以0.5 可以代替除以2.0。
然后人们想知道这样的替换还有哪些其他除数有效,假设我们允许任何短指令序列替换除法但运行速度明显更快,同时提供位相同的结果。除了普通乘法之外,还特别允许融合乘加运算。 在 cmets 中,我指出了以下相关论文:
Nicolas Brisebarre、Jean-Michel Muller 和 Saurabh Kumar Raina。当除数已知时,加速正确舍入浮点除法。 IEEE 计算机汇刊,卷。 53,第 8 期,2004 年 8 月,第 1069-1072 页。
论文作者提倡的技术将除数 y 的倒数预先计算为归一化的头尾对 zh:z l 如下: zh = 1 / y, zl = fma (-y, zh, 1) / 是的。稍后,除法 q = x / y 然后计算为 q = fma (zh, x, zl * x )。本文推导了除数 y 必须满足的各种条件才能使该算法起作用。正如人们容易观察到的那样,当头尾符号不同时,该算法存在无穷大和零的问题。更重要的是,它无法为幅度非常小的股息 x 提供正确的结果,因为计算商尾 zl * x em>,遭受下溢。
该论文还顺便提及了一种基于 FMA 的替代除法算法,该算法由 Peter Markstein 在 IBM 时率先提出。相关参考是:
P. W.马克斯坦。在 IBM RISC System/6000 处理器上计算基本函数。 IBM 研究与开发杂志,卷。 34,第 1 期,1990 年 1 月,第 111-119 页
在 Markstein 的算法中,首先计算一个倒数 rc,从中形成一个初始商 q = x * rc。然后,用 FMA 精确计算除法的余数 r = fma (-y, q, x),最后计算出一个改进的、更准确的商 q = fma (r, rc, q).
该算法对于零或无穷大的 x 也存在问题(通过适当的条件执行很容易解决),但使用 IEEE-754 单精度 float 数据进行的详尽测试表明它提供在这些许多小整数中,许多除数 y 的所有可能除数 x 的正确商。这段 C 代码实现了它:
/* precompute reciprocal */
rc = 1.0f / y;
/* compute quotient q=x/y */
q = x * rc;
if ((x != 0) && (!isinf(x))) {
r = fmaf (-y, q, x);
q = fmaf (r, rc, q);
}
在大多数处理器架构上,这应该转换为无分支指令序列,使用谓词、条件移动或选择类型指令。举一个具体的例子:对于除以3.0f,CUDA 7.5 的nvcc 编译器为开普勒级GPU生成以下机器代码:
LDG.E R5, [R2]; // load x
FSETP.NEU.AND P0, PT, |R5|, +INF , PT; // pred0 = fabsf(x) != INF
FMUL32I R2, R5, 0.3333333432674408; // q = x * (1.0f/3.0f)
FSETP.NEU.AND P0, PT, R5, RZ, P0; // pred0 = (x != 0.0f) && (fabsf(x) != INF)
FMA R5, R2, -3, R5; // r = fmaf (q, -3.0f, x);
MOV R4, R2 // q
@P0 FFMA R4, R5, c[0x2][0x0], R2; // if (pred0) q = fmaf (r, (1.0f/3.0f), q)
ST.E [R6], R4; // store q
在我的实验中,我编写了如下所示的微型 C 测试程序,该程序按递增顺序遍历整数除数,并对每个整数除数进行详尽的测试,以对照正确的除法。它打印出通过这个详尽测试的除数列表。部分输出如下:
PASS: 1, 2, 3, 4, 5, 7, 8, 9, 11, 13, 15, 16, 17, 19, 21, 23, 25, 27, 29, 31, 32, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 64, 65, 67, 69,
要将替换算法合并到编译器中作为优化,可以安全地应用上述代码转换的除数白名单是不切实际的。到目前为止,程序的输出(大约每分钟一个结果的速度)表明,对于那些为奇数或为 2 的幂的除数 y,快速代码在所有可能的 x 编码中都能正常工作。当然,轶事证据,而不是证据。
哪组数学条件可以先验确定除法转换为上述代码序列是否安全?答案可以假设所有浮点运算都在默认舍入模式下进行“四舍五入到最接近或偶数”。
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
int main (void)
{
float r, q, x, y, rc;
volatile union {
float f;
unsigned int i;
} arg, res, ref;
int err;
y = 1.0f;
printf ("PASS: ");
while (1) {
/* precompute reciprocal */
rc = 1.0f / y;
arg.i = 0x80000000;
err = 0;
do {
/* do the division, fast */
x = arg.f;
q = x * rc;
if ((x != 0) && (!isinf(x))) {
r = fmaf (-y, q, x);
q = fmaf (r, rc, q);
}
res.f = q;
/* compute the reference, slowly */
ref.f = x / y;
if (res.i != ref.i) {
err = 1;
break;
}
arg.i--;
} while (arg.i != 0x80000000);
if (!err) printf ("%g, ", y);
y += 1.0f;
}
return EXIT_SUCCESS;
}
【问题讨论】:
-
不知道为什么该问题被标记为“太宽泛”以关闭。如果反对者能解释他们的推理,我将不胜感激。我试图确定何时用问题中显示的 very specific 代码序列将浮点除法替换为常量整数除数是“安全的”。我的测试结果中的轶事证据似乎表明它适用于奇数,以及那些是 2 的幂。但是要将其作为通用优化提出,需要有可靠的数学推理来说明哪些整数是“安全的”;我没有这方面的数学技能
-
我希望这个问题的答案能列出必须对除数施加的几个条件,以及最多一页用于证明或推导的页面,我不会认为“太长” " 对于 SO 格式。我没有在数学 Stackexchange 上问这个问题的原因是因为浮点问题在那里几乎没有任何吸引力,而 Stackoverflow 上有许多数学家,而且这个问题肯定与编程有关,所以恕我直言,适合 [数学]标记在这里。
-
@aka.nice 是的。这个事实让我感到困惑,我也有同样的想法,将这种划分分为两个阶段。我还没有尝试过,但我认为它可能不起作用,因为当结果是异常时,除以二并不总是准确的。
-
@Claudiu 基于对计算机科学 Stackexchange 的一般阅读,搜索相关标签,并检查该站点上与浮点运算相关的选定问答线程,我期望得到一个有意义的答案(甚至是有用的 cmets ) 会非常低。由于在 SO/SE 领域中似乎强烈反对交叉发布,因此我不能简单地进行相关实验来找出其中一种方法。
-
@Claudiu 我认为没有任何浮点专家在 CS 堆栈交换上闲逛,所以不是真的,不。而这里有许多知识渊博的定期贡献者(包括 njuffa 本人)。
标签: c algorithm math floating-point division