循环内的函数,或函数内的循环

时间:2013-10-19 20:12:25

标签: c function optimization

简单地说,以下哪一项可能是更好的选择:

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.. }

OR

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 *个函数调用。

如果我的编译器决定内联a,b和c,那么第一个选项应该是最好的选择(因为我们现在拥有两个世界中最好的选择),但是我的感觉是在大多数情况下如果没有,函数调用的成本远远高于内存访问。

我知道唯一可以知道的方法是对我的特定应用程序进行基准测试,但是如果有任何我在这里缺少的经验法则,我很好奇。第一个版本为我的特定情况生成了更清晰的代码,但没有什么区别。

修改

我试图询问一个关于输入函数(分配堆栈指针和其他什么)的成本与缓存未命中成本相比的问题。我认为通用答案也可能对其他人有用。然而,似乎问题的一般形式无法得到满意的回答,所以这里是完整的统计数据,假设我算是正确的:

  • 数据是100,000个结构,每个结构包含六个双精度和一个int。
  • a()是六次双读,三次双重乘法,三次双重加,三次双重存储。
  • b()是三次双读,三次双倍乘,四次加,两次双重比较,并有条件地调用c。
  • c()是三次双倍乘法,三次函数调用和三次双重写入。

1 个答案:

答案 0 :(得分:0)

您应该问的主要问题是函数调用和内存访问(特别是缓存未命中)之间的区别是什么。 如果数据结构很大(大到足以使缓存无法容纳它),则会出现一些缓存未命中。在您提供的第一种情况下,这些缓存未命中将重复3次,而在第一种情况下,它们只会发生一次。而且,这些函数可以按照每第10个元素的顺序访问数据结构。这将增加缓存未命中数。 如果数据结构足够小,则不会出现缓存未命中,并且函数调用开销将接管(如果编译器不会内联它们)。但是,一般来说,你不能假设将被分析的数据结构的大小,所以我选择第一个解决方案,保持问题的一般性。在某些特定情况下,第二种解决方案可能更好。