-2

问题的意图是使用局部变量而不是成员的一个很好的理由,我不想在这里测试编译器优化......请

如果您认为我的比较不完美,请提供替代代码。

任何人都可以详细解释为什么?为什么本地访问速度更快,即使它每次调用函数时都必须创建堆栈并拆除堆栈虽然成员它只需要取消引用这个指针我可以看到本地堆栈更快?


结果当地时间:271 会员时间:418

代码 :

class local {
public:
 void incr() {
        int i;
        ++i;
}


};


class Member {
int i;

public:
 void incr() {
        ++i;
}

};

#include <ctime>
#include <iostream>
#include <time.h>
int main(int argc, char**argv) {

        time_t star;
        time_t end;
        Member m;
        local l;
        time(&star);
        for(unsigned int j=0;j<200000;++j)
        for(unsigned int i=0;i<400000;++i) {
                l.incr();
        }
        time(&end);
        std::cout << "\nlocal time:" << end-star << "\n";

        time(&star);
        for(unsigned int j=0;j<200000;++j)
        for(unsigned int i=0;i<400000;++i) {
                m.incr();
        }
        time(&end);
        std::cout << "\nmember time:" << end-star<< "\n";
return 0;
}

新代码:g++ -O1 localmember.cpp

当地时间:128 会员时间:117

代码 :

class local {
public:
 int diff(int a, int b) {
        int d=0;
        d=a-b;
        return d;
}
};


class Member {
int d;

public:
 int diff(int a, int b) {
        d=0;
        d=a-b;
        return d;
}

};

static int gr;
#include <ctime>
#include <iostream>
#include <time.h>
void dumpdiff(int r) {
gr=r ;
}
int main(int argc, char**argv) {

        time_t star;
        time_t end;
        Member m;
        local l;
        int r=0;
        int r2=0;
        time(&star);
        int in1=2,in2=0;
        for(unsigned int j=0;j<200000;++j)
        for(unsigned int i=0;i<200000;++i) {
                r = l.diff(in1*i,in2*i);
                in1+=1;
                in2+=1;
                if(r){r2=r;}
                dumpdiff(r);
        }
        time(&end);
        std::cout << "\nlocal time:" << end-star << "\n";

        time(&star);
        for(unsigned int j=0;j<200000;++j)
        for(unsigned int i=0;i<200000;++i) {
                r = m.diff(in1,in2);
                in1+=1;
                in2+=1;
                if(r){r2=r;}
                dumpdiff(r);
        }
        time(&end);
        std::cout << "\nmember time:" << end-star<< "\n";
return 0;
4

4 回答 4

3

那是因为你的编译器可以看到你从来没有读过i. local::incr如果您不阅读它,则无需递增它,因此编译器可以优化与local. 什么都不做当然比什么都做要快。

但是,我怀疑您是否使用完全优化进行编译,否则编译器会看到Member与甚至优化循环,因为它们没有副作用。

于 2013-06-17T04:12:27.417 回答
3

变量是本地还是成员对性能而言不如缓存位置和寄存器位移重要。

鉴于您对“不测试优化”的评论,我怀疑您的“问题”是“我如何测试证明一个是否比另一个更快?”

答案是:你必须查看程序集(例如 gcc -o test.S -S test.cpp)。使用 -O1 或更高版本,GCC 完全消除了对 Local.incr() 函数的调用,这显然会使测试无效。

但是:如果您大概是使用 -O0 编译的,那么您会提前加载测试以支持局部变量,因为使用 -O0 会提高成员操作的成本 - 调用访问成员变量的成员函数会更加昂贵。

我以您的示例并将其更改为:

void incr() {
    int i;
    ++i;
}

class local {
public:
    void incr() {
        int i;
        ++i;
    }
};

class member {
    int m_i;
public:
    void incr() {
        ++m_i;
    }
};

int main(int argc, const char** argv)
{
    local l;
    member m;

    for(unsigned int j = 0; j < 200000; ++j) {
        for(unsigned int i = 0; i < 400000; ++i) {
            incr();
        }
    }

    for(unsigned int j = 0; j < 200000; ++j) {
        for(unsigned int i = 0; i < 400000; ++i) {
            l.incr();
        }
    }

    for(unsigned int j = 0; j < 200000; ++j) {
        for(unsigned int i = 0; i < 400000; ++i) {
            m.incr();
        }
    }

    return 0;
}

使用“g++ -std=c++11 -Wall -O0 -g -o test.S -S test.cpp”,“incr”的实现是

_Z4incrv:
.LFB0:
    .file 1“test.cpp”
    .loc 1 1 0
    .cfi_startproc
    pushq %rbp
.LCFI1:
    .cfi_def_cfa_register 6
.LBB2:
    .loc 1 3 0
    添加 $1, -4(%rbp)
.LBE2:
    .loc 1 4 0
    流行音乐%rbp
.LCFI2:
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc

而 local::incr 是

_ZN5local4incrEv:
.LFB1:
    .loc 1 8 0
    .cfi_startproc
    pushq %rbp
.LCFI3:
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq %rsp, %rbp
.LCFI4:
    .cfi_def_cfa_register 6
    movq %rdi, -24(%rbp)
.LBB3:
    .loc 1 10 0
    添加 $1, -4(%rbp)
.LBE3:
    .loc 1 11 0
    流行音乐%rbp
.LCFI5:
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc

因为它必须接收“this”指针。但它没有访问任何成员变量,因此它实际上不必以任何方式使用 this 指针。

_ZN6member4incrEv:
.LFB2:
    .loc 1 17 0
    .cfi_startproc
    pushq %rbp
.LCFI6:
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq %rsp, %rbp
.LCFI7:
    .cfi_def_cfa_register 6
    movq %rdi, -8(%rbp)
    .loc 1 18 0
    movq -8(%rbp), %rax
    movl (%rax), %eax
    leal 1(%rax), %edx
    movq -8(%rbp), %rax
    movl %edx, (%rax)
    .loc 1 19 0
    流行音乐%rbp
.LCFI8:
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc

在这种调试版本中,使用 -O0,成员访问总是会更昂贵。如果我将“m_j”添加到 Member 并在 Member::incr() 中增加它,编译器会继续并生成:

    .loc 1 20 0
    movq -8(%rbp), %rax
    movl 4(%rax), %eax
    leal 1(%rax), %edx
    movq -8(%rbp), %rax
    movl %edx, 4(%rax)

所以是的 - 在未优化的构建中,在大多数情况下,对于微不足道的情况,成员变量比局部变量更昂贵。

“大多数情况”?如果类型不是简单类型,具有昂贵的构造函数等,那么每次进入函数时都必须运行构造函数,而不是必须运行一次。考虑:

void simulated_work() { std::this_thread::sleep_for(std::chrono::milliseconds<5000>; }

struct DatabaseInteger {
    int m_i;
public:
    DatabaseInteger() {
        simulated_work();
    }
    inline DatabaseInteger& operator++() { ++m_i; }
    operator int() { return m_i; }
};

class local {
public:
    void incr() {
        DatabaseInteger i; // does simulated_work every time.
        ++i;
    }
};

“局部”变量在任何时候都会比成员变量更有效,也就是说,如果:

  • 没有施工费用,
  • 在导致寄存器位移的情况下,您不会使用它们,
  • 你没有让堆栈愚蠢地深,
  • 您不会强迫自己从/向函数传递大量附加参数,
  • 您不会强迫自己使用额外的寄存器来捕获大量返回值。
于 2013-06-17T06:48:32.247 回答
1

我的水晶球告诉我编译器正在优化局部变量代码,但它不能证明以后没有访问成员变量并且实际上已经完成了增量。

于 2013-06-17T04:14:06.593 回答
0

我喜欢@Tony D 在我的收件箱中的回答“每次调用函数时都必须创建堆栈并拆除堆栈”适用于两者(实际上,任何非内联函数),调用函数时的单个堆栈指针调整可能包括局部变量的空间 - 之后访问堆栈指针相关数据不应该比对象的 this 指针相关数据慢。如果您想感受一下,请查看优化的 asm。– 托尼 D

于 2013-06-17T04:44:07.187 回答