2012-02-16 52 views
8

GCC的vector extensions提供了一個很好的,合理的便攜方式訪問不同硬件架構上的一些SIMD指令,而不訴諸於hardware specific intrinsics(或自動矢量化)。爲GCC的向量擴展加載數據

一個真實的用例,正在計算一個簡單的加法校驗和。不清楚的一件事是如何安全地將數據加載到矢量中。

typedef char v16qi __attribute__ ((vector_size(16))); 

static uint8_t checksum(uint8_t *buf, size_t size) 
{ 
    assert(size%16 == 0); 
    uint8_t sum = 0; 

    vec16qi vec = {0}; 
    for (size_t i=0; i<(size/16); i++) 
    { 
     // XXX: Yuck! Is there a better way? 
     vec += *((v16qi*) buf+i*16); 
    } 

    // Sum up the vector 
    sum = vec[0] + vec[1] + vec[2] + vec[3] + vec[4] + vec[5] + vec[6] + vec[7] + vec[8] + vec[9] + vec[10] + vec[11] + vec[12] + vec[13] + vec[14] + vec[15]; 

    return sum; 
} 

鑄造的指針向量類型似乎工作,但我很擔心,如果SIMD硬件預計矢量類型正確對齊,這可能在一個可怕的方式爆炸。

我想到的唯一的其他選擇是使用臨時向量並顯式加載值(通過memcpy或元素明智的賦值),但是在測試中,這抵消了大部分加速獲得的SIMD指令的使用。理想情況下,我會想象這將是一個通用的__builtin_load()函數,但似乎都不存在。

將數據加載到矢量中的更安全方式可能會導致對齊問題?

+2

在GCC x86_64的上對齊的內存運行,這將導致SIGSEGV,當CPU嘗試將未對齊的內存加載到SSE寄存器時。 一個合理的選擇似乎只是校驗和對齊的內存,或者使用正常循環將字節總和,直到第一個16字節邊界。 – dcoles 2012-02-17 00:18:39

+0

在您當前的代碼中,如果編譯器知道輸入(但總和不好),加載數據實際上編譯得很好:https://godbolt.org/g/DeR3Qv。沒有關於輸入的知識就不太好:https:// godbolt。組織/克/ LxEkhp – ZachB 2016-09-21 18:14:20

回答

0

你可以使用一個初始化加載的價值觀,即做

const vec16qi e = { buf[0], buf[1], ... , buf[15] } 

,並希望GCC變成SSE加載指令這一點。我會用一個反彙編器來驗證,但是;-)。此外,爲了獲得更好的性能,您嘗試使對齊的16字節對齊,並通過aligned屬性通知該編譯器。如果可以保證輸入緩衝區將對齊,按字節方式處理它,直到達到16字節的邊界。

+0

我不認爲調整buf是必要的。這將是,如果我們正在處理指針。 – user1095108 2013-10-15 22:06:08

+0

@ user1095108您希望編譯器將其轉換爲SSE加載指令,該指令相當於'e = * buf'(但由於類型不匹配,您無法這麼寫)。所以你實際上在這裏處理指針。如果編譯器可以推斷出buf是16字節對齊的,那麼它可以使用一個對齊的負載,該負載比pre-ivy-bridge至少快於未對齊的負載。 – fgp 2013-10-16 13:17:45

+0

不,如果您是根據我的經驗將「buf」投射到「vec16qi」,那麼您會處理指針。 – user1095108 2013-10-16 14:16:45

1

編輯(感謝彼得·科德斯)可以投三分球:

typedef char v16qi __attribute__ ((vector_size (16), aligned (16))); 

v16qi vec = *(v16qi*)&buf[i]; // load 
*(v16qi*)(buf + i) = vec; // store whole vector 

這編譯爲vmovdqa加載和vmovups存儲。如果不知道數據是否對齊,請設置aligned (1)以生成vmovdqu。 (godbolt

注意,也有裝載了幾個特殊用途的內建和卸載這些寄存器(編輯2):

v16qi vec = _mm_loadu_si128((__m128i*)&buf[i]); // _mm_load_si128 for aligned 
_mm_storeu_si128((__m128i*)&buf[i]), vec); // _mm_store_si128 for aligned 

這似乎是必要使用-flax-vector-conversionschar s到去到v16qi與此功能。

參見:C - How to access elements of vector using GCC SSE vector extension
參見:SSE loading ints into __m128

(提示:最好的短語谷歌是一樣的東西「GCC負荷__m128i」)

+1

顯然,將未對齊數據加載到GNU C矢量的推薦方法是在聲明矢量類型時使用'aligned(1)'屬性,並將指針指向該未對齊矢量類型。例如'typedef char __attribute__((vector_size(16),aligned(1)))unaligned_byte16;'。請參閱[我的答案的結尾](http://stackoverflow.com/a/39115055/224132)和Marc Glisse對此的評論。 – 2016-09-21 07:03:29

+0

@PeterCordes謝謝!編輯答案,要簡單得多。 – ZachB 2016-09-21 18:41:04

+0

爲了提取,我認爲你應該使用'vec [0]'。據我瞭解,矢量類型上的別名標量指針是* not * ok。它適用於'char *',因爲'char *'是特殊的,並且允許別名。將'int *'鑄造到'v4si *'甚至不會算作別名,因爲v4si是用'int'定義的。因爲一個額外的屬性,英特爾內在函數類型('__m128i')可以別名到其他的東西:'typedef long long __m128i __attribute__((__vector_size__(16),__may_alias __));'沒有may_alias, ivec = *(v4si)short_pointer'。我在 – 2016-09-21 19:20:14