C++ 如何优化向量的二进制搜索?

C++ 如何优化向量的二进制搜索?,c++,performance,algorithm,vector,C++,Performance,Algorithm,Vector,我试图在键值对的排序向量上实现find方法。现在它的执行速度比map.find(键)慢。理论上它应该更快,因为向量可以更好地利用CPU缓存,因为它的内存是连续的。我只是想知道这个实现是否有明显的错误,是否有任何方法可以优化它?我不认为在这里使用标准算法是一个选项,因为最接近的选项是下限,这将导致额外的检查开销,我将不得不执行这些检查来验证它是否发现了任何东西。除此之外,下限将要求我构造一对(加上我在其周围放置的包装器),将其作为我正在搜索的值,从而产生更多不必要的开销 FlatMap<KE

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

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();
}
包装器本身只是将该对作为数据成员保存,并对不应影响搜索的赋值进行一些处理:

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;
};
模板
类平面映射元素{
bsl::对d_数据;
...
pair&data();
pair const&data()const;
};
我知道,使用包装器的业务不是减速的根源,因为我已经在没有包装器的向量或向量对上测试了该算法,并且具有相同的性能


任何关于调整的建议都将不胜感激。

我将在汇编语言级别单步执行。 每一条指令都应该发挥作用。 如果它看起来太复杂,那么这就是为什么它是一个性能问题

请记住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;

大O不是一切。这仍然只是O(logn),但它围绕着朴素的实现运行。

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

这里一个潜在的陷阱是缓存关联性可能会使跨步为二次幂的高阶搜索表现不佳


我还想补充一点,您额外的comparator调用实际上对您帮助不大。在不到一半的时间里,你会很幸运(在上一次迭代之前找到你想要的东西)。如果您修复了二进制搜索,您只需要在最后一次迭代中检查它。

您的版本使用两次
m\u comparator
循环结果,而
std::lower\u bound
只使用一次比较

因此,您可以使用如下内容:(C++03)

模板
结构辅助程序组件
{
布尔运算符(常数标准::成对和左侧、常数键和右侧)常数{
返回组件(左前,右后);
}
键比较器;
};
模板
typename std::vector::const_迭代器
my_find(常数标准::向量和向量,常数键和键)
{
auto it=std::下限(v.begin(),v.end(),key,helper\u comp());
if(it!=v.end()&&it->first==key){
归还它;
}
返回v.end();
}

或者使用lambda代替外部
struct helper_comp
(C++11)(

一个明显的改进是只使用d_comparator一次。理论上,调用给定两个值的函数返回-1、0或1,并使用该信息重新指定边界。显然,比较器的工作方式不同,因为如果一个参数大于另一个参数,它将返回
true
,否则返回
false
。我会考虑切换到一个只能被调用一次的比较器,看看它是否有什么不同。你拒绝<代码> STD::LooRixLime进行相等的最终测试,而你的版本在每次迭代中进行检查…使用<代码>中间=(下+上)/ 2;<代码>而不是
中间=下+(上下)/2也许它会减少一点运算量。“下限在每次迭代中都会进行完全相同的检查”。如果您已经通过查看源代码验证了这一点,那么不要认为没有基准测试的下限太慢。事实上,在没有基准测试/评测的情况下,不要假设任何事情。每当你开始猜测性能时,你很有可能会出错。当我尝试时,你似乎不会绕圈子。请记住,二进制搜索中的大部分时间都花在等待数据从内存运回,而不是让CPU做一些有效率的事情。@tmyklebu:您是在汇编语言级别单步执行的吗?在汇编语言级别,每行应该有大约4-5条指令?好的,由于严重的内存延迟,它可能会慢一些。你为什么在这里谈论计数指令?这个技巧生成了一段漂亮的代码。但是,这段漂亮的代码运行速度并没有明显快于循环版本,因为执行时间主要是缓存未命中,而不是指令执行。(实际上每行大约有三条指令。)“几年前”当指令和内存速度更为均衡时,这可能是值得的。虽然不是很有用,因为您需要一个2次方的常量元素数。
helper\u comp
是否需要能够处理对/键和键/对比较的两种情况?不确定,我认为msvc需要在调试中检查容器是否排序。这将是对/对比较。我认为为了安全起见,您应该提供所有3个,即使您的特定编译器实现没有使用所有3个。
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;
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();
}