如何防止声明为局部变量的对象在堆栈中分配?

Posted

技术标签:

【中文标题】如何防止声明为局部变量的对象在堆栈中分配?【英文标题】:How to prevent objects declared as local variables from being allocated in the stack? 【发布时间】:2016-11-12 17:21:55 【问题描述】:

我在使用 ARM/Keil 编译器为小型 ARM 处理器编译 C++ 时遇到以下性能问题。

在执行某些处理的函数中,我的代码具有以下结构:


    MyClass temp = global_variable_input;

    Operation 1 on temp;
    Operation 2 on temp;
    ...
    Operation N on temp;

    global_variable_output = temp;

MyClass 用于对数学对象进行建模,唯一成员是 32 位整数(即对象的完整大小为 4 个字节)。

所有操作都涉及使用重载运算符或 MyClass 的方法,并因此更改 'temp' 的值。有些操作是微不足道的内联的(方法在类中声明为内联),而有些则比较复杂,需要生成对方法的调用。

查看编译器为我的例程生成的汇编代码,我注意到编译器在堆栈中为“temp”分配空间,并且每个操作(也是内联操作!)都将操作的结果存储在放入堆栈,然后继续使用上次操作中存储在寄存器中的值。对于非内联对象,编译器传递一个指向寄存器 r1 中对象 (this) 的指针,以及一个指向在堆栈中创建的另一个对象的指针,以将结果存储在寄存器 r0 中。

代码实现了一种信号处理算法,您可以将其想象为对 temp 的一系列算术运算,因此在每次操作之后都有这个附加的“存储”指令以及相应的内存访问(可能只是一个操作码)在实现中引入了巨大的性能损失。

理想情况下,我希望编译器仅使用一个寄存器来完成操作,而不是保留每次操作后都需要更新的“temp”的堆叠版本。

另一个希望是将对象的当前值传递给方法,只需使用寄存器(就像 ARM C 调用约定为普通 C 函数指定的那样)并以相同的方式获取结果,而不是使用指针到内存位置。

我要求太多了吗?我怎样才能让我的 ARM/Keil 编译器以这种方式工作?

PS:这个函数很简单,所以编译器不需要在堆栈中分配我的变量,因为它用完了寄存器。我怀疑这样做的原因是它觉得需要一个指针来传递给非内联方法,然后认为有必要使堆栈中的值始终保持最新。

非常感谢!

【问题讨论】:

MyClass& temp = global_variable_input; 怎么样? ` 您尝试过不同的优化级别吗? 感谢您的建议!我会尝试参考。但我可以想象编译器会发现在每次操作后更新值更合理? (因为它现在是对全局变量的引用,其他人可能正在使用它) 是的,我尝试了不同的优化级别,但即使使用 -O3,编译器也会以这种方式运行。只有 O3 + 内联 ALL 方法的组合似乎导致了改进。 您在编译时和链接时都尝试过g++ -O3 -flto 吗?您是否尝试过最近的 GCC 和/或Clang 编译器? 【参考方案1】:

使用类似的引用

MyClass& temp = global_variable_input;

将避免在堆栈上分配MyClass 的完整副本(本地存储)

尽管任何一个

Operation 1 on temp;
Operation 2 on temp;
// ...

也会影响原来的global_variable_input

【讨论】:

不幸的是,我的性能问题仍然存在。【参考方案2】:

您可以将您的类更改为结构。它将存储在堆栈上而不是堆上。

【讨论】:

以上是关于如何防止声明为局部变量的对象在堆栈中分配?的主要内容,如果未能解决你的问题,请参考以下文章

为啥局部变量在 Java 中是线程安全的

为啥 alloca 与创建局部变量不同?

UnboundLocalError:在同步数据库中分配之前引用了局部变量“full_path”

编译器如何在堆栈上安排局部变量?

java线程安全问题之静态变量实例变量局部变量

静态变量全局变量和局部变量