[英]Fastest way to initialize a __m128i constant with intrinsics?
目前,我有一個 __m128i 變量,我們稱之為X
。 我想用一個恆定的 128 位值對它進行異或並將該值保存回X
。 因此,對於某些常數C
,本質上X ^= C
C
。
目前,我正在做一些事情:
X = _mm_xor_si128(X, _mm_set_epi64x(C_a, C_b))
它從C
的兩個 64 位部分為異或構建了一個__m128i
。
我的問題是,這似乎不是為 xor 初始化 __m128i 常量的最有效方法。 嘗試從對齊的數組中進行加載會更好嗎? 或者其他什么方法?
我目前正在 Visual Studio 中使用 MSVC。
這個答案純粹是關於常數C
的情況。 如果您有非常量輸入,那么它們來自何處(內存、寄存器、您可能首先可以在向量寄存器中進行的最近計算?)以及您對結果的處理可能很重要向量。 將單獨的標量變量混入/移出 SIMD 向量有點糟糕,需要在 ALU 端口瓶頸與延遲和存儲/重新加載吞吐量(以及標量 -> 向量的存儲轉發停頓)之間進行權衡。 不過,當您確實需要所有小元素時,存儲/重新加載在 asm 中非常適合從SIMD 向量中獲取大量小元素。
對於常量C_a
和C_b
,即使是 MSVC 在通過_mm_set
進行常量傳播方面也_mm_set
。 因此,編寫特定於實現的初始化程序(如SSE 錯誤 - 使用 m128i_i32 來定義 __m128i 變量的字段)沒有任何優勢
請記住,性能的真正決定因素是您可以誘使編譯器生成的程序集,而不是真正用於執行此操作的內在函數。
#include <immintrin.h>
__m128i xor_const(__m128i v) {
return _mm_xor_si128(v, _mm_set_epi64x(0x789abc, 0x123456));
}
使用 x64 MSVC -O2 Gv 編譯( 在 Godbolt 上)(使用 vectorcall 以便我們可以看到當向量已經在寄存器中時它做了什么,比如當這個內聯時),我們得到了這個相當愚蠢的 asm,希望不會是這個內聯后在更大的函數中不好:
;; MSVC 19.10
;; this is in the .rdata section; godbolt just filters directives that aren't interesting
;; "everyone knows" that compilers put data in the right sections
__xmm@0000000000789abc0000000000123456 DB 'V4', 012H, 00H, 00H, 00H, 00H, 00H
DB 0bcH, 09aH, 'x', 00H, 00H, 00H, 00H, 00H
xor_const@@16 PROC ; COMDAT
movdqa xmm1, XMMWORD PTR __xmm@0000000000789abc0000000000123456
pxor xmm1, xmm0
movdqa xmm0, xmm1
ret 0
xor_const@@16 ENDP
我們可以看到_mm_set
內部函數在靜態存儲中編譯為 16 字節的常量,就像我們想要的那樣。 未能使用pxor xmm0, xmm1
令人驚訝,但MSVC 以 asm 而聞名,與 GCC 和/或 clang 相比,它通常不太好。 同樣,作為大型函數的一部分,當它可以選擇寄存器時,我們可能沒有額外的movdqa
。 如果異或在循環中,無論如何我們都想要在循環外加載一次。 這不是最新的 MSVC 版本; Godbolt 只為 C++ 安裝了最新的 MSVC 版本,而不是 C,但是你標記了這個 C。
相比之下,GCC9.2 -O3 編譯為在所有 CPU 上都有效的預期內存源 PXOR。
xor_const:
pxor xmm0, XMMWORD PTR .LC0[rip]
ret
.section .rodata # Godbolt strips out stuff like section directive; re-added manually
.LC0:
.quad 1193046
.quad 7903932
您可能會讓編譯器發出相同的 asm,其中包含一個alignas(16)
常量的靜態alignas(16)
數組,並_mm_load_si128()
。 但何必呢?
要避免的一件事是編寫static const __m128i C = _mm_set...
- 編譯器對此非常愚蠢,並且不會將_mm_set
折疊成_mm_set
的靜態常量初始值設定__m128i
。 C 編譯器將拒絕編譯非常量靜態初始值設定項。 C++ 編譯器將保留一些 BSS 空間並運行類似構造函數的函數來從只讀常量復制到該 BSS 空間,因為在這種情況下_mm_set
不會完全優化掉。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.