在指针上执行取消引用操作有多贵?
我可以想象内存传输在某种程度上与对象大小成比例,但我想知道解除引用操作部分有多昂贵.
在解释为机器代码时,解除引用可能意味着不同的事情取决于您对解除引用的对象所做的事情.通过指针访问类的单个成员通常很便宜.例如,如果c是一个指向的一个实例class C
用int
构件N则是这样的:
int n = c->n;
可能会转换为一个或两个机器指令,并可能加载具有单个内存访问的寄存器.
另一方面,这意味着制作c指向的对象的完整副本:
C d = *c;
这个的成本将取决于C的大小,但请注意,它是主要费用的副本,而"取消引用"部分实际上只是"使用"复制指令中的指针地址.
请注意,访问大对象的成员通常需要指针偏移计算和内存访问,无论该对象是否是本地对象.通常,只有非常小的对象被优化为仅存在于寄存器中.
如果你担心指针超过引用的成本,那就不要了.它们之间的区别在于语言语义的差异,并且在生成机器代码时,指针和引用访问看起来完全相同.
这取决于你使用解除引用的指针做什么.仅仅取消引用操作本身并不起作用.它只是得到一个T
代表你的对象的左值类型,如果你的指针是aT*
struct a { int big[42]; }; void f(a * t) { // does nothing. Only interesting for standard or compiler writers. // it just binds the lvalue to a reference t1. a & t1 = *t; }
如果实际上从取消引用操作返回的左值表示的对象中获取了值,则编译器必须复制对象包含的数据.对于一个简单的POD,这只是一个单纯的memcpy
:
a aGlobalA; void f(a * t) { // gets the value of of the object denoted by *t, copying it into aGlobalA aGlobalA = *t; }
我的gcc端口输出f的代码:
sub $29, $29, 24 ; subtract stack-pointer, creating this frame stw $31, $29, 20 ; save return address add $5, $0, $4 ; copy pointer t into $5 (src) add $4, $0, aGlobalA ; load address of aGlobalA into $4 (dst) add $6, $0, 168 ; put size (168 bytes) as 3rd argument jal memcpy ; call memcpy ldw $31, $29, 20 ; restore return address add $29, $29, 24 ; add stack-pointer, destroying this frame jr $31
优化的机器代码将使用内联代码而不是调用memcpy
,但这实际上只是一个实现细节.重要的是,仅仅*t
没有执行任何代码,但访问该对象的值实际上需要复制它.
我们是否必须使用具有用户定义的复制赋值运算符的类型,事务更复杂:
struct a { int big[42]; void operator=(a const&) { } };
f
现在,相同功能的代码如下所示:
sub $29, $29, 8 add $29, $29, 8 jr $31
哈.但这不是一个惊喜,不是吗?毕竟,编译器应该调用我们的operator=
,如果它什么都不做,整个函数也什么都不做!
我认为我们可以得出的结论是,这一切都取决于如何使用返回值operator*
.如果我们只有一个我们取消引用的指针,我们在上面看到生成的代码在很大程度上取决于环境.如果我们取消引用具有重载的类类型,我还没有展示它的行为operator*
.但基本上,它只是表现得像我们所看到的那样operator=
.所有测量都是用-O2
,所以编译器正确内联调用:)
解除对普通系统指针的最重要因素是您可能会生成缓存未命中.SDRAM存储器中的随机访问花费数十纳秒(例如64).在gigaherz处理器上,这意味着您的处理器闲置了数百(或数千)个周期,而在此期间无法执行任何其他操作.
只有基于SRAM的系统(您只能在嵌入式软件中找到),或者当您的软件进行缓存优化时,其他帖子中讨论的因素才会发挥作用.
取消引用可能很昂贵,主要是因为它需要从内存中获取数据的指令,这些数据可能很远并且没有显示引用的位置.在这种情况下,处理器应从非缓存内存甚至硬盘中获取数据(如果出现硬页错误).
解除引用(多个)成本CPU周期.
而不是写:
string name = first->next->next->next->name; int age = first->next->next->next->age; this is O(n)
把它写成:
node* billy_block = first->next->next->next; string name = billy_block->name; int age = billy_block->age; this is O(1)
因此,您的代码不会"询问"每个块只是为了到达第四个块.
多次解除引用就像拥有一个只知道他们旁边的邻居的邻居.
想象一下,如果你问第一街区的人你的朋友比利居住在哪里,他会告诉你他不认识你的朋友,他会告诉你他只知道他们旁边的邻居,然后他会告诉你问他的邻居,然后你会问他的邻居,他会回答与第一个街区相同的事情,你一直问,直到你到达你朋友的街区.不是很有效率