![](/img/trans.png)
[英]how to set a int32 value at some index within an m128i with only SSE2?
[英]How can I set __m128i without using of any SSE instruction?
我有很多函數使用相同的常量__m128i值。 例如:
const __m128i K8 = _mm_setr_epi8(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
const __m128i K16 = _mm_setr_epi16(1, 2, 3, 4, 5, 6, 7, 8);
const __m128i K32 = _mm_setr_epi32(1, 2, 3, 4);
所以我想將所有這些常量存儲在一個地方。 但是有一個問題:我在運行時檢查現有的CPU擴展。 如果CPU不支持例如SSE(或AVX),那么在常量初始化期間程序將崩潰。
那么可以在不使用SSE的情況下初始化這些常量嗎?
可以在不使用SSE指令的情況下初始化__m128i向量,但這取決於編譯器如何定義__m128i。
對於Microsoft Visual Studio,您可以定義下一個宏(它將__m128i定義為char [16]):
template <class T> inline char GetChar(T value, size_t index)
{
return ((char*)&value)[index];
}
#define AS_CHAR(a) char(a)
#define AS_2CHARS(a) \
GetChar(int16_t(a), 0), GetChar(int16_t(a), 1)
#define AS_4CHARS(a) \
GetChar(int32_t(a), 0), GetChar(int32_t(a), 1), \
GetChar(int32_t(a), 2), GetChar(int32_t(a), 3)
#define _MM_SETR_EPI8(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, aa, ab, ac, ad, ae, af) \
{AS_CHAR(a0), AS_CHAR(a1), AS_CHAR(a2), AS_CHAR(a3), \
AS_CHAR(a4), AS_CHAR(a5), AS_CHAR(a6), AS_CHAR(a7), \
AS_CHAR(a8), AS_CHAR(a9), AS_CHAR(aa), AS_CHAR(ab), \
AS_CHAR(ac), AS_CHAR(ad), AS_CHAR(ae), AS_CHAR(af)}
#define _MM_SETR_EPI16(a0, a1, a2, a3, a4, a5, a6, a7) \
{AS_2CHARS(a0), AS_2CHARS(a1), AS_2CHARS(a2), AS_2CHARS(a3), \
AS_2CHARS(a4), AS_2CHARS(a5), AS_2CHARS(a6), AS_2CHARS(a7)}
#define _MM_SETR_EPI32(a0, a1, a2, a3) \
{AS_4CHARS(a0), AS_4CHARS(a1), AS_4CHARS(a2), AS_4CHARS(a3)}
對於GCC,它將(它將__m128i定義為long long [2]):
#define CHAR_AS_LONGLONG(a) (((long long)a) & 0xFF)
#define SHORT_AS_LONGLONG(a) (((long long)a) & 0xFFFF)
#define INT_AS_LONGLONG(a) (((long long)a) & 0xFFFFFFFF)
#define LL_SETR_EPI8(a, b, c, d, e, f, g, h) \
CHAR_AS_LONGLONG(a) | (CHAR_AS_LONGLONG(b) << 8) | \
(CHAR_AS_LONGLONG(c) << 16) | (CHAR_AS_LONGLONG(d) << 24) | \
(CHAR_AS_LONGLONG(e) << 32) | (CHAR_AS_LONGLONG(f) << 40) | \
(CHAR_AS_LONGLONG(g) << 48) | (CHAR_AS_LONGLONG(h) << 56)
#define LL_SETR_EPI16(a, b, c, d) \
SHORT_AS_LONGLONG(a) | (SHORT_AS_LONGLONG(b) << 16) | \
(SHORT_AS_LONGLONG(c) << 32) | (SHORT_AS_LONGLONG(d) << 48)
#define LL_SETR_EPI32(a, b) \
INT_AS_LONGLONG(a) | (INT_AS_LONGLONG(b) << 32)
#define _MM_SETR_EPI8(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, aa, ab, ac, ad, ae, af) \
{LL_SETR_EPI8(a0, a1, a2, a3, a4, a5, a6, a7), LL_SETR_EPI8(a8, a9, aa, ab, ac, ad, ae, af)}
#define _MM_SETR_EPI16(a0, a1, a2, a3, a4, a5, a6, a7) \
{LL_SETR_EPI16(a0, a1, a2, a3), LL_SETR_EPI16(a4, a5, a6, a7)}
#define _MM_SETR_EPI32(a0, a1, a2, a3) \
{LL_SETR_EPI32(a0, a1), LL_SETR_EPI32(a2, a3)}
所以在你的代碼初始化__m128i常量將是這樣的:
const __m128i K8 = _MM_SETR_EPI8(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
const __m128i K16 = _MM_SETR_EPI16(1, 2, 3, 4, 5, 6, 7, 8);
const __m128i K32 = _MM_SETR_EPI32(1, 2, 3, 4);
我建議將初始化數據全局定義為標量數據,然后將其本地加載到const __m128i
:
static const uint8_t gK8[16] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
static inline foo()
{
const __m128i K8 = _mm_loadu_si128((__m128i *)gK8);
// ...
}
你可以使用聯盟。
union M128 {
char[16] i8;
__m128i i128;
};
const M128 k8 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
如果M128聯合在本地定義,您使用循環,這應該沒有性能開銷(它將在循環開始時加載到內存中)。 因為它包含__m128i類型的變量,所以M128繼承了正確的對齊方式。
void foo()
{
M128 k8 = ...;
// use k8.i128 in your for loop
}
如果它在其他地方定義,則需要在啟動循環之前復制到本地寄存器,否則編譯器可能無法對其進行優化。
void foo()
{
__m128i tmp = k8.i128;
// for loop here
}
這將把k8加載到cpu寄存器並在循環期間保持在那里,只要有足夠的空閑寄存器來執行循環體。
根據您使用的編譯器,這些聯合可能已經定義(VS確實),但編譯器提供的定義可能不可移植。
你通常不需要這個。 編譯器非常擅長將相同的存儲用於使用相同常量的多個函數。 就像合並相同的字符串文字的多個實例為一個字符串常量,多個實例相同的_mm_set*
在不同的功能由相同的矢量常數所有負載(或在飛行中產生用於_mm_setzero_si128()
或_mm_set1_epi8(-1)
。
使用Godbolt的二進制輸出(反匯編)模式可以查看是否從同一塊內存加載不同的函數。 查看它添加的注釋,它將RIP相對地址解析為絕對地址。
gcc: 所有相同的常量共享相同的存儲 ,無論它們來自自動矢量化還是_mm_set
。 即使16B常數是32B的子集,32B常數也不能與16B常數重疊。
clang: 相同的常量共享存儲 。 16B和32B常數不重疊,即使一個是另一個的子集。 一些使用重復常量的函數使用AVX2 vpbroadcastd
廣播加載(它甚至不會在Intel SnB系列CPU上使用ALU uop)。 出於某種原因,它選擇基於操作的元素大小而不是常量的重復性來執行此操作。 請注意,clang的asm輸出會為每次使用重復常量,但最終的二進制不會。
MSVC: 相同的常量共享存儲 。 與gcc的功能幾乎相同。 (完整的asm輸出很難通過;使用搜索。我只能通過main
找到.exe的路徑來獲取asm,然后計算出使用cl.exe -O2 /FAs
生成的asm輸出的路徑cl.exe -O2 /FAs
和運行system("type .../foo.asm")
)。
編譯器很擅長這個,因為它不是一個新問題。 從編譯器的早期開始就存在字符串。
我沒有檢查它是否適用於源文件(例如,對於多個編譯單元中使用的內聯向量函數)。 如果你仍然想靜態/全球向量常數,見下圖:
這似乎沒有一種簡單和方便的靜態初始化靜態/全局__m128
。 C編譯器甚至不接受_mm_set*
作為初始化器,因為它的功能類似於函數。 他們沒有利用他們實際上可以通過它看到編譯時常量16B的事實
const __m128i K32 = _mm_setr_epi32(1, 2, 3, 4); // Illegal in C
// C++: generates a constructor that copies from .rodata to the BSS
盡管構造函數只需要SSE1或SSE2,但無論如何都不需要它。 這太糟糕了。 不要這樣做 。 您最終會兩次支付常量的內存成本。
Fabio的union
答案看起來是靜態初始化向量常量的最佳可移植方式,但這意味着您必須訪問__m128i
聯合成員。 它可能有助於將相關常量分組到彼此附近(希望在同一緩存行中),即使它們被分散的函數使用也是如此。 還有不可移植的方法,也就是說(例如,將相關的常量放在他們自己的ELF部分中,使用GNU C __attribute__ ((section ("constants_for_task_A")))
)。 希望可以將它們組合在.rodata
部分(它成為.text
部分的一部分)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.