我想通过例子,前缀增量比后缀递增更有效的展现。
在理论上,这是有道理的:我++需要能够返回unincremented原始值,因此其存储,而++我可以不保存以前的值返回增加后的值。
但有一个很好的例子在实践中证明这一点?
我尝试下面的代码:
int array[100];
int main()
{
for(int i = 0; i < sizeof(array)/sizeof(*array); i++)
array[i] = 1;
}
我用gcc 4.4.0这样编译它:
gcc -Wa,-adhls -O0 myfile.cpp
我再次做到这一点,与后缀递增改为前缀增量:
for(int i = 0; i < sizeof(array)/sizeof(*array); ++i)
其结果是在两种情况下相同的汇编代码。
这是有些出人意料。 这似乎是通过关闭优化(用-O0)我应该看到的差异,显示的概念。 我在想什么? 有没有更好的例子来说明这一点?
Answer 1:
在一般情况下,后增量将导致副本,其中预增不会。 当然,这将在大量的病例,并在它不是复制操作将是微不足道的情况下被优化掉(即,用于内建类型)。
这里有一个小例子,显示增量后的潜在低效率。
#include <stdio.h>
class foo
{
public:
int x;
foo() : x(0) {
printf( "construct foo()\n");
};
foo( foo const& other) {
printf( "copy foo()\n");
x = other.x;
};
foo& operator=( foo const& rhs) {
printf( "assign foo()\n");
x = rhs.x;
return *this;
};
foo& operator++() {
printf( "preincrement foo\n");
++x;
return *this;
};
foo operator++( int) {
printf( "postincrement foo\n");
foo temp( *this);
++x;
return temp;
};
};
int main()
{
foo bar;
printf( "\n" "preinc example: \n");
++bar;
printf( "\n" "postinc example: \n");
bar++;
}
从经优化的构建的结果(实际上去除在后增量情况下由于RVO第二复制操作):
construct foo()
preinc example:
preincrement foo
postinc example:
postincrement foo
copy foo()
一般来说,如果你不需要后递增的语义,为什么还要将发生不必要的副本的机会?
当然,这是很好的记住,一个运营商定制++() - 无论是前或后的变种 - 自由返回为所欲为(甚至为所欲为),和我想像,有相当多的不按照通常的规则。 偶尔我遇到返回“实现void
”,这使得一般的语义差别消失。
Answer 2:
你不会看到任何整数差异。 您需要使用迭代器或一些地方后和前缀真正做不同的事情。 而你需要把所有的优化上 ,不脱!
Answer 3:
我喜欢跟着的“说你是什么意思”的规则。
++i
简单地递增。 i++
增量和评价具有一种特殊的,非直观的结果。 我只用i++
,如果我明确希望这种行为,并使用++i
在其他所有情况。 如果按照这个做法,当你看到i++
代码,很明显,后增的行为实在之意。
Answer 4:
几点:
- 首先,你不可能看到任何方式的主要性能差异
- 其次,你的标杆是无用的,如果你有禁用优化。 我们想知道的是,如果这种变化给了我们更多或更少的高效的代码,这意味着我们必须以最高效的代码编译器能够产生使用它。 我们并不关心它是否在未经优化的构建速度更快,我们需要知道,如果它是在优化更快。
- 对于内置数据类型一样整数,编译器通常能够优化的区别了。 这个问题主要发生在更复杂的类型与重载增量迭代器,其中编译器不能平凡看到,两个操作将在上下文等同。
- 您应该使用最清晰的表达你的意图的代码。 你想“增加一个值”或“添加一个值,但保留在原始值的工作时间长一点”? 通常情况下,前者的情况下,再预增较好的表达你的意图。
如果你想以示区别,最简单的选择就是impement无论是运营商,并指出一个需要额外的拷贝,其他没有。
Answer 5:
此代码和它的评论应当证明两者之间的差异。
class a {
int index;
some_ridiculously_big_type big;
//etc...
};
// prefix ++a
void operator++ (a& _a) {
++_a.index
}
// postfix a++
void operator++ (a& _a, int b) {
_a.index++;
}
// now the program
int main (void) {
a my_a;
// prefix:
// 1. updates my_a.index
// 2. copies my_a.index to b
int b = (++my_a).index;
// postfix
// 1. creates a copy of my_a, including the *big* member.
// 2. updates my_a.index
// 3. copies index out of the **copy** of my_a that was created in step 1
int c = (my_a++).index;
}
你可以看到,后缀有涉及创建对象的副本,一个额外的步骤(步骤1)。 这有两个内存消耗和运行时都产生影响。 这就是为什么前缀是更高效,后缀的非基本类型。
根据some_ridiculously_big_type
和也不管你用incrememt的结果呢,你就可以看出差别有或没有优化。
Answer 6:
为了应对米哈伊尔,这是一个较为便携版他的代码:
#include <cstdio>
#include <ctime>
using namespace std;
#define SOME_BIG_CONSTANT 100000000
#define OUTER 40
int main( int argc, char * argv[] ) {
int d = 0;
time_t now = time(0);
if ( argc == 1 ) {
for ( int n = 0; n < OUTER; n++ ) {
int i = 0;
while(i < SOME_BIG_CONSTANT) {
d += i++;
}
}
}
else {
for ( int n = 0; n < OUTER; n++ ) {
int i = 0;
while(i < SOME_BIG_CONSTANT) {
d += ++i;
}
}
}
int t = time(0) - now;
printf( "%d\n", t );
return d % 2;
}
外循环是有让我编造的时序来获取适合我的平台上的东西。
我不使用VC ++了,所以我编译它(在Windows上)有:
g++ -O3 t.cpp
然后,我通过交替运行它:
a.exe
和
a.exe 1
我的计时结果约为两种情况下是相同的。 有时候一个版本将是高达20%的速度,有时等。 这我猜是因为我的系统上运行的其他进程。
Answer 7:
尝试同时使用或做一些与返回值,例如:
#define SOME_BIG_CONSTANT 1000000000
int _tmain(int argc, _TCHAR* argv[])
{
int i = 1;
int d = 0;
DWORD d1 = GetTickCount();
while(i < SOME_BIG_CONSTANT + 1)
{
d += i++;
}
DWORD t1 = GetTickCount() - d1;
printf("%d", d);
printf("\ni++ > %d <\n", t1);
i = 0;
d = 0;
d1 = GetTickCount();
while(i < SOME_BIG_CONSTANT)
{
d += ++i;
}
t1 = GetTickCount() - d1;
printf("%d", d);
printf("\n++i > %d <\n", t1);
return 0;
}
使用/ O2或/牛用VS 2005编译,试图在我的桌面和笔记本电脑。
稳定地得到的东西围绕在笔记本电脑,台式机上的数字是有点不同(但速度是差不多的):
i++ > 8xx <
++i > 6xx <
XX意味着数字是不同的如813 VS 640 - 仍然在20%左右的速度了。
而且还有一点 - 如果你更换“d + =”和“d =”你会看到很好的优化技巧:
i++ > 935 <
++i > 0 <
然而,这是相当具体。 但毕竟,我看不出有任何理由要改变我的想法,并认为没有什么区别:)
Answer 8:
也许你可以只显示由写出两个版本的x86汇编指令的理论区别? 正如许多人以前指出的那样,编译器会一直就如何最好地编译/汇编程序作出自己的决定。
如果示例是为学生不熟悉的x86指令集,你可以考虑使用MIPS32指令集 - 一些奇怪的原因,很多人似乎找到它更容易比x86汇编理解。
Answer 9:
好吧,这一切的前缀/后缀“优化”只是......一些大的误解。
我++返回其原始拷贝,因此主要的想法需要复制的价值。
这对于迭代器的一些效率不高的实现是正确的。 然而,即使是STL迭代器的情况下99%是没有区别的,因为编译器知道如何优化它与实际的迭代器只是指针看起来像类。 当然还有就是对于基本类型,像整数指针没有区别。
所以......忘掉它。
编辑:Clearification
正如我所提到的, 大多数 STL迭代器类的都只是指针包裹着班,有内联的所有成员函数允许这样的无关紧要的副本进行优化。
是的,如果你有没有内联成员函数你自己的迭代器,那么它可能工作比较慢。 但是,你应该明白什么编译器,哪些没有。
作为一个小证明,采取这样的代码:
int sum1(vector<int> const &v)
{
int n;
for(auto x=v.begin();x!=v.end();x++)
n+=*x;
return n;
}
int sum2(vector<int> const &v)
{
int n;
for(auto x=v.begin();x!=v.end();++x)
n+=*x;
return n;
}
int sum3(set<int> const &v)
{
int n;
for(auto x=v.begin();x!=v.end();x++)
n+=*x;
return n;
}
int sum4(set<int> const &v)
{
int n;
for(auto x=v.begin();x!=v.end();++x)
n+=*x;
return n;
}
它编译成汇编和比较SUM1和SUM2,SUM3和SUM4 ...
我可以告诉你...... GCC与给予完全一样的代码-02
。
文章来源: i++ less efficient than ++i, how to show this?