【问题标题】:Functions inside loop, or loops inside functions循环内的函数,或函数内的循环
【发布时间】: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


【解决方案1】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-09-09
    • 1970-01-01
    • 1970-01-01
    • 2014-02-13
    • 2014-11-27
    相关资源
    最近更新 更多