更多互联网精彩资讯、工作效率提升关注【飞鱼在浪屿】(日更新)
几位Boost作者开始了一个项目(
https://pdimov.github.io/articles/unordered_dev_plan.html),提高Boost.Unordered(std::unordered_map,以及multimap,set和multiset的替代品)的性能,提供基于开放寻址的更快,非标准替代方案。
该项目的第一个目标已完成Boost 1.80(将于2022年8月发布)。这里描述的boost::unordered_map中引入的技术创新,使其成为市场上std::unordered_map的最快实现。
hash冲突上,哈希表实现有两种办法:
最新的高性能哈希表使用开放式寻址,并利用其固有的更好的缓存位置和广泛可用的( SIMD ,单指令多数据)操作。但是,封闭寻址仍有一些功能优势,也是 std::unodered_map.的实现。
C++无序关联容器的标准化基于Matt Austern的2003年N1456论文。
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2003/n1456.html
过去,开放式寻址方法被认为不够成熟,因此封闭式寻址被视为安全的实现。尽管C++标准没有明确要求必须使用闭式寻址,但这种假设会让std::unordered_map:
因此,所有标准库实现都对其 std::unordered_map(以及相关容器)的内部结构使用某种形式的闭式寻址。
而困难是,有两个复杂性要求:
排除了封闭寻址的教科书实现(有关详细信息,请参阅N2023,https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2023.pdf)。为了解决这个问题,标准库以引入速度和内存损失的方式偏离了教科书布局:例如,这就是libstdc++-v3和libc++布局的样子:
为了提供恒定的迭代器增量,所有节点都链接在一起,这反过来又会强制对数据结构进行两次调整:
Visual Studio标准库(以前来自Dinkumware)使用完全不同的方法来规避这个问题,但一般的结果是,生成的数据结构在速度、内存消耗或两者方面的表现明显不如教科书布局。
Boost.Unordered使用的新数据布局可以追溯到教科书式的方法:
与其他标准库实现不同,节点不是跨容器链接的,而是仅在每个存储桶内链接的。这使得常数时间erase可以简单实现,但常数时间迭代器增量的问题却没有得到解决:为了实现它,引入了所谓的桶组(图的顶部)。每个存储桶组由一个 32/64 位存储桶占用掩码以及将非空存储桶组链接在一起next和prev指针组成。跨存储桶的迭代采用位掩码上的位操作操作的组合,以及通过next个指针进行组遍历的组合,这不仅是恒定时间,而且在执行时间和内存开销(每个存储桶 4 位)方面也非常轻量级。
插入或查找元素时,哈希表实现需要将元素哈希值映射到存储桶数组(或打开寻址情况下的插槽)中。通常有两种常用方法:
通过素数方法使用模,因为即使哈希值分布不均匀,它也能产生非常好的传播。然而,在现代CPU中,模是一种涉及整数除法的昂贵操作。另一方面,编译器知道如何更有效地通过常量执行模,因此一种可能的优化是保留一个指向函数fp的指针表:h→h mod p。这种技术用表跳转加上乘以常数的模运算取代了昂贵的模计算。
在Boost.Unordered 1.80中,更进一步优化。丹尼尔·勒迈尔等展示如何将 h mod p 计算为一个操作,涉及一些移位和乘以 p 以及一个预先计算的 c 值,作为 p 的倒数。已经使用这项工作实现了h→fastmod(h,p,c)的哈希映射(省略了一些细节)。注意,尽管 fastmod 通常比模数快一个常数,但大多数性能提升实际上来自这样一个事实,即消除了选择 fp 所需的表跳转,这阻止了代码内联。
针对 libstdc++-v3、libc++ 和 Visual Studio 标准库提供了一些针对插入、查找和擦除方案的 boost::unordered_map 基准测试结果。boost::unordered_map总体上更快,在某些情况下甚至更快。有三个因素促成了这种性能优势:
至于内存消耗,设 N 是具有 B 存储桶的容器中的元素数:64 位架构上不同实现的内存开销(即分配的内存减去严格用于元素本身的内存)为:
实现 | 内存开销(字节) |
libstdc++-v3 | 16 N + 8 B(哈希缓存) |
libc++ | 16 N + 8 B |
Visual Studio (Dinkumware) | 16 N + 16 B |
Boost.Unordered | 8 N + 8.5 B |
选择闭式寻址(在C++领域,这几乎是使用std::unordered_map实现的同义词)或选择面向速度的开放式寻址容器实际上不是一个明确的决定。列出了一些有利于一种或另一种选择的因素:
还有一些进一步的改进领域,需要boost::unordered_map在Boost 1.80之后进行调查:
与此同时,boost正在研究未来的boost::unordered_flat_map,这将超越std::unordered_map接口所施加的限制的顶级开放寻址容器。
留言与评论(共有 0 条评论) “” |