我们的问题是,在C语言中i++和++i的性能有区别吗?
c++的答案是什么?
我们的问题是,在C语言中i++和++i的性能有区别吗?
c++的答案是什么?
当前回答
当您将操作符视为值返回函数以及它们的实现方式时,++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;
}
当增量前和增量后之间没有功能差异时,编译器可以执行优化,使两者之间没有性能差异。但是,如果涉及到结构或类等复合数据类型,则在增量后调用复制构造函数,如果需要深度复制,则不可能执行此优化。因此,前增量通常比后增量更快,需要的内存更少。
其他回答
[执行摘要:如果没有特定的理由使用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变量及其相关的复制构造函数。如果复制构造函数的开销很大,则会对性能产生重大影响。
当您将操作符视为值返回函数以及它们的实现方式时,++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后缀的成本现在是一个参考,如果这是自定义操作符做大量的移动,前缀和后缀产生了相同的运行时为我。
既然你也要求c++,下面是java的基准测试(用jmh制作):
private static final int LIMIT = 100000;
@Benchmark
public void postIncrement() {
long a = 0;
long b = 0;
for (int i = 0; i < LIMIT; i++) {
b = 3;
a += i * (b++);
}
doNothing(a, b);
}
@Benchmark
public void preIncrement() {
long a = 0;
long b = 0;
for (int i = 0; i < LIMIT; i++) {
b = 3;
a += i * (++b);
}
doNothing(a, b);
}
结果表明,即使在某些计算中实际使用了增量变量(b)的值,迫使需要存储额外的值以防止后增量,每个操作的时间也完全相同:
Benchmark Mode Cnt Score Error Units
IncrementBenchmark.postIncrement avgt 10 0,039 0,001 ms/op
IncrementBenchmark.preIncrement avgt 10 0,039 0,001 ms/op
我想指出Andrew Koenig最近在Code Talk上发表的一篇出色的文章。
http://dobbscodetalk.com/index.php?option=com_myblog&show=Efficiency-versus-intent.html&Itemid=29
在我们公司,我们也在适用的情况下使用++iter的一致性和性能。但Andrew提出了关于意图与性能的忽略细节。有时我们想用iter++而不是++iter。
所以,首先决定你的意图,如果pre或post不重要,那么使用pre,因为它将有一些性能优势,避免创建额外的对象并抛出它。