你喜欢快速还是精确?

这个博客是关于什么的?

我叫Jim Hogg,是编译器团队的程序经理。

null

我们希望您对VisualC++编译器的一个特性产生影响,它会影响我们为浮点运算生成的代码。你的回答将有助于决定我们做什么。 你可以通过 调查 -填写不应超过几分钟!

好吧,我还在读。

C语言和C++语言让你声明类型变量 浮动 双重的 . 我们称之为“浮点”类型。VisualC++编译器允许您指定它应该如何处理涉及这些浮点变量的计算。我们在这个博客中讨论的选项是 /fp:快速 /fp:精确 .

今天的默认值是 /fp:精确 . 本博客询问您对我们是否应该将默认设置更改为 /fp:快速 . 这种改变会使你的代码运行得更快;但可能会降低结果的准确性,这取决于所涉及的计算。

有许多优秀的文章详细解释了浮点运算。相比之下,这个博客包含了一个附录,提供了一个简单的概述,足以让你对将默认值改为 /fp:快速 . 想深入挖掘的读者可以在这篇文章的末尾浏览链接。

[请注意,您有两种控制方式:您可以指定编译器应该遵循 /fp:快速 /fp:精确 到每个.cpp文件,甚至每个函数的级别]

请让我们知道你的想法,在阅读了这篇博文后,通过填写 这个简短的调查 .

符号

这个博客使用符号1.2E+34作为1.2*10的缩写 34 . 如果“分数”部分是1.0,我们进一步缩写:因此1.0E+23被缩短为E+23。

浮点基础知识

在C++中,A 浮动 可以将值存储在3个(近似)不相交的范围{[-E+38,-E-38],0,[E-38,E+38]}。每个 浮动 占用32位内存。在这个有限的空间里 浮动 只能存储大约40亿个不同的值。它以一种巧妙的方式做到了这一点,小数字的相邻值靠得很近;而大数的相邻值相差很远。你们可以互相依靠 浮动 数值精确到大约7位小数。

浮点计算

我们都知道计算机是如何计算的 内景 s。但那你呢 浮动 什么?一个明显的效果是,如果我加上一个大的数字和一个小的数字,小的可能会丢失。例如,E+20+E-20导致E+20–一个系统中没有足够的精度位 浮动 表示精确/准确/正确的值。

类似地,每个计算使用 浮动 s必须对精确结果进行四舍五入,以适应可用的空间(实际上是23位)。根据计算的不同,结果可能会与数学结果(如果你有很多可用的位,你会得到的结果)有一点或很多不同。

下面是一个简单的例子:

int main() { float inc = 0.000001, sum = 0.0; for (int i = 1; i <= 1000000; ++i) sum += inc; printf("Sum = %f ", sum);}

你希望这个程序 股份有限公司 (百万分之一)到 总和 ,一百万次,得到1.0的答案。但百万分之一只能近似表示为 浮动 (实际上是0x358637bd),所以得到的结果不是1.0,而是1.009039。

为了让自己更害怕,请注意 浮动 s不遵守代数的所有规则。例如,加法的结合性表示:(a+b)+c==a+(b+c)。但花车并不完全遵守这一规则。例如:

  • (E-10+E10)+-E10=E10+-E10=0
  • E-10+(E10+-E10)=E-10+0=E-10

因此,结果可能会有所不同,这取决于我们执行操作的顺序。

浮点运算并不遵循所有的代数定律,但在许多情况下,它与数学上精确的答案“足够接近”[例句:如果我们计算桥桁上的应力为1.2593吨,但准确值为1.2592吨,我们可能会高兴:桥不会倒下来]

/fp:fast做什么?

通过投掷 /fp:快速 你告诉编译器它应该假装 浮动 s(和 双重的 s) 遵守简单代数的规则(结合性和分配性)。这允许编译器优化代码,使其运行更快。它以准确度换取速度(它还允许编译器对称为 s–“不是数字”–见下文)

有多快/fp:Fast?

通过启用 /fp:快速 ? 以下是我们使用几个常见基准测试得出的结果:

姓名 面积 加速(x86)
帕塞克 下一代共享内存 1.58
本征 线性代数 1.03
规范FP 2006 CPU和内存 1.03

“加速”的定义如下:表示执行基准测试的时间,在 /fp:精确 ,作为 t精确 . 相应地, 特法斯特 . 那么“加速”就是 t精确/t快速 .

请注意,你实现的加速将取决于你的应用程序的细节。例如,我们在各个Parsec基准测试中测量了大量的加速:从1.0(即,没有加速)到5.2倍!

/fp:fast有多不准确?

与加速一样,结果的准确性也会因应用程序而异。如果你的应用程序或测试程序计算出一个简单的结果,那么比较就很简单了。但如果你的应用程序计算的是绕翼型的高超音速气流,比较起来就更具挑战性了。

如果你的应用程序是一个游戏,那么一些计算只需要精确到在正确的像素上绘制正确的颜色(因此2048列的显示需要几千分之一的精度)。有了游戏应用程序,你甚至不太可能看到两者在显示上有什么不同 /fp:快速 /fp:精确 .  [默认情况下,Xbox游戏的编译 /fp:快速 ]

反例

到目前为止的解释会让你想到 /fp:快速 有时(也许总是?)会产生一个不如预期准确的结果 /fp:精确 . 作为一个简单的例子,让我们考虑前一百万个倒数的和,或者n=1..1000000的和(1/n)。我用 浮动 s、 用Boost的 cpp下降浮动 (精确到100位小数)。与 /氧气 优化级别,结果如下:

浮点/浮点:精确 14.3574
浮点/浮点:快速 14.3929
cppu decu float<100> 14.39272672286

所以 /fp:快速 结果比结果更接近正确答案 /fp:精确 !

怎么会这样?  与 /fp:快速 自动矢量器发出SIMD RCPP公司 机器指令,比 DIVSS公司 发射 /fp:精确 .

这只是一个具体案例。但关键是,即使是一个完整的错误分析也不能告诉你 /fp:快速 在你的应用程序中是可以接受的-还有更多的事情要做。唯一可以确定的方法是在每种模式下测试你的应用程序并比较答案。

双打怎么样?

这个博客描述了浮动在 /fp:快速 . 双重的 s与 浮动 s、 但是占用64位,而不是32位;它们有更多的位用于有效位和指数。在某种意义上(我们将不详细说明),他们比其他人更遵守代数规则 浮动 s。但您仍然可以通过计算观察舍入误差的影响及其传播。 /fp:快速 影响两者的行为 浮动 s和 双重的 s。

下一步?

请尝试一个应用程序或测试程序 /fp:快速 而不是 /fp:精确 . 比较速度和准确度。基于此经验,请告诉我们,您是否同意我们更改VisualC++编译器的默认值 /fp:快速 . 填写以下表格,让我们知道您的想法 这个简短的调查 .

附录

接下来的几节(编号为A1、A2等)提供了关于浮点的更多细节。如果这激起你对更多的胃口,请按照帖子末尾的链接。

A1。整数

内景 Visual C++中的变量是32位宽的。它可以存储-2147483648到2147483647(含)范围内的任何整数。相邻值沿实数线均匀分布,每个值与相邻值相差1个单位。

A2。浮点格式

科学或工程中的计算需要表示分数值,分数值的范围也要比标准提供的40亿左右宽 内景 s。我们怎么可能在组成一个数字的32位中表示如此庞大的数字范围呢 浮动 ? 答:我们将宝贵的32位分成3块,如下所示:

  • S、 1位符号。0表示正。1表示否定。
  • 五、 23位“有效位”。一种二进制分数,其位的取值范围为2-1到2-23。(实际上,我们将原始二进制数标准化,使其最高有效位为1;因此我们不需要储存;所以我们真的达到了24位的精度)
  • E、 8位指数。作为8位无符号整数,此字段可以存储值[0,255]。但是值0和255是保留的(用于表示零、次法线、无穷大和 s(有关详细信息,请参阅链接)。从存储的指数值中,我们减去127(指数“偏差”-所有的都是固定的 浮动 s) 要得到实际指数,在[-126,127]范围内。

a的价值 浮动 计算公式为:(-1)S*(1+V)*2(E–127)。 举个例子:

0    0111 1110     101 0000 0000 0000 0000 0000

  • S=sign=0,所以这是一个正数
  • E=指数=0111110,或126(十进制)。减去127得到-1的实际指数。
  • V=有效位=1+(1*0.5)+(0*0.25)+(1*0.125)=1.625

所以这个特殊的 浮动 为1.625*2-1=0.8125

我们很容易看出最小的 浮动 因此,震级为:1*2^(-126)或约为E-38,最大的震级为:2*2^127或约为E+38。(感兴趣的读者可以在博客末尾的链接中探索“次正常”值的主题,该值接近于零)

A3。他们是怎么做到的?

我们似乎实现了不可能的目标!在32位内, 浮动 s可以表示近似范围内的任何数字[-E38,+E38]。这比32位的要宽得多 内景 ,跨度约为[-2E9,+2E9]。怎么回事?

一种跨越大范围的方法是使用 内景 ,但将其值乘以一个大数字,如E29。我们可以跨越范围[-2E38,+2E38]。但我们能代表的零后最小的数字是许多英里以外的E29[我们称之为定点格式,而不是浮点格式]。这样的制度注定要失败。我们需要更好的。

事实上, 浮动 s改变邻居之间的距离:小值,例如E-20,非常接近;较大的值(如E+20)相距“英里”。当你继续通过范围,你需要采取越来越大的跳跃,以达到下一个 浮动 价值观。所以呢 浮动 s允许我们表示近似范围内的有限个值[-E38,+E38]——但不是所有这些可能的值。以下是3个相邻浮点数的示例(它们的有效位相差最低有效位):

  • 0    0011 1111     000万~=5.42101E-20
  • 0    0011 1111     000 000 000 0001~=5.4210115E-20

(~=表示近似相等)。所以这两个非常小的相邻值,相距约0.000015E-20(1.5E-25)。  (也就是说,一把约克托米)

  • 0    0111 1111     000 0000 0000 0000 0000 0000 = 1.0
  • 0    0111 1111     000 0000 0000 0000 0000 0001 ~= 1.000 000 1

所以这两个,中间的,相邻的值,相隔大约E-7(即100纳米)

  • 0    1100 0010     000万~=1.4757395E+20
  • 0    1100 0010     000 000 000 0001~=1.4757397E+20

所以这两个非常大,相邻的值,相隔2E14((一个轻松的星期)

A4页。舍入误差-类比

使用袖珍计算器计算:  1.23 * 2.45 * 3.67.  我得到答案11.059545。

现在重复,但是将每个中间结果四舍五入,使其仅保留3个有效数字。所以我们得到:

  • 1.23*2.45=3.0135,四舍五入得3.01
  • 3.01*3.67=11.0467,四舍五入得11.05

这个答案有点错误。  太小了0.009545。这是因为我们强迫中间结果符合我们蹒跚的计算器的3位小数。当计算机使用 浮动 s–计算出的答案与数学上正确的答案有上移或下移的趋势,因为中间结果是为了符合 浮动 的大小有限[这是一个简化过程-有关详细信息,请参见链接]

第5条。讨厌的数字

给一些 浮动 变量, ,编译器将假定任何涉及表达式的中间计算 (x–x) 可以替换为0。但如果 有什么特别的价值吗 、+无穷大或–无穷大。  (请参阅后面的链接以获取解释)。如果您指定 /fp:快速 ,编译器将进行优化 (x–x) 归零。否则,它将执行计算,从而运行得更慢。如果 正好有价值 ,则 (x–x) 应该是,不是0,但是 .

答6。常数子表达式消元

本节和下面两节给出了启用 /fp:快速 . 假设编译器为程序中的函数生成以下简化C代码:

t1=a*b;
t2=t1*c;
. . // 中间代码-a、b或c无变化
t3=b*c;
t4=a*t3

注意t2=(a*b)*c,而t4=a*(b*c)。 与 /fp:精确 ,编译器不能假定t2==t4,并且将生成代码来计算t2,并分别计算t4。 与 /fp:快速 ,编译器可以推断t2和t4具有相同的值。  因此它将计算t2,并简单地将该值重新用于t4(而不是再次计算)。  当然,在许多情况下,计算值是相同的,或者非常接近。  如果你运气不好(参与操作数的数量级存在病理性差异),计算结果可能会有所不同。

第七章。自动矢量化

这个 /fp:快速 开关允许优化器执行不允许的代码模式的自动矢量化(查看上的博客文章序列 自动矢量化 ). 例如,假设我们的程序计算100个浮点数组的和。这将需要一个简单循环的100次迭代。但是我们可以使用芯片的向量寄存器,在25次迭代中得到答案,在每次迭代中并行执行4次计算。所以,不是:

  • 总和=a[0]+a[1]+a[2]+a[3]+a[4]+a[5]+。a[99]

我们把计算分成4部分和, 相扑 通过 sum3公司 ,我们并行运行;然后将它们相加:

  • sum0=a[0]+a[4]+a[8]+。甲[96]
  • sum1=a[1]+a[5]+a[9]+。a[97]
  • sum2=a[2]+a[6]+a[10]+。a[98]
  • sum3=a[3]+a[7]+a[11]+。a[99]
  • “总和”  = sum0+sum1+sum2+sum3

sum’==sum吗? 仅当(a[0]+a[4]+…)+(a[1]+a[5]+…)+(a[2]+a[6]+…)  + ([a[3]+a[7]+…)==a[0]+a[1]+a[2]+…这在结合性下成立,它 浮动 不要一直坚持。 指定 /fp:快速 允许编译器将代码转换为运行更快的速度—对于这个简单的计算,最多快4倍。

© 版权声明
THE END
喜欢就支持一下吧,技术咨询可以联系QQ407933975
点赞0 分享