原 作 者:MS
原 出 处:enet
发 布 者:loose_went
发布类型:转载
C++ 优化编译器不仅能够将源代码转换为机器码,而且能够对机器指令进行适当的排列以便改善性 能和/或减小大小。遗憾的是,许多常用的优化在应用于浮点计算时未必安全。在下面的求和算法 [1] 中,可以看到这方面的一个恰当的示例:
float KahanSum( const float A[], int n ) { float sum=0, C=0, Y, T; for (int i=0; i<n i></n> { Y = A[i] - C; T = sum + Y; C = T - sum - Y; sum = T; } return sum; } |
该函数将数组向量 A 中的 n 个浮点值相加。在循环体中,算法计算 一个“修正”值,然后将其应用于求和的下一步。与简单的求和相比,该方法大大减小了累积性舍入 误差,同时保持了 O(n) 时间复杂性。
一个不完善的 C++ 编译器可能假设浮点算法遵循与实数算法相同的代数规则。这样的编译器可能 继而错误地断定
C = T - sum - Y ==> (sum+Y)-sum-Y ==> 0;
也就是说,C 得到的值总是常量零。如果随后将该常量值传播到后续表达式中,循环体将化简为简 单的求和。更准确地说,就是
Y = A[i] - C ==> Y = A[i]
T = sum + Y ==> T = sum + A[i]
sum = T ==> sum = sum + A[i]
因此,对于不完善的编译器而言,KahanSum 函数的逻辑转换将是:
float KahanSum( const float A[], int n ) { float sum=0; // C, Y & T are now unused for (int i=0; i<n i></n> sum = sum + A[i]; return sum; }
|
尽管转换后的算法更快,但它根本没有准确表达程序员的意图。精心设计的误差修正已经 被完全消除,只剩下一个具有所有其关联误差的简单的直接求和算法。
当然,完善的 C++ 编译器知道实数算法的代数规则通常并不适用于浮点算法。然而,即使是完善 的 C++ 编译器,也可能错误地解释程序员的意图。
考虑一种常见的优化措施,它试图在寄存器中存放尽可能多的值(称为“登记”值)。在 KahanSum 示例中,这一优化可能试图登记变量 C、Y 和 T,因为这些变量仅在循环体内使用。如果寄存器精度为 52 位(双精度)而不是 23 位(单精度),这一优化可以有效地将 C、Y 和 T 的类 型提升为 double。如果没有以同样的方式登记 sum 变量,则它仍将编 码为单精度。这会将 KahanSum 的语义转换为下面的语义
float KahanSum( const float A[], int n ) { float sum=0; double C=0, Y, T; // now held in-register for (int i=0; i<n i></n> { Y = A[i] - C; T = sum + Y; C = T - sum - Y; sum = (float) T; } return sum; } |
尽管现在 Y、T 和 C 以更高的精度进行计算,但新的编码可能产生精确性较低的结果,具体取决 于 A[] 中的值。因而,即使看起来无害的优化也可能具有消极的后果。
这些种类的优化问题并不局限于“棘手”的浮点代码。即使是简单的浮点算法,在经过错误的优化 后也可能失败。考虑一个简单的直接求和算法:
float Sum( const float A[], int n ) { float sum=0; for (int i=0; i<n i></n> sum = sum + A[i]; return sum; } |
因为一些浮点单元能够同时执行多个运算,所以编译器可能选择采用标量简化 优化。这一 优化有效地将简单的 Sum 函数从上述形式转换为以下形式:
float Sum( const float A[], int n ) { int n4 = n-n%4; // or n4=n4&(~3) int i; float sum=0, sum1=0, sum2=0, sum3=0; for (i=0; i<n4 i></n4> { sum = sum + A[i]; sum1 = sum1 + A[i+1]; sum2 = sum2 + A[i+2]; sum3 = sum3 + A[i+3]; } sum = sum + sum1 + sum2 + sum3; for (; i<n i></n> sum = sum + A[i]; return sum; } |
该函数现在保持了四个独立的求和运算,它们可以在每个步骤同时处理。尽管优化后的函数现在要 快得多,但优化结果可能与非优化结果完全不同。在进行这一变化时,编译器采用了具有结合律的浮 点加法;即以下两个表达式等效:(a+b)+c == a+(b+c)。然而,对于浮点数而言,结合律并不总是适 用。现在,转换后的函数不是按以下方法求和:
sum = A[0]+A[1]+A[2]+...+A[n-1]
而是按以下方法计算结果:
sum = (A[0]+A[4]+A[8]+...)
+(A[1]+A[5]+A[9]+...)
+(A[2]+A[6]+A[10]+...)
+(A[3]+A[7]+A[11]+...)
+...
对于 A[] 的某些值而言,不同的加法运算顺序可能产生意外的结果。更为复杂的是,某些程序员 可能选择预先针对此类优化做准备,并相应地对这些优化进行补偿。在此情况下,程序可以按不同的 顺序构建数组 A,以便优化的 sum 产生预期的结果。而且,在许多情况 下,优化结果的精确性可能“足够严密”。当优化提供了令人信服的速度优点时,尤其如此。例如, 视频游戏要求具有尽可能快的速度,但通常并不要求进行高度精确的浮点计算。因此,编译器制造商 必须为程序员提供一种机制,以便控制速度和精确性之间经常背离的目标。
某些编译器通过为每种类型的优化单独提供“开关”在速度和精确性之间进行折衷。这使开发人员 可以禁用可能为其特定应用程序的浮点精确性带来变化的优化。尽管该解决方案可能提供对编译器的 高度控制,但它也会带来其他一些问题:
• 通常很难搞清楚需要启用或禁用哪些开关。
• 禁用任一优化都可能对非浮点代码的性能带来不利影响。
• 每个附加的开关都会引起许多新的开关组合;组合数目将很快变得难以控制。
因此,尽管为每种优化提供单独的开关看起来似乎很有吸引力,但使用此类编译器可能非常麻烦并 且不可靠。
许多 C++ 编译器提供了“一致性”浮点模型(通过 /Op 或 /fltconsistency 开关),从而使开 发人员能够创建符合严格浮点语义的程序。采用该模型时,可以防止编译器对浮点计算使用大多数优 化,同时允许其对非浮点代码使用这些优化。但是,该一致性模型具有一个缺点。为了在不同的 FPU 体系结构中返回可预测的结果,几乎所有 /Op 实现都将中间表达式舍入到用户指定的精度;例如,考 虑下面的表达式:
float a, b, c, d, e; . . . a = b*c + d*e;
|
为了在使用 /Op 开关时产生一致的且可重复的结果,该表达式的计算方式按如下方式实现:
float x = b*c; float y = d*e; a = x+y;
|
现在,最终结果在计算该表达式的每一步 中都产生了单精度舍入误差。尽管这种解释在严 格意义上并未破坏任何 C++ 语义规则,但它几乎肯定不是计算浮点表达式的最佳方法。通常,以 尽可能高的可行精度计算中间结果 更为可取。例如,以如下所示的较高精度计算表达式 a=b*c+d*e 将会更好:
double x = b*c; double y = d*e; double z = x+y; a = (float)z; |
或者,采用以下方式会更好:
long double x = b*c; long double y = d*e long double z = x+y; a = (float)z;
|
在以较高精度计算中间结果时,最终结果显然会更为精确。具有讽刺意味的是,如果采用一致性模 型,则当用户试图通过禁用不安全的优化来减少误差时,出现误差的可能性却恰恰增加了。因此,一 致性模型不仅严重降低了效率,同时还无法对精确性的提高提供任何保证。对于认真的数值程序员而 言,这看起来不像是一个很好的折衷,这也是该模型没有被广泛接受的主要原因。
从版本 8.0 (Visual C++?2005) 开始,Microsoft C++ 编译器提供了一种更好的选择。它使程序 员可以选择以下三种常规浮点模式之一:fp:precise、fp:fast 和 fp:strict。
• 在 fp:precise 模式下,仅对浮点代码执行安全优化,并且与 /Op 不同,以最 高可行 精度一致性地执行中间计算。
• fp:fast 模式放松了浮点规则,允许以牺牲精确性为代价进行更为积极的优化。
• fp:strict 模式提供了 fp:precise 的所有常规正确性,同时启用了 fp- exception 语义,并禁止在存在 FPU 环境更改(例如,对寄存器精度、舍入方向的更改等等)时进行 非法转换。
可以通过命令行开关或编译器杂注单独控制浮点异常语义;默认情况下,在 fp:precise 模式下禁 用浮点异常语义,而在 fp:strict 模式下启用该语义。编译器还提供了对 FPU 环境敏感性和某些特 定于浮点的优化(如化简)的控制。这一简单明了的模型为开发人员提供了大量针对浮点代码编译的 控制,并且无须使用太多的编译器开关,也不会带来令人讨厌的副作用。
分享到:
相关推荐
审查代码规范 C++ 1. 范围 3 2. 规范性引用文件 3 3. 术语和定义 3 4. 开发工具 3 5. 目录结构 4 6. 布局规范 5 6.1. 对齐 5 6.2. 代码行内的空格 7 6.3. 空行 8 6.4. 代码行 9 6.5. 长行拆分 10 6.6. 注释 11 ...
前言 第1章 引论 1.1 计算有什么用? 1.1.1 基础科学 1.1.2 应用科学 1.1.3 工程项目 1.1.4 日常生活 1.2 超高精度计算有什么用? 1.3 计算编程概述 ... 第13章 浮点编程中的常见技巧 附录
【原书名】 C++ Primer (4th Edition) 【原出版社】 Addison Wesley/Pearson 【作者】 (美)Stanley B.Lippman,Josée LaJoie,Barbara E.Moo 【译者】 李师贤 蒋爱军 梅晓勇 林...18.1.1 C++中的内存分配 632 18.1.2 ...
C++编程人员在使用SSE指令函数编程时不必关心这些128位的寄存器,你可以使用128位的数据类型“__m128”和一系列C++函数来实现这些算术和逻辑操作,而决定程序使用哪个SSE寄存器以及代码优化是C++编译器的任务。...
12.4.3 代码是浮点型还是整型 473 12.4.4 EMMS准则 474 12.4.5 CPUID的检测MMX技术的用法 474 12.4.6 数据对齐 474 12.4.7 数据安排 475 12.4.8 应用程序最后的调整 476 12.5 本章小结 476 第13章 用DM642实现视频编...
12.4.3 代码是浮点型还是整型 473 12.4.4 EMMS准则 474 12.4.5 CPUID的检测MMX技术的用法 474 12.4.6 数据对齐 474 12.4.7 数据安排 475 12.4.8 应用程序最后的调整 476 12.5 本章小结 476 第13章 用DM642实现视频编...
12.4.3 代码是浮点型还是整型 473 12.4.4 EMMS准则 474 12.4.5 CPUID的检测MMX技术的用法 474 12.4.6 数据对齐 474 12.4.7 数据安排 475 12.4.8 应用程序最后的调整 476 12.5 本章小结 476 第13章 用DM642实现视频编...
12.4.3 代码是浮点型还是整型 473 12.4.4 EMMS准则 474 12.4.5 CPUID的检测MMX技术的用法 474 12.4.6 数据对齐 474 12.4.7 数据安排 475 12.4.8 应用程序最后的调整 476 12.5 本章小结 476 第13章 用DM642...
功能 支持多输入输出网络,支持多分支结构 ...算子合并和优化 支持 float 和量化 uint8 推理 支持训练后量化,使用浮点模型和量化校准集 平坦模型,支持零拷贝加载 更多详情、使用方法,请下载后阅读README.md文件
12.4.3 代码是浮点型还是整型 473 12.4.4 EMMS准则 474 12.4.5 CPUID的检测MMX技术的用法 474 12.4.6 数据对齐 474 12.4.7 数据安排 475 12.4.8 应用程序最后的调整 476 12.5 本章小结 476 第13章 用DM642实现视频编...
为准确解算仅有少数几个历元的GPS载波相位观测数据的病态定位方程,将GPS快速定位的病态法方程求解问题转化为一个函数优化问题,应用遗传算法求解病态方程,避免了法方程的求逆运算,从而可以得到参数的近似最优解。...
12.4.3 代码是浮点型还是整型 473 12.4.4 EMMS准则 474 12.4.5 CPUID的检测MMX技术的用法 474 12.4.6 数据对齐 474 12.4.7 数据安排 475 12.4.8 应用程序最后的调整 476 12.5 本章小结 476 第13章 用DM642实现视频编...
12.4.3 代码是浮点型还是整型 473 12.4.4 EMMS准则 474 12.4.5 CPUID的检测MMX技术的用法 474 12.4.6 数据对齐 474 12.4.7 数据安排 475 12.4.8 应用程序最后的调整 476 12.5 本章小结 476 第13章 用DM642实现视频编...
12.4.3 代码是浮点型还是整型 473 12.4.4 EMMS准则 474 12.4.5 CPUID的检测MMX技术的用法 474 12.4.6 数据对齐 474 12.4.7 数据安排 475 12.4.8 应用程序最后的调整 476 12.5 本章小结 476 第13章 用DM642...
instance.zip 使程序一运行一个实例(29KB)<END><br>48,div.zip 这是一个异常处理的例子,主要处理被零除的浮点操作DbgOut.shtml.htm(14KB)<END><br>49,cpu32.zip Intel的CPU检测程序的源代码,酷吧!...
12.4.3 代码是浮点型还是整型 473 12.4.4 EMMS准则 474 12.4.5 CPUID的检测MMX技术的用法 474 12.4.6 数据对齐 474 12.4.7 数据安排 475 12.4.8 应用程序最后的调整 476 12.5 本章小结 476 第13章 用DM642实现视频编...
8 Vivado HLS中的C++基本运算 23 9 测试平台的基本架构 25 9.1 Test Bench 25 9.2 C Test Bench 26 10 测试激励 28 11 测试输出检测与格式控制 28 11.1 Scoreboard 28 11.2 输出格式控制 30 12 接口综合基本介绍 33 ...
这一阶段一般使用高级语言(如C\C++),利用算法级建模和仿真工具(如Matlab,SPW)进行浮点和定点的仿真,进而对算法进行评估和优化。 3、 架构设计。根据设计的功能需求和算法分析的结果,设计芯片的架构,并对...
Matlab集成的c代码隐喻 用于数值计算,机器学习和计算机视觉的C ++库 Impressive Machines LLC,2015年 该库的最终目标是使用机器学习为图像,视频和音频识别创建一套独立的工具。 库中名为Core的部分非常稳定,并...
Turbo译码采用并行算法,为提高程序执行效率,充分利用Max-Log-Map译码算法的结构特点,对程序进行寄存器级优化:把Viusal C++实现的浮点算法改为定点算法,将前后向累积路径度量计算的最内层循环展开,合理分配...