我们的问题是,在C语言中i++和++i的性能有区别吗?
c++的答案是什么?
我们的问题是,在C语言中i++和++i的性能有区别吗?
c++的答案是什么?
当前回答
i++有时比++ I快!
对于使用ILP(指令级并行)的x86架构,i++在某些情况下可能优于++i。
为什么?因为数据依赖关系。现代cpu可以并行化很多东西。如果接下来的几个CPU周期对i的增量值没有任何直接依赖,CPU可能会省略微码来延迟i的增量,并将其塞到“空闲插槽”中。这意味着您实际上得到了一个“免费”增量。
我不知道ILE在这种情况下走多远,但我认为如果迭代器变得太复杂,并做指针解引用,这可能不会工作。
下面是Andrei Alexandrescu对这个概念的解释:https://www.youtube.com/watch?v=vrfYLlR8X8k&list=WL&index=5
其他回答
说编译器不能优化掉后缀情况下的临时变量副本是不完全正确的。用VC进行的快速测试表明,至少在某些情况下,它可以做到这一点。
在下面的例子中,生成的代码对于前缀和后缀是相同的,例如:
#include <stdio.h>
class Foo
{
public:
Foo() { myData=0; }
Foo(const Foo &rhs) { myData=rhs.myData; }
const Foo& operator++()
{
this->myData++;
return *this;
}
const Foo operator++(int)
{
Foo tmp(*this);
this->myData++;
return tmp;
}
int GetData() { return myData; }
private:
int myData;
};
int main(int argc, char* argv[])
{
Foo testFoo;
int count;
printf("Enter loop count: ");
scanf("%d", &count);
for(int i=0; i<count; i++)
{
testFoo++;
}
printf("Value: %d\n", testFoo.GetData());
}
无论您使用的是++testFoo还是testfoo++,都将得到相同的结果代码。事实上,无需从用户读取计数,优化器将整个事情归结为一个常数。所以这个:
for(int i=0; i<10; i++)
{
testFoo++;
}
printf("Value: %d\n", testFoo.GetData());
结果如下:
00401000 push 0Ah
00401002 push offset string "Value: %d\n" (402104h)
00401007 call dword ptr [__imp__printf (4020A0h)]
因此,虽然后缀版本肯定会更慢,但如果你不使用它,优化器可能会足够好,可以摆脱临时副本。
是的。有。
++操作符可以定义为函数,也可以不定义为函数。对于基本类型(int, double,…),操作符是内置的,因此编译器可能能够优化您的代码。但对于定义了++运算符的对象,情况就不一样了。
操作符++(int)函数必须创建一个副本。这是因为postfix ++被期望返回一个与它所保存的值不同的值:它必须将其值保存在一个临时变量中,自增其值并返回临时值。在操作符++(),前缀++的情况下,不需要创建一个副本:对象可以自增,然后简单地返回自己。
下面是关于这一点的一个例子:
struct C
{
C& operator++(); // prefix
C operator++(int); // postfix
private:
int i_;
};
C& C::operator++()
{
++i_;
return *this; // self, no copy created
}
C C::operator++(int ignored_dummy_value)
{
C t(*this);
++(*this);
return t; // return a copy
}
每次调用操作符++(int)都必须创建一个副本,编译器对此无能为力。当有选择时,使用运算符++();这样就不需要保存副本。在很多增量(大循环?)和/或大对象的情况下,它可能很重要。
@wilhelmtell
编译器可以省略临时对象。从另一个线程逐字逐句:
c++编译器允许消除基于堆栈的临时对象,即使这样做会改变程序行为。MSDN链接vc8:
http://msdn.microsoft.com/en-us/library/ms364057 (VS.80) . aspx
++i比i++快,因为它不返回值的旧副本。
它也更直观:
x = i++; // x contains the old value of i
y = ++i; // y contains the new value of i
这个C语言的例子输出的是“02”而不是你所期望的“12”:
#include <stdio.h>
int main(){
int a = 0;
printf("%d", a++);
printf("%d", ++a);
return 0;
}
c++也是一样:
#include <iostream>
using namespace std;
int main(){
int a = 0;
cout << a++;
cout << ++a;
return 0;
}
下面是自增操作符位于不同转换单元时的基准测试。g++ 4.5编译器。
现在先忽略样式问题
// a.cc
#include <ctime>
#include <array>
class Something {
public:
Something& operator++();
Something operator++(int);
private:
std::array<int,PACKET_SIZE> data;
};
int main () {
Something s;
for (int i=0; i<1024*1024*30; ++i) ++s; // warm up
std::clock_t a = clock();
for (int i=0; i<1024*1024*30; ++i) ++s;
a = clock() - a;
for (int i=0; i<1024*1024*30; ++i) s++; // warm up
std::clock_t b = clock();
for (int i=0; i<1024*1024*30; ++i) s++;
b = clock() - b;
std::cout << "a=" << (a/double(CLOCKS_PER_SEC))
<< ", b=" << (b/double(CLOCKS_PER_SEC)) << '\n';
return 0;
}
O (n)增加
Test
// b.cc
#include <array>
class Something {
public:
Something& operator++();
Something operator++(int);
private:
std::array<int,PACKET_SIZE> data;
};
Something& Something::operator++()
{
for (auto it=data.begin(), end=data.end(); it!=end; ++it)
++*it;
return *this;
}
Something Something::operator++(int)
{
Something ret = *this;
++*this;
return ret;
}
结果
在虚拟机上使用g++ 4.5的结果(计时以秒为单位):
Flags (--std=c++0x) ++i i++
-DPACKET_SIZE=50 -O1 1.70 2.39
-DPACKET_SIZE=50 -O3 0.59 1.00
-DPACKET_SIZE=500 -O1 10.51 13.28
-DPACKET_SIZE=500 -O3 4.28 6.82
O(1)增加
Test
现在让我们看看下面的文件:
// c.cc
#include <array>
class Something {
public:
Something& operator++();
Something operator++(int);
private:
std::array<int,PACKET_SIZE> data;
};
Something& Something::operator++()
{
return *this;
}
Something Something::operator++(int)
{
Something ret = *this;
++*this;
return ret;
}
它对增量没有任何影响。这模拟了增量具有恒定复杂度的情况。
结果
结果现在变化很大:
Flags (--std=c++0x) ++i i++
-DPACKET_SIZE=50 -O1 0.05 0.74
-DPACKET_SIZE=50 -O3 0.08 0.97
-DPACKET_SIZE=500 -O1 0.05 2.79
-DPACKET_SIZE=500 -O3 0.08 2.18
-DPACKET_SIZE=5000 -O3 0.07 21.90
结论
属性
如果不需要前一个值,请养成使用预递增的习惯。即使与内置类型保持一致,您也会习惯它,如果您用自定义类型替换内置类型,也不会有遭受不必要性能损失的风险。
Semantic-wise
i++表示递增i,但我对之前的值感兴趣。 ++i表示自增i,我对当前值感兴趣或者自增i,对之前的值不感兴趣。再说一次,你会习惯的,即使你现在还不习惯。
Knuth。
过早的优化是万恶之源。过早的悲观也是如此。