我们正在尝试重构解释器循环SWI。这是一个巨大的函数,使用GCC的标签地址来引用虚拟机指令。这个函数有许多VM寄存器变量。事实证明,一个人的写作是有区别的
PL_next_solution(qid_t qid)
{ type1 v1;
type2 v2;
// 13 in total
// lots of code
}或
PL_next_solution(qid_t qid)
{ struct
{ type1 v1;
type2 v2;
// 13 in total
} registers;
// lots of code
}如果没有剖面引导优化,结果程序的性能差异很小(<5%),而PGO优化的性能差异约为20%。这是相当出乎意料的。这一切为什么要发生?gcc是否尊重这个结构的记忆布局,尽管它在这个功能之外并不为人所知?
发布于 2021-05-11 14:53:18
根据您的进一步测试,性能影响似乎来自于获取一个或多个struct成员的地址并将其传递给编译器看不到的函数。我相信,当您这样做时,编译器必须假设整个结构都有转义。这就抑制了许多可能的优化:这意味着结构必须放置在内存中,而且每个函数调用都必须将成员存储到该内存中并从内存中重新加载。
为了了解原因,请看一个这样的例子:
void foo(int *);
void bar(void);
struct qux {
int a,b,c;
char huge[5000];
};
int fum(void) {
struct qux s = { 1,2,3 };
foo(&s.b);
s.c=4;
bar();
return s.a+s.c;
}请注意,即使进行了最大的优化:
huge仍然被分配和初始化。s.c=4涉及到内存存储s.a和s.c在调用bar()后从内存中重新加载我认为问题在于,对于所有编译器来说,foo()和bar()可能正在执行的函数:
int *global_ptr;
void foo(int *ip) {
struct qux *qp = (struct qux *)((char *)ip - offsetof(struct qux, b));
global_ptr = &qp->c;
printf("%d %d\n", qp->a, qp->huge[1234]);
}
void bar(void) {
*global_ptr = 37;
}我相信这样的代码会有很好的定义;foo必须打印出1 0,fum必须返回38。当然,程序员需要确保foo只与struct qux的b成员的地址一起调用,并且在fum返回后不会再次调用bar(),因为有一个悬空的指针--但是如果他们这样做了,那么代码就会正常工作。
另一方面,如果将fum中的调用更改为foo(NULL),则可以看到一切都消失了:s.huge从未初始化,甚至没有分配,s.b完全消失,s.a和s.c被优化,return s.a+s.c;不断折叠到return 5;中。在这种情况下,编译器可以确保结构不会“转义”,因此它可以根据自己的意愿进行优化。
https://stackoverflow.com/questions/67476965
复制相似问题