为什么std :: tr1 :: unordered_map比自己开发的哈希映射慢?

时间:2011-04-16 05:20:43

标签: c++ hashmap unordered-map

我编写了一个基本程序,它通过将字符串插入字符串 - >整数哈希映射来获取字符串并计算唯一字符的发生率。

我使用std :: tr1 :: unordered_map作为存储,模板化自定义散列函数和自定义相等函数。密钥类型实际上是char*,而不是太慢std::string

然后我改变了相同的代码,使用一个非常非常简单的哈希表(实际上是一个由哈希索引的{key,value}结构数组),具有2的幂大小和线性探测冲突。该计划的速度提高了33%。

鉴于当我使用tr1 :: unordered_map时,我预先设定了哈希表,因此它永远不会增长,并且我使用完全相同的哈希和比较例程,tr1 :: unordered_map做什么会减慢它的速度与可以想象的最基本的哈希映射相比,有50%?

哈希地图类型的代码我在这里说的是“简单”:

typedef struct dataitem {
    char* item;
    size_t count;
} dataitem_t;

dataitem_t hashtable[HASHTABLE_SIZE] = {{NULL,0}}; // Start off with empty table

void insert(char* item) {
    size_t hash = generate_hash(item);
    size_t firsthash = hash;
    while (true) {
        hash &= HASHTABLE_SIZE_MASK; // Bitmasking effect is hash %= HASHTABLE_SIZE
        if (hashtable[hash].item == NULL) { // Free bucket
            hashtable[hash].item = item;
            hashtable[hash].count = 1;
            break;
        }
        if (strcmp(hashtable[hash].item, item) == 0) { // Not hash collision; same item
            hashtable[hash].count += 1;
            break;
        }
        hash++; // Hash collision.  Move to next bucket (linear probing)
        if (hash == firsthash) {
            // Table is full.  This does not happen because the presizing is correct.
            exit(1);
        }
    }
}

4 个答案:

答案 0 :(得分:11)

我希望延长@AProgrammer的答案。

您的哈希地图很简单,因为它是根据您的需求量身定制的。另一方面,std::tr1::unordered_map必须完成许多不同的任务,并且在所有情况下都做得很好。这需要在所有情况下采用均值 - 性能方法,因此在任何特定领域都不会出色。

哈希容器非常特殊,因为有很多方法可以实现它们,你选择了Open-Addressing,而标准强制实现了一个bucket方法。两者都有不同的权衡,这就是为什么标准实际上强制执行特定实现的一个原因:因此当从一个库切换到另一个库时,性能不会发生显着变化。简单地指定Big-O复杂性/摊销的复杂性在这里是不够的。

你说你指示了unordered_map关于决赛元素的数量,但是你是否更改了加载因子?在发生碰撞的情况下,链接是众所周知的“坏”(因为缺少内存局部性),并且使用较小的加载因子会有利于扩散元素。

最后,指出一个区别:调整哈希映射大小时会发生什么?通过使用链接,unordered_map不会移动内存中的元素:

  • 对它们的引用仍然有效(即使迭代器可能无效)
  • 如果是大型或复杂的对象,则不会调用复制构造函数

这与您的简单实现相反,后者会产生O(N)份副本(除非您使用线性重组来分散工作,但这绝对不简单)。

因此,似乎unordered_map的选择是平滑峰值,代价是平均插入速度较慢。

可以做一些事情:提供自定义分配器。通过为您的用例编写一个特定的分配器,并一次性分配所有内存(因为您知道将插入多少个对象,并且可以让分配器报告节点有多少内存)。然后以类似堆栈的方式分配节点(简单指针增加)。它应该(稍微)改善性能。

答案 1 :(得分:6)

你的“自制的哈希映射”根本不是哈希映射,它是一个侵入式哈希集。 这就是它更快的原因。就这么简单。

嗯,实际上,侵入式哈希集也不准确,但它是最接近的匹配。

答案 2 :(得分:4)

一般来说,比较不构建相同规格的组件的速度是不公平的。

如果不确切知道您测量的是什么 - 哪种操作混合了哪些载荷因子与现有/缺失数据的混合 - 很难解释差异的来源。

g ++的TR1通过链接解决冲突。这意味着动态分配。但这也可以在高负载水平下提供更好的性能。

答案 3 :(得分:1)

你的“本土”哈希映射比std::tr1::unordered_map更快 1 ,因为正如你自己所说,你自己开发的哈希映射是“simple”而它< em>不处理检查哈希表是否已满。可能还有许多你在操作之前没有检查过的东西。这可能是您的哈希映射比std::tr1::unordered_map快的原因。

此外,std::tr1::unordered_map的性能由实现定义,因此不同的实现将以不同的速度执行。您可以看到它的实现并与您的实施进行比较,因为这是您可以做的第一件事,我相信,这也将在一定程度上回答您的问题。

1。我只是假设你的说法是正确的,并在此基础上我说了上面的事情。