我应该缓存用作哈希键的STL字符串的哈希码吗?

时间:2010-02-03 19:51:52

标签: c++ stl caching hash hashtable

我正在对我开发的软件进行一些性能分析,并且我发现URL的全局字典上的查找大约占应用程序“加载”阶段时间的10%。该字典实现为C ++ STL std :: map,它具有O(lg n)查找。我要将它移动到hash_map,它具有大致固定的时间查找。 stl字符串类没有哈希代码属性,它肯定不会缓存哈希代码。这意味着每次查找都需要重新生成哈希码。

我怀疑缓存哈希码是值得的。这将意味着更改许多代码行以使用具有缓存哈希代码属性的新字符串类。鉴于当前的实现在每次查找时都会记录(n)完整的字符串比较,我认为将它减少到每次查找基本上一次字符串遍历(通过哈希函数)是一个很大的胜利。

有没有人有缓存字符串哈希码的经验?有没有证明值得付出努力?

5 个答案:

答案 0 :(得分:3)

一句警告。

虽然哈希映射可以具有固定时间查找,但它也可能最终具有O(N)查找。虽然这不是常见的情况,但确实会发生。

因此,虽然您总是需要在地图中支付O(log N)时间,但您也可以保证它不会更糟。

答案 1 :(得分:3)

我没有缓存哈希码的经验,但我最近做了一些工作,将std::map转换为std::tr1::unordered_map。想到两个想法。首先,尝试首先分析相对简单的更改,因为有时会使事情变得更糟,具体取决于您的代码正在执行的操作。在您尝试进一步优化之前,它可能会为您提供足够的加速。其次,您的探查器对初始化时间的其他90%的评价是什么?即使您将全局字典内容优化到0次,您最多也可以将性能提高10%。

答案 2 :(得分:3)

当您将哈希地图与地图进行比较时,还可以尝试使用Trie或相关数据结构(无论您可以从现成的任何地方获得):

Trie implementation

不幸的是,您可能会花费大量时间来担心缓存友好性。在这方面,Trie类似于您已经拥有的树,并且哈希映射可能比天真分配的树更好。

另外,我对这个问题感到有些困惑。如果您多次查找相同的字符串 object ,那么缓存其哈希值是值得的,您是否应该缓存查找结果?哈希表的重点是不同的对象,它们的值相等,哈希值相同。如果您不是从包含相同字符的不同字符串中多次计算相同的哈希值,那么您的哈希表可能无法正常工作。

如果你的意思是缓存哈希表中已经存在的键的值,那就是哈希表。

答案 3 :(得分:2)

您当然需要剖析以检查结果。更改为哈希映射,然后查看大部分时间花在哪里。除非你左右摇晃键,否则我怀疑你的大部分时间都会花在那里。散列是一种快速操作,否则散列映射没有优于有序容器的优势。

编译器本身将知道字符串是否未被更改,并且可能会为您缓存结果(在同一范围内)。也就是说,你想继承std::string; STL课程不是为此而制作的。

相反,制作一个std::pair并传递它:

std::pair<const std::string, const size_t> string_hash_pair;

然后你需要重载(在这里通过Boost,而不是TR1;我不知道它们有多相似)hash_value函数用于你的类型,在与定义对相同的命名空间中:

size_t hash_value(const string_hash_pair& pPair)
{
    return pPair.second; // don't actually hash
}

就是这样。请注意,在对中,stringsize_t都是不可变的。这是因为如果string发生更改,则表示您的哈希错误。所以我们将其设为const,我们也可以制作哈希const

你需要一个辅助功能:

string_hash_pair make_string_hash(const std::string& pStr)
{
    return std::make_pair(pStr, boost::hash_value(pStr));
}

现在,如果您要使用字符串进行查找,只需从中选择一对,就可以进行恒定时间散列。

那就是说,我真的怀疑这项工作是否必要。散列函数通常是微不足道的。此外,不会自己创建。使用预先存在的经过实践检验的哈希值;制作糟糕的哈希非常容易。

答案 4 :(得分:1)

我在字典中对4k - 64k字符串的集合和无序集进行了一些比较。

我发现std :: set和unordered_set在我的情况下具有大约相同的运行时,因为hash_value计算花费了无序集的运行时间的大约80%。

它使查找节省相形见绌(对std :: string FWIW使用了boost :: hash_value)

YMMV,对于一般情况,我会说配置文件,不要被不考虑cpu体系结构等的理论缩放所欺骗。由于哈希成本,哈希映射可能会运行得更慢,并且会占用更多内存

我的用例是我长时间存储信息并定期更新,不会更改information_id哈希值,但可能会更改其他内容。

然后将每个更新传递给我的查找功能,以决定是否需要在外部通知此更新。

要通知的information_id列表位于此查找中,并且可以独立于信息进行更改。

通过缓存information_id的哈希值,可能会在信息的生命周期内重复使用10次。

我的两行更改缓存哈希改进的unordered_set的运行时由&gt; X8

测试集:在MSVC 2012更新4上进行测试 对于4k和64k字典,1M条目每次查找10次: 除了10次检查之外的所有检查都是4k,未命中率达到64k(更多的aardvarks :))

设置:1373 ms / 1938 ms

multiset:1376 ms / 1913 ms

unordered_set initial 64k bucket / 0.5 load factor:168 ms / 362 ms

unordered_set 4k / 1.0:331 ms / 452 ms

c.f预缓存

unordered_set 64k / 0.5:1519 ms / 1881 ms

FWIW与MinGW 4.9.1 -O3相同的事情

设置:2003 ms / 2490 ms

multiset:1978 ms / 2306 ms

unordered_set initial 64k bucket / 0.5 load factor:140 ms / 605 ms

unordered_set 4k / 1.0:318 ms / 683 ms

c.f预缓存

unordered_set 64k / 0.5:1619 ms / 2455 ms