ARM-v8 NEON: есть ли инструкция для разделения одного нормального регистра на несколько дорожек регистра NEON? - PullRequest
1 голос
/ 25 мая 2020

Я новичок в ARM-v8 (AArch64) и только немного кодировал NEON в ARM-v7 (но мне очень комфортно с A32 и хорошо (*) с нормальным A64).

В конечном итоге я пытаюсь подсчитать частоту каждого установленного бита [31: 0] в группе (до 15) 32-битных значений. То есть в этих 15 значениях, сколько раз устанавливается бит 0, сколько раз устанавливается бит 1, и т. Д. c.

Итак, что я хотел бы сделать, это разделить 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.

Предполагая, что для этого нет ни одной инструкции, какой способ сделать это наиболее эффективно? - Не ищу комплексного решения, но может ли НЕОН вообще помочь? Не было бы большого смысла, если бы мне пришлось загружать каждую полосу отдельно ...

(*) Не могу сказать, что мне нравится A64. :-(

Ответы [ 3 ]

3 голосов
/ 26 мая 2020

Вы должны мыслить нестандартно. То, что исходные данные имеют ширину 32 бита, не означает, что вы должны обращаться к ним с 32 бита.
Читая их в режиме 4x8 бит, проблема намного упрощается. Ниже показано разделение и подсчет каждого из 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 так много из-за огромного количества регистров

2 голосов
/ 25 мая 2020

Я не думаю, что это можно сделать для каждого полубайта, но для каждого байта должно работать.

Загрузить вектор с соответствующим исходным битом, установленным в каждом байте (вам понадобится два из них, поскольку мы, вероятно, может делать это только за байт, а не за полубайт). Дублируйте каждый байт слова в элементы размером 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

РЕДАКТИРОВАТЬ: Подход ld4r, предложенный Джейком 'Alquimista' ЛИ, на самом деле лучше, чем загрузка здесь; ld1r, за которым следуют четыре dup, здесь можно заменить на ld4r {v4.8b, v5.8b, v6.8b, v7.8h}, [x0], #4, оставив logi c прежним. В остальном, будет ли cmtst или ushl + and закончиться быстрее, нужно будет проверить и измерить, чтобы увидеть. И обработка двух 32-битных слов одновременно, как в его решении, вероятно, дает лучшую пропускную способность, чем мое решение здесь.

0 голосов
/ 30 мая 2020

Объединив приведенные выше ответы и изменив свои требования ;-) Я придумал:

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, v0.8b, v1.8b
    uzp2    v3.8b, v0.8b, v1.8b
    mov     v2.d[1], v3.d[0]

    // v2 contains 16 bytes, in order.

Следующее составляет до пятнадцати образцы с 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   0x0808040402020101
    .quad   0x8080404020201010

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
...