多个单词之间的有效位混洗

时间:2016-03-01 16:19:40

标签: c assembly arm bit-manipulation shuffle

假设我有8个32位寄存器:

A 0-31        E 0-31
B 0-31        F 0-31
C 0-31        G 0-31
D 0-31        H 0-31

我希望他们的位重新排列如下:

A' := A0 E0 A8 E8 A16 E16 A24 E24 B0 F0 B8 F8 B16 F16 B24 F24 C0 G0 ...etc. H24
B' := A1 E1 A9 E9 A17 E17 A25 E25 B1 F1 B9 F9 B17 F17 B25 F25 C1 G1 ...etc. H25 
C' := A2 E2 A10 E10 A18 E18 A26 E26 B2 ... etc.
D' := ... etc.
E' := ... etc.
F' := ... etc.
G' := ... etc.
H' := ... etc.

在C或ARM程序集中计算此混洗的最有效方法是什么? (所以没有SSE的intel,没有64位寄存器,没有足够的寄存器来包含输入和输出。)http://programming.sirrida.de/calcperm.php的计算器非常好,但它不容易扩展到多个单词。我相信它可以比当时选择一位的天真方式更有效率。

4 个答案:

答案 0 :(得分:2)

如果你制作组件A0 _ _ _ _ _ _ _ A8 _ _ _ _ _ _ _ A16等(只是简单的掩盖)。 与其他寄存器类似,您可以轻松实现:

A0 E0 B0 F0 C0 G0 D0 H0 A8 E8 ..

你可以用两个bit_permute_step转换成正确的顺序,如calcperm所示:

x = bit_permute_step(x, 0x00cc00cc, 6);  // Bit index swap 1,3
x = bit_permute_step(x, 0x0000f0f0, 12);  // Bit index swap 2,4

其他寄存器的类似故事,只是稍微偏移了一点。

基本上一次移动4位,只需要8次修复。

答案 1 :(得分:0)

; 1) Copy the top most 8 bits of H into the lowest bits of the output registers:

lsr H  ; H.31 -> carry
rol H' ; carry -> H'.0

lsr H  ; H.30 -> carry
rol G' ; carry -> G'.0

lsr H
rol F'

...

lsr H  ; H.24 -> carry
rol A' ; carry to A'.0

; 2) go on with top 8 bits of D

lsr D  ; D.31 -> carry
rol H' ; H'.0 -> H'.1 and carry -> H'.0

lsr D
rol G'

...

lsr D
rol A'

继续,直到所有位都到位。最后一步是

lsr A  ; A.0 -> carry
rol A' ; A'.0 -> A'.1 -> A'.2 ... and carry -> A'.0

答案 2 :(得分:0)

我提出的最快版本:

// merges 32 bit a (low) and b (hi) into single 64 bit
#define m(a, b) (((uint64_t) (a)) | (((uint64_t) (b)) << 32))

// gets bit at position opos and moves it to position npos
#define s(a, opos, npos) (((opos) >= (npos)) ? (((a) & ( ((uint64_t)1) << (opos))) >> ((opos) - (npos))) : (((a) & (((uint64_t)1) << (opos))) << ((npos) - (opos))))

// gets 8 different bits from 64 bit number and puts them together into 1 byte, starting from idx
#define b(a, idx) (s(a, 0, idx) | s(a, 32, (idx - 1)) | s(a, 8, (idx - 2)) | s(a, 40, (idx - 3)) | s(a, 16, (idx - 4)) | s(a, 48, (idx - 5)) | s(a, 24, (idx - 6)) | s(a, 56, (idx - 7)))

// takes 8 32 bit registers in in, outputs in out
void shuffle(const uint32_t* in, uint32_t* out) {
    uint64_t t[4] = { m(in[0], in[4]), m(in[1], in[5]), m(in[2], in[6]), m(in[3], in[7]) };
    for (int i = 0; i < 8; i++, t[0] >>= 1, t[1] >>= 1, t[2] >>= 1, t[3] >>= 1)
        out[i] = b(t[0], 31) | b(t[1], 23) | b(t[2], 15) | b(t[3], 7);
}

与直接方法相比,唯一的“优化”是将两个32位寄存器合并为单个64位,因此我们可以减少循环中的移位数

答案 3 :(得分:0)

在带有SSE的x86上:punpcklbw_mm_unpacklo_epi8可以交错源regs的字节。

使用向量移位然后pmovmskb获取每个字节的高位,给出结果,如
A0 E0 A8 E8 A16 E16 A24 E24

然后组合这些字节结果以获得8个dest寄存器。这样很糟糕,因为它需要为每个结果字节移位/ pmovmskb。有8 * 4个结果字节,因此需要很多代码。