XOP instruction set確實提供_mm_rot_epi8()
(這不是微軟特有的;它自4.4或更早版本以後也可在GCC中使用,並且最近也應該可用)。它可以用來以128位爲單位執行所需的任務。不幸的是,我沒有支持XOP的CPU,所以我無法測試它。
在AVX2上,將256位寄存器分成兩部分,一部分包含偶數字節,另一部分奇數字節右移8位,允許16位向量乘。給定常數(使用GCC 64位元件陣列格式)
static const __m256i epi16_highbyte = { 0xFF00FF00FF00FF00ULL,
0xFF00FF00FF00FF00ULL,
0xFF00FF00FF00FF00ULL,
0xFF00FF00FF00FF00ULL };
static const __m256i epi16_lowbyte = { 0x00FF00FF00FF00FFULL,
0x00FF00FF00FF00FFULL,
0x00FF00FF00FF00FFULL,
0x00FF00FF00FF00FFULL };
static const __m256i epi16_oddmuls = { 0x4040101004040101ULL,
0x4040101004040101ULL,
0x4040101004040101ULL,
0x4040101004040101ULL };
static const __m256i epi16_evenmuls = { 0x8080202008080202ULL,
0x8080202008080202ULL,
0x8080202008080202ULL,
0x8080202008080202ULL };
旋轉操作可被寫爲
__m256i byteshift(__m256i value)
{
return _mm256_or_si256(_mm256_srli_epi16(_mm256_mullo_epi16(_mm256_and_si256(value, epi16_lowbyte), epi16_oddmuls), 8),
_mm256_and_si256(_mm256_mullo_epi16(_mm256_and_si256(_mm256_srai_epi16(value, 8), epi16_lowbyte), epi16_evenmuls), epi16_highbyte));
}
這已被證實使用GCC-上的Intel Core i5-4200U得到正確的結果4.8.4。作爲一個例子,輸入矢量(作爲單個256位的16進制數)
88 87 86 85 84 83 82 81 38 37 36 35 34 33 32 31 28 27 26 25 24 23 22 21 FF FE FD FC FB FA F9 F8
被旋轉到
44 E1 D0 58 24 0E 05 81 1C CD C6 53 A1 CC 64 31 14 C9 C4 52 21 8C 44 21 FF BF BF CF DF EB F3 F8
在最左側的八比特組向左旋轉由7位,接下來的6個比特,和等等;第七個字節不變,第八個八位字節旋轉7位,依此類推,全部32個八位字節。
我不確定上述函數定義是否編譯爲最佳機器碼 - 取決於編譯器 - ,但我對它的性能感到滿意。
因爲你很可能不喜歡上面的簡明格式的功能,這在程序上,展開形式:
static __m256i byteshift(__m256i value)
{
__m256i low, high;
high = _mm256_srai_epi16(value, 8);
low = _mm256_and_si256(value, epi16_lowbyte);
high = _mm256_and_si256(high, epi16_lowbyte);
low = _mm256_mullo_epi16(low, epi16_lowmuls);
high = _mm256_mullo_epi16(high, epi16_highmuls);
low = _mm256_srli_epi16(low, 8);
high = _mm256_and_si256(high, epi16_highbyte);
return _mm256_or_si256(low, high);
}
在評論,Peter Cordes建議用srli
更換srai
+ and
,並可能最後and
+ or
與blendv
。前者具有很大的意義,因爲它純粹是一種優化,但後者可能不會(現在的英特爾CPU上)實際上會更快。
我嘗試了一些microbenchmarking,但無法獲得可靠的結果。我通常在x86-64上使用TSC,並使用存儲在數組中的輸入和輸出進行數十萬次測試的中位數。
我認爲這是最有用的,如果我只是在這裏列出變體,所以任何需要這種功能的用戶都可以在他們的實際工作負載上做一些基準測試,並測試是否有任何可測量的差異。
我還與他的建議同意使用的high
和low
odd
和even
代替,但要注意的是,由於在向量的第一個元素的編號爲第0個元素,第一個元素是甚至,第二奇,等等。
#include <immintrin.h>
static const __m256i epi16_oddmask = { 0xFF00FF00FF00FF00ULL,
0xFF00FF00FF00FF00ULL,
0xFF00FF00FF00FF00ULL,
0xFF00FF00FF00FF00ULL };
static const __m256i epi16_evenmask = { 0x00FF00FF00FF00FFULL,
0x00FF00FF00FF00FFULL,
0x00FF00FF00FF00FFULL,
0x00FF00FF00FF00FFULL };
static const __m256i epi16_evenmuls = { 0x4040101004040101ULL,
0x4040101004040101ULL,
0x4040101004040101ULL,
0x4040101004040101ULL };
static const __m256i epi16_oddmuls = { 0x8080202008080202ULL,
0x8080202008080202ULL,
0x8080202008080202ULL,
0x8080202008080202ULL };
/* Original version suggested by Nominal Animal. */
__m256i original(__m256i value)
{
return _mm256_or_si256(_mm256_srli_epi16(_mm256_mullo_epi16(_mm256_and_si256(value, epi16_evenmask), epi16_evenmuls), 8),
_mm256_and_si256(_mm256_mullo_epi16(_mm256_and_si256(_mm256_srai_epi16(value, 8), epi16_evenmask), epi16_oddmuls), epi16_oddmask));
}
/* Optimized as suggested by Peter Cordes, without blendv */
__m256i no_blendv(__m256i value)
{
return _mm256_or_si256(_mm256_srli_epi16(_mm256_mullo_epi16(_mm256_and_si256(value, epi16_evenmask), epi16_evenmuls), 8),
_mm256_and_si256(_mm256_mullo_epi16(_mm256_srli_epi16(value, 8), epi16_oddmuls), epi16_oddmask));
}
/* Optimized as suggested by Peter Cordes, with blendv.
* This is the recommended version. */
__m256i optimized(__m256i value)
{
return _mm256_blendv_epi8(_mm256_srli_epi16(_mm256_mullo_epi16(_mm256_and_si256(value, epi16_evenmask), epi16_evenmuls), 8),
_mm256_mullo_epi16(_mm256_srli_epi16(value, 8), epi16_oddmuls), epi16_oddmask);
}
下面是以顯示各個操作的方式編寫的相同功能。儘管它不會影響到理智的編譯器,但我已經標記了函數參數和每個臨時值const
,這樣就很明顯如何將每個表達式插入到後續表達式中,以便將函數簡化爲上述簡潔形式。
__m256i original_verbose(const __m256i value)
{
const __m256i odd1 = _mm256_srai_epi16(value, 8);
const __m256i even1 = _mm256_and_si256(value, epi16_evenmask);
const __m256i odd2 = _mm256_and_si256(odd1, epi16_evenmask);
const __m256i even2 = _mm256_mullo_epi16(even1, epi16_evenmuls);
const __m256i odd3 = _mm256_mullo_epi16(odd3, epi16_oddmuls);
const __m256i even3 = _mm256_srli_epi16(even3, 8);
const __m256i odd4 = _mm256_and_si256(odd3, epi16_oddmask);
return _mm256_or_si256(even3, odd4);
}
__m256i no_blendv_verbose(const __m256i value)
{
const __m256i even1 = _mm256_and_si256(value, epi16_evenmask);
const __m256i odd1 = _mm256_srli_epi16(value, 8);
const __m256i even2 = _mm256_mullo_epi16(even1, epi16_evenmuls);
const __m256i odd2 = _mm256_mullo_epi16(odd1, epi16_oddmuls);
const __m256i even3 = _mm256_srli_epi16(even2, 8);
const __m256i odd3 = _mm256_and_si256(odd2, epi16_oddmask);
return _mm256_or_si256(even3, odd3);
}
__m256i optimized_verbose(const __m256i value)
{
const __m256i even1 = _mm256_and_si256(value, epi16_evenmask);
const __m256i odd1 = _mm256_srli_epi16(value, 8);
const __m256i even2 = _mm256_mullo_epi16(even1, epi16_evenmuls);
const __m256i odd2 = _mm256_mullo_epi16(odd1, epi16_oddmuls);
const __m256i even3 = _mm256_srli_epi16(even2, 8);
return _mm256_blendv_epi8(even3, odd2, epi16_oddmask);
}
我親手做的寫我的測試功能最初在其上面詳細的形式,形成簡潔的版本是一個微不足道的一組的複製粘貼的。不過,我確實測試了兩個版本,以驗證是否引入任何錯誤,並保持冗長的版本可以訪問(作爲評論等),因爲簡潔版本基本上是隻寫的。編輯詳細版本比簡化版本更容易,然後簡化爲簡潔版本。
如果您有多個這樣的向量要修改,請執行字節轉置,將轉置向量中的所有字節旋轉相同的量,然後轉回。 – EOF