【问题标题】:static const vs const declaration performance difference on uCuC 上的静态 const 与 const 声明性能差异
【发布时间】:2019-02-05 10:27:23
【问题描述】:

假设我有一个查找表,一个包含 256 个元素的数组,在名为 lut.h 的头文件中定义和声明。该数组将在程序的生命周期内被多次访问。

根据我的理解,如果它被定义和声明为静态,它将一直保留在内存中,直到程序完成,即如果它是在 uC 上运行的任务,则数组一直在内存中。

如果没有静态,它会在访问时加载到内存中。

在 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 有限,最注重性能的方法是什么?

【问题讨论】:

  • 也许this 会有所帮助。 :)
  • Performance oriented 可以有不同的含义。可能是您想保存在 RAM 上,或者您的 ROM 有限,或者您使用 uC 控制的任何设备的响应都需要快速。
  • 您在谈论“声明”,但您正在显示“定义”。该数组应在头文件中声明(以防您需要在多个源文件中访问它)。然后应该在源中定义数组(例如lut.c)。见this
  • “如果没有静态,它会在访问时加载到内存中” - 不,这两个通常会提供完全相同的程序集。唯一的区别是静态版本的范围有限。
  • Nitpick:浮点常量应该写成1.342f。否则,您可能会触发工具链链接到您没有用的更高精度的浮点库中。

标签: c microcontroller


【解决方案1】:

您在这里有一些误解,因为 MCU 不是 PC。只要 MCU 有电,MCU 内存中的所有内容都会持续存在。程序不会结束或将资源返回给托管操作系统。

MCU 上的“任务”意味着您拥有 RTOS。他们使用自己的堆栈,这是它自己的主题,与您的问题完全无关。 RTOS 上的所有任务都永远执行是正常的,而不是像 PC 中的进程那样在运行时分配/解除分配。

static 与本地范围内的自动相比确实意味着不同的 RAM 内存使用,但不一定是更多/更少的内存使用。程序执行时,局部变量会被压入/弹出堆栈。 static 的人坐在他们指定的地址上。


如果没有静态,它会在访问时加载到内存中。

仅当您要加载的数组在本地声明时。那就是:

void func (void)
{
  int my_local_array[] = {1,2,3};
  ...
}

这里my_local_array 将仅在执行该函数期间将值从闪存加载到 RAM。这意味着两件事:

  • 从闪存到 RAM 的实际复制是。首先,无论情况如何,复制东西总是很慢。但在从 RAM 复制到闪存的特定情况下,它可能会特别慢,具体取决于 MCU。

    在具有闪存等待状态且无法利用数据缓存进行复制的高端 MCU 上速度会特别慢。在无法直接寻址数据的怪异哈佛架构 MCU 上速度会特别慢。等等。

    因此,如果您每次调用函数时都执行此复制,而不是只执行一次,那么您的程序会变得慢得多。

  • 大型本地对象导致需要更大的堆栈大小。堆栈必须足够大以应对最坏的情况。如果您有大型本地对象,则需要将堆栈大小设置得更高以防止堆栈溢出。这意味着这实际上会导致内存使用效率降低。

因此,通过将对象设置为本地对象来判断您是否节省或丢失内存并非易事。

嵌入式系统编程中的一般良好实践设计是在堆栈上分配大对象,因为它们使堆栈处理更加详细,并且堆栈溢出的可能性增加。此类对象应在文件范围内声明为static。特别是如果速度很重要。


static const float arrayconst float array

这里是另一个误解。在 MCU 系统中创建 const,同时将其置于文件范围(“全局”)中,很可能意味着该变量最终将出现在闪存 ROM 中,而不是 RAM 中。不管static

这在大多数情况下是首选,因为通常 RAM 是比闪存更有价值的资源。 static 在这里扮演的角色仅仅是好的程序设计,因为它将对变量的访问限制在本地翻译单元,而不是弄乱全局命名空间。


在 lut.h

你不应该在头文件中定义变量。

从程序设计的角度来看,这很糟糕,因为您将变量暴露在所有地方(“意大利面条式编程”),并且从链接器的角度来看,如果多个源文件包含相同的头文件,那么这很糟糕文件 - 极有可能。

正确设计的程序将变量放在 .c 文件中,并通过将其声明为 static 来限制访问。如果需要,可以通过 setter/getter 从外部访问。


他 uC 的 spiflash 有限

什么是“spiflash”?通过 SPI 访问的外部串行闪存?那么这一切都没有意义,因为这样的闪存不是内存映射的,通常编译器无法使用它。必须由您的应用程序手动访问此类内存。

【讨论】:

  • 感谢冗长的回答。这清楚了很多。 uC 内部有 ~450kb ROM 和 520kb SRAM。
  • @p_d 所以这将是一个高端的。等待状态和缓存绝对是需要考虑的事情。
【解决方案2】:

如果您的数组是在文件级别定义的(您提到了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 中,以加快读取速度。

【讨论】:

  • 感谢您的说明。考虑到我希望数组可以从不同的文件和线程中读取,没有静态的 const 限定符就足够了。如果我将声明/定义和初始化拆分为 lut.h/lut.c 或将所有内容保留在 lut.h 中,这有关系吗?
  • “两者都有 const 限定符,它们不会被加载到 RAM 中” 我认为这取决于编译器和底层架构。如果我没记错的话,有些需要特殊关键字才能将数据放在 RAM 中。
  • @P.Dias:是的,如果它是一个共享查找表,您的lut.h 应该只包含extern const float array[256];,而实际的 const 数组应该驻留在lut.c 中。如果将实际的 const 数组放在头文件中,则需要将其设为 static 否则会出现“重复符号”编译时错误。
  • @user694733:没错,我想保持简单,但我会澄清一下。
  • @Groo 谢谢。出于好奇(没有编程太久)如果它是一个共享的 lut 并且我没有在 lut.h 中将数组声明为 extern 会发生什么?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-19
  • 1970-01-01
  • 1970-01-01
  • 2010-12-10
  • 1970-01-01
相关资源
最近更新 更多