取消引用指针有多昂贵?

对指针执行取消引用操作有多昂贵?

我可以想象,内存传输在某种程度上与对象大小成正比,但我想知道取消引用操作部分是多么昂贵。

当解释为机器码时,解引用可能意味着不同的事情,取决于你对解除引用的对象所做的事情。 通过指针访问类的单个成员通常是便宜的。 例如,如果c是一个指向class C的实例的int成员n,那么就像这样:

 int n = c->n; 

可能翻译成一个或两个机器指令,并可能加载一个单一的内存访问寄存器。

另一方面,这意味着完成c:所指向的对象的拷贝。

 C d = *c; 

这个成本取决于C的大小,但是请注意,它是主要开销的副本,而“取消引用”部分实际上只是在复制指令中“使用”指针地址。

请注意,访问大对象的成员通常需要指针偏移计算和内存访问,而不pipe对象是否是本地对象。 通常只有非常小的对象才被优化,只能在寄存器中生存。

如果你关心指针的引用成本,那么不要这样做。 这两者之间的差异是语言语义的差异,并且在生成机器码的时候指针和参考访问看起来完全一样。

这取决于你对解除引用的指针做了什么。 单纯的解除引用操作本身并不起作用。 它只是得到一个代表你的对象的typesT的左值,如果你的指针是一个T*

 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不执行任何代码,但访问该对象的值实际上需要复制它。

我们需要做一个具有用户定义的复制赋值操作符的types,事务更复杂:

 struct a { int big[42]; void operator=(a const&) { } }; 

现在,相同函数f的代码如下所示:

  sub $29, $29, 8 add $29, $29, 8 jr $31 

哈。 但是这并不奇怪,不是吗? 毕竟,编译器应该调用我们的operator= ,如果它什么都不做,整个函数也什么都不做!

结论

我认为我们可以得出的结论是,这一切都取决于如何使用operator*的返回值。 如果我们只有一个我们废除的指针,我们可以看到上面的代码生成在很大程度上取决于情况。 我没有表明,如果我们取消引用重载operator*的类types,它的行为如何。 但本质上,它只是像我们用operator=看到的那样。 所有的测量都是用-O2完成的,所以编译器正确地内联了:)

在普通系统上取消引用指针最重要的因素是你可能会产生caching未命中。 SDRAM存储器中的随机访问花费几十纳秒(例如64)。 在gigaherz处理器上,这意味着你的处理器正在闲置数百(或者>千)个周期,而不能在此期间做其他任何事情。

只有基于SRAM的系统(您只能在embedded式软件中find),或者当您的软件进行caching优化时,其他post中讨论的因素才会起作用。

取消引用的代价可能很高,主要是因为它会花费一个从内存中获取数据的指令,这些数据可能很远,而且不会显示引用的局部性。 在这种情况下,处理器应该从非高速缓冲存储器,甚至是硬盘中读取数据(在发生硬页面错误的情况下)。

解引用(多个)成本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) 

所以你的代码将不会“询问”每一个块只是为了到达第四个块。

多个解引用就像有一个邻居只知道旁边的邻居。

想象一下,如果你问第一个街区的人,你的朋友Billy居住在哪里,他会告诉你他不认识你的朋友,他会告诉你,他只知道旁边的邻居,然后他会告诉你问他的邻居,然后你问邻居,他会回答第一个街区相同的东西,你不停地问,直到你到达你朋友的街区。 效率不高

指针的解除引用应该不会超过将地址复制到(地址)寄存器。 就这样。