C++中后增量与前增量的效率

Jae*_*ege 24 c++ c++11

我通常认为preincrement比C++中的postincrement更有效.但是,当我最近阅读" 游戏引擎架构"(第二版)这本书时,有一节说,后增量优于for循环中的preincrement.因为,正如我引用的那样,"preincrement会在代码中引入数据依赖关系 - CPU必须等待增量操作完成才能在表达式中使用它的值." 这是真的?(这真的颠覆了我对这个问题的看法.)

以下是您感兴趣的部分的引用:

5.3.2.1增量前与增量后增量

请注意,在上面的示例中,我们使用的是C++的postincrement运算符 p++,而不是preincrement运算符++p.这是一个微妙但有时重要的优化.preincrement运算符在表达式中使用其(现在已修改的)值之前递增变量的内容.postincrement运算符在使用后递增变量的内容.这意味着写入会在代码中++p引入数据依赖关系 - CPU必须等待增量操作完成才能在表达式中使用其值.在深度流水线的CPU上,这引入了一个停顿.另一方面,p++没有数据依赖性.变量的值可以立即使用,增量操作可以在以后或与其使用并行进行.无论哪种方式,管道中都没有失速.

当然,在forloop(for(init_expr; test_expr; update_expr) { ... })的"update"表达式中,前后增量之间应该没有区别.这是因为任何好的编译器都会认识到没有使用变量的值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...
    }
}
Run Code Online (Sandbox Code Playgroud)

eer*_*ika 20

preincrement在您的代码中引入了数据依赖关系 - CPU必须等待增量操作完成才能在表达式中使用它的值."

这是真的?

这大多是正确的 - 尽管可能过于严格.预增量不一定会引入数据依赖 - 但它可以.

博览会的一个简单例子:

a = b++ * 2;
Run Code Online (Sandbox Code Playgroud)

这里,增量可以与乘法并行执行.增量和乘法的操作数立即可用,并且不依赖于任一操作的结果.

另一个例子:

a = ++b * 2;
Run Code Online (Sandbox Code Playgroud)

这里,乘法必须在增量之后执行,因为乘法的一个操作数取决于增量的结果.

当然,这些语句稍有不同,因此编译器可能无法始终将程序从一种形式转换为另一种形式,同时保持语义相同 - 这就是为什么使用post增量可能会使性能略有不同.

一个实际的例子,使用循环:

for(int i= 0; arr[i++];)
    count++;

for(int i=-1; arr[++i];) // more typically: (int i=0; arr[i]; ++i;)
    count++;
Run Code Online (Sandbox Code Playgroud)

人们可能会认为后者必然更快,如果他们推断"后增量复制" - 在非基本类型的情况下这是非常正确的.但是,由于数据依赖性(并且因为int是增量运算符没有过载函数的基本类型),前者在理论上可以更有效.是否取决于cpu架构和优化器的能力.

对于它的价值 - 在一个简单的程序中,在x86 arch上,使用启用了优化的g ++编译器,上面的循环具有相同的汇编输出,因此在这种情况下它们完全相同.


经验法则:

如果计数器是基本类型并且未使用增量的结果,那么使用post/pre增量也没有区别.

如果计数器不是基本类型并且未使用增量的结果并且禁用了优化,则预增量可能更有效.启用优化后,通常没有区别.

如果计数器是基本类型并且使用增量的结果,那么在某些上下文中使用某些编译器,后增量在理论上可以稍微更有效 - 在某些cpu体系结构中.

如果计数器不是基本类型并且使用增量的结果,则预增量通常比后增量快.另见R Sahu关于此案的答案.


R S*_*ahu 8

从我的经验中得到的一点数据.

将后增量更改为std::map::iteratorin for循环的预增量会导致我工作中的核心算法显着节省.

通常,当对作为类的迭代器进行icrement,即它不是指针时,在使用预增量运算符时应注意节省.原因是预增量运算符函数将对象更改到位,而后增量运算符函数通常涉及创建临时对象.

预增量运算符通常实现为:

typename& typename::operator++()
{
   // Change state
   ...

   // Return the object
   return *this;
}
Run Code Online (Sandbox Code Playgroud)

而后增量运算符通常实现为:

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;
}
Run Code Online (Sandbox Code Playgroud)

  • 这是死定了。如果您增加 POD,那么这实际上并不重要。如果它是底层的类,则首选预增量,因为它可以完成更少的工作。 (2认同)