我试图通过示例显示前缀增量比后缀增量更有效。
理论上这是有道理的:i ++需要能够返回未增加的原始值并因此存储它,而++我可以返回增量值而不存储先前的值。
但是有一个很好的例子可以在实践中证明这一点吗?
我尝试了以下代码:
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)我应该看到显示概念的差异。我错过了什么?有没有更好的例子来展示这个?
答案 0 :(得分:23)
在一般的情况下,后增量将导致一个副本,其中预增量不会。当然,这将在大量情况下进行优化,并且在不是复制操作的情况下,可以忽略不计(即,对于内置类型)。
这是一个小例子,显示了后增量的潜在低效率。
#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
”的实现,这会使通常的语义差异消失。
答案 1 :(得分:8)
你不会看到整数有任何区别。你需要使用迭代器或post和prefix真正做不同的东西。你需要将所有优化打开,而不是关闭!
答案 2 :(得分:5)
我喜欢遵循“说出你的意思”的规则。
++i
只是递增。 i++
增量和具有特殊的,非直观的评估结果。如果我明确地想要这种行为,我只使用i++
,而在所有其他情况下使用++i
。如果您遵循这种做法,当您在代码中看到i++
时,显然确实存在增量后行为。
答案 3 :(得分:4)
有几点:
如果你想显示差异,最简单的选择就是强制两个操作员,并指出一个需要额外的副本,另一个不需要。
答案 4 :(得分:0)
尝试使用while或使用返回值执行某些操作,例如:
#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或/ Ox编译VS 2005,在我的桌面和笔记本电脑上试用。
在笔记本电脑上稳定地获取一些东西,桌面数字有点不同(但速度大致相同):
i++ > 8xx <
++i > 6xx <
xx表示数字不同,例如813 vs 640 - 仍然加速20%左右。
还有一点 - 如果用“d =”替换“d + =”,你会看到很好的优化技巧:
i++ > 935 <
++i > 0 <
但是,它非常具体。但毕竟,我认为没有任何改变主意的理由并认为没有区别:)
答案 5 :(得分:0)
此代码及其注释应证明两者之间的差异。
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
以及您对增量结果所做的任何事情,您将能够看到有或没有优化的区别。
答案 6 :(得分:0)
对Mihail的回应,他的代码是一个更便携的版本:
#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%,有时则更快。我猜这是由于我的系统上运行的其他进程。
答案 7 :(得分:0)
也许您可以通过用x86汇编指令写出两个版本来显示理论差异?正如许多人之前指出的那样,编译器总是会自己决定如何最好地编译/组装程序。
如果该示例适用于不熟悉x86指令集的学生,则可以考虑使用MIPS32指令集 - 由于某些奇怪的原因,许多人似乎发现它比x86汇编更易于理解。
答案 8 :(得分:-4)
好的,所有这些前缀/后缀“优化”只是......一些很大的误解。
i ++返回其原始副本的主要思想因此需要复制该值。
对于迭代器的一些低效实现,这可能是正确的。然而,在99%的情况下,即使使用STL迭代器也没有区别,因为编译器知道如何优化它,而实际的迭代器只是看起来像类的指针。当然,对于像指针上的整数这样的原始类型没有区别。
所以......忘掉它。
编辑:澄清
正如我所提到的,大多数 STL迭代器类只是用类包裹的指针,所有成员函数内联允许out-优化这种无关的副本。
是的,如果你有自己的没有内联成员函数的迭代器,那么它可能 工作慢。但是,你应该只了解编译器做什么和不做什么。
作为一个小证明,请使用此代码:
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
提供完全相同的代码。