当前位置: 首页 > 知识库问答 >
问题:

为什么编译器不优化琐碎的包装器函数指针?

颛孙子民
2023-03-14

考虑下面的代码片段

#include <vector>
#include <cstdlib>

void __attribute__ ((noinline)) calculate1(double& a, int x) { a += x; };
void __attribute__ ((noinline)) calculate2(double& a, int x) { a *= x; };
void wrapper1(double& a, int x) { calculate1(a, x); } 
void wrapper2(double& a, int x) { calculate2(a, x); } 

typedef void (*Func)(double&, int);

int main()
{
    std::vector<std::pair<double, Func>> pairs = {
        std::make_pair(0, (rand() % 2 ? &wrapper1 : &wrapper2)),
        std::make_pair(0, (rand() % 2 ? &wrapper1 : &wrapper2)),
    };

    for (auto& [a, wrapper] : pairs)
        (*wrapper)(a, 5);

    return pairs[0].first + pairs[1].first;
}

通过-O3优化,最新的gcc和clang版本没有优化指向包装器的指针、指向底层函数的指针。参见第22行的组装:

mov     ebp, OFFSET FLAT:wrapper2(double&, int)   # tmp118,

在后面的call+JMP中,编译器将指针放置到calculate1,而不只是call

编辑2。同样模式的更简单的例子:

#include <vector>
#include <cstdlib>

typedef void (*Func)(double&, int);

static void __attribute__ ((noinline)) calculate(double& a, int x) { a += x; };
static void wrapper(double& a, int x) { calculate(a, x); } 

int main() {
    double a = 5.0;
    Func f;
    if (rand() % 2)
        f = &wrapper; // f = &calculate;
    else
        f = &wrapper;
    f(a, 0); 
    return 0;
}

gcc 8.2通过抛出指向包装器的指针并将&calculate直接存储在其位置(https://gcc.godbolt.org/z/nmibeo)成功地优化了这段代码。然而,按照注释更改代码行(即手动执行相同优化的一部分)打破了这种魔力,并导致毫无意义的JMP

共有1个答案

毛镜
2023-03-14

您似乎建议将&calculate1而不是&wrapper1存储在向量中。通常,这是不可能的:以后的代码可能会尝试将存储的指针与&calculate1进行比较,结果必须比较为false。

我进一步假设您的建议是,编译器可能会尝试做一些静态分析,并确定向量中的函数指针值从未与其他函数指针进行相等性比较,事实上,对向量元素所做的其他操作都不会产生可观察行为的变化;因此,在这个精确的程序中,它可以存储&calculate1

通常,对于“为什么编译器不执行某些特定的优化”的回答是,没有人设想并实现过这个想法。另一个常见的原因是,在一般情况下,所涉及的静态分析相当困难,可能会导致编译速度放缓,而在无法保证分析成功的实际程序中没有任何好处。

 类似资料:
  • 我经常注意到gcc在可执行文件中将乘法转换为移位。当将与相乘时,可能会发生类似的情况。例如,可能只是将的指数增加1,从而节省一些周期。如果有人要求编译器这样做(例如,通过),编译器通常会这样做吗? 编译器通常是否足够聪明来执行此操作,还是我需要自己使用或函数系列来执行此操作?

  • (这个问题与此密切相关,但它是一个更具体的问题,我希望能就此得到答案)

  • Nicolai Josuttis在其著作《C标准库(第二版)》中指出,与普通函数相比,编译器可以更好地优化lambdas。 此外,C 编译器优化 lambda 的效果比普通函数更好。(第213页) 这是为什么呢? 我想当涉及到内联时,应该不会再有任何区别了。我能想到的唯一原因是编译器可能有更好的lambdas本地上下文,这样可以做出更多假设并执行更多优化。

  • 问题内容: 假设我在C代码中有类似的内容。我知道您可以使用a 代替,以使编译器不对其进行编译,但是出于好奇,我问编译器是否也可以解决此问题。 我认为这对于Java编译器来说更为重要,因为它不支持。 问题答案: 在Java中,if内的代码甚至都不是已编译代码的一部分。它必须编译,但不会写入已编译的字节码。它实际上取决于编译器,但我不知道没有对它进行优化的编译器。规则在JLS中定义: 优化的编译器可能

  • 如果关闭了编译器优化(gcc-o0...),那么说'volatile'关键字没有区别是可以的吗? 我制作了一些示例“C”程序,并且仅当打开编译器优化时,才在生成的汇编代码中看到易失性和非易失性之间的区别,即((gcc-o1....)。

  • 今天我遇到了一些我不太理解的复制构造函数。 考虑下一个代码: 然后在将原点分配给复制时调用复制构造函数,这是有意义的。但是,如果我将复制的声明更改为 它不叫。即使当我使用< code>create()函数时,它也不会调用复制构造函数。但是,当将其更改为 它确实调用了复制构造函数。一些研究解释说,允许编译器优化复制构造函数,这听起来是件好事。直到复制构造函数是非默认的,因为那时它可能会,也可能不会做