尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

mold 内置 TBB 深度解析:concurrent_unordered_map 的 Hash Policy 设计与 Rehash 机制

mold 内置 TBB 深度解析:concurrent_unordered_map 的 Hash Policy 设计与 Rehash 机制 mold 内置 TBB 深度解析concurrent_unordered_map 的 Hash Policy 设计与 Rehash 机制【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold本文基于 Intel oneAPI Threading Building BlocksoneTBB以第三方库形式内置于 mold 仓库third-party/tbb/中的官方规范文档hash_policy.rst完整讲解concurrent_unordered_map的 Hash policy 接口族load_factor()、max_load_factor()、rehash()与reserve()的语义并结合 实现源码 揭示“负载因子超限自动扩容”的无锁细节。读完你能掌握负载因子的取值含义、桶数量只增不减的设计以及在高并发批量插入前如何正确预分配容量。一、Hash Policy 在并发无序容器中的职责规范文档 hash_policy.rst 对该机制的总述只有三行却定义了整个容量管理的行为契约Hash policy ofconcurrent_unordered_mapmanages the number of buckets in the container and the allowed maximum number of elements per bucket (load factor). If the maximum load factor is exceeded, the container can automatically increase the number of buckets.即 Hash policy 管理两件事桶的数量bucket count决定哈希槽位总数直接影响查找平均比较次数与内存占用负载因子上限maximum load factor每个桶允许存放的最大元素数一旦超限容器可以can自动增加桶数量。文档中特意使用 can 而非 will这为并发实现留出了自由裁量空间——后文源码分析会说明自动扩容实际上发生在“插入成功之后”这一具体时机。该接口族定义在concurrent_unordered_map以及结构同源的concurrent_unordered_set上。公开头文件为 concurrent_unordered_map.h而上述五个方法的实际实现集中在基类 concurrent_unordered_base 中。以下逐项对照规范与实现。二、Load Factor 接口族2.1load_factor()当前平均负载float load_factor() const;Returns: the average number of elements per bucket即size()/unsafe_bucket_count()规范原文。源码实现_concurrent_unordered_base.h#L657-L659float load_factor() const { return float(size() / float(my_bucket_count.load(std::memory_order_acquire))); }两个值得注意的细节分母在实现中直接读取原子变量my_bucket_countacquire序语义上对应规范里unsafe_bucket_count()的定义桶数量在整个容器生命周期中单调不减见第四节因此这一读取本身是稳定的。分子size()是近似值。规范同目录下的 size_and_capacity.rst 明确写道“The result may differ from the actual container size in case of pending concurrent insertions.” 因此在多线程插入期间load_factor()返回的是快照近似值而非严格瞬时值——这是并发容器与 STL 容器在语义上的本质差异。2.2max_load_factor()读取与设置float max_load_factor() const; // Returns: the maximum number of elements per bucket void max_load_factor( float ml ); // Sets the maximum number of elements per bucket to ml.实现位于 _concurrent_unordered_base.h#L661-L668float max_load_factor() const { return my_max_load_factor; } void max_load_factor( float mlf ) { if (mlf ! mlf || mlf 0) { tbb::detail::throw_exception(exception_id::invalid_load_factor); } my_max_load_factor mlf; } // TODO: unsafe?从源码结构看可以确认两点实现事实参数校验mlf ! mlf是 IEEE 754 的 NaN 判断加上mlf 0校验非法值会抛出invalid_load_factor异常而不是静默回退为默认值非原子性my_max_load_factor是普通float成员见 成员声明赋值不加锁源码中的// TODO: unsafe?注释表明作者也意识到并发读写该值并非常规安全操作。可以推断max_load_factor()的设置应在容器开始并发使用之前完成例如初始化阶段运行中热路径读取该值只用于“是否扩容”的判断即便读到稍旧的值也仅影响扩容时机的早晚不破坏正确性。三、Manual Rehashingrehash()与reserve()3.1rehash( size_type n )规范语义Sets the number of buckets tonand rehashes the container.实现_concurrent_unordered_base.h#L670-L676void rehash( size_type bucket_count ) { size_type current_bucket_count my_bucket_count.load(std::memory_order_acquire); if (current_bucket_count bucket_count) { my_bucket_count.compare_exchange_strong(current_bucket_count, round_up_to_power_of_two(bucket_count)); } }与 STL 版本的std::unordered_map::rehash相比此实现有两处关键差异只允许增长不允许收缩。if (current_bucket_count bucket_count)使传入更小的n被静默忽略。收缩需要重新哈希迁移全部节点并协调多线程读者代价极高规范用词“rehashes the container”在此实际退化为“提升桶数量并对后续插入重新分布”。桶数量向上取整为 2 的幂。round_up_to_power_of_twoL235-L237static constexpr size_type round_up_to_power_of_two( size_type bucket_count ) { return size_type(1) size_type(tbb::detail::log2(uintptr_t(bucket_count 0 ? 1 : bucket_count) * 2 - 1)); }取 2 的幂使得桶下标计算hash_key % my_bucket_count见 prepare_bucket在底层可退化为位与操作同时保证扩容永远是“乘以 2”的整数倍关系。扩容通过单次compare_exchange_strong完成并发线程各自尝试翻倍CAS 失败说明别线程已将其推高到足够值无需重试。3.2reserve( size_type n )规范语义Sets the number of buckets to the value that is needed to storenelements即“为存放 n 个元素所必需的桶数量”。实现_concurrent_unordered_base.h#L678-L693void reserve( size_type elements_count ) { size_type current_bucket_count my_bucket_count.load(std::memory_order_acquire); size_type necessary_bucket_count current_bucket_count; // max_load_factor() is currently unsafe, so we can assume that my_max_load_factor // would not be changed during the calculation // TODO: Log2 seems useful here while (necessary_bucket_count * max_load_factor() elements_count) { necessary_bucket_count 1; } while (!my_bucket_count.compare_exchange_strong(current_bucket_count, necessary_bucket_count)) { if (current_bucket_count necessary_bucket_count) break; } }逻辑可以拆解为两步计算目标桶数从当前桶数起步按“桶数 × 负载因子 ≥ 元素数”为条件每次翻倍得到能满足n个元素且不超过max_load_factor()的最小 2 的幂。注释里作者自问“Log2 seems useful here”说明当前线性倍增循环在极端值下并非最优雅但正确性不受影响CAS 循环发布失败后若发现别的线程已经把桶数推得更高current_bucket_count necessary_bucket_count直接退出——“只增不减”原则下这已经满足要求。典型用法是在多线程批量insert之前单线程调用一次reserve(预估规模)避免插入过程中反复触发自动扩容带来的桶数频繁翻倍与哈希分布抖动#include tbb/concurrent_unordered_map.h tbb::concurrent_unordered_mapint, std::string table; table.max_load_factor(2.0f); // 降低平均每桶元素数换取更短的查找链 table.reserve(1 20); // 预置桶容量容纳约 2^20 × 2.0 个元素 // 此后进入多线程并发 insert / find 阶段四、自动扩容max_load_factor如何触发桶数增长规范第一段的“超过最大负载因子后可自动增桶”在源码中对应插入路径末尾的 adjust_table_sizevoid adjust_table_size( size_type total_elements, size_type current_size ) { // Grow the table by a factor of 2 if possible and needed if ( (float(total_elements) / float(current_size)) my_max_load_factor ) { // Double the size of the hash only if size hash not changed in between loads my_bucket_count.compare_exchange_strong(current_size, 2u * current_size); } }调用点在插入成功、my_size.fetch_add(1)之后L1014-L1016先原子递增元素计数再检查“总元素数 / 当前桶数”是否越过my_max_load_factor越过则以 CAS 将桶数翻倍。由此得到完整的行为模型触发时机是惰性的扩容检查只跟随某一次具体插入发生不做全表再平衡——已存在的节点不会被迁移新桶按需惰性初始化get_bucket中桶为空时init_bucket见 L1088-L1093。因此“rehash”在并发语义下是分布密度层面的再哈希而非 STL 那种全量节点重挂增长策略是倍增每次恰 ×2且因桶数恒为 2 的幂% bucket_count与位运算等价默认参数无参构造使用initial_bucket_count 8initial_max_load_factor 4L787-L788static constexpr size_type initial_bucket_count 8; static constexpr float initial_max_load_factor 4; // TODO: consider 1?源码中// TODO: consider 1?的注释表明默认 4.0 是偏保守偏向内存效率、容忍更长查找链的取舍且尚未最终定论reset()路径也会将两者恢复为初始值L1368-L1369。如果场景以读多写少、延迟敏感为主显式调低max_load_factor()是文档语义允许的直接手段。五、与 STL 语义对照及工程要点接口STLstd::unordered_map语义oneTBB 实现事实源码证据load_factor()size() / bucket_count()的精确值快照近似值size 可能滞后于并发插入规范 size_and_capacity.rstmax_load_factor(mlf)非法值行为未充分约束NaN / 负值抛invalid_load_factor异常存储为非原子floatL663-L668rehash(n)桶数可增可减仅当n更大时生效且向上取整到 2 的幂不迁移已有节点L670-L676reserve(n)桶数 ≥ceil(n / max_load_factor)以当前桶数为起点倍增直至桶数 × mlf ≥ nCAS 发布L678-L693工程上使用 Hash policy 的三条实践建议容量规划前置能预估规模时在并发开始前max_load_factor()reserve()一次性配置避免运行期倍增扩容造成哈希分布阶段性抖动不要指望rehash收缩删除大量元素后想“瘦身”该 API 无能为力需新建容器并迁移把max_load_factor()视为一次性配置源码中该成员非原子且带有// TODO: unsafe?标注运行中多线程同时改写不属于被保证的使用方式。六、延伸阅读路径本篇对应规范hash_policy.rstCC-BY-4.0Intel 2019-2020桶遍历接口unsafe_bucket_count所在的章节bucket_interface.rst构造与桶数初值约定construction_destruction_copying.rst安全/非安全插入与自动扩容的触发路径safe_modifiers.rst、unsafe_modifiers.rst接口实现主体_concurrent_unordered_base.h、公开容器头 concurrent_unordered_map.h以上结论均以当前仓库中third-party/tbb/目录的实际文档与源码为准oneTBB 作为 mold 的第三方依赖被内嵌若上游 TBB 版本更新initial_max_load_factor等默认值与扩容细节可能随之变化。【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表