引用指针参数的C ++优化

时间:2009-10-28 10:10:47

标签: c++ optimization

我想知道如下函数,是否使用临时变量(p):

void parse_foo(const char*& p_in_out,
               foo& out) {
    const char* p = p_in_out;

    /* Parse, p gets incremented etc. */

    p_in_out = p;
}

或者我可以只使用原始参数并期望它与上述类似地进行优化吗?看起来应该有这样的优化,但我已经在Mozilla的代码等一些地方看到了上述内容,对“避免别名”的评论模糊不清。

6 个答案:

答案 0 :(得分:2)

所有好的答案,但是如果你担心性能优化,实际的解析几乎都会花费所有的时间,所以指针别名可能会“在噪音中”。

答案 1 :(得分:1)

具有临时变量的变体可能更快,因为它并不意味着指针的每个更改都会反映回参数,并且编译器有更好的机会生成更快的代码。然而,正确的测试方法是编译并查看反汇编。

与此同时,这与避免混叠有关。实际上,带有临时变体的变量确实使用了别名 - 现在你有两个指向同一个数组的指针,而这正是别名所在。

答案 2 :(得分:1)

如果函数可能是事务性的,我会使用临时函数。

即。功能完全成功或失败(没有中间立场) 在这种情况下,我会在函数执行时使用temp维护状态,并在函数成功完成时仅返回in_out参数。

如果函数提前退出(即通过异常),那么我们有两种情况:

  • 使用临时(外部指针不变)
  • 直接使用参数修改外部状态以反映位置。

我认为这两种方法都没有任何优化优势。

答案 3 :(得分:1)

是的,您应该将其分配给标记为restrict的本地(MSVC中为__restrict)。

原因是如果编译器不能完全确定作用域中没有其他内容指向p_in_out ,则它无法将指针下的内容存储在本地寄存器中。每次写入同一范围内的任何其他char * 时,它必须将数据读回。这不是它是否是“智能”编译器的问题;这是正确性要求的结果。

通过编写char* __restrict p,您承诺编译器同一范围内没有其他指针指向与p 相同的地址。如果没有这种保证,*p的值可以在任何其他指针写入时随时更改,或者每次写入*p时它都可能更改其他指针的内容。因此,编译器必须立即将*p的每个赋值写回内存,并且每次写入另一个指针后都必须将它们读回。

因此,保证编译器这不会发生 - 它可以加载*p一次并假设没有其他指针影响它 - 可以改善性能。究竟有多少取决于特定的编译器和情况:在受到负载命中存储惩罚的处理器上,它是巨大的;在大多数x86 CPU上,它是适度的。

这里更喜欢指向引用的指针的原因很简单,指针可以标记为restrict,而引用则不能。这就是C ++的方式。

你可以尝试两种方式并测量结果,看看哪种方法真的更快。如果你很好奇,I've written in depth on restrict and the load-hit-store elsewhere

附录:在写完上述内容之后,我意识到Moz的人们更担心引用本身是别名的 - 也就是说,其他东西可能指向同一地址{{1存储,而不是p指向的char。但我的答案是一样的:const char *p意味着const char *&p意味着const char **p,这与任何其他指针存在相同的别名问题。

答案 4 :(得分:1)

编译器如何知道p_in_out没有别名?它实际上无法优化通过引用来回写数据。

struct foo {
    setX(int); setY(int); 
    const char* current_pos;
} x;
parse_foo(x.current_pos, x);

我看看这个,并问为什么你不只是返回指针然后你没有对指针的引用,你不必担心修改原始。

const char* parse_foo(const char* p, foo& out) {
    //use p;
    return p;
}

这也意味着您可以使用右值调用该函数:

p = parse_foo(p+2, out); 

答案 5 :(得分:0)

立即想到一个想法:异常安全。如果在解析过程中抛出异常,则应该使用临时变量来提供强大的异常安全性:函数调用完全成功或者没有做任何事情(从用户的角度来看)。