后增量效率vs.在C ++中预先增量

时间:2016-08-15 00:18:14

标签: c++ c++11

我通常认为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...
    }
}

2 个答案:

答案 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;
}