当元素数量较少时,为什么向量优于列表? 在他的《C++程序设计语言》(第四版)中,Bjarne Stroustrup指出:

当元素数量较少时,为什么向量优于列表? 在他的《C++程序设计语言》(第四版)中,Bjarne Stroustrup指出:,c++,vector,linked-list,C++,Vector,Linked List,请注意,向量通常是(令人惊讶的是,除非您 了解机器架构)比列表更有效 小元素的短序列(即使对于insert()和erase()) 他没有进一步阐述这一点,因此我想知道为什么这是真的,以及这些序列的长度(即元素的数量)?我认为这是因为列表使用动态方法来扩展列表(可能指向下一个,以便您可以轻松地在特定位置插入,而无需重新排列内存)。然而,向量实际上是一个简单的数组,带有一些漂亮的糖衣,使其更易于使用(如果您使用正确的函数,边界问题的可选例外) 基本上,当你实例化一个向量时,它会分配一个T类型的小数组

请注意,
向量
通常是(令人惊讶的是,除非您 了解机器架构)比列表更有效 小元素的短序列(即使对于
insert()
erase()


他没有进一步阐述这一点,因此我想知道为什么这是真的,以及这些序列的长度(即元素的数量)?

我认为这是因为列表使用动态方法来扩展列表(可能指向下一个,以便您可以轻松地在特定位置插入,而无需重新排列内存)。然而,向量实际上是一个简单的数组,带有一些漂亮的糖衣,使其更易于使用(如果您使用正确的函数,边界问题的可选例外)

基本上,当你实例化一个向量时,它会分配一个T类型的小数组。当你最后推送一个新项目时,如果你还没有变得太大,它只会将值写入已经分配的空间。但是,当你添加到一个列表中时,它必须分配内存并将该位置存储在各个指针中(我在这里猜测,但链表就是这样工作的。)如果你知道向量有多大(或者你能猜得很好),这是非常有效的。但是,一旦你超过了它的底层数组的大小,它就必须为新扩展的数据结构重新分配内存,然后复制所有内容。这对于将向量增大到较大的大小来说是非常昂贵的。在我的脑海中,向量以指数形式增长,因此在多次失败后,故障数量会减少,但是还是很贵

…以及这些序列的大致长度

正确的答案是:你必须测量

这取决于您在容器中存储的内容、构造、复制和移动该类型的成本、大小以及您的访问和插入模式

它在不同的机器之间也会有所不同,可能在编译器之间也会有所不同


回答问题的第一部分:

…所以我想知道为什么这是真的

在这种情况下,它是正确的原因是向量的连续且最小填充的内存布局与现代CPU的缓存子系统的协作比列表的要好得多

  • vector

    • 您的循环可能类似于

      for (size_t i=0; i < v.size(); ++i) {
      
      for (list::const_iterator i=l.begin(); i != l.end(); ++i) {
      
      其中,
      i
      的每个连续值必须从内存中加载(通过上一个节点的指针)

    • 您的下一次访问(
      *++i
      )可能已在缓存中,但:

    • 无论缓存线中适合的值有多大,列表节点也会更少:至少会有两个指针(一个在单链表中)的开销。如果您的值与指针大小相同(我们讨论的是小值),则使用列表的缓存线中的值会增加三分之一,从而导致缓存未命中率增加三倍

    • 还要注意,下一次迭代的推测性执行取决于加载一个值,而不是增加一个寄存器:这里的缓存未命中不仅本身较慢,而且还会暂停推测性执行


看Herb Sutter的演讲:。它实际上说明了它们必须有多短。FWIW,这里有一张幻灯片:这有点像说“什么更快,O(1)或O(n)”。有人说“显然是O(1)”,但你会回答“啊哈,O(1)算法对所有输入大小都需要30秒,但是O(n)算法需要
4n
秒,而这次我们只有5项“。具体细节将取决于实现。
insert
erase
不在
vector
末尾的操作是最糟糕的操作。但是如果vector的大小很小,即使这些操作也可以在不做太多工作的情况下执行。简单操作的效率加上缓存一致性使它们变得很快呃,比你想象的要快。你假设向量更快,因为列表不需要重新排列内存?