优化编译器中的常量合并

7
我有一个包含许多小内联函数的头文件。其中大部分都包含常量数据。由于这些函数对性能至关重要,它们处理常量的方式变得很重要。据我所知,有两种方法可以引用常量:
1)在单独的源文件中定义它们,然后将其与应用程序链接。
2)直接定义常量。
我选择后者,因为它更易于维护。但是,如果编译器没有优化通过内联创建的成千上万个相等常量,那么它可能会变慢。
问题是:编译器是否会组合这些相等的常量?特别是将使用以下哪种方法:
1)跨编译单元组合相等的常量。
2)跨链接模块(整个程序或库)组合相等的常量。
3)将常量与任何具有相同位模式并满足编译单元或整个程序中的对齐要求的静态常量数据组合。
我使用现代编译器(GCC4.5)。
我不是汇编专家,因此我无法使用几个简单的测试来回答这个问题 :)
编辑:
这些常量相当大(其中大部分至少为16字节),因此编译器无法使它们成为立即值。
编辑2:
代码示例:
这个使用常量:
float_4 sign(float_4 a)
{
    const __attribute__((aligned(16))) float mask[4] = { //I use a macro for this line
        0x80000000, 0x80000000, 0x80000000, 0x80000000};
    const int128 mask = load(mask);
    return b_and(a, mask);
}

“在原地定义常量”是什么意思?像 const int i = 5; 这样吗? - ThomasMcLeod
是的。通过常量,我指的是任何与函数参数无关的数据。 - user283145
2
我不是GCC的专家,但大多数编译器并不会合并const字面量,除非它们是字符串字面量。此外,const字面量通常作为立即值嵌入到代码中。一旦将其加载到内存变量中,编译器就需要执行该值的const-ness。 - ThomasMcLeod
1
我认为您对如何使用const数据影响性能有所困惑,例如在“由于这些函数对性能至关重要,它们处理常量的方式变得很重要”和“但是,如果编译器不优化通过内联创建的数千个相等常量,则可能会变慢。” - Fred Nurk
@Fred Nurk:不,我没有困惑。实际上,这个问题很令人困惑,因为它几乎与数据的常量性(即const关键字)无关。它只是关于编译器如何处理只读数据的问题。 - user283145
显示剩余5条评论
3个回答

8
根据GCC文档,以下选项可以实现您的要求:
-fmerge-constants
尝试在编译单元之间合并相同的常量(字符串常量和浮点数常量)。如果汇编器和链接器支持,则此选项是优化编译的默认选项。使用-fno-merge-constants以禁止此行为。
在级别-O,-O2,-O3,-Os启用。

2

如果您在头文件中像这样定义常量:

int const TEN = 10;
// or
enum { ELEVEN = 11 };

也就是说,不仅常量声明,定义也会在编译一个翻译单元(即.cc源文件)时对编译器可见。因此,即使未启用任何优化,编译器也会将其替换为常量值并生成代码。

[max@truth test]$ cat test.cc
int const TEN = 10; // definition available
extern int const TWELVE; // only declaration

int foo(int x) { return x + TEN; }
int bar(int x) { return x + TWELVE; }

[max@truth test]$ g++ -S -o - test.cc | c++filt | egrep -v " *\."
foo(int):
    pushq   %rbp
    movq    %rsp, %rbp
    movl    %edi, -4(%rbp)
    movl    -4(%rbp), %eax
    addl    $10, %eax
    leave
    ret
bar(int):
    pushq   %rbp
    movq    %rsp, %rbp
    movl    %edi, -4(%rbp)
    movl    TWELVE(%rip), %eax
    addl    -4(%rbp), %eax
    leave
    ret
TEN:

注意在foo(int)中,它将加法操作写为addl $10, %eax,即用TEN常量的值替代了它本身。而在bar(int)中,它首先执行movl TWELVE(%rip), %eax将TWELVE的值从内存中加载到eax寄存器中(地址将由链接器解析),然后执行addl -4(%rbp), %eax进行加法运算。

优化后的版本如下:

[max@truth test]$ g++ -O3 -S -o - test.cc | c++filt | egrep -v " *\."
foo(int):
    leal    10(%rdi), %eax
    ret
bar(int):
    movl    TWELVE(%rip), %eax
    addl    %edi, %eax
    ret

0

我认为你的问题没有通用答案。我只能给出C语言的答案,因为C++的规则是不同的。

这在很大程度上取决于你的常量类型。一个重要的类别是“整数常量表达式”。这些可以在编译时确定,并且特别适用于“整数枚举常量”的值。在可能的情况下,请使用它们。

enum { myFavoriteDimension = 55/2 };

对于这样的常量,通常最好的情况是:它们被实现为汇编立即数。它们甚至没有存储位置,直接写入汇编程序中,你的问题甚至没有意义。
对于其他数据类型,问题就比较微妙了。尝试强制不要取您的“const 限定变量”的地址。这可以使用“register”关键字来完成。
register double const something = 5.7;

可能会产生与上述相同的效果。

对于组合类型(structunion,数组),没有通用的答案或方法。我已经看到gcc能够完全优化小型数组(大约10个元素)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接