我想知道如下函数,是否使用临时变量(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的代码等一些地方看到了上述内容,对“避免别名”的评论模糊不清。
答案 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)
立即想到一个想法:异常安全。如果在解析过程中抛出异常,则应该使用临时变量来提供强大的异常安全性:函数调用完全成功或者没有做任何事情(从用户的角度来看)。