簡體   English   中英

是否有更好的方法來檢測 16 字節標志數組中設置的位?

[英]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++ 中,使用memcpyuint64_t tmp 變量進行嚴格別名安全加載,然后if(a | b) 這仍然是 SIMD,只是SWAR (寄存器內的 SIMD)。

add甚至比or更好:它可以與 Intel Sandybridge 系列(但不是 AMD)上的大多數jcc指令進行宏融合。 or不能與任何 CPU 上的分支指令融合。 由於您的值是01 ,我們不能有兩個非零值相加來產生零的情況,這就是您通常使用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 只能與cmptest融合的 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.

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