【发布时间】:2013-10-19 20:12:24
【问题描述】:
简单地说,以下哪个可能是更好的选择:
somestruct data[lots];
for(int i=0;i<lots;i++) {
a(&data[i]);
b(&data[i]);
c(&data[i]);
}
//....
void a(somestruct* d) { ..stuff with d.. }
void b(somestruct* d) { ..stuff with d.. }
void c(somestruct* d) { ..stuff with d.. }
或
somestruct data[lots];
a(lots,data);
b(lots,data);
c(lots,data);
//...
void a(int n, somestruct* d) {
for(int i<n;i++) {
..stuff with d[i]..
} }
void b(int n, somestruct* d) {
for(int i<n;i++) {
..stuff with d[i]..
} }
void c(int n, somestruct* d) {
for(int i<n;i++) {
..stuff with d[i]..
} }
我的理解是,对于 A,当前活动的结构将被缓存,提供改进,但会有一大堆函数调用(否定)。另一方面,对于 B,我将丢弃我的缓存,但有 3 个函数调用,而不是 3*lots 函数调用。
如果我的编译器决定内联 a、b 和 c,那么第一个选项应该是最好的选择(因为我们现在拥有两全其美的选择),但我的感觉是,在大多数情况下,如果它没有,函数调用的成本远高于内存访问。
我知道唯一知道的方法是对我的特定应用程序进行基准测试,但很好奇我是否在这里遗漏了任何经验法则。第一个版本针对我的具体情况生成了一些更简洁的代码,但差别不大。
编辑
我试图问一个问题,即输入函数的成本(分配堆栈指针和其他任何东西)与缓存未命中的成本相比如何。我认为一个通用的答案也可能对其他人有用。但是,问题的一般形式似乎无法令人满意地回答,所以这里是完整的统计数据,假设我计算正确:
- 数据是 100,000 个结构体,每个结构体包含六个双精度数和一个整数。
- a() 是六个双读、三个双乘、三个双加和三个双存储。
- b() 是三个双读、三个双乘、一个四加、两个双比较,以及有条件地调用 c。
- c() 是三个双乘、三个函数调用和三个双写。
【问题讨论】:
-
你只是在猜测。我们不能再做任何事情了,因为我们不知道这些函数到底在做什么,你有多少数据,你的代码中还有什么,等等。对其进行基准测试或停止浪费自己的时间。也就是说,我的猜测是没有值得改进的地方。
-
@delnan:我基本上是在问“单个函数调用与单个缓存未命中相比如何”。除此之外,负载应该相同。我基本上只是想知道是否有令人信服的理由选择一种形式而不是另一种形式。 E:你的编辑似乎表明“没有真正的区别”,很酷。
-
除了你所拥有的关于数据之间的关系之外,我不知道更多,单循环案例对我很有吸引力,因为它更多地反映了三种不同案例之间的关系(所有这些都与需要循环)。
-
@no_answer_not_upvoted 我很确定我对编译器优化和缓存效果了解很多。我的经验是,甚至很难可靠地衡量与单个缓存意识更改和类似的微优化之间的差异。顺便说一句,通常的内联启发式(除了琐碎的“只是用
__always_inline内联的东西)几乎完全取决于函数的内容,所以不透明的函数确实很重要。我喜欢阅读有关性能的文章,但我也知道这种优化尝试很少能带来实际的改进。 -
@zebediah49 我冒着听起来太自以为是的风险,但我的经验是编译器非常擅长内联,所以也许你应该忽略函数调用的成本:只有在函数很小的情况下它才有意义,然后它们被内联,所以成本为 0
标签: c function optimization