在C ++中,i ++和++ i之间有性能差异吗?

我们有问题是在C中的 i++++i之间有性能差异吗?

C ++的答案是什么?

[执行摘要:使用++i如果你没有一个特定的理由使用i++ 。]

对于C ++来说,答案有点复杂。

如果i是一个简单的types(不是C ++类的一个实例), 那么给出的答案是C(“不存在性能差异”) ,因为编译器正在生成代码。

但是,如果i是一个C ++类的实例,那么i++++i正在调用一个operator++函数。 以下是这些function的标准配对:

 Foo& Foo::operator++() // called for ++i { this->data += 1; return *this; } Foo Foo::operator++(int ignored_dummy_value) // called for i++ { Foo tmp(*this); // variable "tmp" cannot be optimized away by the compiler ++(*this); return tmp; } 

由于编译器不是生成代码,而只是调用operator++函数,因此无法优化tmpvariables及其关联的拷贝构造函数。 如果复制构造函数很昂贵,那么这会对性能产生重大影响。

(感谢Paul询问C和C ++的区别。)

是。 有。

++运算符可以被定义为一个函数,也可以不被定义。 对于原始types(int,double,…),运算符是内置的,所以编译器可能会优化您的代码。 但是在定义++运算符的对象的情况下,事情是不同的。

运算符++(int)函数必须创build一个副本。 这是因为postfix ++需要返回一个不同于它所保存的值:它必须在tempvariables中保存它的值,增加它的值并返回temp。 在运算符++()前缀++的情况下,不需要创build副本:对象可以自行增加,然后简单地返回自己。

这里是一个例子:

 struct C { C& operator++(); // prefix C operator++(int); // postfix private: int i_; }; C& C::operator++() { ++i_; return *this; // self, no copy created } CC::operator++(int ignored_dummy_value) { C t(*this); ++(*this); return t; // return a copy } 

每次调用operator ++(int)时,都必须创build一个副本,而编译器对此无能为力。 当给出select时,使用operator ++(); 这样你不保存副本。 在许多增量(大循环?)和/或大对象的情况下,这可能是重要的。

以下是增量运算符处于不同翻译单位的情况的基准。 用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)增量

testing

 // 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)增量

testing

现在让我们拿下面的文件:

 // 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 

结论

性能方面

如果你不需要以前的价值,使它成为一个习惯使用前增量。 即使与内buildtypes保持一致,您也会习惯它,并且如果您使用自定义typesreplace了内buildtypes,则不会有遭受不必要的性能损失的风险。

语义明智

  • i++increment i, I am interested in the previous value, though
  • ++iincrement i, I am interested in the current valueincrement i, no interest in the previous value 。 再一次,即使你现在不适应,你也会习惯的。

克努特。

不成熟的优化是万恶之源。 由于过早的悲观化。

说编译器不能在postfix的情况下优化临时variables拷贝是不完全正确的。 用VC进行快速testing表明,在某些情况下,至less可以这样做。

在以下示例中,生成的代码对前缀和后缀是相同的,例如:

 #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)] 

所以虽然后缀版本可能会变慢,但如果不使用临时版本的话,优化器可能会更好。

Google C ++风格指南说:

增量和前量

使用迭代器和其他模板对象的增量和减量运算符的前缀forms(++ i)。

定义:当variables递增(++ i或i ++)或递减( – i或i–)且expression式的值未被使用时,必须决定是预先递增(递减)还是后递增(递减)。

优点:当返回值被忽略时,“pre”forms(++ i)永远不会比“post”forms(i ++)低效,而且通常效率更高。 这是因为后递增(或递减)需要创build一个i的副本,这是expression式的值。 如果我是一个迭代器或其他非标量types,复制我可能是昂贵的。 由于这两种types的增量在忽略时performance相同,为什么不总是预先增加?

缺点:在C语言中,当不使用expression式值时,使用后增量传统,特别是在for循环中。 有些人发现后增加更容易阅读,因为“主语”(i)在“动词”(++)之前,就像英语一样。

决定:对于简单的标量(非对象)值,没有理由偏好一种forms,我们也允许。 对于迭代器和其他模板types,请使用预增量。

我想指出Andrew Koenig最近在Code Talk上发表的一篇很好的文章。

http://dobbscodetalk.com/index.php?option=com_myblog&show=Efficiency-versus-intent.html&Itemid=29

在我们公司,我们还使用++ iter惯例来保证一致性和性能。 但是Andrew提出了关于意图与性能的细节。 有时我们想用iter ++而不是++ iter。

所以,首先要确定你的意图,如果事前或事后无关紧要,那么就和pre一样,因为避免创build额外的对象并抛出它会带来一些性能上的好处。

@Ketan

…提出了有关意图vs性能的过分详细的细节。 有时我们想用iter ++而不是++ iter。

显然,post和pre-increment具有不同的语义,我相信每个人都同意,当使用结果时,应该使用适当的操作符。 我认为这个问题是在丢弃结果时应该做什么(如在for循环中)。 这个问题(恕我直言)的答案是,由于性能考虑因素充其量是微不足道的,你应该做更自然的事情。 对于我自己++i是更自然的,但是我的经验告诉我,我是less数,使用i++将会减less大多数人阅读你的代码的金属开销。

毕竟这就是语言不叫“ ++C ”的原因。

插入关于++C作为一个更合乎逻辑的名称的义务性讨论。

马克:只是想指出,运算符++是内联的好候选者,如果编译器select这样做,在大多数情况下多余的副本将被删除。 (例如,迭代器通常是PODtypes)。

也就是说,在大多数情况下使用++ iter还是更好的方式。 🙂

  1. ++我 – 更快地不使用返回值
  2. 我++ – 更快地使用返回值

不使用返回值时,编译器保证在++ i的情况下不使用临时值。 不保证更快,但保证不会变慢。

使用返回值时, i ++允许处理器将增量和左侧都推入stream水线,因为它们不相互依赖。 ++我可能会拖延pipe道,因为处理器不能启动左侧,直到预增量操作一直蜿蜒。 同样,stream水线失速并不能保证,因为处理器可能会发现其他有用的东西。

@Mark:我删除了我之前的回答,因为它有点翻转,应该得到一个单独的倒计时。 我觉得这是一个很好的问题,因为它会问很多人的想法。

通常的答案是++ ++比i ++更快,毫无疑问,但是更大的问题是“你应该关心什么?

如果在增加迭代器中花费的CPU时间小于10%,那么你可能不在意。

如果在增加迭代器中花费的CPU时间比例大于10%,则可以查看哪些语句正在进行迭代。 看看你是否可以增加整数,而不是使用迭代器。 机会是你可以的,虽然它可能在某种意义上是不太可取的,机会是相当不错的,你将基本上节省在这些迭代器中花费的所有时间。

我已经看到一个例子,迭代器增量消耗了90%以上的时间。 在这种情况下,进行整数递增会大大减less执行时间。 (即好于10倍加速)

预期的问题是关于什么时候结果未被使用(从C的问题中可以清楚)。 有人可以解决这个问题,因为问题是“社区维基”?

关于过早的优化,Knuth经常被引用。 那就对了。 但是唐纳德·克努特永远不会用那些你可以看到的那些可怕的代码来防守。 在Java整数(不是int)中看过a = b + c? 这相当于3次装箱/拆箱转换。 避免这样的东西是重要的。 和无用的写我++而不是++我是同样的错误。 编辑:正如phresnel很好地把它放在一个评论,这可以归纳为“过早优化是邪恶的,因为是过早pessimization”。

即使人们更习惯于i ++这个事实,也是一个不幸的C遗留问题,由K&R的一个概念错误引起(如果你遵循这个意图的说法,这是一个合乎逻辑的结论;并且捍卫K&R,因为它们是K&R是无意义的,但是他们并不是语言devise师,从C ++devise中可以看出无数的错误,从gets()到strcpy()到strncpy()API(从第1天开始应该有strlcpy()API) )。

顺便说一句,我是其中一个不够用C ++find++我讨厌阅读。 不过,我承认这是对的,

如果将运算符看作是函数的返回值以及如何实现,则++ii++之间的性能差异会更加明显。 为了更容易理解发生了什么,下面的代码示例将使用int ,就像它是一个struct

++i递增variables, 然后返回结果。 这可以在最短的CPU时间内完成,在很多情况下只需要一行代码:

 int& int::operator++() { return *this += 1; } 

但是, i++不能说的i++

后递增, i++通常被认为是递增之前返回原始值。 但是, 一个函数只能在结束时返回一个结果 。 因此,有必要创build一个包含原始值的variables副本,增加variables,然后返回保存原始值的副本:

 int int::operator++(int& _Val) { int _Original = _Val; _Val += 1; return _Original; } 

当前增量和后增量之间没有function差异时,编译器可以执行优化,使得两者之间没有性能差异。 但是,如果涉及复合数据types(如structclass ),则将在后增量时调用复制构造函数,如果需要进行深度复制,则不可能执行此优化。 因此,预增量通常更快,并且需要比后增量更less的内存。

为什么你应该使用++我甚至在没有性能优势的内置types中为你自己创造一个好习惯。

两者都是一样的快;)如果你想要它是相同的计算处理器,它只是在不同的顺序。

例如,下面的代码:

 #include <stdio.h> int main() { int a = 0; a++; int b = 0; ++b; return 0; } 

生成以下程序集:

  0x0000000100000f24 <main+0>: push %rbp 0x0000000100000f25 <main+1>: mov %rsp,%rbp 0x0000000100000f28 <main+4>: movl $0x0,-0x4(%rbp) 0x0000000100000f2f <main+11>: incl -0x4(%rbp) 0x0000000100000f32 <main+14>: movl $0x0,-0x8(%rbp) 0x0000000100000f39 <main+21>: incl -0x8(%rbp) 0x0000000100000f3c <main+24>: mov $0x0,%eax 0x0000000100000f41 <main+29>: leaveq 0x0000000100000f42 <main+30>: retq 

你会发现,对于++和b ++来说,这是一个助记符,所以它是相同的操作;)

@wilhelmtell

编译器可以暂时中止。 从其他线程逐字:

允许C ++编译器消除基于堆栈的临时对象,即使这样做会改变程序的行为。 VC 8的MSDN链接:

http://msdn.microsoft.com/en-us/library/ms364057(VS.80).aspx

++ii++更快,因为它不会返回值的旧副本。

这也更直观:

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