C++ 高效C++;开放寻址哈希表的API 无需构建和销毁临时值。您的目标是什么?演出内存使用情况?在任何情况下,您能否详细说明为什么std::unordered_map不够?目标是获得一个性能良好的哈希表。std::unordered_映射的一个问题是,它解决了与链表的冲突,这从性能角度来看是不好的。另外,尝试用std::unordered_映射编写“compute”。我还不明白为什么要将“插槽索引”公开给插入的调用者。因为哈希表的全部要点是,您可以通过“键”插入某些内容,然后通过“键”进行查找。如果您将向调用者返回一个插槽索引,调用者维护该索引以供后续查找,那么您的实现也可以是一个带有增量索引的平面数组。但我认为您真正想要的是公开三种方法:void Insert(k,v)、void Remove(k)、和v Lookup(k)。此外,几年前我编写了自己的哈希表类,目标是在构建实例后永远不分配内存。因为在服务器端,内存分配会影响性能。欢迎您参考或使用。使用selbie:如果您想编写一个只对键进行一次散列的“compute”函数,则需要返回i。显然,哈希表的常规用法是稍后请求值并通过哈希函数执行。感谢您在github上的参考。

C++ 高效C++;开放寻址哈希表的API 无需构建和销毁临时值。您的目标是什么?演出内存使用情况?在任何情况下,您能否详细说明为什么std::unordered_map不够?目标是获得一个性能良好的哈希表。std::unordered_映射的一个问题是,它解决了与链表的冲突,这从性能角度来看是不好的。另外,尝试用std::unordered_映射编写“compute”。我还不明白为什么要将“插槽索引”公开给插入的调用者。因为哈希表的全部要点是,您可以通过“键”插入某些内容,然后通过“键”进行查找。如果您将向调用者返回一个插槽索引,调用者维护该索引以供后续查找,那么您的实现也可以是一个带有增量索引的平面数组。但我认为您真正想要的是公开三种方法:void Insert(k,v)、void Remove(k)、和v Lookup(k)。此外,几年前我编写了自己的哈希表类,目标是在构建实例后永远不分配内存。因为在服务器端,内存分配会影响性能。欢迎您参考或使用。使用selbie:如果您想编写一个只对键进行一次散列的“compute”函数,则需要返回i。显然,哈希表的常规用法是稍后请求值并通过哈希函数执行。感谢您在github上的参考。,c++,api,hash,hashtable,unordered-map,C++,Api,Hash,Hashtable,Unordered Map,类型键有两个特殊元素:空和墓碑。第一个用于说明插槽是空闲的,第二个用于说明插槽已被使用,但后来被删除(这是探测所必需的) 主要的挑战是为这种结构设计一个高效的API。我想尽量减少散列密钥和查找插槽的次数 到目前为止,我发现以下API不安全: // Return the slot index if the key is in the table // or a slot index where I can construct the KeyValue // if the key is not he

类型键有两个特殊元素:空和墓碑。第一个用于说明插槽是空闲的,第二个用于说明插槽已被使用,但后来被删除(这是探测所必需的)

主要的挑战是为这种结构设计一个高效的API。我想尽量减少散列密钥和查找插槽的次数

到目前为止,我发现以下API不安全:

// Return the slot index if the key is in the table
// or a slot index where I can construct the KeyValue
// if the key is not here (or -1 if there is no slot
// available and the insertion of such a key would
// need to grow the hash table)
int search(const K& key)

// Tells if the slot is empy (or if i == -1)
bool empty(int i)

// Construct a KeyValue in the HashTable in the slot i
// which has been found by search. The i might be changed
// if the table needs to grow.
void insert(const K& key, const V& value, int& i)

// Accessors for a slot i which is occupied
const V& value(int i);
请注意,该表还具有经典功能,例如

void insert(const K& key, const V& value)
它计算散列,搜索插槽,并将该对插入表中。但是我想在这里集中讨论允许程序员非常有效地使用表的接口

例如,这里有一个函数,如果f(key)的值从未计算过,则返回它的值;如果已经计算过,则从哈希表返回它的值

const V& compute(const K& key, HashTable<K, V>& table) {
    int i = table.search(key);
    if (table.empty(i)) {
        table.insert(key, f(key), i);
    }
    return table.value(i);
 }
const V&compute(const K&key、HashTable&table){
int i=表搜索(键);
如果(表空(i)){
表.插入(键,f(键),i);
}
返回表.值(i);
}
我并不完全喜欢这个哈希表的接口,因为方法insert(const K&,const V&,int&)对我来说确实不安全

你对更好的API有什么建议吗


PS:Chandler Carruth的演讲“算法的性能,数据结构的效率”,特别是在23:50之后,对于理解std::unordered_map的问题真的很好

我认为你应该尝试一下超快的散列函数

看看这个。我引用它的描述:“xxHash是一种极快的散列算法,以RAM速度限制运行。它成功地完成了SMHasher测试套件,该套件评估散列函数的冲突、分散和随机性。代码具有高度可移植性,并且散列在所有平台上都是相同的(little/big endian)。”

另外,此线程来自此站点上的另一个问题:。众所周知,FNV、詹金斯和哈什都很快

看看这篇帖子,我在这里发布了与我相同的答案,还有其他答案:

您可以制作一个
get\u或\u insert
函数模板,该模板接受任意函子而不是值。然后可以使用lambda调用:

template <class K, class V>
class HashTable {
private:
    int search(const K& key);
    bool empty(int i);
    void insert(const K& key, const V& value, int& i);
    const V& value(int i);

public:    
    template <class F>
    const V& get_or_insert(const K& key, F&& f) {
        int i = search(key);
        if (empty(i)) {
            insert(key, f(), i);
        }
        return value(i);
    }
};

double expensive_computation(int key);

void foo() {
    HashTable<int, double> ht;
    int key = 42;
    double value = ht.get_or_insert(key, [key]{ return expensive_computation(key); });
}
模板
类哈希表{
私人:
整数搜索(常数K和键);
bool-empty(inti);
无效插入(常数K和键、常数V和值、int和i);
常数V和值(int i);
公众:
模板
常数V&get\u或插入(常数K&key、F&F){
int i=搜索(键);
if(空(i)){
插入(键,f(),i);
}
返回值(i);
}
};
双昂贵_计算(int键);
void foo(){
哈希表ht;
int键=42;
double value=ht.get_或_insert(key,[key]{返回昂贵的_计算(key);});
}
如果
get\u或\u insert
是内联的,并且您不需要捕获很多,那么这应该与您显示的代码一样高效。如果有疑问,请使用Godbolt的编译器资源管理器或类似工具比较生成的代码。(如果它没有内联,它仍然可以,除非你必须捕获很多不同的变量。假设你捕获了smart,也就是说,如果复制成本很高的话,通过引用捕获内容。)

注释:在C++中传递函子的“标准”方式似乎是按值计算的,但我认为通过引用更为合理。如果所有内容都内联,那么它应该不会有什么区别(在我用GCC、Clang和MSVC检查的示例中没有),如果

get\u或\u insert
调用没有内联,那么如果functor捕获了1或2个以上的小而琐碎的变量,您真的不想复制它

我可以想象使用通用引用的唯一缺点是,如果有一个函子在
operator()
中改变其状态。对于这样的函子,至少在我能想到的例子中,我希望原始的函子被变异。所以在我看来,这并不是一个真正的缺点



或者是上面的一个修改版本,如果创建/分配/销毁值很昂贵(如
std::string
):使用插槽中的值的可变引用调用函子。然后,函子可以直接分配/变异哈希表中的值->无需构建和销毁临时值。

您的目标是什么?演出内存使用情况?在任何情况下,您能否详细说明为什么
std::unordered_map
不够?目标是获得一个性能良好的哈希表。std::unordered_映射的一个问题是,它解决了与链表的冲突,这从性能角度来看是不好的。另外,尝试用std::unordered_映射编写“compute”。我还不明白为什么要将“插槽索引”公开给插入的调用者。因为哈希表的全部要点是,您可以通过“键”插入某些内容,然后通过“键”进行查找。如果您将向调用者返回一个插槽索引,调用者维护该索引以供后续查找,那么您的实现也可以是一个带有增量索引的平面数组。但我认为您真正想要的是公开三种方法:
void Insert(k,v)
void Remove(k)
、和
v Lookup(k)
。此外,几年前我编写了自己的哈希表类,目标是在构建实例后永远不分配内存。因为在服务器端,内存分配会影响性能。欢迎您参考或使用。使用selbie:如果您想编写一个只对键进行一次散列的“compute”函数,则需要返回i。显然,哈希表的常规用法是稍后请求值并通过哈希函数执行。感谢您在github上的参考。