[英]floating point multiplication vs repeated addition
設N
是編譯時無符號整數。
GCC可以優化
unsigned sum = 0;
for(unsigned i=0; i<N; i++) sum += a; // a is an unsigned integer
簡單地說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
到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))
雖然在N=16
GCC之后的一些點回到做N-1
總和。
我的問題是為什么GCC不用-Ofast
做a*(float)N
?
它可以簡單地為O(1)
而不是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);
}
結果是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;
}
和
float funct( int N, float sum )
{
float value = 10.0;
sum += value * N;
return sum;
}
當總和接近FLT_EPSILON *大於值時,重復的和傾向於無操作。 因此任何大的N值都不會導致重復加法的總和沒有變化。 對於乘法選擇,結果(值* N)需要FLT_EPSILON *小於具有無操作的操作的總和。
因此編譯器無法進行優化,因為它無法判斷您是否需要確切的行為(乘法更好),或者實現的行為,其中sum的大小會影響加法的結果。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.