Bjarne Stroustrup在他的“C ++编程语言”(第4版)一书中指出:
请注意,
vector
通常是(令人惊讶的是,除非你 了解机器架构)比list
更高效 短元素的小序列(即使是insert()
和erase()
)。
他没有详细说明,所以我想知道为什么它是真的,大致有多短这些序列(即元素的数量)?
答案 0 :(得分:1)
......大致有多短这些序列
正确的答案是:你必须衡量。
这取决于您在容器中存储的内容,构建,复制和移动的类型,它的大小以及访问和插入模式的费用。
它也会因机器和编译器而异。
回答问题的第一部分:
...所以我想知道为什么这是真的......
在这种情况下,它的真实之处在于,矢量的连续且最小填充的存储器布局与现代CPU协作得更好。缓存子系统而不是列表。
vector
您的循环可能类似于
for (size_t i=0; i < v.size(); ++i) {
其中v.size()
通常只能被调用一次,并且i
和v[i+1]
都适合寄存器。如果其他依赖项允许,分支预测可以很好地允许多个连续迭代运行交错,保持指令管道已满
您的下一次访问(v[i]
)可能已在缓存中,因此非常快。它与list
或同一个缓存行位于同一缓存行中,顺序读取相对容易预取
for (list::const_iterator i=l.begin(); i != l.end(); ++i) {
您的循环可能类似于
i
其中*++i
的每个连续值必须从内存加载(通过前一节点的指针)
您的下一次访问({{1}})可能已在缓存中,但是:
但是你的许多值都适合缓存行,更少的列表节点适合:至少有两个指针(单链表中有一个)的开销。如果您的值与指针的大小相同(我们讨论的是小值),则使用列表在缓存行中获得的数量减少三分之一,从而使缓存未命中数增加三倍。
另请注意,下一次迭代的推测性执行取决于加载值而不是递增寄存器:此处的缓存未命中本身不仅较慢,而且还会拖延推测性执行
答案 1 :(得分:0)
我认为这是因为列表使用动态方法来扩展列表(可能指向下一个列表,以便您可以轻松地插入特定位置而无需重新排列内存)。矢量实际上是一个简单的数组,带有一些很好的糖涂层,使其更易于使用(如果使用正确的函数,可选择例外的边界问题)。
基本上,当你实例化一个向量时,它会分配一个类型为T的小数组。当你在最后推送一个新项时,它只是将值写入已经分配的空间,如果你还没有增长太大了。但是,当您添加到列表时,它必须分配内存并将该位置存储在各种指针中(我猜这里,但链接列表的工作原理)。如果您知道矢量有多大(或者可以相当好地猜测),那么它非常有效。但是,一旦超出它的基础数组的大小,它就必须为新放大的数据结构重新分配内存,然后复制所有内容。对于向大尺寸生长载体而言,这是非常昂贵的。在我的脑海中,向量成倍增长,因此在几次失败后故障的数量会减少,但它仍然非常昂贵。