我++小于效率++我,如何显示呢?(i++ less efficient than ++i, how

2019-06-25 17:11发布

我想通过例子,前缀增量比后缀递增更有效的展现。

在理论上,这是有道理的:我++需要能够返回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?