GCC 可以使用编译时常量变量优化类的方法吗?

Posted

技术标签:

【中文标题】GCC 可以使用编译时常量变量优化类的方法吗?【英文标题】:Can GCC optimize methods of a class with compile-time constant variables? 【发布时间】:2014-03-02 16:46:48 【问题描述】:

序言

我正在使用 avr-g++ 对 AVR 微控制器进行编程,因此我总是需要获得非常高效的代码。

如果函数的参数是编译时常量,GCC 通常可以优化函数,例如我有函数pin_write(uint8_t pin, bool val) 确定AVR 的pin 寄存器(使用我从整数pin 到一对端口/引脚的特殊映射)并写入这些寄存器对应的值。由于它的通用性,这个函数并不算太小。但是如果我用编译时常量pinval 调用这个函数,GCC 可以在编译时进行所有计算并消除对几个 AVR 指令的调用,例如

sbi PORTB,1
sbi DDRB,1

安布尔

让我们编写这样的代码:

class A 
        int x;
public:
        A(int x_): x(x_) 
        void foo()  pin_write(x, 1); 
;

A a(8);
int main()  
        a.foo();

我们只有一个 A 类对象,它被初始化为一个常量 (8)。因此,可以在编译时进行所有计算:

foo() -> pin_write(x,1) -> pin_write(8,1) -> a couple of asm instructions

但 GCC 不这样做。

令人惊讶的是,但如果我删除全局 A a(8) 并只写

 A(8).foo()

我得到了我想要的:

00000022 <main>:
  22:   c0 9a           sbi     0x18, 0 ; 24
  24:   b8 9a           sbi     0x17, 0 ; 23

问题

那么,有没有办法强制 GCC 在编译时对具有常量初始化器的单个全局对象进行所有可能的计算?

由于这个麻烦,我不得不手动扩展这种情况并用这个替换我的原始代码:

const int x = 8; 
class A 
public:
    A() 
    void foo()  pin_write(x, 1); 


UPD. 非常棒:A(8).foo()main 优化为 2 个 asm 指令。 A a(8); a.foo() 也是!但是如果我将A a(8) 声明为全局——编译器会生成大的通用代码。我尝试添加static - 它没有帮助。为什么?

【问题讨论】:

如果您将x 声明为const,会发生什么? 你可以使用模板吗?或者至少是常量表达式? 使构造函数constexpr? @StefanoSanfilippo:没有帮助 @KerrekSB:我不擅长模板。你能说明你的意思吗? 【参考方案1】:

但是如果我将A a(8) 声明为全局--编译器会生成大的通用代码。我尝试添加static - 它没有帮助。为什么?

根据我的经验,如果对象/函数具有外部链接,gcc 非常不愿意。由于我们没有要编译的代码,因此我对您的代码做了一些修改:

#include <cstdio>

class A 
        int x;
public:
        A(int x_): x(x_) 
        int f()  return x*x; 
;

A a(8);

int main()  
        printf("%d", a.f());

我找到了两种方法来实现生成的程序集与此相对应:

int main()  
        printf("%d", 64);

换句话说:在编译时消除一切,只剩下必要的最小值。

使用 clang 和 gcc 实现这一目标的一种方法是:

#include <cstdio>

class A 
        int x;
public:
        constexpr A(int x_): x(x_) 
        constexpr int f() const  return x*x; 
;

constexpr A a(8);

int main()  
        printf("%d", a.f());

gcc 4.7.2 已经消除了-O1 的所有内容,clang 3.5 主干需要-O2

实现此目的的另一种方法是:

#include <cstdio>

class A 
        int x;
public:
        A(int x_): x(x_) 
        int f() const  return x*x; 
;

static const A a(8);

int main()  
        printf("%d", a.f());

它仅适用于 -O3 的 clang。显然 gcc 中的 constant folding 并没有那么激进。 (如clang所示,可以做到,但gcc 4.7.2没有实现。)

【讨论】:

【参考方案2】:

您可以通过将 pin_write 函数更改为模板来强制编译器使用所有已知常量对函数进行全面优化。我不知道标准是否保证了特定的行为。

template< int a, int b >
void pin_write()  some_instructions; 

这可能需要修复所有使用 pin_write 的行。

此外,您可以将函数声明为内联。编译器不保证内联函数(inline 关键字只是一个提示),但如果这样做,它有更大的机会优化编译时间常数(假设编译器可以知道它是一个编译时间常数,这可能并非总是如此)。

【讨论】:

我希望有机会将 pin_write 写入非编译时常量引脚。但我也希望 if 参数是常量,编译器自己进行所有可能的计算。它适用于简单的函数,但不适用于使用类变量的方法。 @Corvus 您可以将类变量设为模板。但是,如果您希望将选项写入常量或变量引脚,我认为您不能用一个类来做到这一点。【参考方案3】:

您的a 具有外部链接,因此编译器无法确定是否有其他代码在修改它。

如果你要声明a const,那么你要明确它不应该改变,并停止它与外部链接;这两个都应该有助于编译器不那么悲观。

(我可能也会声明 x const ——它在这里可能没有帮助,但如果没有别的,它会让编译器和代码的下一个读者清楚你永远不会改变它。)

【讨论】:

我在A a(8) 之前添加了static。它没有帮助。 @Corvus 可以做到(见我的回答,clang 做到了)但显然 gcc 中的不断折叠并不是那么激进。

以上是关于GCC 可以使用编译时常量变量优化类的方法吗?的主要内容,如果未能解决你的问题,请参考以下文章

从文字字符串生成编译时常量整数

C#TS和Dart对比3:编译时常量和运行时常量

如何在 C++11 中创建结构的编译时常量实例

所有编译时常量都内联吗?

编译时常量和变量

编译器会优化和重用变量吗