我知道数组被分配为一个连续的内存块,因此我们可以通过非常容易地计算从数组开头的字节/字偏移来访问它的元素。
我知道链接列表遍历的效率低于数组遍历,因为缓存效率低,其中分支预测不会像数组那样运行良好。但是,我也听说过,由于我们使用偏移量访问数组的方式,从数组的一个元素到下一个元素的迭代比访问链表中下一个元素的指针更快。
链表中的指针访问速度比数组中的偏移访问速度慢吗?
答案 0 :(得分:7)
缓存效率低下,分支预测无法正常运行
这些是不同的东西。链接列表遭受缓存无效率的影响:
链接列表并非真正受到分支错误预测的影响。是的,如果你迭代一个,最后一个分支(退出循环的分支)有可能被错误预测,但这不是链接列表特有的。
链表中的指针访问速度比数组中的偏移访问速度慢吗?
在所有中加载指针比计算数组中元素的下一个地址要慢,无论是在延迟方面还是在吞吐量方面。为了快速比较,在现代机器上典型的是加载该点需要大约4个周期(最好!如果有高速缓存未命中,则需要更长时间)并且每个周期可以完成两次。将数组元素的大小添加到当前地址需要1个周期,每个周期可以完成4次,并且您(或编译器)可以通过一些巧妙的编码重新使用循环计数器的增量。例如,也许你可以使用索引寻址与循环计数器(无论如何递增)作为索引,或者你可以“偷”"完全循环计数器并按元素的大小递增(相应地缩放循环结束),或者没有循环计数器,直接将当前地址与数组末尾的地址进行比较。编译器喜欢自动使用这些技巧。
实际上它比声音更糟糕,因为在链表中加载这些指针是完全串行的。是的,CPU可以在每个周期加载两个东西,但它需要4个周期,直到它知道下一个节点的位置,以便它可以开始加载下一个指针,所以实际上它每隔4个周期只能找到一个节点的地址。计算数组元素的地址没有这样的问题,也许在连续地址的计算之间会有1的延迟,但是(因为实际的循环不能比任何时候更快)只会在循环展开时受到伤害,并且如果必要的话k元素的地址可以通过添加k*sizeof(element)
来计算(因此可以独立计算多个地址,编译器在展开循环时也会这样做。)
每步"做足够的工作"步骤"通过链表可以隐藏延迟问题。
答案 1 :(得分:2)
访问指针需要额外的内存读取(与计算相比较慢):要读取下一个元素的值,首先需要从内存中读取指针,然后需要读取引用地址的内容。对于数组,该值只有一个存储器读访问权(假设在迭代期间基址保存在寄存器中)。