2012-10-19 85 views
2

我在使用*+時使用fmaf函數的性能正在下降。我在兩臺Linux機器上,使用g ++ 4.4.3和g ++ 4.6.3fmaf奇怪的表現

在兩臺不同的機器上,如果在不使用fmaf的情況下填充了myOut向量,以下代碼運行得更快。

與克服務器++ 4.6.3和Intel(R)至強(R)CPU E5-2650 @ 2.00GHz

$ ./a.out fmaf 
Time: 1.55008 seconds. 
$ ./a.out muladd 
Time: 0.403018 seconds. 

與克服務器++ 4.4.3和Intel(R)至強(R)CPU X5650 @ 2.67GHz

$ ./a.out fmaf 
Time: 0.547544 seconds. 
$ ./a.out muladd 
Time: 0.34955 seconds. 

不應該fmaf版本(除了以避免額外的綜述,然後更精確)快?

#include <stddef.h> 
#include <iostream> 
#include <math.h> 
#include <string.h> 
#include <stdlib.h> 

#include <sys/time.h> 

int main(int argc, char** argv) { 
    if (argc != 2) { 
    std::cout << "missing parameter: 'muladd' or 'fmaf'" 
       << std::endl; 
    exit(-1); 
    } 
    struct timeval start,stop,result; 
    const size_t mySize = 1e6*100; 

    float* myA = new float[mySize]; 
    float* myB = new float[mySize]; 
    float* myC = new float[mySize]; 
    float* myOut = new float[mySize]; 

    gettimeofday(&start,NULL); 
    if (!strcmp(argv[1], "muladd")) { 
    for (size_t i = 0; i < mySize; ++i) { 
     myOut[i] = myA[i]*myB[i]+myC[i]; 
    } 
    } else if (!strcmp(argv[1], "fmaf")) { 
    for (size_t i = 0; i < mySize; ++i) { 
     myOut[i] = fmaf(myA[i], myB[i], myC[i]); 
    } 
    } else { 
    std::cout << "specify 'muladd' or 'fmaf'" << std::endl; 
    exit(-1); 
    } 

    gettimeofday(&stop,NULL); 
    timersub(&stop,&start,&result); 
    std::cout << "Time: " << result.tv_sec + result.tv_usec/1000.0/1000.0 
      << " seconds." << std::endl; 

    delete []myA; 
    delete []myB; 
    delete []myC; 
    delete []myOut; 
} 

回答

2

您的問題的答案被稱爲矢量化。當g++ -O3 -S編譯通過比較G ++ 4.4.6代碼的兩部分產生的彙編代碼:

muladd部分:

.L10: 
    movaps %xmm2, %xmm0 
    movaps %xmm2, %xmm1 
    movlps (%rbx,%rax), %xmm0 
    movlps (%r12,%rax), %xmm1 
    movhps 8(%rbx,%rax), %xmm0 
    movhps 8(%r12,%rax), %xmm1 
    mulps %xmm1, %xmm0 
    movaps %xmm2, %xmm1 
    movlps 0(%rbp,%rax), %xmm1 
    movhps 8(%rbp,%rax), %xmm1 
    addps %xmm1, %xmm0 
    movaps %xmm0, 0(%r13,%rax) 
    addq $16, %rax 
    cmpq $400000000, %rax 
    jne  .L10 

所有這些*ps進行了打包單精度數運算。這些是SSE指令,因此每個數據包由每個數組的4個連續元素組成。

它實現了fmaf版本的循環爲:

.L14: 
    movss (%rbx,%r14,4), %xmm0 
    movss 0(%rbp,%r14,4), %xmm2 
    movss (%r12,%r14,4), %xmm1 
    call fmaf 
    movss %xmm0, 0(%r13,%r14,4) 
    addq $1, %r14 
    cmpq $100000000, %r14 
    jne  .L14 

這裏標量SSE指令用於將一個數組元素在一個函數調用fmaf是在每次迭代由時間移動數據。

循環的向量部分比較長,但執行次數少了4次。

2

據我所知,英特爾至強處理器不支持熔合乘法 - 加法指令。維基百科表示這些可用於AMD Piledriver和Bulldozer架構處理器,而英特爾在2013/14年才推出Haswell/Broadwell。因此,如果沒有直接的指令支持,fmaf函數可能被編譯爲模擬指令的實際函數調用。因此,函數調用開銷加上實際的乘法和加法指令。非fmaf選項產生內聯乘法和加法指令,不帶函數調用開銷,因此速度更快。如有疑問,請使用g++ -S,並檢查生成的彙編代碼。

此外,內聯代碼可以更好地優化甚至矢量化(如另一個答案中所述),但當然,結果取決於編譯時通過的編譯器和確切的標誌。

+0

「函數調用開銷加上實際的乘法和加法指令」:實現不提供指令的處理器的fmaf需要乘法和加法。下面是libc如何執行FPU四捨五入模式的變化:http://www.sourceware.org/ml/libc-alpha/2010-10/msg00007.html –