如何优化向量的二分搜索?

时间:2014-05-29 15:48:47

标签: c++ performance algorithm vector

我正在尝试在键值对的排序向量上实现find方法。现在它的执行速度比map.find(key)慢。理论上它应该更快,因为矢量可以更好地利用CPU缓存,因为它的连续内存。我只是想知道这个实现是否有任何明显的错误,如果有任何方法我可以优化它?我不认为使用标准算法是一个选项,因为最接近的选项是lower_bound,这将导致我必须执行的检查的额外开销,以验证它是否找到任何东西。除此之外,lower_bound将要求我构造一对(加上我放在它周围的包装器)以将其作为我搜索的值,从而产生更多不必要的开销。

FlatMap<KEY, VALUE, COMPARATOR>::findImp(const key_type &key)
{
    typename VectorType::iterator lower = d_elements.begin();
    typename VectorType::iterator upper = d_elements.end();
    typename VectorType::iterator middle;
    while(lower < upper) {
        middle = lower + (upper-lower)/2;
        if(d_comparator(middle->data().first, key)){
            lower = middle;
            ++lower;
        } else if(d_comparator(key, middle->data().first)){
            upper = middle;
        } else {
            return middle;
        }
    }
    return d_elements.end();
}

请注意d_elements是成对的向量(对在包装器中):

vector<FlatMap_Element<KEY, VALUE> >  d_elements;

包装器本身只是将该对保存为数据成员,并使用不应影响搜索的赋值做一些魔术:

template <class KEY, class VALUE>
class FlatMap_Element {
    bsl::pair<const KEY, VALUE> d_data;
    ...
    pair<const KEY, VALUE>& data();
    pair<const KEY, VALUE> const& data() const;
};

我知道包装器的业务是而不是减速的来源,我已经在没有包装器的矢量或配对上测试了这个算法并且具有相同的性能。

任何有关调整的建议都表示赞赏。

3 个答案:

答案 0 :(得分:1)

您的版本循环使用m_comparator两次,而std::lower_bound只使用一个比较。

所以你可以使用类似的东西:(C ++ 03)

template <typename Key, typename Value, typename KeyComparator>
struct helper_comp
{
    bool operator (const std::pair<const Key, Value>& lhs, const Key& rhs) const {
        return comp(lhs.first, rhs);
    }
    KeyComparator comp;
};

template <typename Key, typename Value, typename KeyComparator>
typename std::vector<std::pair<const Key, Value>>::const_iterator
my_find(const std::vector<std::pair<const Key, Value>>& v, const Key& key)
{
    auto it = std::lower_bound(v.begin(), v.end(), key, helper_comp<Key, Value, KeyComparator>());
    if (it != v.end() && it->first == key) {
        return it;
    }
    return v.end();
}

或使用lambda而不是外部struct helper_comp(C ++ 11)(https://ideone.com/snZTRu

答案 1 :(得分:0)

我会在汇编语言级别单步执行它。 每条指令都应该拉动它的重量。 如果它看起来太复杂,那么这就是性能问题的原因。

请记住Jon Bentley多年前给出的二进制搜索示例。 如果表有1024个条目,它看起来像这样:

i = 0;
if (v >= a[i+512]) i += 512;
if (v >= a[i+256]) i += 256;
if (v >= a[i+128]) i += 128;
if (v >= a[i+ 64]) i +=  64;
if (v >= a[i+ 32]) i +=  32;
if (v >= a[i+ 16]) i +=  16;
if (v >= a[i+  8]) i +=   8;
if (v >= a[i+  4]) i +=   4;
if (v >= a[i+  2]) i +=   2;
if (v >= a[i+  1]) i +=   1;

Big-O不是一切。这仍然只是O(log n),但它围绕着天真的实现运行。

答案 2 :(得分:0)

您可以尝试三元搜索或四元搜索。第一批迭代基本上是进行随机内存访问。这有相当大的延迟。你可以在这种延迟中隐藏更多的随机内存访问,并减少它们。

这里存在一个潜在的缺陷,即高速缓存关联性可能会使高阶搜索的步幅为2的幂,表现不佳。

我还补充一点,你的额外比较器调用对你来说真的没什么用。你很幸运(在最后一次迭代之前找到你要找的东西)不到一半的时间。如果你修复二进制搜索,你只需要在最后一次迭代中检查它。