提问人:Thomas Matthews 提问时间:11/9/2023 更新时间:11/9/2023 访问量:92
从数据缓存到内存的存储效率
Efficiency of storing from data cache to memory
问:
背景
我正在优化一个函数,该函数从实时时钟 (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
笔记
- 编译器是 IAR Embedded Workbench IDE - Arm 8.42.2
- 在调试模式下编译(无优化或最少优化)(故意,因为它驻留在医疗设备平台上)
- 该函数从 RTC 硬件设备读取数据。
Get_RTC_Time()
- 该结构具有用于对硬件设备进行建模的字段。
Time_t
uint32_t
- 缓冲区是为了节省内存空间(平台受内存限制)。
uint8_t
答:
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]
也许编译器对加载和存储进行分组以增加它找到上述转换的可能性,但无论出于何种原因,都没有注意到替换 .或者,某些编译器配置可能会避免将加载序列转换为指令,因为某些内核将能够处理地址未对齐的指令,但无法同样处理指令。我不希望常见的编译器配置以这种方式容纳未对齐的访问,但也许编译器是这样配置的,因为项目中的某些代码依赖于它。LDM
LDM
LDR
LDM
评论
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
评论
date_time
p_buffer