2014-09-26 46 views
1

我一直在嘗試使用SSE內在函數,我似乎遇到了一個我無法弄清楚的奇怪的錯誤。我正在計算兩個浮點數組的內部產物,每次4個元素。SSE內在函數運算錯誤

爲了測試我已經將兩個數組的每個元素設置爲1,所以產品應該是==大小。

它運行正常,但每當我運行大小>〜68000000的代碼時,使用sse intrinsics的代碼開始計算錯誤的內積。它似乎陷入了一定的數額,永遠不會超過這個數字。下面是一個例子來看:

joe:~$./test_sse 70000000 
sequential inner product: 70000000.000000 
sse  inner product: 67108864.000000 
sequential   time: 0.417932 
sse     time: 0.274255 

編譯:

gcc -fopenmp test_sse.c -o test_sse -std=c99 

這個錯誤似乎是我測試過它的計算機的少數之間是一致的。下面是代碼,也許有人也許能幫助我弄清楚是怎麼回事:

#include <stdio.h> 
#include <stdlib.h> 
#include <time.h> 
#include <omp.h> 
#include <math.h> 
#include <assert.h> 

#include <xmmintrin.h> 

double inner_product_sequential(float * a, float * b, unsigned int size) { 

    double sum = 0; 

    for(unsigned int i = 0; i < size; i++) { 
    sum += a[i] * b[i]; 
    } 

    return sum; 

} 

double inner_product_sse(float * a, float * b, unsigned int size) { 

    assert(size % 4 == 0); 

    __m128 X, Y, Z; 

    Z = _mm_set1_ps(0.0f); 

    float arr[4] __attribute__((aligned(sizeof(float) * 4))); 

    for(unsigned int i = 0; i < size; i += 4) { 

    X = _mm_load_ps(a+i); 
    Y = _mm_load_ps(b+i); 

    X = _mm_mul_ps(X, Y); 
    Z = _mm_add_ps(X, Z); 

    } 

    _mm_store_ps(arr, Z); 

    return arr[0] + arr[1] + arr[2] + arr[3]; 

} 

int main(int argc, char ** argv) { 

    if(argc < 2) { 
    fprintf(stderr, "usage: ./test_sse <size>\n"); 
    exit(EXIT_FAILURE); 
    } 

    unsigned int size = atoi(argv[1]); 

    srand(time(0)); 

    float *a = (float *) _mm_malloc(size * sizeof(float), sizeof(float) * 4); 
    float *b = (float *) _mm_malloc(size * sizeof(float), sizeof(float) * 4); 

    for(int i = 0; i < size; i++) { 
    a[i] = b[i] = 1; 
    } 



    double start, time_seq, time_sse; 


    start = omp_get_wtime(); 

    double inner_seq = inner_product_sequential(a, b, size); 

    time_seq = omp_get_wtime() - start; 


    start = omp_get_wtime(); 

    double inner_sse = inner_product_sse(a, b, size); 

    time_sse = omp_get_wtime() - start; 


    printf("sequential inner product: %f\n", inner_seq); 
    printf("sse  inner product: %f\n", inner_sse); 
    printf("sequential   time: %f\n", time_seq); 
    printf("sse     time: %f\n", time_sse); 




    _mm_free(a); 
    _mm_free(b); 
} 
+1

你有沒有考慮過你用完精度的可能性? – Mysticial 2014-09-26 04:22:41

+1

您是否注意到67108864是2^26?你認爲這只是一個巧合嗎? – rici 2014-09-26 04:34:45

+0

您是以32位或64位模式編譯? – 2014-09-26 09:52:22

回答

2

您正在運行到單精度浮點數的精度限制。當達到「極限」內積時,矢量Z的每個分量的值16777216(2^24)以32位浮點表示爲十六進制0x4b800000或二進制0 10010111 00000000000000000000000,即23位尾數是全零(隱含前導1位),而8位指數部分是151,表示指數151-127 = 24。如果將1加到該值,則需要增加指數,但添加的指數不能是不再以尾數表示,所以在單精度浮點運算2^24 + 1 = 2^24中。

在順序函數中沒有看到這種情況,因爲您使用的是64位雙精度值來存儲結果,而且由於我們正在x86平臺上工作,所以在內部最有可能是80位超量精度寄存器用來。

您可以強制通過重寫它作爲

float sum; 

float inner_product_sequential(float * a, float * b, unsigned int size) { 
    sum = 0; 

    for(unsigned int i = 0; i < size; i++) { 
    sum += a[i] * b[i]; 
    } 

    return sum; 
} 

在連續的代碼中使用整個單精度,你會看到16777216.000000爲最大計算值。

+0

謝謝你指出。這實際上是一個疏忽,我宣佈和數爲雙倍。我們想到的問題是,SSE指令是否針對FPU用於正常指令的每個打包值使用相同的80位寄存器進行浮點運算。 – joemasterjohn 2014-09-26 17:46:03

+0

SSE指令使用與標量浮點指令不同的寄存器。 SSE寄存器是128位寬,可以包含四個單精度值,如你的例子。 – 2014-09-26 21:29:55

+0

沒錯,但是我要問的是,128位SSE寄存器中的四個打包浮點數是否都是通過擴展精度80位寄存器處理其各自的op?因爲如果不是這樣,那麼對於使用SSE指令與單值指令執行的相同計算,可能會遇到複雜的舍入和溢出錯誤。或者是中間值的精度損失與使用SSE的折衷? – joemasterjohn 2014-09-27 22:15:08