我们有一个问题是Ci++
和++i
C之间有性能差异吗?
C++的答案是什么?
[执行摘要:++i
如果您没有特定的理由可以使用i++
.]
对于C++,答案有点复杂.
如果i
是一个简单类型(不是C++类的实例),则由于编译器正在生成代码,因此C的答案("没有没有性能差异")成立.
但是,如果i
是C++类的实例,那么i++
它++i
正在调用其中一个operator++
函数.这是一对标准的这些功能:
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++
函数,因此无法优化tmp
变量及其关联的复制构造函数.如果复制构造函数很昂贵,那么这会对性能产生重大影响.
是.有.
++运算符可以定义为函数,也可以不定义.对于原始类型(int,double,...),内置运算符,因此编译器可能能够优化您的代码.但是在定义++运算符的对象的情况下,事物是不同的.
operator ++(int)函数必须创建一个副本.这是因为postfix ++应该返回一个与它所拥有的值不同的值:它必须在temp变量中保存其值,增加其值并返回temp.对于operator ++(),前缀++,不需要创建副本:对象可以自行递增,然后只返回自身.
以下是一个例子:
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 }
每次调用operator ++(int)时都必须创建一个副本,编译器无法对其进行任何操作.给出选择时,使用operator ++(); 这样您就不会保存副本.在许多增量(大循环?)和/或大对象的情况下,它可能很重要.
这是增量运算符在不同翻译单元中的基准.用g ++ 4.5编译.
暂时忽略样式问题
// a.cc #include#include class Something { public: Something& operator++(); Something operator++(int); private: std::array 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; }
// b.cc #includeclass Something { public: Something& operator++(); Something operator++(int); private: std::array 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
现在让我们采取以下文件:
// c.cc #includeclass Something { public: Something& operator++(); Something operator++(int); private: std::array 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
如果您不需要以前的值,请养成使用预增量的习惯.即使使用内置类型也要保持一致,如果你用自定义类型替换内置类型,你将习惯它并且不会冒着遭受不必要的性能损失的风险.
i++
说increment i, I am interested in the previous value, though
.
++i
说increment i, I am interested in the current value
或increment i, no interest in the previous value
.再一次,你会习惯它,即使你现在不是.
过早优化是万恶之源.因为过早的悲观化.
说后缀情况下编译器无法优化临时变量副本并不完全正确.使用VC进行的快速测试表明,在某些情况下,它至少可以做到这一点.
在以下示例中,生成的代码对于前缀和后缀是相同的,例如:
#includeclass 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 无论你是使用++ 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)]因此,虽然后缀版本的速度肯定会慢一些,但如果您不使用它,优化器可能会很好地摆脱临时副本.
你忘了注意重要一点,这里所有内容都是内联的.如果操作员的定义不可用,则无法避免在外线代码中完成的复制; 内联优化是非常明显的,所以任何编译器都会这样做.
5> martjno..:在谷歌C++风格指南说:
预增量和预先减少量
使用带有迭代器和其他模板对象的递增和递减运算符的前缀形式(++ i).
定义:当变量递增(++ i或i ++)或递减(--i或i--)并且未使用表达式的值时,必须决定是否预先递增(递减)或后递增(递减).
优点:当忽略返回值时,"pre"形式(++ i)的效率从不低于"post"形式(i ++),并且通常更有效.这是因为后递增(或递减)需要制作i的副本,这是表达式的值.如果我是迭代器或其他非标量类型,复制我可能会很昂贵.由于当忽略该值时,两种类型的增量表现相同,为什么不总是预先递增?
缺点:在C中开发的传统是在不使用表达式值时使用后递增,特别是在for循环中.有些人认为后增量更容易阅读,因为"主题"(i)在"动词"(++)之前,就像在英语中一样.
决策:对于简单的标量(非对象)值,没有理由选择一种形式,我们也允许.对于迭代器和其他模板类型,请使用预增量.
呃,......,那是什么东西?