【发布时间】:2021-07-31 16:46:54
【问题描述】:
以下 3 行使用 "gcc -Ofast -march=skylake" 给出了不精确的结果:
int32_t i = -5;
const double sqr_N_min_1 = (double)i * i;
1. - ((double)i * i) / sqr_N_min_1
显然,sqr_N_min_1 得到 25.,并且在第三行中,(-5 * -5) / 25 应该变为 1.,因此第三行的总体结果正好是 0.。事实上,编译器选项 "gcc -O3 -march=skylake" 也是如此。
但是使用 "-Ofast" 最后一行产生 -2.081668e-17 而不是 0. 和其他 i 而不是 -5(例如 6 或 7)它得到与0. 的其他非常小的正或负随机偏差。
我的问题是:这种不精确的根源在哪里?
为了调查这个问题,我用 C 编写了一个小测试程序:
#include <stdint.h> /* int32_t */
#include <stdio.h>
#define MAX_SIZE 10
double W[MAX_SIZE];
int main( int argc, char *argv[] )
{
volatile int32_t n = 6; /* try 6 7 or argv[1][0]-'0' */
double *w = W;
int32_t i = 1 - n;
const int32_t end = n - 1;
const double sqr_N_min_1 = (double)i * i;
/* Here is the crucial part. The loop avoids the compiler replacing it with constants: */
do {
*w++ = 1. - ((double)i * i) / sqr_N_min_1;
} while ( (i+=2) <= end );
/* Then, show the results (only the 1st and last output line matters): */
w = W;
i = 1 - n;
do {
fprintf( stderr, "%e\n", *w++ );
} while ( (i+=2) <= end );
return( 0 );
}
Godbolt 向我展示了由 "x86-64 gcc9.3" 生成的程序集,带有选项 "-Ofast -march=skylake" 与 " -O3 -march=skylake"。请检查网站的五个栏目(1. 源代码,2. "-Ofast" 汇编,3. "-O3" 汇编,4. 输出第 1 次装配,5. 第 2 次装配的输出):
Godbolt site with five columns
正如您所见,程序集的差异很明显,但我无法弄清楚不精确的确切来源。那么,问题是,哪些汇编指令对此负责?
后续问题是:是否有可能通过重新编写 C 程序来避免使用“-Ofast -march=skylake”的这种不精确性?
【问题讨论】:
-
什么不精确?它比“正常”的 FP 不精确性更糟吗?请有问题的详细信息,而不是链接。
-
-Ofast不是很稳定,可能会偏离标准 C。它可能会在某处偷工减料以降低速度的准确性。 -
一眼看去,
-Ofast使用vfnmadd132sd计算1- i*i/sqr_N_min_1作为1 - y*z其中y确实是i*i但z是1 / sqr_N_min_1(计算在循环之前)。另一个版本使用普通的vmulsd/vmulsb/vsubsd。取倒数会影响精度,以及 FMA 比等效的三个指令序列具有更高的精度这一事实。 -
你知道
-Ofast是-O3 -ffast-math的同义词,对吧?-ffast-math的一部分是-funsafe-math-optimizations。这正是您通过使用该选项所要求的那种速度超过精度的优化。如果您不想这样做,请不要启用所有-ffast-math子选项。
标签: c assembly gcc x86-64 fast-math