簡體   English   中英

用內在函數初始化 __m128i 常量的最快方法?

[英]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_aC_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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM