【问题标题】:Why does g++ not optimize a local array but a global?为什么g ++不优化局部数组而是全局?
【发布时间】:2019-07-19 17:38:41
【问题描述】:

我有以下两个功能基本相同:

enum Direction{
    N = 0,
    NW,
    W,
    SW,
    S,
    SE,
    E,
    NE,
    TOTAL_DIRS
};

char const * const strings[] = {"N", "NW", "W", "SW", "S", "SE", "E", "NE"};

char const *
getDirString2(unsigned dir) {
    if (TOTAL_DIRS > dir)
        return strings[dir];
    return nullptr;
}

char const *
getDirString3(unsigned dir) {
    char const * const strings[] = {"N", "NW", "W", "SW", "S", "SE", "E", "NE"};
    if (TOTAL_DIRS > dir)
        return strings[dir];
    return nullptr;
}

但是,虽然 g++ 像我期望的那样优化了使用全局数组的函数。它为替代方案创建了更多复杂的代码。 Clang 为两者创建相同的代码,如果我改用 switch 语句,clang 和 c++ 也会创建与 getDirString2 相同的代码。

这里是编译器资源管理器https://godbolt.org/z/GxvrTv的链接

这是我应该为 g++ 提交错误报告还是有充分的理由?

【问题讨论】:

  • 对我来说似乎错过了优化。
  • 也许这取决于你调用这些函数的方式。 G++ 有一个 LTO 阶段。
  • @Ripi2 嗯,我添加了一个主函数,我调用这两个函数只是为了打印输出,并且它们都通过相同的优化被内联。
  • 顺便说一句,N = 0, 中的 = 0 是多余的。如果不指定值,则第一个枚举器的值为 0。

标签: c++ g++ compiler-optimization


【解决方案1】:

我想你可以称之为错过的优化,尽管这对 gcc 的人来说有点苛刻。

gcc,在编译getDirString3 时,完全按照你的要求去做——在堆栈上构造一个字符串数组,然后只返回其中的一个元素。

另一方面,clang 看到这个数组永远不会改变,而是在静态存储中构造它,请参阅:https://godbolt.org/z/24n-N7

要让 gcc 像 clang 一样生成代码,请将 getDirString3 中的数组声明为 static(这本来是个好主意),请参阅:https://godbolt.org/z/henD2Z

【讨论】:

  • 有趣的是,我永远不会想到添加静态会在这里创建更好的优化。我也从未听说过我应该将常量声明为静态的。如果它们是编译时常量,我是否应该始终将它们声明为静态常量?这是一般规则吗?
  • @MichaelMahn 对于像const int x = 4 这样简单的东西,将其声明为静态没有什么区别,因为编译器无论如何都会优化变量,但是在函数内,声明类似const int x [] = { 1, 2, 3, 4, 5 } static 可能会这样做,因为如果没有 static,您将声明一个数组,每次调用函数时都必须在堆栈上构建。
【解决方案2】:

注意:一开始我说这是一个不合格的优化,但是在我和cmets中的Chris Dodd讨论之后,我意识到这是部分错误的。

最新答案:

这是我应该为 g++ 提交错误报告还是有充分的理由?

clang 和 gcc 都可以做同样的优化,但默认情况下它在 gcc 中是禁用的。所以这不是错误,而且是有原因的。

clang 做了什么?

在您的示例中,编译器看到本地数组是常量,因此每次调用函数时都无需在堆栈上构造它。

如何在 gcc 中启用同样的优化?

要在 gcc 中启用相同的优化,请使用标志 -fmerge-all-constants。生成的代码将与 clang 的代码大小几乎相同(参见:https://godbolt.org/z/Ldx_qe):

getDirString3(unsigned int):
        xor     eax, eax
        cmp     edi, 7
        ja      .L1
        mov     edi, edi
        mov     rax, QWORD PTR strings.2080[0+rdi*8]

为什么在 gcc 中默认禁用它?

来自 gcc 文档website

-fmerge-all-constants ... C 或 C++ 等语言需要每个变量,包括同一变量的多个实例 递归调用,有不同的位置,所以使用这个选项 导致不合格的行为。

在上面的示例中,优化是安全的并且遵循as-if rule,因为这个本地数组的地址没有被使用或与来自不同调用的地址进行比较。但是,这种优化似乎在某些情况下会导致不一致的行为。您可以检查这些情况:herehere

那么,您应该使用 -fnmerge-all-constants 标志吗?

两个编译器都不检查是否使用了常量的地址,因此我不推荐它。在您的示例中很明显优化是安全的,但是您不能每次都知道编译器将在哪里优化(否则您会自己完成)并且您不能确定它有时不会导致不合规行为。

【讨论】:

  • 请注意,仅当局部常量的地址从函数中传递出来或以可能比较来自不同调用的地址的方式使用时,优化才不一致。 getDirString3 都不是这样,所以它应该是安全和合法的。问题是如果没有进行正确的潜在别名检查,导致以不安全的方式应用优化。
  • @ChrisDodd 你能引用标准吗?这将有助于改进答案。而且这种优化是以不安全的方式应用的,请参阅:lkml.org/lkml/2018/3/20/872
  • 这遵循as-if规则——如果无法观察到两者之间的差异,那么优化是安全的。观察这种差异的唯一方法是某些代码是否依赖于不同帧中常量的地址。如果没有任何东西依赖于这些地址,那么它就无法被观察到。
  • @mt3d 这里不一样。在错误报告中使用了变量的地址。这里不是。
  • @ChrisDodd 让我们试着在我和你之间找到共同点。我们都同意getDirString3 案例中的优化是安全的,并且它遵循 as-if 规则,但我的问题是:是什么促成了这种优化?它是-fmerge-all-constants 标志,它存在于clang 和gcc 中(所以gcc 家伙不会错过它)并且在clang 中默认启用。现在,我认为这面旗帜安全吗?我不这么认为。我认为clang不会检查地址是否被使用,如果是这样,它就不会导致像上面两种情况那样的不合格行为。这只是运气的问题。
猜你喜欢
  • 2017-09-20
  • 2021-12-10
  • 1970-01-01
  • 1970-01-01
  • 2018-11-01
  • 1970-01-01
  • 2021-06-20
  • 2019-06-12
相关资源
最近更新 更多