[英]Is there a better way to any detect bits that are set in a 16-byte array of flags?
ALIGNTO(16) uint8_t noise_frame_flags[16] = { 0 };
// Code detects noise and sets noise_frame_flags omitted
__m128i xmm0 = _mm_load_si128((__m128i*)noise_frame_flags);
bool isNoiseToCancel = _mm_extract_epi64(xmm0, 0) | _mm_extract_epi64(xmm0, 1);
if (isNoiseToCancel)
cancelNoises(audiobuffer, nAudioChannels, audio_samples, noise_frame_flags);
這是我在 Linux 上的 AV Capture 工具的代碼片段。 這里的 noise_frame_flags 是 16 聲道音頻的標志數組。 對於每個通道,對應的字節可以是 0 或 1。1 表示通道有一些噪聲需要消除。 例如,如果noise_frame_flags[0] == 1,這意味着設置了第一個通道噪聲標志(通過省略的代碼)。
即使設置了一個“標志”,我也需要調用cancelNoises
。 這段代碼似乎在這方面工作得很好。 如您所見,我使用_mm_load_si128
加載正確對齊的整個標志數組,然后使用兩個_mm_extract_epi64
來提取“標志”。 我的問題是有更好的方法來做到這一點(也許使用流行計數)?
注意: ALIGNTO(16)
是一個宏,它擴展為更正 GCC 等價物,但看起來更好看。
是的,您最終需要一個 64 位 OR 來查找任一半中的任何非零位,但是從 128 位加載中獲取這些uint64_t
值然后提取效率不高。
在 asm 中,您只需要一個mov
load 和一個 memory-source or
or or add
,它將像您現在一樣設置 ZF 。 來自同一緩存行的兩個負載非常便宜; 當前的 CPU 至少有 2 個/時鍾的負載吞吐量。 從單個 128 位負載中提取的額外 ALU 工作是不值得的,即使您為單個movq
設置了 shuffle / por
也是如此。
在 C++ 中,使用memcpy
對uint64_t
tmp 變量進行嚴格別名安全加載,然后if(a | b)
。 這仍然是 SIMD,只是SWAR (寄存器內的 SIMD)。
add
甚至比or
更好:它可以與 Intel Sandybridge 系列(但不是 AMD)上的大多數jcc
指令進行宏融合。 or
不能與任何 CPU 上的分支指令融合。 由於您的值是0
或1
,我們不能有兩個非零值相加來產生零的情況,這就是您通常使用or
對於一般情況的原因。
(某些尋址模式可能會破壞 Intel 上的微觀或宏觀融合。或者它可能總是有效,因為沒有直接參與。 add rax, [mem]
/ jnz
確實有可能通過前端和 ROB 作為一個單一的uop,並在后端僅作為 2 執行(加載 + 添加/子和分支)。假設它與我的 Skylake 上的cmp
大致相同,除了它確實寫入了目標,因此 Haswell 和以后可能會保持微-即使對於索引尋址模式也融合了。)
uint64_t a, b;
memcpy(&a, noise_frame_flags+0, sizeof(a)); // strict-aliasing-safe loads
memcpy(&b, noise_frame_flags+8, sizeof(b)); // which optimize to MOV qword
bool isNoiseToCancel = a + b; // equivalent to a | b for bool inputs
這應該編譯為 3 個 asm 指令,這些指令將總共解碼為 2 個 uops,或者在 JCC 只能與cmp
或test
融合的 AMD CPU 上為 3 個。
union { alignas(16) uint8_t flags[16]; uint64_t chunks[2];};
在 C99 中是安全的,但在 ISO C++ 中不安全。 大多數但不是所有支持英特爾內在函數的 C++ 編譯器都定義了聯合類型雙關語的行為。 (我認為@jww 說過 SunCC 沒有。)
在 C++11 中,您不需要ALIGNTO(16)
的自定義宏,只需使用alignas(16)
。 如果你#include <stdalign.h>
在 C11 中也支持
movdqa
16-byte load / SSE4.1 ptest xmm0, xmm0
/ jnz
- Intel CPU 上 4 uop,AMD 上 3。
英特爾將ptest
作為 2 個微指令運行,它不能與jcc
進行宏融合。
AMD CPU 以 1 uop 運行ptest
,但仍然無法融合。
如果您在寄存器中有一個全一或全零常量, ptest xmm0, [mem]
可以在 Intel 上保存一個 uop(取決於尋址模式),但總共仍然是 3 個。
PTEST 僅適用於使用 AVX1 或 AVX2 檢查 32 字節數組。 (令人驚訝的是, vptest ymm
只需要 AVX1 )。 然后是 AVX2 vmovdqa
/ vpslld ymm0, 7
/ vpmovmskb eax,ymm0
/ test+jnz
的收支平衡。 請參閱 TrentP 對可移植 GNU C 本機矢量源代碼的回答,該源代碼應編譯為帶有 AVX 的 x86 上的vptest
,並且可能編譯為其他 ISA(如 ARM)上的一些笨拙的東西,具體取決於它們的水平 OR 支持的好壞。
popcnt
不會有用,除非您想根據設置的位數分解工作。
在這種情況下,是的,當然,您可以將 bool 數組轉換為可以輕松掃描的位圖,這可能比_mm_sad_epu8
更有效地將零寄存器相加成兩個 8 字節的一半。
__m128i vflags = _mm_load_si128((__m128i*)noise_frame_flags);
vflags = _mm_slli_epi32(vflags, 7);
unsigned flagmask = _mm_movemask_epi8(vflags);
if (flagmask) {
unsigned flagcount = __builtin_popcount(flagmask); // popcnt with -march=nehalem or higher
unsigned first_setflag = __builtin_ctz(flagmask); // tzcnt if available, else BSF
vflags &= vflags - 1; // clear lowest set bit. blsr if compiled with -march=haswell or bdver2 or newer.
...
}
(實際上不要使用-march=bdver2
或-march=nehalem
,除非您想設置 ISA 基線但也使用-mtune=haswell
或更現代的東西。有單獨的選項,如-mpopcnt
和-mbmi
,但通常很好啟用某些 CPU 支持的所有 ISA 擴展,因此您不會錯過編譯器可以使用的有用內容。)
這是我想出這樣做的:
#define VLEN 8
typedef int vNb __attribute__((vector_size(VLEN*sizeof(int))));
// Constants for 128 or 256 bit registers
#if VLEN == 8
#define V(a,b,c,d,e,f,g,h) a,b,c,d,e,f,g,h
#else
#define V(a,b,c,d,e,f,g,h) a,b,c,d
#endif
#define SWAP128 V(4,5,6,7, 0,1,2,3)
#define SWAP64 V(2,3, 0,1, 6,7, 4,5)
#define SWAP32 V(1, 0, 3, 2, 5, 4, 7, 6)
static bool any(vNb x) {
if (VLEN >= 8)
x |= __builtin_shufflevector(x,x, SWAP128);
x |= __builtin_shufflevector(x,x, SWAP64);
x |= __builtin_shufflevector(x,x, SWAP32);
return x[0];
}
VLEN
= 8 時,如果架構支持,這將使用 256 位寄存器。 更改為 4 以使用 128 位。
這應該編譯為單個vptest
指令。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.