【问题标题】:SRAM usage optimization in ARM devicesARM 设备中的 SRAM 使用优化
【发布时间】:2020-05-28 12:33:02
【问题描述】:

变量大小和数据总线大小之间的关系让我感到困惑,所以我决定通过检查汇编代码来深入了解它。 我在STM32CubeIDE Version 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 并进行位移以访问更高的字节,但从速度优化的角度来看,这似乎代价高昂。这里推荐的做法是什么?

【问题讨论】:

    标签: optimization memory arm stm32


    【解决方案1】:

    在这种情况下,总线大小无关紧要。内存使用情况相同。

    某些 Cortex 内核不允许非对齐访问。什么是非对齐访问?当您尝试访问(作为单个操作)从不能被 N 整除的地址(即 addr % N != 0)开始的 N 字节数据时,会发生未对齐的内存访问。在我们的例子中,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 消耗完全相同

    【讨论】:

    • 是的,我发现即使对于 SDRAM,无论内存宽度如何,您都可以在唯一地址上单独访问每个内存字节。据我了解,这只是通过对 16 位宽的 SDRAM 使用类似的东西来实现的。 SDRAM_PHYSICAL_ADDRESS = (MEMORY_ADDRESS - SDRAM_BANK_BASE_ADDRESS) >> 1. MEMORY_ADDRESS 的第一位然后确定我们在 8 位 LDR/SDR 访问期间使用 SDRAM 的字节掩码功能访问低字节还是高字节。但这对我来说一开始并不明显。
    【解决方案2】:

    给定的代码没有为 aTxBuffer 使用更多的 BUFFER_SIZE*8 位。 请注意程序集中的以下行

    800052c: 54d1 strb r1, [r2, r3]

    注意这里指令的b后缀,表示'byte'。

    实际上,该指令转换为“在 aTxBuffer(存储在 r2)+ i(存储在 r3)处存储 1 个字节的值 0xFF(存储在 r1)”。

    因此,尽管程序集没有指示缓冲区的结束,但它肯定会毫无浪费地访问 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-M7 的内存映射,每个字节在 4GB 地址空间中被分配一个唯一的地址。如果我使用外部存储器怎么办?例如,如果使用 1,048,576 位 x 16 位 x 4-bank SDRAM,我是否能够访问唯一地址的每个字节?
    • 它取决于外部 RAM 是否可字节寻址(大多数是)。但是,C 编译器应该按照您的预期处理数据布局。根据 C 标准,编译器可能不会在数组元素之间插入任何填充。因此,大小为 1 字节的元素数组将占用 1 个字节的空间。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-02
    • 1970-01-01
    • 2018-06-07
    • 2019-05-18
    • 1970-01-01
    • 2017-07-19
    相关资源
    最近更新 更多