我有一个在 STM32f103C8 上运行的简单闪烁 LED 程序(没有初始化样板):
void soft_delay(void) {
for (volatile uint32_t i=0; i<2000000; ++i) { }
}
uint32_t iters = 0;
while (1)
{
LL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
soft_delay();
++iters;
}
Run Code Online (Sandbox Code Playgroud)
它是用Keil uVision v.5(默认编译器)和CLionusingarm-none-eabi-gcc编译器编译的。令人惊讶的是,arm-none-eabi-gcc 程序在发布模式 (-O2 -flto) 下运行速度慢 50%,在调试模式下运行速度慢 100%。
我怀疑有3个原因:
Keil过度优化(不太可能,因为代码很简单)
由于错误的编译器标志,arm-none-eabi-gcc 优化不足(我使用 CLion Embedded 插件`CMakeLists.txt)
初始化中的一个错误,导致芯片在使用arm-none-eabi-gcc时具有较低的时钟频率(待调查)
我还没有深入到优化和反汇编的丛林中,我希望有许多经验丰富的嵌入式开发人员已经遇到过这个问题并有答案。
更新1
通过尝试 Keil ArmCC 的不同优化级别,我了解了它如何影响生成的代码。它影响很大,尤其是执行时间。soft_delay()以下是每个优化级别的基准测试和函数反汇编(RAM 和 Flash 数量包括初始化代码)。
-O0:RAM:1032,闪存:1444,执行时间(20 次迭代):18.7 秒
soft_delay PROC
PUSH {r3,lr}
MOVS r0,#0
STR r0,[sp,#0]
B |L6.14|
|L6.8|
LDR r0,[sp,#0]
ADDS r0,r0,#1
STR r0,[sp,#0]
|L6.14|
LDR r1,|L6.24|
LDR r0,[sp,#0]
CMP r0,r1
BCC |L6.8|
POP {r3,pc}
ENDP
Run Code Online (Sandbox Code Playgroud)
-O1:RAM:1032,闪存:1216,执行时间(20 次迭代):13.3 秒
soft_delay PROC
PUSH {r3,lr}
MOVS r0,#0
STR r0,[sp,#0]
LDR r0,|L6.24|
B |L6.16|
|L6.10|
LDR r1,[sp,#0]
ADDS r1,r1,#1
STR r1,[sp,#0]
|L6.16|
LDR r1,[sp,#0]
CMP r1,r0
BCC |L6.10|
POP {r3,pc}
ENDP
Run Code Online (Sandbox Code Playgroud)
-O2 -Otime:RAM:1032,闪存:1136,执行时间(20 次迭代):9.8 秒
soft_delay PROC
SUB sp,sp,#4
MOVS r0,#0
STR r0,[sp,#0]
LDR r0,|L4.24|
|L4.8|
LDR r1,[sp,#0]
ADDS r1,r1,#1
STR r1,[sp,#0]
CMP r1,r0
BCC |L4.8|
ADD sp,sp,#4
BX lr
ENDP
Run Code Online (Sandbox Code Playgroud)
-O3:RAM:1032,闪存:1176,执行时间(20 次迭代):9.9 秒
soft_delay PROC
PUSH {r3,lr}
MOVS r0,#0
STR r0,[sp,#0]
LDR r0,|L5.20|
|L5.8|
LDR r1,[sp,#0]
ADDS r1,r1,#1
STR r1,[sp,#0]
CMP r1,r0
BCC |L5.8|
POP {r3,pc}
ENDP
Run Code Online (Sandbox Code Playgroud)
TODO:. 的基准测试和反arm-none-eabi-gcc汇编
第二个答案演示了会影响 OP 可能看到的性能结果的因素,以及可能测试这些 STM32F103C8 蓝色药丸的示例。
完整源代码:
闪存.ld
MEMORY
{
rom : ORIGIN = 0x08000000, LENGTH = 0x1000
ram : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > rom
.rodata : { *(.rodata*) } > rom
.bss : { *(.bss*) } > ram
}
Run Code Online (Sandbox Code Playgroud)
闪存
.cpu cortex-m0
.thumb
.thumb_func
.global _start
_start:
stacktop: .word 0x20001000
.word reset
.word hang
.word hang
.thumb_func
reset:
bl notmain
b hang
.thumb_func
hang: b .
.align
.thumb_func
.globl PUT32
PUT32:
str r1,[r0]
bx lr
.thumb_func
.globl GET32
GET32:
ldr r0,[r0]
bx lr
.thumb_func
.globl dummy
dummy:
bx lr
Run Code Online (Sandbox Code Playgroud)
测试.s
.cpu cortex-m0
.thumb
.word 0,0,0
.word 0,0,0,0
.thumb_func
.globl TEST
TEST:
bx lr
Run Code Online (Sandbox Code Playgroud)
notmain.c
//PA9 TX
//PA10 RX
void PUT32 ( unsigned int, unsigned int );
unsigned int GET32 ( unsigned int );
void dummy ( unsigned int );
#define USART1_BASE 0x40013800
#define USART1_SR (USART1_BASE+0x00)
#define USART1_DR (USART1_BASE+0x04)
#define USART1_BRR (USART1_BASE+0x08)
#define USART1_CR1 (USART1_BASE+0x0C)
#define USART1_CR2 (USART1_BASE+0x10)
#define USART1_CR3 (USART1_BASE+0x14)
//#define USART1_GTPR (USART1_BASE+0x18)
#define GPIOA_BASE 0x40010800
#define GPIOA_CRH (GPIOA_BASE+0x04)
#define RCC_BASE 0x40021000
#define RCC_APB2ENR (RCC_BASE+0x18)
#define STK_CSR 0xE000E010
#define STK_RVR 0xE000E014
#define STK_CVR 0xE000E018
#define STK_MASK 0x00FFFFFF
static void uart_init ( void )
{
//assuming 8MHz clock, 115200 8N1
unsigned int ra;
ra=GET32(RCC_APB2ENR);
ra|=1<<2; //GPIOA
ra|=1<<14; //USART1
PUT32(RCC_APB2ENR,ra);
//pa9 TX alternate function output push-pull
//pa10 RX configure as input floating
ra=GET32(GPIOA_CRH);
ra&=~(0xFF0);
ra|=0x490;
PUT32(GPIOA_CRH,ra);
PUT32(USART1_CR1,0x2000);
PUT32(USART1_CR2,0x0000);
PUT32(USART1_CR3,0x0000);
//8000000/16 = 500000
//500000/115200 = 4.34
//4 and 5/16 = 4.3125
//4.3125 * 16 * 115200 = 7948800
PUT32(USART1_BRR,0x0045);
PUT32(USART1_CR1,0x200C);
}
static void uart_putc ( unsigned int c )
{
while(1)
{
if(GET32(USART1_SR)&0x80) break;
}
PUT32(USART1_DR,c);
}
static void hexstrings ( unsigned int d )
{
//unsigned int ra;
unsigned int rb;
unsigned int rc;
rb=32;
while(1)
{
rb-=4;
rc=(d>>rb)&0xF;
if(rc>9) rc+=0x37; else rc+=0x30;
uart_putc(rc);
if(rb==0) break;
}
uart_putc(0x20);
}
static void hexstring ( unsigned int d )
{
hexstrings(d);
uart_putc(0x0D);
uart_putc(0x0A);
}
void soft_delay(void) {
for (volatile unsigned int i=0; i<2000000; ++i) { }
}
int notmain ( void )
{
PUT32(STK_CSR,4);
PUT32(STK_RVR,0x00FFFFFF);
PUT32(STK_CVR,0x00000000);
PUT32(STK_CSR,5);
uart_init();
hexstring(0x12345678);
hexstring(GET32(0xE000E018));
hexstring(GET32(0xE000E018));
return(0);
}
Run Code Online (Sandbox Code Playgroud)
建造
arm-none-eabi-as --warn --fatal-warnings -mcpu=cortex-m3 flash.s -o flash.o
arm-none-eabi-as --warn --fatal-warnings -mcpu=cortex-m3 test.s -o test.o
arm-none-eabi-gcc -Wall -Werror -O2 -nostdlib -nostartfiles -ffreestanding -mthumb -mcpu=cortex-m0 -march=armv6-m -c notmain.c -o notmain.thumb.o
arm-none-eabi-ld -o notmain.thumb.elf -T flash.ld flash.o test.o notmain.thumb.o
arm-none-eabi-objdump -D notmain.thumb.elf > notmain.thumb.list
arm-none-eabi-objcopy notmain.thumb.elf notmain.thumb.bin -O binary
arm-none-eabi-gcc -Wall -Werror -O2 -nostdlib -nostartfiles -ffreestanding -mthumb -mcpu=cortex-m3 -march=armv7-m -c notmain.c -o notmain.thumb2.o
arm-none-eabi-ld -o notmain.thumb2.elf -T flash.ld flash.o test.o notmain.thumb2.o
arm-none-eabi-objdump -D notmain.thumb2.elf > notmain.thumb2.list
arm-none-eabi-objcopy notmain.thumb2.elf notmain.thumb2.bin -O binary
Run Code Online (Sandbox Code Playgroud)
串口输出如图
12345678
00FFE445
00FFC698
Run Code Online (Sandbox Code Playgroud)
如果我接受你的代码,让它更短,不用一整天。
void soft_delay(void) {
for (volatile unsigned int i=0; i<0x2000; ++i) { }
}
arm-none-eabi-gcc -c -O0 -mthumb -mcpu=cortex-m0 hello.c -o hello.o
Run Code Online (Sandbox Code Playgroud)
是的,我知道这是一辆 m3
arm-none-eabi-gcc --version
arm-none-eabi-gcc (GCC) 5.4.0
Run Code Online (Sandbox Code Playgroud)
给出
00000000 <soft_delay>:
0: b580 push {r7, lr}
2: b082 sub sp, #8
4: af00 add r7, sp, #0
6: 2300 movs r3, #0
8: 607b str r3, [r7, #4]
a: e002 b.n 12 <soft_delay+0x12>
c: 687b ldr r3, [r7, #4]
e: 3301 adds r3, #1
10: 607b str r3, [r7, #4]
12: 687b ldr r3, [r7, #4]
14: 4a03 ldr r2, [pc, #12] ; (24 <soft_delay+0x24>)
16: 4293 cmp r3, r2
18: d9f8 bls.n c <soft_delay+0xc>
1a: 46c0 nop ; (mov r8, r8)
1c: 46bd mov sp, r7
1e: b002 add sp, #8
20: bd80 pop {r7, pc}
22: 46c0 nop ; (mov r8, r8)
24: 00001fff
Run Code Online (Sandbox Code Playgroud)
首先检查测试基础设施
.cpu cortex-m0
.thumb
.align 8
.word 0,0
.thumb_func
.globl TEST
TEST:
push {r4,r5,r6,lr}
mov r4,r0
mov r5,r1
ldr r6,[r4]
inner:
bl soft_delay
sub r5,#1
bne inner
ldr r3,[r4]
sub r0,r6,r3
pop {r4,r5,r6,pc}
.align 8
soft_delay:
bx lr
Run Code Online (Sandbox Code Playgroud)
在 openocd telnet 窗口中
reset halt
flash write_image erase notmain.thumb.elf
reset
Run Code Online (Sandbox Code Playgroud)
给出
12345678
00001B59
Run Code Online (Sandbox Code Playgroud)
7001 个时钟,假设 systick 与 cpu 匹配,那就是 7001 个 Arm 时钟,每个循环 4 条指令。
退一步注意我对齐了一些东西
08000108 <TEST>:
8000108: b570 push {r4, r5, r6, lr}
800010a: 1c04 adds r4, r0, #0
800010c: 1c0d adds r5, r1, #0
800010e: 6826 ldr r6, [r4, #0]
08000110 <inner>:
8000110: f000 f876 bl 8000200 <soft_delay>
8000114: 3d01 subs r5, #1
8000116: d1fb bne.n 8000110 <inner>
8000118: 6823 ldr r3, [r4, #0]
800011a: 1af0 subs r0, r6, r3
800011c: bd70 pop {r4, r5, r6, pc}
08000200 <soft_delay>:
8000200: 4770 bx lr
Run Code Online (Sandbox Code Playgroud)
两个循环都很好地对齐。
现在如果我这样做:
0800010a <TEST>:
800010a: b570 push {r4, r5, r6, lr}
800010c: 1c04 adds r4, r0, #0
800010e: 1c0d adds r5, r1, #0
8000110: 6826 ldr r6, [r4, #0]
08000112 <inner>:
8000112: f000 f875 bl 8000200 <soft_delay>
8000116: 3d01 subs r5, #1
8000118: d1fb bne.n 8000112 <inner>
800011a: 6823 ldr r3, [r4, #0]
800011c: 1af0 subs r0, r6, r3
800011e: bd70 pop {r4, r5, r6, pc}
Run Code Online (Sandbox Code Playgroud)
只需更改应该测试被测代码的代码的对齐方式,我现在得到:
00001F40
Run Code Online (Sandbox Code Playgroud)
8000 个刻度来执行该循环 1000 次,并且测试中的代码函数仍保持对齐
08000200 <soft_delay>:
8000200: 4770 bx lr
Run Code Online (Sandbox Code Playgroud)
.align 8,一般不要将 .align 与 gnu 上的数字一起使用,它的行为不会跨目标转换。.balign 更好。反正我用过。这两个词是因为align使TEST对齐,但inner是我想要对齐的所以我添加了两个词使其对齐。
.align 8
.word 0,0
nop
.thumb_func
.globl TEST
TEST:
push {r4,r5,r6,lr}
mov r4,r0
mov r5,r1
ldr r6,[r4]
inner:
bl soft_delay
sub r5,#1
bne inner
ldr r3,[r4]
sub r0,r6,r3
pop {r4,r5,r6,pc}
Run Code Online (Sandbox Code Playgroud)
进行一些代码审查以确保我在这里没有犯错误。
r0 是 systick 当前值寄存器
r1 是我要运行测试代码的循环数
调用约定允许 r0-r3 被破坏,因此我需要将 r0 和 r1 移动到非易失性寄存器(根据调用约定)。
我想对循环之前的指令和循环之后的指令的时间进行采样。
所以我需要两个寄存器用于 r0 和 r1,以及一个寄存器来存储开始时间,因此 r4、r5、r6 非常适合将偶数个寄存器压入堆栈。必须保留lr,这样我们才能返回。
现在我们可以在循环中安全地调用 soft_delay,减去计数,如果不等于内部则转移,一旦计数完成,就读取 r3 中的计时器。从上面的输出来看,这是一个递减计数器,因此从开始处减去 end,从技术上讲,因为这是一个 24 位计数器,我应该使用 0x00FFFFFF 来正确执行该减法,但因为这不会翻转,所以我可以假设该操作。结果/返回值进入 r0,弹出所有内容,包括弹出 pc 以返回到打印出 r0 值的 C 调用函数。
我认为测试代码很好。
读取CPUID寄存器
411FC231
Run Code Online (Sandbox Code Playgroud)
这意味着 r1p1,而我使用的 TRM 是为 r2p1 编写的,您必须非常小心地使用正确的文档,但有时也使用当前文档或中间的所有文档(如果可用)来查看更改的内容。
ICode内存接口
从代码存储空间 0x00000000 到 0x1FFFFFFF 的指令读取是通过 32 位 AHB-Lite 总线执行的。调试器无法访问此接口。所有提取都是字范围的。每个字获取的指令数量取决于正在运行的代码以及代码在内存中的对齐方式。
有时在 ARM TRM 中,您会在处理器功能附近看到顶部的获取信息,这告诉我我想知道什么。
08000112 <inner>:
8000112: f000 f875 bl 8000200 <soft_delay>
8000116: 3d01 subs r5, #1
8000118: d1fb bne.n 8000112 <inner>
Run Code Online (Sandbox Code Playgroud)
这需要在 110、114 和 118 处进行提取。
08000110 <inner>:
8000110: f000 f876 bl 8000200 <soft_delay>
8000114: 3d01 subs r5, #1
8000116: d1fb bne.n 8000110 <inner>
Run Code Online (Sandbox Code Playgroud)
这是 110 和 114 处的取指,但不是 118 处的取指,因此额外的取指可能是我们添加的时钟。m3 是第一个公开发布的产品,它的核心中有很多功能已经消失,但类似的功能又回来了。一些较小的核心的获取方式不同,您不会看到这种对齐问题。对于更大的内核(例如全尺寸内核),它们有时一次获取 4 或 8 条指令,您必须更多地更改对齐方式才能达到边界,但您可以达到边界,因为它是 2 或 4 个时钟加上额外的总线开销获取你可以看到那些。
如果我放两个 no
nop
nop
.thumb_func
.globl TEST
TEST:
Run Code Online (Sandbox Code Playgroud)
给出
08000114 <inner>:
8000114: f000 f874 bl 8000200 <soft_delay>
8000118: 3d01 subs r5, #1
800011a: d1fb bne.n 8000114 <inner>
800011c: 6823 ldr r3, [r4, #0]
800011e: 1af0 subs r0, r6, r3
8000120: bd70 pop {r4, r5, r6, pc}
Run Code Online (Sandbox Code Playgroud)
给出
00001B59
Run Code Online (Sandbox Code Playgroud)
所以很好,我们又回到了这个数字,可以尝试更多一些来确认,但看起来对齐对我们的外部测试循环很敏感,这很糟糕,但我们可以管理它,不要改变它,它不会影响考试。如果我不关心对齐并且有这样的事情:
void soft_delay(void) {
for (volatile unsigned int i=0; i<0x2000; ++i) { }
}
int notmain ( void )
{
unsigned int ra;
unsigned int beg;
unsigned int end;
PUT32(STK_CSR,4);
PUT32(STK_RVR,0x00FFFFFF);
PUT32(STK_CVR,0x00000000);
PUT32(STK_CSR,5);
uart_init();
hexstring(0x12345678);
beg=GET32(STK_CVR);
for(ra=0;ra<1000;ra++)
{
soft_delay();
}
end=GET32(STK_CVR);
hexstring((beg-end)&0x00FFFFFF);
return(0);
}
Run Code Online (Sandbox Code Playgroud)
然后,当我使用优化选项并且还使用不同的编译器时,测试循环前面的程序/二进制文件中的任何更改都会/可能会移动测试循环,从而改变其性能,在我的简单示例中,性能差异为 14% ,如果您正在进行性能测试,那么这是巨大的。让编译器处理所有这些,而不让我们控制被测试函数前面的所有内容可能会扰乱被测试函数,如上面所写,编译器可能会选择内联函数而不是调用它,从而使函数变得更有趣作为测试循环的情况虽然可能不像我的那么干净,如果不优化的话肯定不会,但现在被测试的代码是动态的,随着选项或对齐的变化。
我很高兴你碰巧使用这个核心/芯片......
如果我重新调整内部并现在搞乱这个
.align 8
nop
soft_delay:
bx lr
08000202 <soft_delay>:
8000202: 4770 bx lr
Run Code Online (Sandbox Code Playgroud)
这是一条从 0x200 处获取的指令,我们已经读到并且似乎能够分辨出来。没想到这会改变任何事情,而且它没有
00001B59
Run Code Online (Sandbox Code Playgroud)
但现在我们知道了我们所知道的,我们可以用我们的经验来搞乱这个琐碎的、一点也不有趣的例子。
.align 8
nop
soft_delay:
nop
bx lr
Run Code Online (Sandbox Code Playgroud)
给出
00001F41
Run Code Online (Sandbox Code Playgroud)
正如预期的那样。我们还可以享受更多乐趣:
.align 8
.word 0,0
nop
.thumb_func
.globl TEST
TEST:
Run Code Online (Sandbox Code Playgroud)
综合给出
08000112 <inner>:
8000112: f000 f876 bl 8000202 <soft_delay>
8000116: 3d01 subs r5, #1
8000118: d1fb bne.n 8000112 <inner>
08000202 <soft_delay>:
8000202: 46c0 nop ; (mov r8, r8)
8000204: 4770 bx lr
Run Code Online (Sandbox Code Playgroud)
如果您知道自己在做什么,那就不足为奇了:
00002328
Run Code Online (Sandbox Code Playgroud)
9000 个时钟,性能差异 29%。我们实际上谈论的是 5 条(技术上是 6 条)指令,完全相同的机器代码,通过简单地更改对齐方式,性能可能会出现 29% 的差异,编译器和选项与之无关,但还没有达到这一点。
我们如何期望使用循环方法中多次代码的时间来对程序进行任何类型的性能评估?除非我们知道我们在做什么、了解架构等,否则我们不能。
现在,很明显,阅读文档后,我正在使用内部 8Mhz 时钟,所有内容都源自该时钟,因此 systick 时间有时不会像您在 dram 中看到的那样发生变化。对于 0 < SYSCLK <- 24Mhz,FLASH_ACR 寄存器中的 LATENCY 位应默认为零等待状态。如果我将时钟提高到 24Mhz 以上,处理器的运行速度会更快,但闪存相对于处理器来说会变慢。
无需扰乱时钟,只需将 FLASH_ACR 寄存器更改为 0x31 即可添加等待状态。
000032C6
Run Code Online (Sandbox Code Playgroud)
从 9000 涨到 12998,我没想到它一定会翻倍,但事实并非如此。
嗯,为了好玩,使用 strh 创建一个 PUT16,并且
.thumb_func
.globl HOP
HOP:
bx r2
Run Code Online (Sandbox Code Playgroud)
和
PUT16(0x2000010a,0xb570); // 800010a: b570 push {r4, r5, r6, lr}
PUT16(0x2000010c,0x1c04); // 800010c: 1c04 adds r4, r0, #0
PUT16(0x2000010e,0x1c0d); // 800010e: 1c0d adds r5, r1, #0
PUT16(0x20000110,0x6826); // 8000110: 6826 ldr r6, [r4, #0]
PUT16(0x20000112,0xf000); // 8000112: f000 f876 bl 8000202 <soft_delay>
PUT16(0x20000114,0xf876); // 8000112: f000 f876 bl 8000202 <soft_delay>
PUT16(0x20000116,0x3d01); // 8000116: 3d01 subs r5, #1
PUT16(0x20000118,0xd1fb); // 8000118: d1fb bne.n 8000112 <inner>
PUT16(0x2000011a,0x6823); // 800011a: 6823 ldr r3, [r4, #0]
PUT16(0x2000011c,0x1af0); // 800011c: 1af0 subs r0, r6, r3
PUT16(0x2000011e,0xbd70); // 800011e: bd70 pop {r4, r5, r6, pc}
PUT16(0x20000202,0x46c0); // 8000202: 46c0 nop ; (mov r8, r8)
PUT16(0x20000204,0x4770); // 8000204: 4770 bx lr
hexstring(HOP(STK_CVR,1000,0x2000010B));
Run Code Online (Sandbox Code Playgroud)
给出 0000464B
这是完全出乎意料的。但基本上是18,000
之后让公羊上床睡觉
PUT16(0x20000108,0xb570); // 800010a: b570 push {r4, r5, r6, lr}
PUT16(0x2000010a,0x1c04); // 800010c: 1c04 adds r4, r0, #0
PUT16(0x2000010c,0x1c0d); // 800010e: 1c0d adds r5, r1, #0
PUT16(0x2000010e,0x6826); // 8000110: 6826 ldr r6, [r4, #0]
PUT16(0x20000110,0xf000); // 8000112: f000 f876 bl 8000202 <soft_delay>
PUT16(0x20000112,0xf876); // 8000112: f000 f876 bl 8000202 <soft_delay>
PUT16(0x20000114,0x3d01); // 8000116: 3d01 subs r5, #1
PUT16(0x20000116,0xd1fb); // 8000118: d1fb bne.n 8000112 <inner>
PUT16(0x20000118,0x6823); // 800011a: 6823 ldr r3, [r4, #0]
PUT16(0x2000011a,0x1af0); // 800011c: 1af0 subs r0, r6, r3
PUT16(0x2000011c,0xbd70); // 800011e: bd70 pop {r4, r5, r6, pc}
PUT16(0x20000200,0x46c0); // 8000202: 46c0 nop ; (mov r8, r8)
PUT16(0x20000200,0x4770); // 8000204: 4770 bx lr
hexstring(HOP(STK_CVR,1000,0x20000109));
00002EDE
Run Code Online (Sandbox Code Playgroud)
机器码没有改变,因为我将两者都向后移动了 2,所以它们之间的相对地址是相同的。请注意,bl 是两条单独的指令,而不是一条 32 位指令。您在较新的文档中看不到这一点,您需要返回到原始/早期的 ARM ARM 来解释它。做实验很容易,你可以将两条指令分开,然后在中间放入其他东西,它们就可以正常工作,因为它们是两条单独的指令。
此时,读者应该能够创建一个 2 指令测试循环,对其进行计时,并使用完全相同的机器代码显着改变该平台上这两条指令的执行性能。
那么让我们尝试一下您编写的易失性循环。
.align 8
soft_delay:
push {r7, lr}
sub sp, #8
add r7, sp, #0
mov r3, #0
str r3, [r7, #4]
b L12
Lc:
ldr r3, [r7, #4]
add r3, #1
str r3, [r7, #4]
L12:
ldr r3, [r7, #4]
ldr r2, L24
cmp r3, r2
bls Lc
nop
mov sp, r7
add sp, #8
pop {r7, pc}
nop
.align
L24: .word 0x1FFF
Run Code Online (Sandbox Code Playgroud)
我相信这是未优化的 -O0 版本。从一个测试循环开始
hexstring(TEST(STK_CVR,1));
Run Code Online (Sandbox Code Playgroud)
根据经验,我们看到的时间会溢出我们的 24 位计数器,结果会非常奇怪或导致错误的结论。
0001801F
Run Code Online (Sandbox Code Playgroud)
98,000,快速检查安全:
.align
L24: .word 0x1F
Run Code Online (Sandbox Code Playgroud)
0000019F
不错,相当于快了 256 倍。
所以我们的测试循环中有一些回旋余地,但没有太多尝试 10
hexstring(TEST(STK_CVR,10));
000F012D
Run Code Online (Sandbox Code Playgroud)
每个循环 98334 个刻度。
改变对齐方式
08000202 <soft_delay>:
8000202: b580 push {r7, lr}
8000204: b082 sub sp, #8
Run Code Online (Sandbox Code Playgroud)
给出了相同的结果
000F012D
Run Code Online (Sandbox Code Playgroud)
并非闻所未闻,如果您想计算每个指令检查获取周期等,您可以检查差异。
我做了测试吗:
soft_delay:
nop
nop
bx lr
Run Code Online (Sandbox Code Playgroud)
它的两个提取周期,无论对齐方式如何,或者如果我将其保留为没有 nops 的 bx lr,正如我们所看到的,因此通过在测试中简单地使用奇数条指令,那么对齐不会影响提取的结果,但请注意据我们现在所知,程序中的一些其他代码移动了外部计时/测试循环,这可能会改变性能,结果可能会显示两个测试之间的差异,这两个测试纯粹是计时代码,而不是被测代码(请阅读 Michael Abrash )。
cortex-m3基于armv7-m架构。如果我将编译器从 -mcpu=cortex-m0 (到目前为止所有 cortex-m 兼容)更改为 -mcpu=cortex-m3 (并非所有 cortex-m 兼容都会在其中一半上中断),它会生成更少的代码。
.align 8
soft_delay:
push {r7}
sub sp, #12
add r7, sp, #0
movs r3, #0
str r3, [r7, #4]
b L12
Lc:
ldr r3, [r7, #4]
add r3, #1
str r3, [r7, #4]
L12:
ldr r3, [r7, #4]
/*14: f5b3 5f00 cmp.w r3, #8192 ; 0x2000*/
//cmp.w r3, #8192
.word 0x5f00f5b3
bcc Lc
nop
add r7, #12
mov sp, r7
pop {r7}
bx lr
Run Code Online (Sandbox Code Playgroud)
000C80FB 81945 代表被测代码。
我讨厌统一语法,这是一个巨大的错误,所以我在遗留模式下摸索。因此 .word 就在中间。
在写这篇文章的过程中,我为了演示一些东西而搞乱了我的系统。我正在构建 gcc 5.4.0,但覆盖了我的 9.2.0,因此必须重新构建两者。
2.95 是我开始使用arm 的版本,不支持thumb gcc 3.xx 是第一个支持的。gcc 4.xx 或 gcc 5.xx 为我的一些项目生成了“较慢”的代码,在工作中,我们目前正在将我们的构建系统从 ubuntu 16.04 迁移到 18.04,如果您使用 apt-got 交叉编译器 for arm将您从 5.xx 移动到 7.xx,它会为相同的源代码生成更大的二进制文件,并且在我们内存紧张的情况下,它会推动我们超出可用范围,因此我们必须删除一些代码(最容易使打印消息更短,删除文本)或通过构建我们自己的编译器或 apt-get 旧的编译器来坚持使用旧的编译器。19.10 不再提供 5.xx 版本。
所以现在两者都已建成。
18: d3f8 bcc.n c <soft_delay+0xc>
1a: bf00 nop
1c: bf00 nop
1e: 370c adds r7, #12
Run Code Online (Sandbox Code Playgroud)
密件抄送后的这些 nops 让我感到困惑......
18: d3f8 bcc.n c <soft_delay+0xc>
1a: bf00 nop
1c: 370c adds r7, #12
Run Code Online (Sandbox Code Playgroud)
gcc 5.4.0 放置了一个,gcc 9.2.0 放置了两个 nop,ARM 没有 MIPS 的分支影子(MIPS 目前也没有)。
000C80FB gcc 5.4.0
000C8105 gcc 9.2.0
Run Code Online (Sandbox Code Playgroud)
我调用该函数 10 次,nop 位于测试循环代码之外,因此影响较小。
使用 gcc 9.2.0 优化了所有 cortex-m 变体(迄今为止)
soft_delay:
mov r3, #0
mov r2, #128
sub sp, #8
str r3, [sp, #4]
ldr r3, [sp, #4]
lsl r2, r2, #6
cmp r3, r2
bcs L1c
L10:
ldr r3, [sp, #4]
add r3, #1
str r3, [sp, #4]
ldr r3, [sp, #4]
cmp r3, r2
bcc L10
L1c:
add sp, #8
bx lr
Run Code Online (Sandbox Code Playgroud)
(还要理解,并非所有 gcc 9.2.0 构建都会产生相同的代码,当您构建编译器时,您有选项,这些选项可能会影响输出,从而使 9.2.0 的不同构建可能产生不同的结果)
000C80B5
Run Code Online (Sandbox Code Playgroud)
为 cortex-m3 构建的 gcc 9.2.0:
soft_delay:
mov r3, #0
sub sp, #8
str r3, [sp, #4]
ldr r3, [sp, #4]
/*8: f5b3 5f00 cmp.w r3, #8192 ; 0x2000*/
.word 0x5F00F5B3
bcs L1c
Le:
ldr r3, [sp, #4]
add r3, #1
str r3, [sp, #4]
ldr r3, [sp, #4]
/*16: f5b3 5f00 cmp.w r3, #8192 ; 0x2000*/
.word 0x5F00F5B3
bcc Le
L1c:
add sp, #8
bx lr
000C80A1
Run Code Online (Sandbox Code Playgroud)
那是在噪音中。尽管构建的代码存在差异。他们只是没有通过使用更少的指令来比较 0x2000 来获得收益。请注意,如果您将 0x2000 更改为其他数字,那么这不仅仅会使循环花费更长的时间,而且还可以更改此类架构的生成代码。
我喜欢制作这些计数延迟循环的方式是使用编译域之外的函数
extern void dummy ( unsigned int );
void soft_delay(void) {
for (unsigned int i=0; i<0x2000; ++i) { dummy(i); }
}
soft_delay:
push {r4, r5, r6, lr}
mov r5, #128
mov r4, #0
lsl r5, r5, #6
L8:
mov r0, r4
add r4, #1
bl dummy
cmp r4, r5
bne L8
pop {r4, r5, r6, pc}
Run Code Online (Sandbox Code Playgroud)
那里的功能是你不需要你有一个调用的易失性的开销,显然也有由于调用而产生的开销,但没有那么多
000B40C9
Run Code Online (Sandbox Code Playgroud)
甚至更好:
soft_delay:
sub r0,#1
bne soft_delay
bx lr
Run Code Online (Sandbox Code Playgroud)
我必须更改被测试代码周围的代码才能使该功能正常工作。
针对这些目标的另一条注释,也是您要处理的内容
unsigned int more_fun ( unsigned int, unsigned int );
unsigned int fun ( unsigned int a, unsigned int b )
{
return(more_fun(a,b)+a+(b<<2));
}
00000000 <fun>:
0: b570 push {r4, r5, r6, lr}
2: 000c movs r4, r1
4: 0005 movs r5, r0
6: f7ff fffe bl 0 <more_fun>
a: 00a4 lsls r4, r4, #2
c: 1964 adds r4, r4, r5
e: 1820 adds r0, r4, r0
10: bd70 pop {r4, r5, r6, pc}
12: 46c0 nop ; (mov r8, r8)
Run Code Online (Sandbox Code Playgroud)
一个问题在 SO 定期重复出现。为什么它推动 r6 它没有使用 r6。
编译器使用我所说的和过去称为调用约定的方式进行操作,现在他们使用术语 ABI、EABI,无论哪种情况,它都是相同的东西,它是编译器针对特定目标遵循的一组规则。Arm 添加了一条规则来保持堆栈在 64 位地址边界而不是 32 位地址边界上对齐,这导致需要额外的项来保持堆栈对齐,其中使用的寄存器可能会有所不同。如果您使用较旧的 gcc 与较新的 gcc,这可能会影响代码本身的性能。
| 归档时间: |
|
| 查看次数: |
2030 次 |
| 最近记录: |