ARM 设备中的 SRAM 使用优化
SRAM usage optimization in ARM devices
变量大小和数据总线大小之间的关系让我感到困惑,所以我决定通过检查汇编代码来深入了解它。
我在 STM32CubeIDE 版本 1.2.0 中编译了下面的源代码。
#define BUFFER_SIZE ((uint8_t)0x20)
uint8_t aTxBuffer[BUFFER_SIZE];
int i;
for(i=0; i<BUFFER_SIZE; i++){
aTxBuffer[i]=0xFF; /* TxBuffer init */
}
查看汇编代码证实了我的怀疑。除非我严重误解它,否则这段代码将分配一个总大小为 BUFFER_SIZE * DATA_BUS_SIZE 的数组(在 Cortex-M 上是 32 位)但我们将仅使用每个内存地址的最低有效字节.
for(i=0; i<BUFFER_SIZE; i++)
//reset i to 0
800051c: 4b09 ldr r3, [pc, #36] ; (8000544 <main+0x3c>)
800051e: 2200 movs r2, #0
8000520: 601a str r2, [r3, #0]
8000522: e009 b.n 8000538 <main+0x30>
{
//store 0xFF in each member of TxBuffer
aTxBuffer[i]=0xFF; /* TxBuffer init */
8000524: 4b07 ldr r3, [pc, #28] ; (8000544 <main+0x3c>)
8000526: 681b ldr r3, [r3, #0]
8000528: 4a07 ldr r2, [pc, #28] ; (8000548 <main+0x40>)
800052a: 21ff movs r1, #255 ; 0xff
800052c: 54d1 strb r1, [r2, r3]
for(i=0; i<BUFFER_SIZE; i++)
//increment i
800052e: 4b05 ldr r3, [pc, #20] ; (8000544 <main+0x3c>)
8000530: 681b ldr r3, [r3, #0]
8000532: 3301 adds r3, #1
8000534: 4a03 ldr r2, [pc, #12] ; (8000544 <main+0x3c>)
8000536: 6013 str r3, [r2, #0]
//compare if i is less than 31. then jump to 8000524
8000538: 4b02 ldr r3, [pc, #8] ; (8000544 <main+0x3c>)
800053a: 681b ldr r3, [r3, #0]
800053c: 2b1f cmp r3, #31
800053e: d9f1 bls.n 8000524 <main+0x1c>
//pointer to i in SRAM
8000544: 2000002c .word 0x2000002c
//pointer to TxBuffer in SRAM
8000548: 20000064 .word 0x20000064
由于 SRAM 在嵌入式设备中非常重要,我相信一定有一些巧妙的方法来优化使用。我能想到的一种天真的解决方案是将缓冲区分配为 uint32_t 并进行位移以访问更高的字节,但从速度优化的角度来看,这似乎代价高昂。这里推荐的做法是什么?
给定的代码没有为 aTxBuffer 使用更多的 BUFFER_SIZE*8 位。
请注意程序集中的以下行
800052c: 54d1 strb r1, [r2, r3]
注意这里指令的b后缀,表示'byte'.
实际上,指令转换为 'store 1 byte of value 0xFF (stored in r1) at aTxBuffer (stored in r2) + i (stored in r3)'。
因此,虽然程序集没有指示缓冲区的末尾,但它肯定会毫无浪费地访问 aTxBuffer 数组中的所有字节。
您的最小示例可能没有捕捉到您在实际代码中遇到的问题,但我发现编译器不太可能有这样浪费的字节,尤其是用于嵌入式设备的字节。
如果您确实发现是这种情况,您可以简单地分配一个相同大小的 uint32 数组(或更高的一个元素)并将第一个元素的地址转换为 uint8_t指向 uint8_t 变量的指针。现在您可以正常访问 uint8_t 变量。
请注意,应避免此类编程,并且仅作为示例显示。具体来说,这使得编译器难以分析指针别名,从而使某些优化变得困难。这也给用户带来了一些负担;需要仔细的内存管理以避免错误(例如,您应该只释放这些指针中的一个以避免双重释放错误)。
示例:
#define BUFFSIZE 0x20
// number of elements in int32 will be BUFFSIZE / 4
#define BUFFSIZE_IN_INT_32 (BUFFSIZE >> 2)
// allocate the buffer
uint32_t uint32_array[BUFFSIZE_IN_INT_32];
// point to 1 byte sized elements
uint8_t * aTxBuffer = (uint8_t *)(uint32_array)
// use aTxBuffer as you like
请注意,我假设 BUFFSIZE 可以被 4 整除。如果不是这种情况,请再将 BUFFSIZE_IN_INT_32 加 1。
在这种情况下,总线大小无关紧要。内存使用将是相同的。
某些 Cortex 内核不允许未对齐的访问。什么是未对齐访问?当您尝试访问(作为单个操作)从不能被 N 整除的地址开始的 N 字节数据(即 addr % N != 0)时,会发生未对齐的内存访问。在我们的例子中,N 可以是 1、2 和 4。
您的示例应该在启用优化的情况下进行分析。
#define BUFFER_SIZE ((uint8_t)0x20)
uint8_t aTxBuffer[BUFFER_SIZE];
void init(uint8_t x)
{
for(int i=0; i<BUFFER_SIZE; i++)
{
aTxBuffer[i]=x;
}
}
不允许未对齐访问的STM32F0将不得不逐字节存储数据
init:
ldr r3, .L5
movs r2, r3
adds r2, r2, #32
.L2:
strb r0, [r3]
adds r3, r3, #1
cmp r3, r2
bne .L2
bx lr
.L5:
.word aTxBuffer
但是 stm32F4 会更快(在更少的操作中)存储完整的单词 32birs - 4 个字节。
init:
movs r3, #0
bfi r3, r0, #0, #8
bfi r3, r0, #8, #8
ldr r2, .L3
bfi r3, r0, #16, #8
bfi r3, r0, #24, #8
str r3, [r2] @ unaligned
str r3, [r2, #4] @ unaligned
str r3, [r2, #8] @ unaligned
str r3, [r2, #12] @ unaligned
str r3, [r2, #16] @ unaligned
str r3, [r2, #20] @ unaligned
str r3, [r2, #24] @ unaligned
str r3, [r2, #28] @ unaligned
bx lr
.L3:
.word aTxBuffer
两种情况下的 SRAM 消耗完全相同
变量大小和数据总线大小之间的关系让我感到困惑,所以我决定通过检查汇编代码来深入了解它。 我在 STM32CubeIDE 版本 1.2.0 中编译了下面的源代码。
#define BUFFER_SIZE ((uint8_t)0x20)
uint8_t aTxBuffer[BUFFER_SIZE];
int i;
for(i=0; i<BUFFER_SIZE; i++){
aTxBuffer[i]=0xFF; /* TxBuffer init */
}
查看汇编代码证实了我的怀疑。除非我严重误解它,否则这段代码将分配一个总大小为 BUFFER_SIZE * DATA_BUS_SIZE 的数组(在 Cortex-M 上是 32 位)但我们将仅使用每个内存地址的最低有效字节.
for(i=0; i<BUFFER_SIZE; i++)
//reset i to 0
800051c: 4b09 ldr r3, [pc, #36] ; (8000544 <main+0x3c>)
800051e: 2200 movs r2, #0
8000520: 601a str r2, [r3, #0]
8000522: e009 b.n 8000538 <main+0x30>
{
//store 0xFF in each member of TxBuffer
aTxBuffer[i]=0xFF; /* TxBuffer init */
8000524: 4b07 ldr r3, [pc, #28] ; (8000544 <main+0x3c>)
8000526: 681b ldr r3, [r3, #0]
8000528: 4a07 ldr r2, [pc, #28] ; (8000548 <main+0x40>)
800052a: 21ff movs r1, #255 ; 0xff
800052c: 54d1 strb r1, [r2, r3]
for(i=0; i<BUFFER_SIZE; i++)
//increment i
800052e: 4b05 ldr r3, [pc, #20] ; (8000544 <main+0x3c>)
8000530: 681b ldr r3, [r3, #0]
8000532: 3301 adds r3, #1
8000534: 4a03 ldr r2, [pc, #12] ; (8000544 <main+0x3c>)
8000536: 6013 str r3, [r2, #0]
//compare if i is less than 31. then jump to 8000524
8000538: 4b02 ldr r3, [pc, #8] ; (8000544 <main+0x3c>)
800053a: 681b ldr r3, [r3, #0]
800053c: 2b1f cmp r3, #31
800053e: d9f1 bls.n 8000524 <main+0x1c>
//pointer to i in SRAM
8000544: 2000002c .word 0x2000002c
//pointer to TxBuffer in SRAM
8000548: 20000064 .word 0x20000064
由于 SRAM 在嵌入式设备中非常重要,我相信一定有一些巧妙的方法来优化使用。我能想到的一种天真的解决方案是将缓冲区分配为 uint32_t 并进行位移以访问更高的字节,但从速度优化的角度来看,这似乎代价高昂。这里推荐的做法是什么?
给定的代码没有为 aTxBuffer 使用更多的 BUFFER_SIZE*8 位。 请注意程序集中的以下行
800052c: 54d1 strb r1, [r2, r3]
注意这里指令的b后缀,表示'byte'.
实际上,指令转换为 'store 1 byte of value 0xFF (stored in r1) at aTxBuffer (stored in r2) + i (stored in r3)'。
因此,虽然程序集没有指示缓冲区的末尾,但它肯定会毫无浪费地访问 aTxBuffer 数组中的所有字节。
您的最小示例可能没有捕捉到您在实际代码中遇到的问题,但我发现编译器不太可能有这样浪费的字节,尤其是用于嵌入式设备的字节。
如果您确实发现是这种情况,您可以简单地分配一个相同大小的 uint32 数组(或更高的一个元素)并将第一个元素的地址转换为 uint8_t指向 uint8_t 变量的指针。现在您可以正常访问 uint8_t 变量。 请注意,应避免此类编程,并且仅作为示例显示。具体来说,这使得编译器难以分析指针别名,从而使某些优化变得困难。这也给用户带来了一些负担;需要仔细的内存管理以避免错误(例如,您应该只释放这些指针中的一个以避免双重释放错误)。
示例:
#define BUFFSIZE 0x20
// number of elements in int32 will be BUFFSIZE / 4
#define BUFFSIZE_IN_INT_32 (BUFFSIZE >> 2)
// allocate the buffer
uint32_t uint32_array[BUFFSIZE_IN_INT_32];
// point to 1 byte sized elements
uint8_t * aTxBuffer = (uint8_t *)(uint32_array)
// use aTxBuffer as you like
请注意,我假设 BUFFSIZE 可以被 4 整除。如果不是这种情况,请再将 BUFFSIZE_IN_INT_32 加 1。
在这种情况下,总线大小无关紧要。内存使用将是相同的。
某些 Cortex 内核不允许未对齐的访问。什么是未对齐访问?当您尝试访问(作为单个操作)从不能被 N 整除的地址开始的 N 字节数据(即 addr % N != 0)时,会发生未对齐的内存访问。在我们的例子中,N 可以是 1、2 和 4。
您的示例应该在启用优化的情况下进行分析。
#define BUFFER_SIZE ((uint8_t)0x20)
uint8_t aTxBuffer[BUFFER_SIZE];
void init(uint8_t x)
{
for(int i=0; i<BUFFER_SIZE; i++)
{
aTxBuffer[i]=x;
}
}
不允许未对齐访问的STM32F0将不得不逐字节存储数据
init:
ldr r3, .L5
movs r2, r3
adds r2, r2, #32
.L2:
strb r0, [r3]
adds r3, r3, #1
cmp r3, r2
bne .L2
bx lr
.L5:
.word aTxBuffer
但是 stm32F4 会更快(在更少的操作中)存储完整的单词 32birs - 4 个字节。
init:
movs r3, #0
bfi r3, r0, #0, #8
bfi r3, r0, #8, #8
ldr r2, .L3
bfi r3, r0, #16, #8
bfi r3, r0, #24, #8
str r3, [r2] @ unaligned
str r3, [r2, #4] @ unaligned
str r3, [r2, #8] @ unaligned
str r3, [r2, #12] @ unaligned
str r3, [r2, #16] @ unaligned
str r3, [r2, #20] @ unaligned
str r3, [r2, #24] @ unaligned
str r3, [r2, #28] @ unaligned
bx lr
.L3:
.word aTxBuffer
两种情况下的 SRAM 消耗完全相同