如何使用 Neon Extension 有效地反转汇编语言 ARM 中的数组?
How to reverse an array in Assembly language ARM with Neon Extension efficiently?
我正在使用一些图像处理函数,我需要以最快的方式反转字节数组。如果我试图解释我的实际功能,那将是不合适的。这就是我要简化问题标准的原因。
Input Array:
37 3B 29 32 C5 E3 F3 5E 04 E2 CA B8 A1 1F 64 1D
E5 6F 7B 2C EA 6A FD 1F A5 6B 8F FA FB 7A F4 2A
DC 08 6D DB B8 D4 77 5D A2 44 E6 8A 59 9C 7D C2
8E FB C6 2A F8 EC 96 ED DC F8 00 2D 63 4C A4 F9
Length: 64
Output Array:
F9 A4 4C 63 2D 00 F8 DC ED 96 EC F8 2A C6 FB 8E
C2 7D 9C 59 8A E6 44 A2 5D 77 D4 B8 DB 6D 08 DC
2A F4 7A FB FA 8F 6B A5 1F FD 6A EA 2C 7B 6F E5
1D 64 1F A1 B8 CA E2 04 5E F3 E3 C5 32 29 3B 37
用 C++ 等高级语言完成这项工作真的很容易。在 C++ 中,此实现可能如下所示:
void Reverse_array(unsigned char* pInData, int iLen, unsigned char* pOutData)
{
int indx = 0;
for(int i=iLen-1; i>=0; i--)
{
pOutData[indx++] = pInData[i];
}
}
但我真的需要找到最有效和优化的解决方案来完成这项工作。由于此任务将在移动设备中执行,因此我决定在带有 Neon Extension 的 ARM 中使用原始汇编语言来实现。现在,我将分享我为实现这个任务所做的努力(这仍然是不完整的)。
NEON_ASM_FUNC_BEGIN Reverse_array_arm_neon
push {r2-r8, lr}
#r0 First parameter, This is the address of <pInData>
#r1 Second Parameter, This is the iLen
#r2 Third Parameter, This is the address of <pOutData>
add r2, r2, r1
ands r3, r1, #7
add r2, r2, #8
loop_Reverse:
vld1.u8 {d0}, [r0]!
vrev64.u8 d1, d0
sub r2, r2, #16
vst1.u8 {d1}, [r2]!
subs r1, #8
bne loop_Reverse
pop {r2-r8, pc}
NEON_ASM_FUNC_END
我还检查了 and Reversing an array in assembly 来自 Whosebug 的解决方案,但我仍然需要更多关于此实现的知识。
- 是否可以用arm汇编语言编写比c++更快的函数来解决这个问题?
- 使用 NEON 扩展功能实现此任务的正确方法是什么? (我的功能不完整,因为它不会在不能被 8 整除的不同长度下工作)。
任何想法或信息都会对我有所帮助。谢谢你。
只要 In 和 Out 不重叠,并且 iLen
大于 64,下面的代码应该有效:
// Written by Jake 'Alquimista' LEE
.syntax unified
.arch armv7-a
.fpu neon
.text
.global Reverse_array_arm_neon
// void Reverse_array_arm_neon(unsigned char* pInData, int iLen, unsigned char* pOutData);
pSrc .req r0
iLen .req r1
pDst .req r2
postInc .req r3
.balign 32
.func
Reverse_array_arm_neon:
add pDst, pDst, iLen
mov postInc, #-32
sub pDst, pDst, #32
sub iLen, iLen, #64 // "withholding tax"
.balign 32
1:
vld1.8 {d16, d17, d18, d19}, [pSrc]!
vld1.8 {d20, d21, d22, d23}, [pSrc]!
subs iLen, iLen, #64
pld [pSrc, #64]
vrev64.8 q8, q8
vrev64.8 q9, q9
vrev64.8 q10, q10
vrev64.8 q11, q11
vswp d19, d16
vswp d18, d17
vswp d23, d20
vswp d22, d21
vst1.8 {d16, d17, d18, d19}, [pDst], postInc
vst1.8 {d20, d21, d22, d23}, [pDst], postInc
bpl 1b
add pSrc, pSrc, iLen
cmp iLen, #-64
sub pDst, pDst, iLen
bxle lr // return
b 1b
.endfunc
.end
- 您必须保留的唯一寄存器是:
r4-r11,lr
和 q4-q7
,并且仅当您确实需要使用它们时。
- 如果您没有保留任何东西并损坏
lr
,您可以 return 和 bx lr
- 你可以按照我的方式最有效地处理'residuals'("withholding tax"、
bpl
和add/sub
通过循环后的负循环计数器) .
- 为了提高 I-Cache 效率,您应该将主循环对齐到 32 字节。
首先,我想对 Jake 'Alquimista' LEE 表示尊重,因为我从他的回答中学到了很多东西。在这里,我将分享这个问题的替代解决方案。
.macro NEON_ASM_FUNC_BEGIN
.syntax unified
.text
.extern printf
.align 2
.arm
.globl _[=10=]
_[=10=]:
.endm
.macro NEON_ASM_FUNC_END
mov pc, lr
.endm
NEON_ASM_FUNC_BEGIN Reverse_array_arm_neon
push {r3-r8, lr}
pInData .req r0
iLen .req r1
pOutData .req r2
iOverlap .req r3
iTemp .req r4
add pOutData, pOutData, iLen
add pOutData, pOutData, #8
ands iOverlap, iLen, #7
beq loop_Reverse //if(iLen % 8 == 0) goto loop_Reverse
sub pOutData, pOutData, #16
vld1.u8 {d0}, [pInData]!
vrev64.u8 d1, d0
vst1.u8 {d1}, [pOutData]!
subs iLen, iLen, iOverlap
beq Reverse_array_arm_neon_completed //if(iLen == 0) go to end
mov iTemp, #8
sub iTemp, iTemp, iOverlap
sub pInData, pInData, iTemp
add pOutData, pOutData, iTemp
loop_Reverse:
vld1.u8 {d0}, [pInData]!
vrev64.u8 d1, d0
sub pOutData, pOutData, #16
vst1.u8 {d1}, [pOutData]!
subs iLen, #8
bne loop_Reverse
Reverse_array_arm_neon_completed:
pop {r3-r8, pc}
NEON_ASM_FUNC_END
我已经在 Apple Ipod touch 5th generation(32bit/Cortex A9/ARMv7-A Platform) 设备上测试了这个功能。此函数适用于每个可能的数组长度。希望能帮助到你。
我正在使用一些图像处理函数,我需要以最快的方式反转字节数组。如果我试图解释我的实际功能,那将是不合适的。这就是我要简化问题标准的原因。
Input Array:
37 3B 29 32 C5 E3 F3 5E 04 E2 CA B8 A1 1F 64 1D
E5 6F 7B 2C EA 6A FD 1F A5 6B 8F FA FB 7A F4 2A
DC 08 6D DB B8 D4 77 5D A2 44 E6 8A 59 9C 7D C2
8E FB C6 2A F8 EC 96 ED DC F8 00 2D 63 4C A4 F9
Length: 64
Output Array:
F9 A4 4C 63 2D 00 F8 DC ED 96 EC F8 2A C6 FB 8E
C2 7D 9C 59 8A E6 44 A2 5D 77 D4 B8 DB 6D 08 DC
2A F4 7A FB FA 8F 6B A5 1F FD 6A EA 2C 7B 6F E5
1D 64 1F A1 B8 CA E2 04 5E F3 E3 C5 32 29 3B 37
用 C++ 等高级语言完成这项工作真的很容易。在 C++ 中,此实现可能如下所示:
void Reverse_array(unsigned char* pInData, int iLen, unsigned char* pOutData)
{
int indx = 0;
for(int i=iLen-1; i>=0; i--)
{
pOutData[indx++] = pInData[i];
}
}
但我真的需要找到最有效和优化的解决方案来完成这项工作。由于此任务将在移动设备中执行,因此我决定在带有 Neon Extension 的 ARM 中使用原始汇编语言来实现。现在,我将分享我为实现这个任务所做的努力(这仍然是不完整的)。
NEON_ASM_FUNC_BEGIN Reverse_array_arm_neon
push {r2-r8, lr}
#r0 First parameter, This is the address of <pInData>
#r1 Second Parameter, This is the iLen
#r2 Third Parameter, This is the address of <pOutData>
add r2, r2, r1
ands r3, r1, #7
add r2, r2, #8
loop_Reverse:
vld1.u8 {d0}, [r0]!
vrev64.u8 d1, d0
sub r2, r2, #16
vst1.u8 {d1}, [r2]!
subs r1, #8
bne loop_Reverse
pop {r2-r8, pc}
NEON_ASM_FUNC_END
我还检查了
- 是否可以用arm汇编语言编写比c++更快的函数来解决这个问题?
- 使用 NEON 扩展功能实现此任务的正确方法是什么? (我的功能不完整,因为它不会在不能被 8 整除的不同长度下工作)。
任何想法或信息都会对我有所帮助。谢谢你。
只要 In 和 Out 不重叠,并且 iLen
大于 64,下面的代码应该有效:
// Written by Jake 'Alquimista' LEE
.syntax unified
.arch armv7-a
.fpu neon
.text
.global Reverse_array_arm_neon
// void Reverse_array_arm_neon(unsigned char* pInData, int iLen, unsigned char* pOutData);
pSrc .req r0
iLen .req r1
pDst .req r2
postInc .req r3
.balign 32
.func
Reverse_array_arm_neon:
add pDst, pDst, iLen
mov postInc, #-32
sub pDst, pDst, #32
sub iLen, iLen, #64 // "withholding tax"
.balign 32
1:
vld1.8 {d16, d17, d18, d19}, [pSrc]!
vld1.8 {d20, d21, d22, d23}, [pSrc]!
subs iLen, iLen, #64
pld [pSrc, #64]
vrev64.8 q8, q8
vrev64.8 q9, q9
vrev64.8 q10, q10
vrev64.8 q11, q11
vswp d19, d16
vswp d18, d17
vswp d23, d20
vswp d22, d21
vst1.8 {d16, d17, d18, d19}, [pDst], postInc
vst1.8 {d20, d21, d22, d23}, [pDst], postInc
bpl 1b
add pSrc, pSrc, iLen
cmp iLen, #-64
sub pDst, pDst, iLen
bxle lr // return
b 1b
.endfunc
.end
- 您必须保留的唯一寄存器是:
r4-r11,lr
和q4-q7
,并且仅当您确实需要使用它们时。 - 如果您没有保留任何东西并损坏
lr
,您可以 return 和 - 你可以按照我的方式最有效地处理'residuals'("withholding tax"、
bpl
和add/sub
通过循环后的负循环计数器) . - 为了提高 I-Cache 效率,您应该将主循环对齐到 32 字节。
bx lr
首先,我想对 Jake 'Alquimista' LEE 表示尊重,因为我从他的回答中学到了很多东西。在这里,我将分享这个问题的替代解决方案。
.macro NEON_ASM_FUNC_BEGIN
.syntax unified
.text
.extern printf
.align 2
.arm
.globl _[=10=]
_[=10=]:
.endm
.macro NEON_ASM_FUNC_END
mov pc, lr
.endm
NEON_ASM_FUNC_BEGIN Reverse_array_arm_neon
push {r3-r8, lr}
pInData .req r0
iLen .req r1
pOutData .req r2
iOverlap .req r3
iTemp .req r4
add pOutData, pOutData, iLen
add pOutData, pOutData, #8
ands iOverlap, iLen, #7
beq loop_Reverse //if(iLen % 8 == 0) goto loop_Reverse
sub pOutData, pOutData, #16
vld1.u8 {d0}, [pInData]!
vrev64.u8 d1, d0
vst1.u8 {d1}, [pOutData]!
subs iLen, iLen, iOverlap
beq Reverse_array_arm_neon_completed //if(iLen == 0) go to end
mov iTemp, #8
sub iTemp, iTemp, iOverlap
sub pInData, pInData, iTemp
add pOutData, pOutData, iTemp
loop_Reverse:
vld1.u8 {d0}, [pInData]!
vrev64.u8 d1, d0
sub pOutData, pOutData, #16
vst1.u8 {d1}, [pOutData]!
subs iLen, #8
bne loop_Reverse
Reverse_array_arm_neon_completed:
pop {r3-r8, pc}
NEON_ASM_FUNC_END
我已经在 Apple Ipod touch 5th generation(32bit/Cortex A9/ARMv7-A Platform) 设备上测试了这个功能。此函数适用于每个可能的数组长度。希望能帮助到你。