眾所周知,GCC/CLang 使用 SIMD 指令可以很好地自動矢量化回圈。
此外,已知存在alignas()標準 C 屬性,該屬性除其他用途外還允許對齊堆疊變數,例如以下代碼:
在線試試吧!
#include <cstdint>
#include <iostream>
int main() {
alignas(1024) int x[3] = {1, 2, 3};
alignas(1024) int (&y)[3] = *(&x);
std::cout << uint64_t(&x) % 1024 << " "
<< uint64_t(&x) % 16384 << std::endl;
std::cout << uint64_t(&y) % 1024 << " "
<< uint64_t(&y) % 16384 << std::endl;
}
輸出:
0 9216
0 9216
這意味著x和y都在 1024 位元組而不是 16384 位元組的堆疊上對齊。
現在讓我們看看另一個代碼:
在線試試吧!
#include <cstdint>
void f(uint64_t * x, uint64_t * y) {
for (int i = 0; i < 16; i)
x[i] ^= y[i];
}
如果-std=c 20 -O3 -mavx512f在 GCC 上使用屬性編譯,它會生成以下 asm 代碼(提供部分代碼):
vmovdqu64 zmm1, ZMMWORD PTR [rdi]
vpxorq zmm0, zmm1, ZMMWORD PTR [rsi]
vmovdqu64 ZMMWORD PTR [rdi], zmm0
vmovdqu64 zmm0, ZMMWORD PTR [rsi 64]
vpxorq zmm0, zmm0, ZMMWORD PTR [rdi 64]
vmovdqu64 ZMMWORD PTR [rdi 64], zmm0
其中兩次是 AVX-512 未對齊加載 異或 未對齊存盤。所以我們可以理解,我們的 64 位陣列異或操作被 GCC 自動向量化以使用 AVX-512 暫存器,并且回圈也被展開。
我的問題是如何告訴GCC,提供給函式指標x,并y都對準64個位元組,因此,與其非對齊負載(vmovdqu64),如上面的代碼,我可以強制GCC使用對齊負載(vmovdqa64)。眾所周知,對齊的加載/存盤可以相當快。
我第一次嘗試強制 GCC 進行對齊加載/存盤是通過以下代碼:
在線試試吧!
#include <cstdint>
void g(uint64_t (&x_)[16],
uint64_t const (&y_)[16]) {
alignas(64) uint64_t (&x)[16] = x_;
alignas(64) uint64_t const (&y)[16] = y_;
for (int i = 0; i < 16; i)
x[i] ^= y[i];
}
但此代碼仍會產生vmovdqu64與上面(先前代碼片段的)asm 代碼相同的未對齊加載 ( )。因此這個alignas(64)提示沒有提供任何有用的東西來改進 GCC 匯編代碼。
我的問題是如何強制 GCC 進行對齊的自動矢量化,除了為所有操作手動撰寫 SIMD 內在函式,如_mm512_load_epi64()?
If possible I need solutions for all of GCC/CLang/MSVC.
uj5u.com熱心網友回復:
盡管并非對所有編譯器都完全可移植,但__builtin_assume_aligned會告訴 GCC 假設指標已對齊。
我經常使用一種不同的策略,它使用輔助結構更具可移植性:
template<size_t Bits>
struct alignas(Bits/8) uint64_block_t
{
static const size_t bits = Bits;
static const size_t size = bits/64;
std::array<uint64_t,size> v;
uint64_block_t& operator&=(const uint64_block_t& v2) { for (size_t i = 0; i < size; i) v[i] &= v2.v[i]; return *this; }
uint64_block_t& operator^=(const uint64_block_t& v2) { for (size_t i = 0; i < size; i) v[i] ^= v2.v[i]; return *this; }
uint64_block_t& operator|=(const uint64_block_t& v2) { for (size_t i = 0; i < size; i) v[i] |= v2.v[i]; return *this; }
uint64_block_t operator&(const uint64_block_t& v2) const { uint64_block_t tmp(*this); return tmp &= v2; }
uint64_block_t operator^(const uint64_block_t& v2) const { uint64_block_t tmp(*this); return tmp ^= v2; }
uint64_block_t operator|(const uint64_block_t& v2) const { uint64_block_t tmp(*this); return tmp |= v2; }
uint64_block_t operator~() const { uint64_block_t tmp; for (size_t i = 0; i < size; i) tmp.v[i] = ~v[i]; return tmp; }
bool operator==(const uint64_block_t& v2) const { for (size_t i = 0; i < size; i) if (v[i] != v2.v[i]) return false; return true; }
bool operator!=(const uint64_block_t& v2) const { for (size_t i = 0; i < size; i) if (v[i] != v2.v[i]) return true; return false; }
bool get_bit(size_t c) const { return (v[c/64]>>(c%64))&1; }
void set_bit(size_t c) { v[c/64] |= uint64_t(1)<<(c%64); }
void flip_bit(size_t c) { v[c/64] ^= uint64_t(1)<<(c%64); }
void clear_bit(size_t c) { v[c/64] &= ~(uint64_t(1)<<(c%64)); }
void set_bit(size_t c, bool b) { v[c/64] &= ~(uint64_t(1)<<(c%64)); v[c/64] |= uint64_t(b ? 1 : 0)<<(c%64); }
size_t hammingweight() const { size_t w = 0; for (size_t i = 0; i < size; i) w = mccl::hammingweight(v[i]); return w; }
bool parity() const { uint64_t x = 0; for (size_t i = 0; i < size; i) x ^= v[i]; return mccl::hammingweight(x)%2; }
};
然后使用 reinterpret_cast 將指向 uint64_t 的指標轉換為指向該結構的指標。
將 uint64_t 上的回圈轉換為這些塊上的回圈通常可以很好地自動矢量化。
uj5u.com熱心網友回復:
剛才@MarcStevens通過使用__builtin_assume_aligned為我的問題提出了一個可行的解決方案:
在線試試吧!
#include <cstdint>
void f(uint64_t * x_, uint64_t * y_) {
uint64_t * x = (uint64_t *)__builtin_assume_aligned(x_, 64);
uint64_t * y = (uint64_t *)__builtin_assume_aligned(y_, 64);
for (int i = 0; i < 16; i)
x[i] ^= y[i];
}
它實際上生成具有對齊vmovdqa64指令的代碼。
但只有 GCC 產生對齊指令。CLang 仍然使用 unaligned,請參見此處,CLang 也僅使用 16 個以上元素的 AVX-512 暫存器。
所以仍然歡迎 CLang 和 MSVC 解決方案。
uj5u.com熱心網友回復:
正如我從您自己的回答中暗示的那樣,您也對 MSVC 解決方案感興趣。
MSVC 了解 的正確使用alignas以及它自己的__declspec(align),它也了解__builtin_assume_aligned,但它故意不想做任何已知對齊的事情。
我的報告以“重復”結束:
- 編譯器不利用 alignas 或 __builtin_assume_aligned 提供的對齊
相關報告以“不是錯誤”結束:
- [MSConnect 3068950] - C :為 alignof(16) 資料而不是 MOVAPS 生成 MOVUPS
- SSSE/AVX 指令生成中的回歸(來自 VS 2015)((V)MOVUPS 而不是 (V)MOVAPS)
MSVC 仍然利用全域變數的對齊方式,如果它可以觀察到指標指向全域變數。即使這樣也不是在所有情況下都有效。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/363488.html
標籤:c performance simd avx512
