C++ 取消引用堆栈上变量的成本,还是最近取消引用的成本?
在现代编译器中,当指针指向的数据最近被取消引用时,第二次取消引用指针的代价是否也一样高C++ 取消引用堆栈上变量的成本,还是最近取消引用的成本?,c++,pointers,memory,C++,Pointers,Memory,在现代编译器中,当指针指向的数据最近被取消引用时,第二次取消引用指针的代价是否也一样高 int * ptr = new int(); ... lots of stuff... *ptr = 1; // may need to load the memory into the cpu *ptr = 2; // accessed again, can I assume this will usually be loaded and cost nothing extra? 如果指针指向堆栈上的一个
int * ptr = new int();
... lots of stuff...
*ptr = 1; // may need to load the memory into the cpu
*ptr = 2; // accessed again, can I assume this will usually be loaded and cost nothing extra?
如果指针指向堆栈上的一个变量,我可以假设通过指针读写堆栈变量的成本与直接读写变量的成本相同吗
int var;
int * ptr = &var;
*ptr = 0; // will this cost the same as if I just said var = 0; ?
最后,这是否扩展到更复杂的事情,例如通过接口操作堆栈上的基本对象
Base baseObject;
Derived * derivedObject = &baseObject;
derivedObject->value = 42; // will this have the same cost as if I just--
derivedObject->doSomething() // --manipulated baseObject directly?
编辑:我问这个问题是为了获得更深入的理解;与其说这是一个需要解决的问题,不如说这是一个需要洞察的问题。请不要担心“过早优化”或其他实际问题,只要尽你所能告诉我:)例如1和2,这取决于上下文。如果您不使用存储,编译器只会忽略它们。对于最后一个示例,这是一个编译错误,您不能从
派生*
自己检查一下:
如果基础扩展,则派生案例(来自注释中的示例):
答案同样取决于编译器知道多少信息。如果它能看到它只是堆栈中的对象,它将对它们进行优化,并同时优化它们
将是同等的
注意:您不必担心这些细节,编译器更了解如何进行优化。使用更具可读性的概念。这是过早的优化。请相信编译器
考虑到CPU体系结构的特殊性以及编译器可以考虑的任何因素,请确保编译器将生成代码以尽可能减少工作量。这个问题包含许多歧义 一个简单的经验法则是,取消引用某个内容将始终具有相同的成本,除非它没有 解引用的代价有很多因素——缓存中的目的地、是否分页以及编译器生成的代码 对于代码段
Obj* p = new Obj;
// <elided> //
p->something = 1;
我们仍然无法确定*p是否被分页/缓存,但大多数现代编译器/优化器不会发出检索p
并存储然后再次获取的代码
在现代硬件的实践中,您真的不应该关心它,如果您关心它,请从查看组件开始
我将使用频谱的两端:
struct Obj { int something; int other; };
Obj* f() {
Obj* p = new Obj;
p->something = 1;
p->other = 2;
return p;
}
extern void fn2(Obj**);
Obj* h() {
Obj* p = new Obj;
fn2(&p);
p->something = 1;
fn2(&p);
p->other = 2;
return p;
}
这个
及
在这里,编译器必须保留并恢复指针,以便在调用后取消引用它,但这有点不公平,因为被调用函数可以修改指针
Obj* h() {
Obj* p = new Obj;
fn2(nullptr);
p->something = 1;
fn2(nullptr);
p->other = 2;
return p;
}
产生
h():
pushq %rbx
movl $8, %edi
call operator new(unsigned long)
xorl %edi, %edi
movq %rax, %rbx
call fn2(Obj**)
xorl %edi, %edi
movl $1, (%rbx)
call fn2(Obj**)
movq %rbx, %rax
movl $2, 4(%rbx)
popq %rbx
ret
我们仍然看到一些注册骗局,但它几乎不贵
关于堆栈指针的问题,一个好的优化器将能够消除这些问题,但同样,您必须参考所选编译器为特定平台生成的程序集
struct Obj { int something; int other; };
void fn(Obj*);
void f()
{
Obj o;
Obj* p = &o;
p->something = 1;
p->other = 1;
fn(p);
}
下面的p
已基本消除
f():
subq $24, %rsp
movq %rsp, %rdi
movl $1, (%rsp)
movl $1, 4(%rsp)
call fn(Obj*)
addq $24, %rsp
ret
当然,如果我们将&p
传递给某个对象,编译器将无法完全省略它,但它可能仍然足够聪明,可以在不一定要使用它时避免使用它
在现代编译器中,取消对指针的引用是否同样昂贵
第二次,当它所指向的数据最近被取消引用时
通过完全优化,编译器可能能够重新排列代码(取决于代码),或者将指针隐藏在寄存器中,或者将值隐藏在寄存器中。。。也许吧
可以查看生成的assy代码进行确认
我考虑过这种过早的优化
此外,如果您正在考虑缓存,那么可能(但不保证)在时间上接近时,当两个mem地址位于同一个缓存块中时,通过指针的两个解引用都将访问缓存mem,而不会丢失缓存
任何写操作都将放在缓存中,并在缓存硬件到达时传递到内存,或者缓存未命中导致内存刷新
如果指针指向堆栈上的一个变量,我可以假设吗 通过指向堆栈变量的指针进行读/写操作的成本相同 直接读取/写入变量
int var;
int * ptr = &var;
*ptr = 0; // will this cost the same as if I just said var = 0; ?
我怀疑你是否能够或应该做出任何假设。您可以检查生成的assy,以查看您的编译器在此目标体系结构上使用此代码以及此编译器版本和构建选项选项选项等执行了哪些操作。数百个(如果不是数千个)变量可能会影响代码生成
请注意,数据缓存也适用于堆栈访问
再次,我考虑这种过早的优化。
最后,这是否延伸到更复杂的事情,例如 通过接口操纵堆栈上的基础对象
Base baseObject;
Derived * derivedObject = &baseObject;
derivedObject->value = 42; // will this have the same cost as if I just--
derivedObject->doSomething() // --manipulated baseObject directly?
一般来说,编译器做得很好。所以,从这个意义上说,可能。不能保证
我认为使用移动语义(C++特性)是有价值的,但这可能与你的问题没有关系。 硬件缓存可能比您希望手动计数(或模拟)的任何循环计数都更重要。我对数据缓存(用于自动变量和动态变量)改善嵌入式系统性能的程度印象深刻。但代码缓存也令人印象深刻。我不想失去他们中的任何一个
我所说的过早优化是指 a) 众所周知,人类无法理解(或“猜测”)程序中的热点在哪里,即20%的代码消耗80%的周期。这就是为什么有工具来帮助定位它们 b) 我一直听说更好的算法比其他选择的算法性能更好,我想说这通常是正确的。更好的算法是你应该学习的。从你的销售代表那里,你可能知道的比我多 然而,我觉得可读性是更合适的评价标准。这个
f():
subq $24, %rsp
movq %rsp, %rdi
movl $1, (%rsp)
movl $1, 4(%rsp)
call fn(Obj*)
addq $24, %rsp
ret