三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

为什么顶级C++项目都在用emhash?揭秘SIMD加速的底层原理

为什么顶级C++项目都在用emhash?揭秘SIMD加速的底层原理

为什么顶级C++项目都在用emhash?揭秘SIMD加速的底层原理

【免费下载链接】emhashFast and memory efficient c++ flat hash table/map/set项目地址: https://gitcode.com/gh_mirrors/em/emhash

emhash是一款Fast and memory efficient c++ flat hash table/map/set,它通过创新的SIMD加速技术,在保持内存高效的同时实现了卓越的性能,成为众多顶级C++项目的首选哈希表解决方案。

🚀 性能王者:emhash如何碾压传统哈希表?

在现代软件开发中,哈希表的性能直接影响整个系统的响应速度。emhash凭借其独特的SIMD加速技术,在各种操作中展现出令人惊叹的性能优势。

从上图的性能测试结果可以清晰地看到,emhash在int64_t类型的各种操作中,如insert_reserve、insert_no_reserve、find_hit等,都显著领先于其他哈希表实现。特别是在高负载场景下,emhash的表现尤为突出,这得益于其精心设计的SIMD加速机制。

💡 SIMD加速:让哈希表飞起来的核心技术

什么是SIMD?

SIMD(Single Instruction Multiple Data)即单指令多数据,是一种并行处理技术。它允许CPU在一条指令下同时处理多个数据元素,极大地提高了数据处理效率。在哈希表操作中,SIMD技术可以同时对多个哈希桶进行检查和比较,从而大幅提升查找和插入速度。

emhash中的SIMD实现原理

emhash的emilib库提供了四个哈希表实现(emihmap1、emihmap2、emihmap3、emihmap4),它们共同的设计理念是:SIMD-accelerated open addressing with metadata-byte filtering。与传统的链表桶方法不同,emilib使用类似Swiss-table的字节级元数据来实现向量化查找,在现代CPU上实现了极快的搜索性能。

1. 双哈希元数据编码

每个键通过一次哈希计算产生两个哈希值:

hash = hasher(key) H1 = hash & _mask // 主桶索引(与SIMD组对齐) H2 = (hash % 253) + 3 // 1字节元数据标签(范围[3..255])
  • H1决定探测的起始SIMD组
  • H2作为1字节标签存储在_states[]数组中,支持SIMD过滤查找,在进行键比较之前快速筛选潜在匹配项
2. SIMD组探测

桶被组织成simd_bytes大小的组(SSE2为16,AVX2为32,AVX-512为64)。一条SIMD指令可以同时比较16/32/64个元数据字节:

// 伪代码:在哈希表中查找键 vec = SIMD_LOAD(&_states[group_start]) // 加载16个元数据字节 mask = SIMD_MOVEMASK(SIMD_CMPEQ(vec, H2)) // 查找匹配的H2标签 for each set_bit in mask: if key == _pairs[bucket].first: return bucket // 验证完整键

这将键比较减少了约93.75%(在SSE2上,15/16的桶通过H2不匹配被过滤掉)。

从上图可以看出,即使在处理字符串这种复杂数据类型时,emhash的SIMD加速技术依然能带来显著的性能提升。无论是插入还是查找操作,emhash都表现出了优异的性能。

🛠️ emhash的SIMD优化策略

emhash提供了多种SIMD优化策略,以适应不同的应用场景和硬件环境。

1. 多版本SIMD实现

emilib库中的四个哈希表实现(emihmap1、emihmap2、emihmap3、emihmap4)采用了不同的SIMD优化策略:

  • emihmap1:基于组探测,探测深度内联存储在_states数组中,每个SIMD组的最后一个字节编码该组的最大探测偏移量。
  • emihmap2:采用PSL(Probe Sequence Length)方法,使用单独的_offset[]数组存储每个主桶的最大探测距离,允许每个SIMD组的所有16个字节都存储H2标签。
  • emihmap3:使用单个全局_max_probe_length变量代替每组偏移跟踪,并使用组级空检查(group_mask)来存储每个SIMD组的摘要字节。
  • emihmap4:实验性的Swiss-table变体,在Clang上插入速度快,但在混合工作负载下会出现墓碑积累问题。

2. 可配置的SIMD级别

emhash允许通过EMH_SIMD_LEVEL控制emilib使用的SIMD指令集(CMake选项):

cmake -DEMH_SIMD_LEVEL=AVX2 ...

不同的SIMD级别对应不同的性能表现:

  • SSE2:基础SIMD支持,适用于大多数x86 CPU
  • AVX2:更宽的SIMD支持,emilib2/3可以利用更宽的SIMD指令
  • NONE:禁用SIMD intrinsics,作为可移植性回退方案

上图展示了在Apple M1 CPU上,不同SIMD级别和哈希表实现的性能对比。可以看到,emhash的各种实现(emhash5、emhash7、emhash8)在不同的操作中都表现出了优异的性能。

📚 如何开始使用emhash?

1. 获取源代码

要开始使用emhash,首先需要克隆仓库:

git clone https://gitcode.com/gh_mirrors/em/emhash

2. 选择合适的哈希表实现

emhash提供了多种哈希表实现,适用于不同的场景:

  • emhash5:内联_pairs[],三向混合探测,默认负载因子0.80,适用于整数键的快速查找/删除,支持SBO
  • emhash6:内联_pairs[]+_bitmask,链表桶,默认负载因子0.80,适用于整数键的查找/删除,快速空扫描
  • emhash7:内联_pairs[]+_bitmask,链表桶,链修复,默认负载因子0.80,适用于高负载因子(0.80-0.999),插入密集型
  • emhash8:分离_index[]+ 密集_pairs[],链表桶,默认负载因子0.80,适用于复杂键/值,极快的迭代

对于SIMD加速的实现,可以选择emilib库中的:

#include "emilib/emihmap1.hpp" // namespace emilib #include "emilib/emihmap2.hpp" // namespace emilib2 #include "emilib/emihmap3.hpp" // namespace emilib3 #include "emilib/emihmap4.hpp" // namespace emilib4 emilib::HashMap<int, std::string> map1; emilib2::HashMap<int, std::string> map2; emilib3::HashMap<int, std::string> map3; emilib4::HashMap<int, std::string> map4;

3. 性能调优

emhash提供了多种性能调优选项,如设置负载因子、选择探测策略等。详细的性能调优指南可以参考性能调优文档。

🏆 为什么选择emhash?

emhash之所以成为顶级C++项目的首选,主要有以下几个原因:

  1. 卓越的性能:通过SIMD加速技术,emhash在各种操作中都表现出优异的性能,尤其是在高负载场景下。

  2. 内存高效:emhash采用了精心设计的内存布局,如分离索引+密集数组的结构,大大提高了内存利用率。

  3. 灵活的实现选择:提供多种哈希表实现,满足不同的应用场景和性能需求。

  4. 广泛的硬件支持:支持从SSE2到AVX-512的各种SIMD指令集,充分利用现代CPU的性能。

  5. 活跃的开发和维护:emhash拥有活跃的开发社区,不断进行性能优化和功能增强。

如果你正在寻找一个高性能、内存高效的C++哈希表实现,emhash无疑是一个理想的选择。它的SIMD加速技术将为你的项目带来显著的性能提升,让你的应用在处理大量数据时更加高效、响应更快。

无论是构建高性能服务器、处理大数据集,还是开发对性能要求苛刻的应用,emhash都能成为你的得力助手。立即尝试emhash,体验SIMD加速带来的极速哈希表性能吧!

【免费下载链接】emhashFast and memory efficient c++ flat hash table/map/set项目地址: https://gitcode.com/gh_mirrors/em/emhash

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

← 返回列表