簡體   English   中英

使用內部函數提取和移位奇數/偶數位

[英]Using Intrinsics to Extract And Shift Odd/Even Bits

有沒有辦法使用內在函數優化以下代碼? 它采用 16 位整數中的所有奇數索引位,並將它們盡可能向右移動。

我在想也許可以使用 Fortran 的 ISHFTC 的 c++ 等價物(甚至有一個 c++ 等價物嗎?)。 但我覺得有一種更有效的方法。

int x = some16bitInt;
x = x&0x5555;
int y = 0;
for (int i = 0; i < 8; i++)
    y = y | ((x >> i) & (0x01 << i));
'''

Fortran ISHFTC只是一個輪換。 C 不直接具有此功能,但您可以輕松地+安全地編寫一個函數,該函數通過模式識別進行編譯並編譯為單個旋轉指令。 C++ 中循環移位(旋轉)操作的最佳實踐

我不確定這是一個有用的構建塊,但它是可用的。


在帶有 BMI2 指令集擴展的 x86 上,有一個pext位提取指令,您可以將其與0x5555控制輸入​​一起使用。 請參閱英特爾的_pext_u32_u64文檔

它在 Intel Haswell 及更高版本上非常快(1 uop,3 周期延遲,1/時鍾吞吐量),
在 AMD 上速度(Ryzen:7 uops,18 周期延遲/吞吐量)。 https://agner.org/optimize/我認為這比我使用純 C 提出的 shift/mask 更糟糕,特別是如果延遲很重要(不僅僅是吞吐量)。

#include <immintrin.h>

unsigned extract_even_bits_bmi2(unsigned a) {
   return _pext_u32(a, 0x5555);
}

使用 GCC / clang,您必須使用-mbmi2 (或更好, -march=haswell )進行編譯以啟用 BMI2 內在函數。


便攜式 ISO C++

我認為通常的乘法技巧(將多個輸入字節移位並添加到結果的頂部字節)在這里不起作用; 你有太多的位,他們太靠近了。 請參閱如何計算 32 位整數中設置的位數? 對於用例:
((n & 0x0F0F0F0F) * 0x01010101) >> 24水平添加n所有字節。

你可以想象在你的輸入上使用類似的東西* 0x08040201來以不同的方式對齊來自不同字節的位。 但這仍然留下了未解決的重大問題。 也許 SIMD 乘以 8 位元素以獲得移位在一起的位對?

但這並不比通過屏蔽、移位和 ORing 或 ADDing 移動位與非移動位來移動位更好。 通過大約 log2(n_bits) 步,我們可以使所有位連續。

有多種方法可以做到這一點,請參閱Godbolt 這方面還有改進的余地,比如調整以更好地為一個 ISA 和另一個 ISA 編譯。 例如,幫助一些 ARM 編譯器看到0b0000011000000110只是另一個常量右移,所以它可以and r0, r1, r2, lsr #4或其他東西。

或者將位向右而不是向左移動,對於無法為左做任何特殊事情的 ISA。

unsigned pack_even_bits16_v2(unsigned x)
{
    x &= 0x5555;        // 0a0b0c0d0e0f0g0h
    x += x<<1;          // aabbccddeeffgghh    // x86 LEA eax, [rdi + rdi*2]
    unsigned move = x &  0b0000011000000110;   // bits to move
    unsigned keep = x &  0b0110000001100000;   // bits to keep
    x = keep + (move << 2);  // 0abcd000 0efgh000

                       // 0abcd000 0efgh000    // with byte boundary shown
    unsigned tmp = x >> 7;  // high group into place, shifting out the low bits
    x &= 0xFF;    // grab the whole low byte ; possibly with a zero-latency movzx
    x = (x>>3) | tmp;
    return x;
}

我正在左移低位而不是右移高位,因為 x86 可以使用一條指令 LEA 進行左移和加法。 在其他 ISA 上,它可能會在最后節省一個移位以向右移動位。

這對於 AArch64 和 PowerPC64 以及 x86 編譯得非常好。 Clang 看穿了 PowerPC 的這個位操作,並使用了強大的rlwinm (旋轉左詞立即與掩碼)和rlwimi (...掩碼插入)指令:)

# clang trunk -O3 for PowerPC64.
# Compiling the  x += x & 0x1111;  version, not the  x += x<<1 version where we get a multiply
        andi. 4, 3, 21845        # x & 0x5555
        andi. 3, 3, 4369         # x & 0x1111
        add 4, 4, 3              # 
        rlwinm 3, 4, 31, 30, 31  # isolate the low 2 bits.  PPC counts bits from MSB=0 LSB=31 for 32-bit registers
        rlwimi 3, 4, 29, 28, 29  # insert the next 2-bit bitfield
        rlwimi 3, 4, 27, 26, 27  # ...
        rlwimi 3, 4, 25, 24, 25
        blr

組合成對而不是形成一個大鏈會更好。


移動位的另一種方法是使用 XOR 將選定的位歸零,然后通過移位和相加將它們移位並存放在其他地方。

   unsigned tmp = x & mask;
    x += tmp;          // left shift those bits
    x += tmp<<1;       // left shift them again.  (x86 can do this with LEA eax, [rax + rdx*2])

或者

    unsigned tmp = x &   0b0000011000000110;   // bits to move
    x ^= tmp;          // clear those bits
    x += tmp << 2;     // LEA eax, [eax + edx*4]  1 fast instruction on x86

當只移動 2 個位置時,add + shift-and-add 與 xor + shift-and-add 的依賴鏈長度基本相同。

但是有條件地清除舊位而不是使用相反的掩碼可能更糟。 至少如果相反的掩碼適合立即數,或者 ISA 有一個 ANDNOT 指令。 或者對於 ARM,一個移位的掩碼。 x上的 AND 2 種方式可以並行運行,而tmp = x & mask; x ^= tmp如果按照編寫的方式編譯,則使用數據依賴性序列化執行。 (它沒有;gcc 和 clang 足夠聰明,可以知道 XOR 的作用並無條件地清除這些位。)

當然,方法如下:

int y = (int)_pext_u32( (unsigned int)some16bitInt, 0x5555 );

對您來說不幸的是,此指令來自 BMI2 集,需要相對較新的 CPU、Intel Haswell 或更新版本、AMD Excavator 或更新版本。 但是在支持它的地方,它非常快。

x86(實際上,幾乎所有 CPU)中最靈活的位操作是從內存中索引讀取。 它可以在恆定時間內完成完全任意的映射,通常在 1-4 個周期內(假設內存已緩存)。

由於您只談論 8 位,並且您可以輕松地將所需的位放入寄存器的低 8 位,盡管順序錯誤,您可以只使用查找表。

unsigned pack_even_bits16_table(unsigned x) {  // x = ?a?b?c?d ?e?f?g?h
  size_t m1 = x & 0x55;         //  m1 = 0e0f0g0h
  size_t m2 = (x >> 7) & 0xAA;  //  m2 = a0b0c0d0
  return map[m1 + m2];          // sum = aebfcgdh
}

地圖在哪里

const unsigned char map[256] = {
    0,   1,   16,  17,  2,   3,   18,  19,  32,  33,  48,  49,  34,  35,  50,  51,
    4,   5,   20,  21,  6,   7,   22,  23,  36,  37,  52,  53,  38,  39,  54,  55,
    64,  65,  80,  81,  66,  67,  82,  83,  96,  97,  112, 113, 98,  99,  114, 115,
    68,  69,  84,  85,  70,  71,  86,  87,  100, 101, 116, 117, 102, 103, 118, 119,
    8,   9,   24,  25,  10,  11,  26,  27,  40,  41,  56,  57,  42,  43,  58,  59,
    12,  13,  28,  29,  14,  15,  30,  31,  44,  45,  60,  61,  46,  47,  62,  63,
    72,  73,  88,  89,  74,  75,  90,  91,  104, 105, 120, 121, 106, 107, 122, 123,
    76,  77,  92,  93,  78,  79,  94,  95,  108, 109, 124, 125, 110, 111, 126, 127,
    128, 129, 144, 145, 130, 131, 146, 147, 160, 161, 176, 177, 162, 163, 178, 179,
    132, 133, 148, 149, 134, 135, 150, 151, 164, 165, 180, 181, 166, 167, 182, 183,
    192, 193, 208, 209, 194, 195, 210, 211, 224, 225, 240, 241, 226, 227, 242, 243,
    196, 197, 212, 213, 198, 199, 214, 215, 228, 229, 244, 245, 230, 231, 246, 247,
    136, 137, 152, 153, 138, 139, 154, 155, 168, 169, 184, 185, 170, 171, 186, 187,
    140, 141, 156, 157, 142, 143, 158, 159, 172, 173, 188, 189, 174, 175, 190, 191,
    200, 201, 216, 217, 202, 203, 218, 219, 232, 233, 248, 249, 234, 235, 250, 251,
    204, 205, 220, 221, 206, 207, 222, 223, 236, 237, 252, 253, 238, 239, 254, 255,
};

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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