我们的问题是,在C语言中i++和++i的性能有区别吗?
c++的答案是什么?
我们的问题是,在C语言中i++和++i的性能有区别吗?
c++的答案是什么?
当前回答
++i -更快,不使用返回值 i++ -使用返回值更快
当不使用返回值时,编译器保证不会在++i的情况下使用临时类型。不保证更快,但保证不会变慢。
当使用返回值i++时,允许处理器同时推送 增量和左侧进入管道,因为它们彼此不依赖。i可能会使管道停止,因为处理器无法启动左侧,直到增量前操作已经蜿蜒完成。同样,也不保证会出现管道失速,因为处理器可能会找到其他有用的东西来插入。
其他回答
当您将操作符视为值返回函数以及它们的实现方式时,++i和i++之间的性能差异将更加明显。为了更容易理解发生了什么,下面的代码示例将使用int,就像它是一个结构体一样。
++i对变量加1,然后返回结果。这可以就地完成,并且只需要最少的CPU时间,在许多情况下只需要一行代码:
int& int::operator++() {
return *this += 1;
}
但是i++就不一样了。
后递增(i++)通常被视为在递增之前返回原始值。但是,函数只能在完成时返回结果。因此,有必要创建一个包含原始值的变量的副本,增加变量,然后返回包含原始值的副本:
int int::operator++(int& _Val) {
int _Original = _Val;
_Val += 1;
return _Original;
}
当增量前和增量后之间没有功能差异时,编译器可以执行优化,使两者之间没有性能差异。但是,如果涉及到结构或类等复合数据类型,则在增量后调用复制构造函数,如果需要深度复制,则不可能执行此优化。因此,前增量通常比后增量更快,需要的内存更少。
是时候给人们提供智慧的宝石了;)-有一个简单的技巧可以让c++的后缀增量表现得和前缀增量几乎一样(为自己发明的,但我在其他人的代码中也看到了它,所以我不是一个人)。
基本上,诀窍是在返回后使用helper类来延迟增量,然后RAII来拯救
#include <iostream>
class Data {
private: class DataIncrementer {
private: Data& _dref;
public: DataIncrementer(Data& d) : _dref(d) {}
public: ~DataIncrementer() {
++_dref;
}
};
private: int _data;
public: Data() : _data{0} {}
public: Data(int d) : _data{d} {}
public: Data(const Data& d) : _data{ d._data } {}
public: Data& operator=(const Data& d) {
_data = d._data;
return *this;
}
public: ~Data() {}
public: Data& operator++() { // prefix
++_data;
return *this;
}
public: Data operator++(int) { // postfix
DataIncrementer t(*this);
return *this;
}
public: operator int() {
return _data;
}
};
int
main() {
Data d(1);
std::cout << d << '\n';
std::cout << ++d << '\n';
std::cout << d++ << '\n';
std::cout << d << '\n';
return 0;
}
Invented用于一些繁重的自定义迭代器代码,它减少了运行时间。前缀vs后缀的成本现在是一个参考,如果这是自定义操作符做大量的移动,前缀和后缀产生了相同的运行时为我。
++i -更快,不使用返回值 i++ -使用返回值更快
当不使用返回值时,编译器保证不会在++i的情况下使用临时类型。不保证更快,但保证不会变慢。
当使用返回值i++时,允许处理器同时推送 增量和左侧进入管道,因为它们彼此不依赖。i可能会使管道停止,因为处理器无法启动左侧,直到增量前操作已经蜿蜒完成。同样,也不保证会出现管道失速,因为处理器可能会找到其他有用的东西来插入。
说编译器不能优化掉后缀情况下的临时变量副本是不完全正确的。用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)]
因此,虽然后缀版本肯定会更慢,但如果你不使用它,优化器可能会足够好,可以摆脱临时副本。
[执行摘要:如果没有特定的理由使用i++,请使用++i。]
对于c++来说,答案有点复杂。
如果i是一个简单类型(不是c++类的实例),那么C给出的答案(“不,没有性能差异”)成立,因为编译器正在生成代码。
但是,如果i是c++类的实例,则i++和++i将调用其中一个操作符++函数。下面是这些函数的标准组合:
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;
}
由于编译器不生成代码,而只是调用运算符++函数,因此没有办法优化掉tmp变量及其相关的复制构造函数。如果复制构造函数的开销很大,则会对性能产生重大影响。