【发布时间】:2014-08-23 22:36:21
【问题描述】:
编辑:我特别将std::vector 的线性 搜索操作与std::map 二进制 搜索操作进行比较,因为这似乎与Herb 的声明有关。我知道使用二分搜索会将性能从 O(N) 提高到 O(log N) 但这不会测试 Herb 的主张
Bjarne Stroustrup 和 Herb Sutter 最近都谈到了std::vector 在人们期望使用std::list 的情况下有多棒,因为在链表遍历期间缓存未命中的成本。 (见 http://channel9.msdn.com/Events/Build/2014/2-661 在 48 分钟标记处)
Herb 进一步声明,对有序向量的操作甚至比 std::map 还要快(参见 http://i.imgur.com/zX07TZR.png,取自上述 channel9 视频的 51:30 标记),我发现这很难理解。所以我创建了一个小测试来证明这一点,但很难重现这些结果:https://ideone.com/MN7DYK
这是测试代码:
template <typename C>
void test(std::string name, std::vector<int> shuffledInputValues, C & c)
{
// fill container 'c' with values from 'shuffledInputValues' then erase them all
{
std::cout << "testing " << name << "..." << std::endl;
timer t;
for (auto val : shuffledInputValues) insert(c, val);
for (auto val : shuffledInputValues) remove(c, val);
}
}
// output:
// testing vector...99.189ms
// testing deque...120.848ms
// testing set...4.077ms
注意 std::vector 的执行速度比 std::set 慢一个数量级。当然这是我预期的结果,但我对 Herb 试图提出的说法感到困惑。
我做错了什么?还是我误解了 Herb 的说法?
关于我的测试应用的注意事项:
- 它必须使用线性运算 - 练习的重点是演示 CPU 缓存魔法,这是 Herb 和 Bjarne 对练习的约束
- 我没有为向量迭代尝试任何棘手的循环解开,但我相信迭代不会对性能产生太大影响
- 我将循环限制为 10K 项,因为 ideone 在较大的集合上超时,但增加大小不会改变结果
编辑:请参阅https://ideone.com/916fVd 以获取仅比较查找性能的修改示例。线性搜索表现出相同的性能。
【问题讨论】:
-
@WhozCraig 是的,我同意。但他确实特别提到了线性查找操作......他顺便提到在现实世界中你会在排序的向量上使用二进制搜索,但他的主张是关于通过向量的线性迭代的性能(除非有人可以另外指出? 就像我说的那样,我很可能是错的!)
-
@Cechner:这很有趣,我很感兴趣,除非晚上发生任何事情,否则我会进行一个简单的测试!实际上,出于好奇,我不久前针对
std::map对boost::container::flat_map进行了基准测试,并且确实发现flat_map更快,当然使用的内存更少。由于每次缓存未命中都会受到持续的惩罚,但是我会说,当向量使用线性搜索时,对于大型集合std::map将始终击败std::vector,这可能就是您正在经历的。 -
我在大约 5 年前对此进行测量的经验是,在大约 200 个条目的编译器符号表的应用程序中,树与数组相得益彰(树节点的块分配以减少
malloc开销)。我在用 C 编程,树的实现是我的。这些是约 16 个字节的小记录加上标识符字符串,这也是比较键。符号表操作中散布着大量应该挑战缓存的代码和数据活动。 -
呵呵,这种话里总有夸张的成分,总想把重点说清楚。但是,我确实认为不应该使用诸如
std::list之类的链表,在实践中,我发现绝对零情况下它的特性更可取。如果您关心订单并且不经常更新收藏,请前往boost::container::flat_map/flat_set。如果您不关心订单,请选择std::vector并在最后一个元素上使用交换技巧。 -
@Cechner:是的,这就是为什么
boost::container::flat_map之类的东西并不总是替代品。根据我的经验,在insert和erase存在的情况下,您很少依赖迭代器的有效性。通常你会填满你的集合,并且在构建之后它保持相当稳定,在这种情况下,像存储这样的向量通常是一个巨大的净赢。如果迭代器有效性很重要,std::map/set可能是最合适的。