从数据缓存到内存的存储效率

Efficiency of storing from data cache to memory

提问人:Thomas Matthews 提问时间:11/9/2023 更新时间:11/9/2023 访问量:92

问:

背景

我正在优化一个函数,该函数从实时时钟 (RTC) 结构读取并将值存储到 8 位字节数组中。

原始函数采用直接赋值,编译器生成交替加载和存储指令:

struct Time_t
{
  uint32_t year;
  uint32_t month;
  uint32_t day;
  uint32_t hours;
  uint32_t minutes;
  uint32_t seconds;
};

void F1_Time_To_Buffer(uint8_t * p_buffer)
{
    Time_t date_time;
   (void)Get_RTC_Time(&date_time); //Discard return code
    p_buffer[0] = (uint8_t) (date_time.year - 2000U);
    p_buffer[1] = (uint8_t) date_time.month;
    p_buffer[2] = (uint8_t) date_time.day;
    p_buffer[3] = (uint8_t) date_time.hours;
    p_buffer[4] = (uint8_t) date_time.minutes;
    p_buffer[5] = (uint8_t) date_time.seconds;
};

编译器生成代码,该代码是加载(从内存中),然后存储到每个语句的内存中:

                  void F1_Time_To_Buffer(uint8_t * p_buffer)
                  {
   \                     F1_Time_To_Buffer: (+1)
   \        0x0   0xB518             PUSH     {R3,R4,LR}
   \        0x2   0xB087             SUB      SP,SP,#+28
   \        0x4   0x0004             MOVS     R4,R0
                      Time_t date_time;
                     (void)Get_RTC_Time(&date_time); //Discard return code
   \        0x6   0x4668             MOV      R0,SP
   \        0x8   0x....'....        BL       Get_RTC_Time
                      
                      p_buffer[0] = (uint8_t) (date_time.year - 2000U);
   \        0xC   0x9806             LDR      R0,[SP, #+24]
   \        0xE   0x3030             ADDS     R0,R0,#+48
   \       0x10   0x7020             STRB     R0,[R4, #+0]
                      p_buffer[1] = (uint8_t) date_time.month;
   \       0x12   0x9805             LDR      R0,[SP, #+20]
   \       0x14   0x7060             STRB     R0,[R4, #+1]
                      p_buffer[2] = (uint8_t) date_time.day;
   \       0x16   0x9804             LDR      R0,[SP, #+16]
   \       0x18   0x70A0             STRB     R0,[R4, #+2]
                      p_buffer[3] = (uint8_t) date_time.hours;
   \       0x1A   0x9803             LDR      R0,[SP, #+12]
   \       0x1C   0x70E0             STRB     R0,[R4, #+3]
                      p_buffer[4] = (uint8_t) date_time.minutes;
   \       0x1E   0x9802             LDR      R0,[SP, #+8]
   \       0x20   0x7120             STRB     R0,[R4, #+4]
                      p_buffer[5] = (uint8_t) date_time.seconds;
   \       0x22   0x9801             LDR      R0,[SP, #+4]
   \       0x24   0x7160             STRB     R0,[R4, #+5]
                  };
   \       0x26   0xB008             ADD      SP,SP,#+32
   \       0x28   0xBD10             POP      {R4,PC}          ;; return

优化版本从内存(作为块)执行所有加载,然后作为块存储到内存中:

                  void F2_Time_To_Buffer(uint8_t * p_buffer)
                  {
   \                     F2_Time_To_Buffer: (+1)
   \        0x0   0xB578             PUSH     {R3-R6,LR}
   \        0x2   0xB087             SUB      SP,SP,#+28
   \        0x4   0x0004             MOVS     R4,R0
                      Time_t date_time;
                      (void)Get_RTC_Time(&date_time); //Discard return code
   \        0x6   0x4668             MOV      R0,SP
   \        0x8   0x....'....        BL       Get_RTC_Time
                      const uint8_t   year        = (uint8_t) (date_time.year - 2000U);
   \        0xC   0x9806             LDR      R0,[SP, #+24]
   \        0xE   0x3030             ADDS     R0,R0,#+48
                      const uint8_t   month       = (uint8_t) date_time.month;
   \       0x10   0x9905             LDR      R1,[SP, #+20]
                      const uint8_t   day         = (uint8_t) date_time.day;
   \       0x12   0x9A04             LDR      R2,[SP, #+16]
                      const uint8_t   hours       = (uint8_t) date_time.hours;
   \       0x14   0x9B03             LDR      R3,[SP, #+12]
                      const uint8_t   minutes     = (uint8_t) date_time.minutes;
   \       0x16   0x9D02             LDR      R5,[SP, #+8]
                      const uint8_t   seconds     = (uint8_t) date_time.seconds;
   \       0x18   0x9E01             LDR      R6,[SP, #+4]
                      p_buffer[0] =   year;
   \       0x1A   0x7020             STRB     R0,[R4, #+0]
                      p_buffer[1] =   month;
   \       0x1C   0x7061             STRB     R1,[R4, #+1]
                      p_buffer[2] =   day;
   \       0x1E   0x70A2             STRB     R2,[R4, #+2]
                      p_buffer[3] =   hours;
   \       0x20   0x70E3             STRB     R3,[R4, #+3]
                      p_buffer[4] =   minutes;
   \       0x22   0x7125             STRB     R5,[R4, #+4]
                      p_buffer[5] =   seconds;
   \       0x24   0x7166             STRB     R6,[R4, #+5]
                  }
   \       0x26   0xB008             ADD      SP,SP,#+32
   \       0x28   0xBD70             POP      {R4-R6,PC}       ;; return

该函数效率更高,因为它减少了数据缓存未命中的机会。F2

问题

在函数中对存储语句进行分组对效率有什么影响? {说明数据缓存是如何涉及的。F2

笔记

  1. 编译器是 IAR Embedded Workbench IDE - Arm 8.42.2
  2. 在调试模式下编译(无优化或最少优化)(故意,因为它驻留在医疗设备平台上)
  3. 该函数从 RTC 硬件设备读取数据。Get_RTC_Time()
  4. 该结构具有用于对硬件设备进行建模的字段。Time_tuint32_t
  5. 缓冲区是为了节省内存空间(平台受内存限制)。uint8_t
C++ C 优化 缓存 内存

评论

0赞 Botje 11/9/2023
“F2 函数效率更高,因为它减少了数据缓存未命中的机会。”这是陈述还是问题?你在说什么“数据缓存”,为什么 F1 会导致缓存未命中?
0赞 Barmar 11/9/2023
@Botje F1 在读取和写入 之间交替。由于每个内存块的访问不是顺序的,因此其中一个内存块更有可能被推出缓存。date_timep_buffer
0赞 Barmar 11/9/2023
例如,假设只有 24 字节的缓存。它不能同时将两者保存在缓存中。
0赞 Botje 11/9/2023
“更多机会”?这种说法的依据是什么?此设备上是否可能运行多个线程?这 24 个字节是假设的还是来自数据表?您是否有文档证明在这种情况下将缓存输出缓冲区?没有它,这个问题是不可能回答的。
1赞 Botje 11/9/2023
你希望我在这里说什么?“是的,如果你的假设处理器按照你声称的方式工作,你的假设是正确的”恐怕不是一个有用的答案。查找数据表。

答:

0赞 supercat 11/9/2023 #1

您没有指定目标平台,但在许多 ARM 内核上,缓存将完全不是一个因素。我认为在许多内核上,完成数据打包的最佳方式是这样的:

add r1,sp,#4
ldmia r1,{r2,r3,r4,r5,r6,r7}  ; Perform all five loads at once
adds r2,r2,#48
strb r2,[r0,#0]
strb r3,[r0,#1]
strb r4,[r0,#2]
strb r5,[r0,#3]
strb r6,[r0,#4]
strb r7,[r0,#5]

也许编译器对加载和存储进行分组以增加它找到上述转换的可能性,但无论出于何种原因,都没有注意到替换 .或者,某些编译器配置可能会避免将加载序列转换为指令,因为某些内核将能够处理地址未对齐的指令,但无法同样处理指令。我不希望常见的编译器配置以这种方式容纳未对齐的访问,但也许编译器是这样配置的,因为项目中的某些代码依赖于它。LDMLDMLDRLDM

评论

0赞 Thomas Matthews 11/9/2023
编译器被设置为优化级别 0,因此它不会执行指令。:-(ldmia
0赞 supercat 11/9/2023
@ThomasMatthews:“优化”版本是什么意思?
0赞 Thomas Matthews 11/9/2023
该功能通过降低数据缓存未命中的机会,在速度方面进行了更优化。F2
2赞 supercat 11/9/2023
@ThomasMatthews:您刚刚指定了优化级别 0,这意味着编译器不会花费太多精力来优化任何内容。
1赞 0___________ 11/9/2023 #2

编译器是 IAR Embedded Workbench IDE - Arm 8.42.2

这意味着最有可能是 ARM uC 或 MPU。

从 RTC 时钟读取将比所有商店操作花费更多的时间。因此,处理器将等待与外设的总线操作。缓存未命中将产生边际效应。

另外,您打算多久阅读一次 RTC?每秒毫秒?可能不是。因此,您尝试微优化完全没有意义。

请描述您遇到的问题,以及为什么您认为优化此功能会有所帮助。

评论

0赞 Thomas Matthews 11/9/2023
实际上,这在 1ms ISR 中称为。
0赞 Thomas Matthews 11/9/2023
我很好奇缓存未命中将如何影响内存中的存储。
0赞 0___________ 11/9/2023
@ThomasMatthews,这是一个非常糟糕的代码。不要在 1ms 中断中读取它。这完全没有意义
0赞 Lundin 11/9/2023
@ThomasMatthews 一般情况下,切勿在 ISR 内执行任何复制/数据铲除操作。后台程序应该已经以预期的格式准备好了数据,然后 ISR 只需要交换指向该缓冲区的指针,而不是浪费时间硬复制数据。uint8_t