基于GCC的联想数学
我在C中创建了一个数据类型。我尝试了使用GCC的基于GCC的联想数学,c,gcc,floating-point,precision,C,Gcc,Floating Point,Precision,我在C中创建了一个数据类型。我尝试了使用GCC的-Ofast,发现它的速度非常快(例如,使用-O3的速度为1.5s,使用-Ofast的速度为0.3s),但结果是假的。我把这个问题归结为社交数学。我很惊讶这不起作用,因为我明确定义了操作的关联性。例如,在下面的代码中,我只使用了括号 static inline doublefloat two_sum(const float a, const float b) { float s = a + b; float v =
-Ofast
,发现它的速度非常快(例如,使用-O3
的速度为1.5s,使用-Ofast
的速度为0.3s),但结果是假的。我把这个问题归结为社交数学。我很惊讶这不起作用,因为我明确定义了操作的关联性。例如,在下面的代码中,我只使用了括号
static inline doublefloat two_sum(const float a, const float b) {
float s = a + b;
float v = s - a;
float e = (a - (s - v)) + (b - v);
return (doublefloat){s, e};
}
因此,我不希望GCC会改变,例如(a-(s-v))
到((a+v)-s)
,即使使用-fassocialmath
。那么,为什么使用-fassocialmath
(而且速度更快)得出的结果会如此错误呢
我用MSVC尝试了/fp:fast
(在将代码转换为C++之后),结果是正确的,但并不比/fp:precise
快
根据GCC手册中关于-fassocialative math
的规定
允许在一系列浮点运算中重新关联操作数。这违反了ISOC和C++语言标准,可能会改变计算结果。注:重新订购也可能会更改零的符号
as忽略NAN并禁止或创建下溢或上溢(因此不能用于依赖舍入行为的代码,如“(x+2^52)-2^52)。也可以对浮点比较进行重新排序,因此可能不会
当需要有序比较时使用。此选项要求-fno有符号零和-fno陷阱数学都有效。此外,它对-fno陷阱数学没有多大意义
编辑:
我使用整数(有符号和无符号)和浮点进行了一些测试,以检查GCC是否简化了关联操作
//test1.c
unsigned foosu(unsigned a, unsigned b, unsigned c) { return (a + c) - b; }
signed fooss(signed a, signed b, signed c) { return (a + c) - b; }
float foosf(float a, float b, float c) { return (a + c) - b; }
unsigned foomu(unsigned a, unsigned b, unsigned c) { return a*a*a*a*a*a; }
signed fooms(signed a, signed b, signed c) { return a*a*a*a*a*a; }
float foomf(float a, float b, float c) { return a*a*a*a*a*a; }
及
我遵守了O3和Ofast的要求,我查看了生成的程序集,这就是我观察到的
- 无符号:加法和乘法的代码相同(减少为三次乘法)
- 有符号:加法的代码不相同,但乘法的代码相同(减少为三次乘法)
- 浮点:与
的加法或乘法的代码不相同,但与-O3
的加法相同,乘法几乎相同,仅使用三次乘法-Ofast
- 如果一个操作是关联的,那么GCC将简化它,不管它选择什么,这样
就可以变成a-(b-c)
(a+c)-b
- 无符号加法和乘法是结合的
- 有符号加法不是关联的
- 有符号乘法是结合的
- 使用
时,整数和浮点的乘法简化为三次-fassocialative math
使浮点加法和乘法具有关联性-fassocialative math
(a-(s-v))
转换为((a+v)-s)
人们可能会认为这在-fassocialmath
中很明显,但在某些情况下,程序员可能希望浮点在一种情况下是关联的,而在另一种情况下是非关联的。但如果这样做,双浮点就不能在同一个模块中使用。因此,唯一的选择是将关联浮点函数n一个模块和另一个模块中的非关联浮点函数,并将它们编译成单独的对象文件
我很惊讶这不起作用,因为我显式地定义了操作的关联性
这正是-fasociative math
所做的:它忽略了程序定义的顺序(与没有括号的定义相同)通常,对于二重加法,错误项被计算为0,因为如果浮点运算是关联的,它将等于0。e=0;
比e=(a-…;
快得多,但当然,这是错误的
在C99标准中,6.5.6:1中的以下语法规则意味着x+y+z
只能解析为(x+y)+z
:
additive-expression:
multiplicative-expression
additive-expression + multiplicative-expression
additive-expression - multiplicative-expression
加法表达式:
乘法表达式
加法表达式+乘法表达式
加法表达式-乘法表达式
显式括号和对中间左值的赋值并不妨碍-fassocialative math
的工作。即使没有括号和赋值,顺序也是定义的(如果是一系列的加减法,从左到右),您告诉编译器忽略已定义的顺序。事实上,在应用优化的中间表示法上,我怀疑信息是否仍然存在,顺序是由中间赋值、括号还是语法强加的
您可以尝试将希望按照C标准规定的顺序编译的所有函数放在不使用-fasociative math
编译的同一个编译单元中,或者在整个程序中完全避免使用此标志。如果您坚持在使用-f编译的编译单元中保留双加关联数学
,您可以尝试使用volatile
变量,但是volatile
类型限定符只将访问左值作为一个可观察的事件,它不会强制进行正确的计算。当然没有人会将(a-(s-v))
重新解释为((a-s)-v)
。这不是这种“关联性”所指的。v=(a+b)-a
被简化为b
,正如文档所说。这正是代码的类型
additive-expression:
multiplicative-expression
additive-expression + multiplicative-expression
additive-expression - multiplicative-expression