Bjarne Stroustrup说我们必须避免链接列表

时间:2015-12-09 04:10:28

标签: c++ vector linked-list

我在YouTube上看过这个视频:https://www.youtube.com/watch?v=YQs6IC-vgmo Bjarne说使用向量而不是链表更好。我无法理解整个事情,所以任何人都可以用外行的方式解释他在说什么吗?

P.S:我是一名高中生,可以轻松处理链接列表,但我很难自己学习矢量。你能建议任何学习载体的来源吗?

1 个答案:

答案 0 :(得分:18)

向量与链表的优点

向量与链接列表的主要优点是内存位置。

通常,每个元素都单独分配在一个链表中。因此,这些元素在内存中可能并不是彼此相邻的。 (内存中元素之间的差距。)

保证向量连续存储所有包含的元素。 (彼此相邻的物品,没有间隙;)

注意:可能会出现过度简化...;)

Imo,关于连续存储的数据存储模式与非连续存储的卓越性能的简化关键点

1。缓存未命中

现代CPU不从内存中获取单个字节,而是稍微更大的块。因此,如果您的数据对象大小小于这些块的大小,并且存储是连续的,则您可以一次获得多个元素,因为多个元素可能位于单个块中。

实施例

64字节块(通常的缓存行大小)一次适合16个32位整数。因此,在从第一个元素进入高速缓存的那一刻开始处理16个元素之后,最早发生高速缓存未命中(尚未在高速缓存中的数据 - >从所需的主存储器加载)。如果使用链接列表,则第一个元素可能是64字节块中唯一的元素。从理论上讲,可能会发生列表中每个元素的缓存未命中。

具体示例:

std::vector<std::uint32_t> v;
// somehow fill 64 values into v
std::uint32_t s{};
for(std::size_t i{0}; i<v.size(); ++i)
{
  s += v[i];
}

想象一下v的内容尚未缓存。

在for循环中处理数据时会发生什么?

  

1)检查元素v [0]是否在缓存中。 - &GT;都能跟得上

     

2)从v [0]的地址开始从主存储器获取64字节到高速缓存行

     

3)通过将其值添加到s

,从缓存加载v [0]并进行处理      

4)缓存中的元素v 1是什么? - &GT;是因为相邻v [0]

而加载了之前的提取      

5)通过将其值添加到s

,从缓存和进程加载v 1      

6)缓存中的元素v [2]是什么? - &GT;是的......

     

7)通过将其值添加到s

,从缓存和进程加载v [2]      

...等......

     

34)缓存中的元素v [16]是什么? - &GT;都能跟得上

     

35)从v [16]的地址开始从主存储器获取64字节到高速缓存行

     

36)通过将其值添加到s

,从缓存和进程加载v [16]      

37)缓存中的元素v [17]是什么? - &GT;是因为邻近的v [16]

加载了之前的提取      

等...

将数据从主存储器提取到缓存所需的时间比将数据从缓存加载到处理器寄存器并执行简单操作要花费更多的时间。因此,多个值可能驻留在单个缓存行上的事实可以显着提高性能。

链接列表不提供连续的存储保证,您无法希望获得此性能提升。这也是为什么随机迭代(随机访问元素)比连续容器的前向迭代(按顺序访问元素)更差的原因。

2。预取

上述效果被称为“prefetcher”的CPU功能放大。

如果从主内存加载了一个块,预取器就会准备加载下一个块/已经将它放入缓存中,从而大大降低了从主内存部分加载内容的麻烦。

当且仅当您实际需要来自下一个准备好的数据块的数据时,这当然是有效的。

向量通常如何工作(内部)?

请参阅:c++ Vector, what happens whenever it expands/reallocate on stack?