静态 const 与 const 声明在 uC 上的性能差异
static const vs const declaration performance difference on uC
假设我有一个查找表,一个包含 256 个元素的数组,在名为 lut.h 的 header 中定义和声明。该数组将在程序的生命周期中被多次访问。
根据我的理解,如果它被定义并声明为静态的,它将保留在内存中直到程序完成,即如果它是 uC 上的任务 运行,则数组一直在内存中.
如果没有static,访问时会加载到内存中。
在lut.h
static const float array[256] = {1.342, 14.21, 42.312, ...}
对比
const float array[256] = {1.342, 14.21, 42.312, ...}
考虑到 uC 的 spiflash 和 psram 有限,最注重性能的方法是什么?
如果您的数组是在文件级别定义的(您提到了 lut.h
),并且都具有 const
限定符,则它们将不会加载到 RAM¹ 中。 static
关键字只限制数组的范围,它不会以任何方式改变它的生命周期。如果您检查代码的程序集,您将看到两个数组 look exactly the same 在编译时:
static const int static_array[] = { 1, 2, 3 };
const int extern_array[] = { 1, 2, 3};
extern void do_something(const int * a);
int main(void)
{
do_something(static_array);
do_something(extern_array);
return 0;
}
生成的程序集:
main:
sub rsp, 8
mov edi, OFFSET FLAT:static_array
call do_something
mov edi, OFFSET FLAT:extern_array
call do_something
xor eax, eax
add rsp, 8
ret
extern_array:
.long 1
.long 2
.long 3
static_array:
.long 1
.long 2
.long 3
另一方面,如果您在函数内声明数组,则数组 will be copied to temporary storage(堆栈)在函数持续时间内,除非您添加 static
限定符:
extern void do_something(const int * a);
int main(void)
{
static const int static_local_array[] = { 1, 2, 3 };
const int local_array[] = { 1, 2, 3 };
do_something(static_local_array);
do_something(local_array);
return 0;
}
生成的程序集:
main:
sub rsp, 24
mov edi, OFFSET FLAT:static_local_array
movabs rax, 8589934593
mov QWORD PTR [rsp+4], rax
mov DWORD PTR [rsp+12], 3
call do_something
lea rdi, [rsp+4]
call do_something
xor eax, eax
add rsp, 24
ret
static_local_array:
.long 1
.long 2
.long 3
¹ 更准确地说,它取决于编译器。一些编译器将需要额外的自定义属性来准确定义您要存储数据的位置。一些编译器会在有足够空闲空间时尝试将数组放入 RAM space,以允许更快的读取。
你这里有些误解,因为MCU不是PC。只要 MCU 有电,MCU 内存中的所有内容都会保留。程序不结束或 return 资源到主机 OS。
"Tasks" 在 MCU 上意味着你有一个 RTOS。他们使用自己的堆栈,这是它自己的主题,与您的问题完全无关。 RTOS 上的所有任务永远执行是正常的,而不是像 PC 中的进程那样在 运行 时间内获得 allocated/deallocated。
static
与本地范围内的自动确实意味着不同的 RAM 内存使用,但不一定 more/less 内存使用。当程序执行时,局部变量在堆栈上得到 pushed/popped。 static
坐在他们指定的地址。
Where as without static, it will be loaded into memory when accessed.
仅当您加载到的数组是在本地声明的。即:
void func (void)
{
int my_local_array[] = {1,2,3};
...
}
这里 my_local_array
将仅在执行该函数期间将值从闪存加载到 RAM。这意味着两件事:
从闪存到 RAM 的实际复制速度慢。首先,无论情况如何,复制东西总是很慢。但在从 RAM 复制到闪存的特定情况下,它可能会特别慢,具体取决于 MCU。
在具有无法利用数据缓存进行复制的闪存等待状态的高端 MCU 上,速度会特别慢。在无法直接寻址数据的怪异哈佛架构 MCU 上,它会特别慢。等等
所以如果你每次调用一个函数时都做这个复制,而不是只调用一次,你的程序会变得更慢。
大型本地对象导致需要更大的堆栈大小。堆栈必须足够大以应对最坏的情况。如果您有大型本地对象,则需要将堆栈大小设置得更高以防止堆栈溢出。这意味着这实际上会导致内存使用效率降低。
因此,通过将对象设为本地来判断您是保存还是丢失内存并非易事。
嵌入式系统编程中的一般良好实践设计是[=59=]不在堆栈上分配大对象,因为它们使堆栈处理更加详细并且堆栈溢出的可能性增加。此类对象应在文件范围内声明为 static
。特别是如果速度很重要。
static const float array
vs const float array
这里又是一个误解。在 MCU 系统中制作一些东西 const
,同时将其放置在文件范围内 ("global"),很可能意味着该变量将最终出现在闪存 ROM 中,而不是 RAM 中。不管static
.
这是大多数时候的首选,因为通常 RAM 是比闪存更有价值的资源。 static
在这里扮演的角色只是良好的程序设计,因为它将对变量的访问限制在本地翻译单元,而不是弄乱全局命名空间。
In lut.h
永远不要在头文件中定义变量。
从程序设计的角度来看这是不好的,因为您在整个地方公开了变量 ("spaghetti programming"),并且从链接器的角度来看,如果包含多个源文件,这也是不好的相同的头文件 - 这极有可能。
正确设计的程序将变量放在 .c 文件中并通过声明它来限制访问 static
。如果需要,可以通过 setters/getters.
从外部访问
he uC has limited spiflash
什么是"spiflash"?通过 SPI 访问的外部串行闪存?那么 none 是有道理的,因为这样的闪存不是内存映射的,通常编译器不能使用它。必须由您的应用程序手动访问此类内存。
假设我有一个查找表,一个包含 256 个元素的数组,在名为 lut.h 的 header 中定义和声明。该数组将在程序的生命周期中被多次访问。
根据我的理解,如果它被定义并声明为静态的,它将保留在内存中直到程序完成,即如果它是 uC 上的任务 运行,则数组一直在内存中.
如果没有static,访问时会加载到内存中。
在lut.h
static const float array[256] = {1.342, 14.21, 42.312, ...}
对比
const float array[256] = {1.342, 14.21, 42.312, ...}
考虑到 uC 的 spiflash 和 psram 有限,最注重性能的方法是什么?
如果您的数组是在文件级别定义的(您提到了 lut.h
),并且都具有 const
限定符,则它们将不会加载到 RAM¹ 中。 static
关键字只限制数组的范围,它不会以任何方式改变它的生命周期。如果您检查代码的程序集,您将看到两个数组 look exactly the same 在编译时:
static const int static_array[] = { 1, 2, 3 };
const int extern_array[] = { 1, 2, 3};
extern void do_something(const int * a);
int main(void)
{
do_something(static_array);
do_something(extern_array);
return 0;
}
生成的程序集:
main:
sub rsp, 8
mov edi, OFFSET FLAT:static_array
call do_something
mov edi, OFFSET FLAT:extern_array
call do_something
xor eax, eax
add rsp, 8
ret
extern_array:
.long 1
.long 2
.long 3
static_array:
.long 1
.long 2
.long 3
另一方面,如果您在函数内声明数组,则数组 will be copied to temporary storage(堆栈)在函数持续时间内,除非您添加 static
限定符:
extern void do_something(const int * a);
int main(void)
{
static const int static_local_array[] = { 1, 2, 3 };
const int local_array[] = { 1, 2, 3 };
do_something(static_local_array);
do_something(local_array);
return 0;
}
生成的程序集:
main:
sub rsp, 24
mov edi, OFFSET FLAT:static_local_array
movabs rax, 8589934593
mov QWORD PTR [rsp+4], rax
mov DWORD PTR [rsp+12], 3
call do_something
lea rdi, [rsp+4]
call do_something
xor eax, eax
add rsp, 24
ret
static_local_array:
.long 1
.long 2
.long 3
¹ 更准确地说,它取决于编译器。一些编译器将需要额外的自定义属性来准确定义您要存储数据的位置。一些编译器会在有足够空闲空间时尝试将数组放入 RAM space,以允许更快的读取。
你这里有些误解,因为MCU不是PC。只要 MCU 有电,MCU 内存中的所有内容都会保留。程序不结束或 return 资源到主机 OS。
"Tasks" 在 MCU 上意味着你有一个 RTOS。他们使用自己的堆栈,这是它自己的主题,与您的问题完全无关。 RTOS 上的所有任务永远执行是正常的,而不是像 PC 中的进程那样在 运行 时间内获得 allocated/deallocated。
static
与本地范围内的自动确实意味着不同的 RAM 内存使用,但不一定 more/less 内存使用。当程序执行时,局部变量在堆栈上得到 pushed/popped。 static
坐在他们指定的地址。
Where as without static, it will be loaded into memory when accessed.
仅当您加载到的数组是在本地声明的。即:
void func (void)
{
int my_local_array[] = {1,2,3};
...
}
这里 my_local_array
将仅在执行该函数期间将值从闪存加载到 RAM。这意味着两件事:
从闪存到 RAM 的实际复制速度慢。首先,无论情况如何,复制东西总是很慢。但在从 RAM 复制到闪存的特定情况下,它可能会特别慢,具体取决于 MCU。
在具有无法利用数据缓存进行复制的闪存等待状态的高端 MCU 上,速度会特别慢。在无法直接寻址数据的怪异哈佛架构 MCU 上,它会特别慢。等等
所以如果你每次调用一个函数时都做这个复制,而不是只调用一次,你的程序会变得更慢。
大型本地对象导致需要更大的堆栈大小。堆栈必须足够大以应对最坏的情况。如果您有大型本地对象,则需要将堆栈大小设置得更高以防止堆栈溢出。这意味着这实际上会导致内存使用效率降低。
因此,通过将对象设为本地来判断您是保存还是丢失内存并非易事。
嵌入式系统编程中的一般良好实践设计是[=59=]不在堆栈上分配大对象,因为它们使堆栈处理更加详细并且堆栈溢出的可能性增加。此类对象应在文件范围内声明为 static
。特别是如果速度很重要。
static const float array
vsconst float array
这里又是一个误解。在 MCU 系统中制作一些东西 const
,同时将其放置在文件范围内 ("global"),很可能意味着该变量将最终出现在闪存 ROM 中,而不是 RAM 中。不管static
.
这是大多数时候的首选,因为通常 RAM 是比闪存更有价值的资源。 static
在这里扮演的角色只是良好的程序设计,因为它将对变量的访问限制在本地翻译单元,而不是弄乱全局命名空间。
In lut.h
永远不要在头文件中定义变量。
从程序设计的角度来看这是不好的,因为您在整个地方公开了变量 ("spaghetti programming"),并且从链接器的角度来看,如果包含多个源文件,这也是不好的相同的头文件 - 这极有可能。
正确设计的程序将变量放在 .c 文件中并通过声明它来限制访问 static
。如果需要,可以通过 setters/getters.
he uC has limited spiflash
什么是"spiflash"?通过 SPI 访问的外部串行闪存?那么 none 是有道理的,因为这样的闪存不是内存映射的,通常编译器不能使用它。必须由您的应用程序手动访问此类内存。