如何优化 Cortex-M3 的滤波器环路?
How do I optimise a filter loop for Cortex-M3?
我只需要更改代码,使其执行相同的基本功能但更优化,基本上我认为过滤器循环是可以更改的主要代码段,因为我觉得其中的指令太多,但不知道从哪里开始。我正在使用 Cortex M3 和 Thumb 2。
我曾尝试篡改过滤器循环,以便我可以将存储在寄存器中的先前数字相加并将其除以 8,但我不知道如何真正执行它。
; Perform in-place filtering of data supplied in memory
; the filter to be applied is a non-recursive filter of the form
; y[0] = x[-2]/8 + x[-1]/8 + x[0]/4 + x[1]/8 + x[2]/8
; set up the exception addresses
THUMB
AREA RESET, CODE, READONLY
EXPORT __Vectors
EXPORT Reset_Handler
__Vectors
DCD 0x00180000 ; top of the stack
DCD Reset_Handler ; reset vector - where the program starts
num_words EQU (end_source-source)/4 ; number of input values
filter_length EQU 5 ; number of filter taps (values)
AREA 2a_Code, CODE, READONLY
Reset_Handler
ENTRY
; set up the filter parameters
LDR r0,=source ; point to the start of the area of memory holding inputs
MOV r1,#num_words ; get the number of input values
MOV r2,#filter_length ; get the number of filter taps
LDR r3,=dest ; point to the start of the area of memory holding outputs
; find out how many times the filter needs to be applied
SUBS r4,r1,r2 ; find the number of applications of the filter needed, less 1
BMI exit ; give up if there is insufficient data for any filtering
; apply the filter
filter_loop
LDMIA r0,{r5-r9} ; get the next 5 data values to be filtered
ADD r5,r5,r9 ; sum x[-2] with x[2]
ADD r6,r6,r8 ; sum x[-1] with x[1]
ADD r9,r5,r6 ; sum x[-2]+x[2] with x[-1]+x[1]
ADD r7,r7,r9,LSR #1 ; sum x[0] with (x[-2]+x[2]+x[-1]+x[1])/2
MOV r7,r7,LSR #2 ; form (x[0] + (x[-2]+x[-1]+x[1]+x[2])/2)/4
STR r7,[r3],#4 ; save calculated filtered value, move to next output data item
ADD r0,r0,#4 ; move to start of next 5 input data values
SUBS r4,r4,#1 ; move on to next set of 5 inputs
BPL filter_loop ; continue until last set of 5 inputs reached
; execute an endless loop once done
exit
B exit
AREA 2a_ROData, DATA, READONLY
source ; some saw tooth data to filter - should blunt the sharp edges
DCD 0,10,20,30,40,50,60,70,80,90,100,0,10,20,30,40,50,60,70,80,90,100
DCD 0,10,20,30,40,50,60,70,80,90,100,0,10,20,30,40,50,60,70,80,90,100
DCD 0,10,20,30,40,50,60,70,80,90,100,0,10,20,30,40,50,60,70,80,90,100
DCD 0,10,20,30,40,50,60,70,80,90,100,0,10,20,30,40,50,60,70,80,90,100
end_source
AREA 2a_RWData, DATA, READWRITE
dest ; copy to this area of memory
SPACE end_source-source
end_dest
END
END
我希望有一种更有效的方法来 运行 代码,天气可以减少代码的整体大小或加快循环的执行时间,只要它做同样的事情.任何帮助将不胜感激。
对于代码大小,尽量只使用可用于短 16 位编码的寄存器 r0..r7。
此外,当非标志设置版本需要 32 位时,带有标志设置的指令版本通常具有 16 位编码。例如
adds r0, #4
是 16 位与 32 位 add r0, #4
movs r7,r7,LSR #2
是 16 位与 32 位 MOV r7,r7,LSR #2
movs r2,#filter_length
是 16 位与 32 位 MOV r2,#filter_length
。 (像 #88
这样的非微型立即数仍然需要一个 32 位的 Thumb2 mov
)
stmia r3!, {r5}
(带回写)是 16 位与 32 位 str r7, [r3], #4
,具有 post-增量。
请参阅我对您先前问题的回答中的 Thumb 代码大小部分:。查看代码的反汇编并查找 32 位指令,并检查为什么它们是 32 位的,并寻找使它们成为 16 位的方法。这只是您可以随时进行的最基本的 Thumb 优化。
r1
和 r2
甚至没有在循环中使用,并且 r4 = r1-r2
是您在运行时计算的 assemble-time 常数有 3 条指令...所以与 movs r4, #num_words - filter_length
.
相比,这显然是疯狂的
如果这些输入在 assemble 时对于您的真实代码来说是未知的(也许相同的函数有时会用于不同的输入?),那么请重用 "dead" 计算循环计数器后。你在 r0 和 r3 中接受指针有点笨拙,所以如果你使用 r1
作为循环计数器,你就可以免费使用 r2
和 r4-r7
,或者 r1-r2
和 r5-r7
如果你使用 r4
.
免费
我选择使用 r1
作为循环计数器。这是我的版本的反汇编 (arm-none-eabi-gcc -g -c -mthumb -mcpu=cortex-m3 arm-filter.S && arm-none-eabi-objdump -drwC arm-filter.o
)
@@ Saving code size without any other changes
00000000 <function>:
0: 480a ldr r0, [pc, #40] ; (2c <exit+0x4>)
2: f05f 0158 movs.w r1, #88 ; 0x58
6: 2205 movs r2, #5
8: 4b09 ldr r3, [pc, #36] ; (30 <exit+0x8>)
a: 1a89 subs r1, r1, r2
c: d40c bmi.n 28 <exit>
0000000e <filter_loop>:
e: e890 00f4 ldmia.w r0, {r2, r4, r5, r6, r7}
12: 443a add r2, r7
14: 4434 add r4, r6
16: 4414 add r4, r2
18: eb15 0554 adds.w r5, r5, r4, lsr #1
1c: 08ad lsrs r5, r5, #2
1e: c320 stmia r3!, {r5}
20: 3004 adds r0, #4
22: 3901 subs r1, #1
24: d5f3 bpl.n e <filter_loop>
00000026 <exit>:
26: e7fe b.n 26 <exit>
Cortex-M3 没有 NEON,但输出之间有数据重用。通过展开,我们绝对可以重用加载结果,以及一些 "inner" add
结果。也许用滑动 window 减去不再属于总数的单词并添加新的单词。
但是由于中间元素是 "special",我们在两边都有两个 2 元素 windows,除非我们在顶部有足够的空闲位来添加 x[0]
两次然后右移3不溢出。然后你甚至不需要展开,只需加载1个元素/调整滑动window并重新计算中间/存储1个元素。
(我的第一个版本的答案是基于对代码的误解。我可能会在稍后更新速度优化,但现在编辑以删除错误的东西。)
我只需要更改代码,使其执行相同的基本功能但更优化,基本上我认为过滤器循环是可以更改的主要代码段,因为我觉得其中的指令太多,但不知道从哪里开始。我正在使用 Cortex M3 和 Thumb 2。
我曾尝试篡改过滤器循环,以便我可以将存储在寄存器中的先前数字相加并将其除以 8,但我不知道如何真正执行它。
; Perform in-place filtering of data supplied in memory
; the filter to be applied is a non-recursive filter of the form
; y[0] = x[-2]/8 + x[-1]/8 + x[0]/4 + x[1]/8 + x[2]/8
; set up the exception addresses
THUMB
AREA RESET, CODE, READONLY
EXPORT __Vectors
EXPORT Reset_Handler
__Vectors
DCD 0x00180000 ; top of the stack
DCD Reset_Handler ; reset vector - where the program starts
num_words EQU (end_source-source)/4 ; number of input values
filter_length EQU 5 ; number of filter taps (values)
AREA 2a_Code, CODE, READONLY
Reset_Handler
ENTRY
; set up the filter parameters
LDR r0,=source ; point to the start of the area of memory holding inputs
MOV r1,#num_words ; get the number of input values
MOV r2,#filter_length ; get the number of filter taps
LDR r3,=dest ; point to the start of the area of memory holding outputs
; find out how many times the filter needs to be applied
SUBS r4,r1,r2 ; find the number of applications of the filter needed, less 1
BMI exit ; give up if there is insufficient data for any filtering
; apply the filter
filter_loop
LDMIA r0,{r5-r9} ; get the next 5 data values to be filtered
ADD r5,r5,r9 ; sum x[-2] with x[2]
ADD r6,r6,r8 ; sum x[-1] with x[1]
ADD r9,r5,r6 ; sum x[-2]+x[2] with x[-1]+x[1]
ADD r7,r7,r9,LSR #1 ; sum x[0] with (x[-2]+x[2]+x[-1]+x[1])/2
MOV r7,r7,LSR #2 ; form (x[0] + (x[-2]+x[-1]+x[1]+x[2])/2)/4
STR r7,[r3],#4 ; save calculated filtered value, move to next output data item
ADD r0,r0,#4 ; move to start of next 5 input data values
SUBS r4,r4,#1 ; move on to next set of 5 inputs
BPL filter_loop ; continue until last set of 5 inputs reached
; execute an endless loop once done
exit
B exit
AREA 2a_ROData, DATA, READONLY
source ; some saw tooth data to filter - should blunt the sharp edges
DCD 0,10,20,30,40,50,60,70,80,90,100,0,10,20,30,40,50,60,70,80,90,100
DCD 0,10,20,30,40,50,60,70,80,90,100,0,10,20,30,40,50,60,70,80,90,100
DCD 0,10,20,30,40,50,60,70,80,90,100,0,10,20,30,40,50,60,70,80,90,100
DCD 0,10,20,30,40,50,60,70,80,90,100,0,10,20,30,40,50,60,70,80,90,100
end_source
AREA 2a_RWData, DATA, READWRITE
dest ; copy to this area of memory
SPACE end_source-source
end_dest
END
END
我希望有一种更有效的方法来 运行 代码,天气可以减少代码的整体大小或加快循环的执行时间,只要它做同样的事情.任何帮助将不胜感激。
对于代码大小,尽量只使用可用于短 16 位编码的寄存器 r0..r7。
此外,当非标志设置版本需要 32 位时,带有标志设置的指令版本通常具有 16 位编码。例如
adds r0, #4
是 16 位与 32 位add r0, #4
movs r7,r7,LSR #2
是 16 位与 32 位MOV r7,r7,LSR #2
movs r2,#filter_length
是 16 位与 32 位MOV r2,#filter_length
。 (像#88
这样的非微型立即数仍然需要一个 32 位的 Thumb2mov
)stmia r3!, {r5}
(带回写)是 16 位与 32 位str r7, [r3], #4
,具有 post-增量。
请参阅我对您先前问题的回答中的 Thumb 代码大小部分:
r1
和 r2
甚至没有在循环中使用,并且 r4 = r1-r2
是您在运行时计算的 assemble-time 常数有 3 条指令...所以与 movs r4, #num_words - filter_length
.
如果这些输入在 assemble 时对于您的真实代码来说是未知的(也许相同的函数有时会用于不同的输入?),那么请重用 "dead" 计算循环计数器后。你在 r0 和 r3 中接受指针有点笨拙,所以如果你使用 r1
作为循环计数器,你就可以免费使用 r2
和 r4-r7
,或者 r1-r2
和 r5-r7
如果你使用 r4
.
我选择使用 r1
作为循环计数器。这是我的版本的反汇编 (arm-none-eabi-gcc -g -c -mthumb -mcpu=cortex-m3 arm-filter.S && arm-none-eabi-objdump -drwC arm-filter.o
)
@@ Saving code size without any other changes
00000000 <function>:
0: 480a ldr r0, [pc, #40] ; (2c <exit+0x4>)
2: f05f 0158 movs.w r1, #88 ; 0x58
6: 2205 movs r2, #5
8: 4b09 ldr r3, [pc, #36] ; (30 <exit+0x8>)
a: 1a89 subs r1, r1, r2
c: d40c bmi.n 28 <exit>
0000000e <filter_loop>:
e: e890 00f4 ldmia.w r0, {r2, r4, r5, r6, r7}
12: 443a add r2, r7
14: 4434 add r4, r6
16: 4414 add r4, r2
18: eb15 0554 adds.w r5, r5, r4, lsr #1
1c: 08ad lsrs r5, r5, #2
1e: c320 stmia r3!, {r5}
20: 3004 adds r0, #4
22: 3901 subs r1, #1
24: d5f3 bpl.n e <filter_loop>
00000026 <exit>:
26: e7fe b.n 26 <exit>
Cortex-M3 没有 NEON,但输出之间有数据重用。通过展开,我们绝对可以重用加载结果,以及一些 "inner" add
结果。也许用滑动 window 减去不再属于总数的单词并添加新的单词。
但是由于中间元素是 "special",我们在两边都有两个 2 元素 windows,除非我们在顶部有足够的空闲位来添加 x[0]
两次然后右移3不溢出。然后你甚至不需要展开,只需加载1个元素/调整滑动window并重新计算中间/存储1个元素。
(我的第一个版本的答案是基于对代码的误解。我可能会在稍后更新速度优化,但现在编辑以删除错误的东西。)