#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
中。请注意,我特意要求没有内联calculate
函数来说明这个问题;如果没有noinline
,编译器会生成两个完全相同的函数来通过指针调用(所以仍然不会优化,只是使用了不同的方式)。我错过了什么?除了手动插入正确的函数(没有包装器)外,有没有任何方法可以引导编译器?编辑1。根据评论中的建议,这里有一个反汇编代码,其中所有函数都声明为静态,结果完全相同(call
+jmp
而不是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;
}
通过将指针丢弃并直接存储&calculate
,gcc 8.2成功优化了此代码(https://gcc.godbolt.org/z/nMIBeo)。然而,根据注释更改行(即手动执行相同的优化的一部分)会破坏魔法并导致无意义的jmp
。