ARM-v8 NEON:是否有指令将单个普通寄存器跨NEON寄存器的多个通道分割?

问题描述 投票:0回答:3

[我是ARM-v8(AArch64)的新手,在ARM-v7中只做过一点NEON编码(但是我对A32和正常A64的ok(*)非常满意)。

[我最终要做的是计算一堆(最多15个)32位值中每个设置位[31:0]的频率。也就是说,在这15个值中,位0设置了多少次,位1设置了多少次,等等。

所以,我想做的是将32位中的32位分成128位NEON寄存器,然后累加NEON寄存器,如下所示:

// args(x0: ptr to array of 16 32-bit words) ret(v0: sum of set bits as 32 nibbles)

   mov     w2, 16                     // w2: loop counter
   mov     v0, 0                      // v0: accumulate count
1:
   ldr     w1, [x0], 4
   split   v1, w1                     // here some magic occurs
   add     v0.16b, v0.16b, v1.16b
   subs    w2, w2, 1
   bne     1b

我对ARM文档不太满意。 ARMv8-ARM只是按字母顺序列出了354条NEON指令(800页伪代码)。 ARMv8-A程序员指南只有14页的介绍,并且引人注目的语句“添加了新的泳道插入和提取指令以支持新的寄存器打包方案。”而且《 NEON程序员指南》是关于ARM-v7的。

假设没有一条指令可以执行此操作,那么最有效的方法是什么? -不是在寻找完整的解决方案,但NEON可以帮到您吗?如果我必须分别加载每个车道,那就没有多大意义了。

(*)不能说我like A64。 :-(

arm arm64 neon
3个回答
0
投票

我不认为每个半字节都可以完成,但是每个字节都可以。

加载一个向量,并在每个字节中设置相关的源位(您将需要其中的两个,因为我们可能只能按字节而不是按半字节执行此操作)。在两个向量中,将单词的每个字节均复制为8个字节大小的元素。用两个掩码进行cmtst(如果已设置了相应的位,则将在元素中设置所有位,即将其设置为-1),然后累加。

类似这样的东西,未经测试:

   .section .rodata
mask: .byte 1, 2, 4, 8, 16, 32, 64, 128, 1, 2, 4, 8, 16, 32, 64, 128

   .text
   mov     w2, 16                     // w2: loop counter
   mov     v0.16b, 0                  // v0: accumulate count 1
   mov     v1.16b, 0                  // v1: accumulate count 2
   adrp    w3, mask
   add     w3, :lo12:mask
   ld1     {v2.16b}, [w3]             // v2: mask with one bit set in each byte
1:
   ld1r    {v3.4s}, [x0], #4          // One vector with the full 32 bit word
   subs    w2, w2, 1

   dup     v4.8b, v3.b[0]             // v4: vector containing the lowest byte of the word
   dup     v5.8b, v3.b[1]             // v5: vector containing the second lowest byte of the word
   dup     v6.8b, v3.b[2]
   dup     v7.8b, v3.b[3]

   ins     v4.d[1], v5.d[0]           // v4: elements 0-7: lowest byte, elements 8-15: second byte
   ins     v6.d[1], v7.d[0]           // v6: elements 0-7: third byte, elements 8-15: fourth byte

   cmtst   v4.16b, v4.16b, v2.16b     // v4: each byte -1 if the corresponding bit was set
   cmtst   v6.16b, v6.16b, v2.16b     // v5: each byte -1 if the corresponding bit was set

   sub     v0.16b, v0.16b, v4.16b     // accumulate: if bit was set, subtract -1 i.e. add +1
   sub     v1.16b, v1.16b, v6.16b
   b.ne    1b
   // Done, count of individual bits in byte sized elements in v0-v1

编辑:Jake'Alquimista'LEE提出的ld4r方法实际上比这里的加载要好; ld1r后跟四个dup可以在此处用ld4r {v4.8b, v5.8b, v6.8b, v7.8h}, [x0], #4替换,保持逻辑相同。对于其余部分,无论cmtst还是ushl + and的生成速度更快,都必须进行测试和测量才能看到。和他的解决方案一样,同时处理两个32位字可能比这里的解决方案提供更好的吞吐量。


1
投票

您应该开箱即用。源数据为32位宽并不意味着您应按32位访问它们。通过以4x8bit的方式读取它们,问题得到了大大简化。下面是对数组中的每个32位进行拆分和计数:

/*
 * alqCountBits.S
 *
 *  Created on: 2020. 5. 26.
 *      Author: Jake 'Alquimista' LEE
 */

    .arch   armv8-a
    .global     alqCountBits
    .text

// extern void alqCountBits(uint32_t *pDst, uint32_t *pSrc, uint32_t nLength);
// assert(nLength % 2 == 0);

pDst    .req    x0
pSrc    .req    x1
length  .req    w2

.balign 64
.func
alqCountBits:
    adr     x3, .LShiftTable
    movi    v30.16b, #1
    ld1r    {v31.2d}, [x3]

    movi    v0.16b, #0
    movi    v1.16b, #0
    movi    v2.16b, #0
    movi    v3.16b, #0
    movi    v4.16b, #0
    movi    v5.16b, #0
    movi    v6.16b, #0
    movi    v7.16b, #0


.balign 64
1:
    ld4r    {v16.8b, v17.8b, v18.8b, v19.8b}, [pSrc], #4
    ld4r    {v20.8b, v21.8b, v22.8b, v23.8b}, [pSrc], #4
    subs    length, length, #2

    trn1    v24.2d, v16.2d, v17.2d
    trn1    v25.2d, v18.2d, v19.2d
    trn1    v26.2d, v20.2d, v21.2d
    trn1    v27.2d, v22.2d, v23.2d

    ushl    v16.16b, v24.16b, v31.16b
    ushl    v17.16b, v25.16b, v31.16b
    ushl    v18.16b, v26.16b, v31.16b
    ushl    v19.16b, v27.16b, v31.16b

    and     v16.16b, v16.16b, v30.16b
    and     v17.16b, v17.16b, v30.16b
    and     v18.16b, v18.16b, v30.16b
    and     v19.16b, v19.16b, v30.16b

    uaddl   v24.8h, v18.8b, v16.8b
    uaddl2  v25.8h, v18.16b, v16.16b
    uaddl   v26.8h, v19.8b, v17.8b
    uaddl2  v27.8h, v19.16b, v17.16b

    uaddw   v0.4s, v0.4s, v24.4h
    uaddw2  v1.4s, v1.4s, v24.8h
    uaddw   v2.4s, v2.4s, v25.4h
    uaddw2  v3.4s, v3.4s, v25.8h
    uaddw   v4.4s, v4.4s, v26.4h
    uaddw2  v5.4s, v5.4s, v26.8h
    uaddw   v6.4s, v6.4s, v27.4h
    uaddw2  v7.4s, v7.4s, v27.8h
    b.gt    1b

.balign 8
    stp     q0, q1, [pDst, #0]
    stp     q2, q3, [pDst, #32]
    stp     q4, q5, [pDst, #64]
    stp     q6, q7, [pDst, #96]

    ret
.endfunc

.balign 8
.LShiftTable:
    .dc.b   0, -1, -2, -3, -4, -5, -6, -7

.end

我也不喜欢aarch64助记符。为了进行比较,我将aarch32版本放在下面:

/*
 * alqCountBits.S
 *
 *  Created on: 2020. 5. 26.
 *      Author: Jake 'Alquimista' LEE
 */


    .syntax unified
    .arm
    .arch   armv7-a
    .fpu    neon
    .global     alqCountBits
    .text


// extern void alqCountBits(uint32_t *pDst, uint32_t *pSrc, uint32_t nLength);
// assert(nLength % 2 == 0);
pDst    .req    r0
pSrc    .req    r1
length  .req    r2

.balign 32
.func
alqCountBits:
    adr     r12, .LShiftTable
    vpush   {q4-q7}
    vld1.64     {d30}, [r12]

    vmov.i8     q14, #1

    vmov.i8     q0, #0
    vmov.i8     q1, #0
    vmov.i8     q2, #0
    vmov.i8     q3, #0
    vmov.i8     q4, #0
    vmov.i8     q5, #0
    vmov.i8     q6, #0
    vmov.i8     q7, #0

    vmov        d31, d30

.balign 32
1:
    vld4.8      {d16[], d17[], d18[], d19[]}, [pSrc]!
    vld4.8      {d20[], d21[], d22[], d23[]}, [pSrc]!
    subs    length, length, #2

    vshl.u8     q8, q8, q15
    vshl.u8     q9, q9, q15
    vshl.u8     q10, q10, q15
    vshl.u8     q11, q11, q15

    vand        q8, q8, q14
    vand        q9, q9, q14
    vand        q10, q10, q14
    vand        q11, q11, q14

    vaddl.u8    q12, d20, d16
    vaddl.u8    q13, d21, d17
    vaddl.u8    q8, d22, d18
    vaddl.u8    q10, d23, d19

    vaddw.u16   q0, q0, d24
    vaddw.u16   q1, q1, d25
    vaddw.u16   q2, q2, d26
    vaddw.u16   q3, q3, d27
    vaddw.u16   q4, q4, d16
    vaddw.u16   q5, q5, d17
    vaddw.u16   q6, q6, d20
    vaddw.u16   q7, q7, d21

    bgt     1b
.balign 8
    vst1.32     {q0, q1}, [pDst]!
    vst1.32     {q2, q3}, [pDst]!
    vst1.32     {q4, q5}, [pDst]!
    vst1.32     {q6, q7}, [pDst]

    vpop        {q4-q7}
    bx      lr
.endfunc

.balign 8
.LShiftTable:
    .dc.b   0, -1, -2, -3, -4, -5, -6, -7

.end

如您所见,在trn1中根本不需要aarch32对等

仍然,由于寄存器的绝对数量,我总体上还是偏爱aarch64


0
投票

结合以上答案,并修改我的要求;-)我想到了:

tst:
    ldr     x0, =test_data
    ldr     x1, =mask
    ld1     {v2.2d}, [x1]                       // ld1.2d       v2, [x1]        // load 2 * 64 = 128 bits
    movi    v0.16b, 0
    mov     w2, 8
1:
    ld1r    {v1.8h}, [x0], 2                    // ld1r.8h      v1, [x0], 2     // repeat one 16-bit word across eight 16-bit lanes
    cmtst   v1.16b, v1.16b, v2.16b              // cmtst.16b    v1, v1, v2      // sets -1 in each 8bit word of 16 8-bit lanes if input matches mask
    sub     v0.16b, v0.16b, v1.16b              // sub.16b      v0, v0, v1      // sub -1 = add +1
    subs    w2, w2, 1
    bne     1b

    // v0 contains 16 bytes, mildly shuffled.

如果有人要他们改组:

    mov     v1.d[0], v0.d[1]
    uzp1    v2.8b, v1.8b, v0.8b
    uzp2    v3.8b, v1.8b, v0.8b
    mov     v2.d[1], v3.d[0]

    // v2 contains 16 bytes, in order.

以下内容最多计数15个32位样本(累加32个半字节):

tst2:
    ldr     x0, =test_data2
    ldr     x1, =mask2
    ld1     {v2.4s, v3.4s, v4.4s, v5.4s}, [x1]  // ld1.4s       {v2, v3, v4, v5}, [x1]
    movi    v0.16b, 0
    mov     w2, 8
1:
    ld1r    {v1.4s}, [x0], 4                    // ld1r.4s      v1, [x0], 4     // repeat one 32-bit word across four 32-bit lanes
    cmtst   v6.16b, v1.16b, v2.16b              // cmtst.16b    v6, v1, v2      // upper nibbles
    cmtst   v1.16b, v1.16b, v3.16b              // cmtst.16b    v1, v1, v3      // lower nibbles
    and     v6.16b, v6.16b, v4.16b              // and.16b      v6, v6, v4      // upper inc        0001.0000 x 16
    and     v1.16b, v1.16b, v5.16b              // and.16b      v1, v1, v5      // lower inc        0000.0001 x 16
    orr     v1.16b, v1.16b, v6.16b              // orr.16b      v1, v1, v6
    add     v0.16b, v0.16b, v1.16b              // add.16b      v0, v0, v1      // accumulate
    subs    w2, w2, 1
    bne     1b

    // v0 contains 32 nibbles -- somewhat shuffled, but that's ok.

// fedcba98.76543210.fedcba98.76543210.fedcba98.76543210.fedcba98.76543210 fedcba98.76543210.fedcba98.76543210.fedcba98.76543210.fedcba98.76543210
// 10000000.10000000.01000000.01000000.00100000.00100000.00010000.00010000 00001000.00001000.00000100.00000100.00000010.00000010.00000001.00000001
// f        7         e        6         d        5         c        4         b        3         a        2         9        1         8        0

mask:
    .quad   0x8080404020201010
    .quad   0x0808040402020101

test_data:
    .hword 0x0103
    .hword 0x0302
    .hword 0x0506
    .hword 0x080A
    .hword 0x1010
    .hword 0x2020
    .hword 0xc040
    .hword 0x8080

// FEDCBA98.76543210.fedcba⁹⁸.⁷⁶⁵⁴³²¹⁰.FEDCBA98.76543210.fedcba⁹⁸.⁷⁶⁵⁴³²¹⁰.FEDCBA98.76543210.fedcba⁹⁸.⁷⁶⁵⁴³²¹⁰.FEDCBA98.76543210.fedcba⁹⁸.⁷⁶⁵⁴³²¹⁰
// 10001000 10001000 10001000 10001000 01000100 01000100 01000100 01000100 00100010 00100010 00100010 00100010 00010001 00010001 00010001 00010001
// F   B    7   3    f   b    ⁷   ³     E   A    6   2    e   a    ⁶   ²     D   9    5   1    d   ⁹    ⁵   ¹     C   8    4   0    c   ⁸    ⁴   ⁰

mask2:
    .quad   0x8080808040404040  // v2
    .quad   0x2020202010101010
    .quad   0x0808080804040404  // v3
    .quad   0x0202020201010101
    .quad   0x1010101010101010  // v4
    .quad   0x1010101010101010
    .quad   0x0101010101010101  // v5
    .quad   0x0101010101010101

test_data2:
    .word 0xff000103
    .word 0xff000302
    .word 0xff000506
    .word 0xff00080A
    .word 0xff001010
    .word 0xff002020
    .word 0xff00c040
    .word 0xff008080
© www.soinside.com 2019 - 2024. All rights reserved.