我通常认为preincrement is more efficient than postincrement in C++。但是当我最近阅读这本书Game Engine Architecture(2nd ed.)时,有一节说明后增量优先于for循环中的preincrement。因为,正如我引用的那样,“preincrement在您的代码中引入了数据依赖 - CPU必须等待增量操作完成才能在表达式中使用它的值。”这是真的? (这真的颠覆了我对这个问题的看法。)
以下是您感兴趣的部分的引用:
5.3.2.1增量前与增量后增量
请注意,在上面的例子中我们使用的是C ++的postincrement运算符,
p++
,而不是preincrement运算符,++p
。这是一个微妙但有时重要的优化。 preincrement运算符在表达式中使用其(现在已修改的)值之前递增变量的内容。 postincrement运算符在使用后递增变量的内容。这意味着写++p
会在代码中引入数据依赖 - CPU必须等待增量操作完成才能在表达式中使用它的值。在深度流水线的CPU上,这引入了 stall 。另一方面,p++
没有数据依赖。变量的值可以立即使用,增量操作可以在以后或与其使用并行进行。无论哪种方式,管道中都没有失速。当然,在
for
循环(for(init_expr; test_expr; update_expr) { ... }
)的“更新”表达式中,应该没有区别 前后增量。这是因为任何好的编译器都会认识到这一点update_expr
中未使用变量的值。但在遇到的情况下 使用了值,后增量是优越的,因为它不会引入失速 在CPU的管道中。因此,养成一直使用的习惯是好的 后增量,除非你绝对需要preincrement的语义。
编辑:添加“上面的例子”。
void processArray(int container[], int numElements)
{
int* pBegin = &container[0];
int* pEnd = &container[numElements];
for (int* p = pBegin; p != pEnd; p++)
{
int element = *p;
// process element...
}
}
void processList(std::list<int>& container)
{
std::list<int>::iterator pBegin = container.begin();
std::list<int>::iterator pEnd = container.end();
std::list<inf>::iterator p;
for (p = pBegin; p != pEnd; p++)
{
int element = *p;
// process element...
}
}
答案 0 :(得分:20)
preincrement在您的代码中引入了数据依赖关系 - CPU必须等待增量操作完成才能在表达式中使用它的值。&#34;
这是真的吗?
这大部分都是正确的 - 尽管可能过于严格。预增量并不一定会引入数据依赖 - 但它可以。
博览会的一个简单例子:
a = b++ * 2;
这里,增量可以与乘法并行执行。增量和乘法的操作数立即可用,并且不依赖于任一操作的结果。
另一个例子:
a = ++b * 2;
这里,乘法必须在增量之后执行,因为乘法的一个操作数取决于增量的结果。
当然,这些语句稍有不同,因此编译器可能无法始终将程序从一种形式转换为另一种形式,同时保持语义相同 - 这就是为什么使用post增量可能会略有不同在表现。
一个实际的例子,使用循环:
for(int i= 0; arr[i++];)
count++;
for(int i=-1; arr[++i];) // more typically: (int i=0; arr[i]; ++i;)
count++;
有人可能会认为后者必然会更快,如果他们认为&#34;后增量制作副本&#34; - 在非基本类型的情况下,这是非常正确的。但是,由于数据依赖性(并且因为int
是一个基本类型而没有增量运算符的重载函数),前者在理论上可以更有效。它是否取决于cpu架构和优化器的能力。
对于它的价值 - 在一个简单的程序中,在x86 arch上,使用启用了优化的g ++编译器,上面的循环具有相同的汇编输出,因此它们在 中完全等效情况下。
经验法则:
如果计数器是基本类型并且未使用增量结果,那么使用后期/预增量也没有区别。
如果计数器不是基本类型并且未使用增量的结果并且禁用了优化,则预增量可能更有效。启用优化后,通常没有区别。
如果计数器是基本类型并且使用了递增的结果,那么在某些上下文中,使用某些编译器,后增量在理论上可以稍微更有效 - 在某些cpu体系结构中。
如果计数器不是基本类型并且使用增量的结果,则预增量通常比后增量快。另见R Sahu关于此案的答案。
答案 1 :(得分:7)
根据我的经验提供的一点数据。
在std::map::iterator
循环中将后增量更改为for
的预增量会导致我工作中的核心算法显着节省。
通常,当对作为类的迭代器进行icrement,即它不是指针时,在使用预增量运算符时应注意节省。原因是预增量运算符函数将对象更改到位,而后增量运算符函数通常涉及创建临时对象。
预增量运算符通常实现为:
typename& typename::operator++()
{
// Change state
...
// Return the object
return *this;
}
而后增量运算符通常实现为:
typename typename::operator++(int)
{
// Create a temporary object that is a copy of the current object.
typename temp(*this):
// Change state of the current object
...
// Return the temporary object.
return temp;
}