[我是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。 :-(
我不认为每个半字节都可以完成,但是每个字节都可以。
加载一个向量,并在每个字节中设置相关的源位(您将需要其中的两个,因为我们可能只能按字节而不是按半字节执行此操作)。在两个向量中,将单词的每个字节均复制为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位字可能比这里的解决方案提供更好的吞吐量。
您应该开箱即用。源数据为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
。
结合以上答案,并修改我的要求;-)我想到了:
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