Z b*_*son 43 c floating-point optimization gcc
我们N是一个编译时无符号整数.
GCC可以优化
unsigned sum = 0;
for(unsigned i=0; i<N; i++) sum += a; // a is an unsigned integer
Run Code Online (Sandbox Code Playgroud)
简单地说 a*N.这可以理解,因为模数运算说(a%k + b%k)%k = (a+b)%k.
但GCC不会优化
float sum = 0;
for(unsigned i=0; i<N; i++) sum += a; // a is a float
Run Code Online (Sandbox Code Playgroud)
到a*(float)N.
但是通过使用关联数学,-Ofast我发现GCC可以按顺序减少这一点log2(N).例如,因为N=8它可以在三个添加中进行总和.
sum = a + a
sum = sum + sum // (a + a) + (a + a)
sum = sum + sum // ((a + a) + (a + a)) + ((a + a) + (a + a))
Run Code Online (Sandbox Code Playgroud)
虽然在N=16海湾合作委员会回到做N-1总结之后有一点.
我的问题是,为什么不GCC做a*(float)N用-Ofast?
而不是O(N)或O(Log(N))可能是简单的O(1).由于N在编译时已知,因此可以确定是否N适合浮点数.即使N对于漂浮物而言太大也无法做到sum =a*(float)(N & 0x0000ffff) + a*(float)(N & ffff0000).实际上,我做了一些测试来检查准确性,a*(float)N无论如何都更准确(参见下面的代码和结果).
//gcc -O3 foo.c
//don't use -Ofast or -ffast-math or -fassociative-math
#include <stdio.h>
float sumf(float a, int n)
{
float sum = 0;
for(int i=0; i<n; i++) sum += a;
return sum;
}
float sumf_kahan(float a, int n)
{
float sum = 0;
float c = 0;
for(int i=0; i<n; i++) {
float y = a - c;
float t = sum + y;
c = (t -sum) - y;
sum = t;
}
return sum;
}
float mulf(float a, int n)
{
return a*n;
}
int main(void)
{
int n = 1<<24;
float a = 3.14159;
float t1 = sumf(a,n);
float t2 = sumf_kahan(a,n);
float t3 = mulf(a,n);
printf("%f %f %f\n",t1,t2,t3);
}
Run Code Online (Sandbox Code Playgroud)
结果61848396.000000 52707136.000000 52707136.000000表明乘法和Kahan求和具有相同的结果,我认为这表明乘法比简单求和更准确.
之间存在一些根本区别
float funct( int N, float sum )
{
float value = 10.0;
for( i = 0; i < N ;i ++ ) {
sum += value;
}
return sum;
}
Run Code Online (Sandbox Code Playgroud)
和
float funct( int N, float sum )
{
float value = 10.0;
sum += value * N;
return sum;
}
Run Code Online (Sandbox Code Playgroud)
当总和接近 FLT_EPSILON * 大于值时,重复的总和趋向于无操作。因此,任何较大的 N 值都不会导致重复加法的总和发生变化。对于乘法选择,结果(值 * N)需要比总和小 FLT_EPSILON * 才能使操作成为无操作。
因此编译器无法进行优化,因为它无法判断您是否想要确切的行为(乘法更好),或者实现的行为(总和的范围影响加法的结果)。
| 归档时间: |
|
| 查看次数: |
1636 次 |
| 最近记录: |