夜晚是一个很好的顾问,我想我可能有一个想法;)
- 这些天内存比 CPU 慢得多,如果所有数据都适合 L1 缓存没什么大不了的,但它很容易溢出到 L2 或 L3:5 组 1000 个元素已经是 5000 个元素,这意味着 5000 个节点,一个集合节点包含至少 3 个指针 + 对象(即,在 32 位机器上至少有 16 个字节,在 64 位机器上至少有 32 个字节)=> 这至少是 80k 内存,而最近的 CPU 只有 32k 用于 L1D,所以我们已经溢出了进入 L2
- 前面的事实因集合节点可能分散在内存周围的问题而更加复杂,而不是紧密地打包在一起,这意味着缓存行的一部分充满了完全不相关的东西。这可以通过提供一个使节点彼此靠近的分配器来缓解。
- 而且,CPU 更擅长顺序读取(它们可以在您需要它之前预取内存,因此您无需等待)而不是随机读取(不幸的是,树结构会导致相当随机读)
这就是为什么在速度很重要的地方, a vector
(或者也许 a deque
)是如此出色的结构:它们与记忆配合得很好。因此,我肯定会推荐使用vector
作为我们的中介结构;尽管需要注意仅从肢体插入/删除以避免重新定位。
所以我想到了一个相当简单的方法:
#include <cassert>
#include <algorithm>
#include <set>
#include <vector>
// Do not call this method if you have a single set...
// And the pointers better not be null either!
std::vector<int> intersect(std::vector< std::set<int> const* > const& sets) {
for (auto s: sets) { assert(s && "I said no null pointer"); }
std::vector<int> result; // only return this one, for NRVO to kick in
// 0. Check obvious cases
if (sets.empty()) { return result; }
if (sets.size() == 1) {
result.assign(sets.front()->begin(), sets.front()->end());
return result;
}
// 1. Merge first two sets in the result
std::set_intersection(sets[0]->begin(), sets[0]->end(),
sets[1]->begin(), sets[1]->end(),
std::back_inserter(result));
if (sets.size() == 2) { return result; }
// 2. Merge consecutive sets with result into buffer, then swap them around
// so that the "result" is always in result at the end of the loop.
std::vector<int> buffer; // outside the loop so that we reuse its memory
for (size_t i = 2; i < sets.size(); ++i) {
buffer.clear();
std::set_intersection(result.begin(), result.end(),
sets[i]->begin(), sets[i]->end(),
std::back_inserter(buffer));
swap(result, buffer);
}
return result;
}
看起来是正确的,但显然我不能保证它的速度。