这个问题的意图是有效地使用局部变量而不是成员的好理由,我不想在这里测试编译器优化…请
如果你认为我的比较不完美,请提供一个替代代码。
有谁能详细解释为什么吗?为什么本地访问更快,即使它必须创建堆栈和拆除堆栈每次调用函数而成员它只需要解引用这个指针我可以看到本地堆栈更快?
结果当地时间: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++ - 01 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;
这是因为编译器可以看到您从未在local::incr
中读取i
。如果你不读它,就没有必要增加它,所以编译器可以优化任何与local
有关的东西。什么都不做当然比什么都不做要快。
然而,我怀疑你用完全优化编译,否则编译器会看到与Member
相关的东西也没有做任何事情,然后你会得到0和0两次,因为好的优化器可以看到足够的优化甚至循环,因为它们没有副作用。
对于性能来说,变量是本地变量还是成员变量并不像缓存位置和寄存器位移那么重要。
考虑到你关于"不测试优化"的评论,我怀疑你的"问题"是"我如何测试证明一个是否比另一个更快?"
答案是:您必须查看程序集(例如gcc -o test。S -S test.cpp)。使用- 01或更高版本,GCC完全消除了对Local.incr()函数的调用,这显然会使测试无效。
然而,如果你,假设,用- 0编译,那么你是在前加载测试支持局部变量,因为- 0提高了成员操作的成本——调用访问成员变量的成员函数的成本更高。
我把你的例子改成了:
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 - 0 -g - 0"测试。S -S test.cpp","incr"的实现是
<>之前_Z4incrv:.LFB0:.file 1"test.cpp".loc 1 10.cfi_startprocpushq % rbp.LCFI1:.cfi_def_cfa_register 6.LBB2:.loc 1 3 0增加$1,-4(%rbp).LBE2:.loc 1 0 0popq % rbp.LCFI2:.cfi_def_cfa 7,8受潮湿腐烂.cfi_endproc之前当local::incr是
<>之前_ZN5local4incrEv:.LFB1:.loc 1 0 0.cfi_startprocpushq % rbp.LCFI3:.cfi_def_cfa_offset 16.cfi_offset 6, -16%rsp, %rbp.LCFI4:.cfi_def_cfa_register 6Movq %rdi, -24(%rbp).LBB3:.loc 1 10 0增加$1,-4(%rbp).LBE3:.loc 1 11 0popq % rbp.LCFI5:.cfi_def_cfa 7,8受潮湿腐烂.cfi_endproc之前因为它必须接收this指针。但是它不访问任何成员变量,所以实际上不需要以任何方式使用this指针。
<>之前_ZN6member4incrEv:.LFB2:.loc 1 17 0.cfi_startprocpushq % rbp.LCFI6:.cfi_def_cfa_offset 16.cfi_offset 6, -16%rsp, %rbp.LCFI7:.cfi_def_cfa_register 6-8(%rbp).loc 1 18 0Movq -8(%rbp), %raxMovl (% x), % xLeal 1(%rax), %edxMovq -8(%rbp), %rax移动%edx, (%rax).loc 1 19 0popq % rbp.LCFI8:.cfi_def_cfa 7,8受潮湿腐烂.cfi_endproc之前在这种调试版本中,使用- 0,成员访问总是会更昂贵。如果我在Member中添加一个"m_j",并在Member::incr()中增加它,编译器继续并生成:
<>之前.loc 1 200Movq -8(%rbp), %rax移动4(%rax), %eaxLeal 1(%rax), %edxMovq -8(%rbp), %rax移动%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;
}
};
"局部"变量在任何时候都比成员变量更有效,也就是说,如果:
- 没有施工开销,
- 在它们导致寄存器移位的情况下不使用它们,
- 你没有把堆栈弄得太深,
- 你没有强迫自己从/到函数传递大量额外的参数,
- 你没有强迫自己使用额外的寄存器来捕获大量的返回值。
我的水晶球告诉我,编译器正在优化局部变量代码,但它不能证明成员变量以后没有被访问,并且增量实际上已经完成。
我喜欢@Tony D在我的收件箱中的回答"每次调用函数时必须创建堆栈并拆除堆栈"适用于两者(实际上,任何非内联函数),并且调用函数时的单个堆栈指针调整可能包括局部变量的空间-之后访问堆栈指针相对数据不应该比对象的this指针相对数据慢。如果你想感受一下,可以看看优化的asm。- Tony D