通过指针访问值与存储为临时值的效率

时间:2013-12-31 19:15:34

标签: c++ c

如果有一个函数将struct指针作为参数,并且该函数有一个循环,可以在每次迭代时访问一个成员,如:

int function_a(struct b *b)
{
    // ... do something then
    for(int i = 0; i < 500; ++i){
        b->c->d[i] = value();
    }
}

是否每次检索c指向的位置和d指向内存?

现在考虑以下情况:

int function_a(struct b *b)
{
    // ... do something then
    float *f = b->c->d;
    for(int i = 0; i < 500; ++i){
        f[i] = value();
    }
}

那会更快吗?

3 个答案:

答案 0 :(得分:7)

我敦促你注意托马斯马修斯关于剖析的建议,但要回答你的问题:这取决于。

此特定转换也称为代码提升,它包含移动代码而没有副作用,并且在循环外的每次调用时具有相同的结果。如上所述,只有在编译器可以证明:

时才会执行此操作
  • 没有副作用
  • 每次通话时计算相同的结果

在这两种情况下,这基本上意味着编译器应该可以访问两者的完整代码(参见定义):

  • 表达本身,证明没有副作用
  • 任何可能改变表达式的东西,以证明每次都计算相同的结果

因此,它实际上不太可能执行优化,除非主体循环的所有代码都包含在头文件中(因此可以内联),因为任何不透明的函数都可能隐藏修改b->c(例如)通过邪恶的全局变量。

在您的示例中,没有任何证据证明value()不会更改b->c ...所以不,编译器提升代码是错误的,除非它有权访问value()的定义可以排除这种可能性。

答案 1 :(得分:4)

使用临时可能不比访问临时更快:取决于平台。

如有疑问,请查看编译器生成的汇编语言。在ARM处理器上,访问内存时:

  • 寄存器加载了变量的地址。
  • 取消引用寄存器以获取值(并存储在 另一个注册)。

这与取消引用指针非常相似:

  • 寄存器加载指针值。
  • 取消引用该寄存器以获取该值。

可能存在第二次从内存加载以获取指针值。事实是汇编语言。

这称为微优化,只应用作加速性能关键区域代码的最后手段。使用分析器找出瓶颈所在并先解决这些问题。

答案 2 :(得分:0)

你没有说清楚为什么要关注这个问题,你是否已经完成了一些性能分析并参与了这个例程,或者你是否正在进行“盲人优化” - 包括查看代码和说“也许这很慢”。

让我先解决你的前期问题:

 a->b->c[i]

VS

 f[i]

如果您编译这两段代码而没有优化那么f[i]的概率很高。

一旦启用优化,所有投注都会关闭。首先,您使用的架构是未知的,因此a->b->c中的顺序提取的成本是未知的,我们也不知道有多少寄存器可用,或者编译器可能使用哪些优化。可以想象,任何单次写入的成本可能足够高,如果CPU使用流水线操作,则写入需要花费足够长的时间,以使我们是否花费一些时间在写入之间进行指针数学运算时无关紧要。

作为一个有点经验丰富的优化器,我会更感兴趣的是“value()做什么?”。编译器可以确定value()不会修改a,a-&gt;中的任何值。或a-> b-> c?

如果你绝对,明确知道这些值不会改变,已经做过perf分析并发现这个循环是瓶颈,看看汇编程序确定编译器没有发出最有效的代码,那么你可能会优化如下:

int function_a(struct b* const b)
{
    /// optimization: we found XYC compiler for Leg architecture was
    /// emitting instructions that repeatedly fetched the array base
    /// address every iteration.
    float* const end = f + 500;
    for (float* it = b->c->d; it < end; ++it)
        *it = value();
}

但是:进行这种低级别优化会带来风险。如今的C / C ++优化器非常聪明。阻止他们生成最有效代码的一种方法是开始手工优化。

我们在这里所做的是一个有效的紧凑循环,但这可能不是在装配中实现结果的最有效方法。

i = 0; i < 500的情况下,取决于value()的实现,实际上可以以保持内存总线繁忙的方式跨越或向量化循环,或者它可能使用特殊的宽寄存器一次做多个操作。我们的优化可能会创建一个逻辑场景,我们强制编译器发出效率最低的操作顺序。

再一次 - 我们不知道你关注这部分代码的原因,但在实践中我总是发现你不太可能通过手工优化这部分循环来获得更多。

如果您是在Linux下开发,可能需要查看valgrind以帮助您进行性能分析。如果您在Visual Studio下进行开发,则“分析” - &gt; “性能和诊断”(ctrl-alt-f9)将打开perf向导。单击开始并选择“Instrumentation”。