我目前正試圖實現我的一些現有的標量代碼的AVX2版本(Haswell CPU)。它實現這樣的步驟:AVX2聚集加載兩個整數的結構
struct entry {
uint32_t low, high;
};
// both filled with "random" data in previous loops
std::vector<entry> table;
std::vector<int> queue; // this is strictly increasing but
// without a constant delta
for (auto index : queue) {
auto v = table[index];
uint32_t rank = v.high + __builtin_popcount(_bzhi_u32(v.low, index % 32));
use_rank(rank); // contains a lot of integer operations which nicely map to avx2
}
我實現了這個與2聚集指令,每個負載這樣的INT32:
__m256iv_low = _mm256_i32gather_epi32 (reinterpret_cast<int *>(table.data()) + 0, index, 8);
__m256i v_high = _mm256_i32gather_epi32 (reinterpret_cast<int *>(table.data()) + 1, index, 8);
有一個更快的方法兩個加載這些值?我曾考慮過使用2 64位加載(只發出讀取數量的一半=執行端口的流量更少),然後將結果向量洗牌以獲得v_low和v_high,但令人傷心的是,據我所知,大部分洗牌函數只允許分別對128位進行混洗。
編輯保羅R: 此代碼是使用我的壓縮算法中使用的Burrows惠勒變換的子字符串枚舉例程的一部分。 table
包含位矢量上的等級數據。高部分包含先前條目中的數目,下部分被屏蔽掉並被彈出,然後被添加以獲得給定索引前面的最終設置位數。之後,更多的計算髮生,幸運地是很好的並行化。
隊列中的增量在開始和結束時都非常高(由於算法的性質)。這導致了很多緩存未命中的情況,這就是爲什麼我使用轉換從SoA切換到AoS的原因,以減少標量代碼中加載端口的壓力。
使用SoA也會導致相同的獨立採集指令,但會使訪問的緩存行數量加倍。
編輯(部分回答): 我試圖使用兩個_mm_i32gather_epi64
到存儲器一半訪問的次數(以及因此週期,見here)。
__m256i index; // contains the indices
__m128i low = _mm256_extractf128_si256(index, 0);
__m128i high = _mm256_extractf128_si256(index, 1);
__m256i v_part1 = _mm256_i32gather_epi64(reinterpret_cast<long long int*>(table.data()), low , 8);
__m256i v_part2 = _mm256_i32gather_epi64(reinterpret_cast<long long int*>(table.data()), high, 8);
它加載我的數據分爲兩個YMM
註冊該格式(沒有C++):
register v_part1:
[v[0].low][v[0].high][v[1].low][v[1].high][v[2].low][v[2].high][v[3].low][v[3].high]
register v_part2:
[v[4].low][v[4].high][v[5].low][v[5].high][v[6].low][v[6].high][v[7].low][v[7].high]
是否有交錯他們,以獲得原始格式的有效途徑:
register v_low:
[v[0].low][v[1].low][v[2].low][v[3].low][v[4].low][v[5].low][v[6].low][v[7].low]
register v_high:
[v[0].high][v[1].high][v[2].high][v[3].high][v[4].high][v[5].high][v[6].high][v[7].high]
該代碼無意義且無效C++。 –
@JohnZwinck:它是AVX內在函數。 – MSalters
@ MSalters:我指的是像'uint64_t v; v.low'。 –