Enhance implementation of ShardedMap class#59
Conversation
|
sharded_map.h line 9 — TBB header portability
|
|
|
|
|
|
|
|
|
|
CMakeLists.txt lines 11–12 — CI failure / unconditional TBB
|
…mplementations of sharded map.
…count in member field for required references.
I opted to make this case unrecoverable by returning false instead. I think the user should be fully aware what value is being used. |
config::configs has hidden visibility (-fvisibility=hidden) and is absent from the DSO's dynamic symbol table. Any external consumer (e.g. test binary) that references it gets an unresolved weak reference; under LTO the compiler folds this to 0, giving num_shards_=0. That triggers UB in GetShard (__builtin_ctz(0)) and an out-of-bounds array access → segfault in pthread_rwlock_wrlock. Fix: replace configs[MAP_SHARDS] with GetConfig(MAP_SHARDS) in the ShardedMap constructor. GetConfig is exported and resolves correctly in all consumers. Also add missing #include <mutex> for std::unique_lock. Also add map_shards = 64 to default_config so LoadConfigFile does not silently leave the key at its hard-coded default.
There was a problem hiding this comment.
Pull request overview
Note
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
This PR enhances ShardedMap to improve concurrent performance by optionally switching to oneTBB’s concurrent hash map, adding configurable sharding, and improving shard index distribution via Fibonacci hashing.
Changes:
- Add a oneTBB-backed concurrent map implementation enabled under QAT/IAA/IGZIP builds.
- Make shard count configurable (
map_shards) and align per-shard storage to 64 bytes. - Replace modulo-based shard selection with Fibonacci hashing and validate
map_shardsis a power of two.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| sharded_map.h | Adds configurable sharding, Fibonacci hashing, and switches between stdlib vs oneTBB concurrent map implementations. |
| logging.h | Removes using namespace config; to avoid namespace pollution in a header. |
| config/default_config | Adds default map_shards = 64. |
| config/config.h | Adds MAP_SHARDS config enum value. |
| config/config.cpp | Initializes and validates map_shards (power-of-two check), and logs on invalid values. |
| common.cmake | Enables TBB linkage and USE_TBB define when QAT/IAA/IGZIP are enabled. |
| README.md | Documents oneTBB and the new map_shards configuration option. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Number of map shards must be a power of 2 | ||
| auto GetShard(const Key& key) const -> unsigned int { | ||
| const auto shard_bits = __builtin_ctz(num_shards_); | ||
| const auto hash = static_cast<uint64_t>(std::hash<Key>{}(key)); | ||
| const auto dist_hash = hash * FIBONACCI_MULTIPLIER; | ||
| const auto top_bits = 64 - shard_bits; | ||
| const auto shard_index = static_cast<unsigned int>(dist_hash >> top_bits); | ||
| return shard_index; | ||
| } |
| // Fibonacci hash constant: value is (2^64 / phi) to spread bits uniformly, | ||
| // where phi is golden ratio and 64 is machine word length - constant is | ||
| // multiplied by hash and then shifted right by top bits to get shard index | ||
| static constexpr std::size_t FIBONACCI_MULTIPLIER = 11400714819323198485ull; |
| #include <filesystem> | ||
| #include <string> | ||
|
|
||
| #include "../logging.h" |
| Log(LogLevel::LOG_ERROR, "config::LoadConfigFile Line ", __LINE__, | ||
| ", map_shards must be a power of 2, ignoring value ", | ||
| configs[MAP_SHARDS], ", will not proceed further\n"); | ||
| return false; |
| typename MapType::accessor acc; | ||
| if (!pd_map_arr_[shard].map.find(acc, key)) { | ||
| // Key doesn't exist - add entry first | ||
| pd_map_arr_[shard].map.insert(acc, key); | ||
| } | ||
|
|
||
| // Set or update value associated with key | ||
| acc->second = std::move(value); |
| In general, the shim is able to offload zlib calls that complete compression/decompression of one deflate stream in one call. "Streaming" compression/decompression (where compression/decompression is done incrementally) are not currently supported. If the shim is not able to offload a job to an accelerator, it will fall back to zlib, ensuring the application still works correctly. | ||
|
|
||
| The shim has only been tested on Linux. | ||
| The shim has only been tested on Linux. Additionally, the [Intel® oneTBB](https://www.intel.com/content/www/us/en/developer/tools/oneapi/onetbb.html) performance library is required and can be acquired by installing the [Intel® oneAPI Base Toolkit](https://www.intel.com/content/www/us/en/developer/tools/oneapi/oneapi-toolkit-download.html). |
- Raise map_shards minimum from 1 to 2; eliminates shift-by-64 UB in GetShard when num_shards_==1 (__builtin_ctz(1)=0, dist_hash>>64 is UB) - Change FIBONACCI_MULTIPLIER to uint64_t; std::size_t truncates the 64-bit constant on 32-bit platforms - Add optional validator param to ConfigReader::GetValue; use it for map_shards power-of-2 check so validation is consistent with other config keys (log error, keep default, continue) rather than aborting config load - Remove now-unused logging.h include from config/config.cpp - Simplify TBB Set: replace find+insert with single insert; accessor is valid either way so the find was redundant - README: clarify TBB is required only with hardware acceleration flags
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (1)
sharded_map.h:109
- GetShard() assumes num_shards_ is a non-zero power of two >= 2. If MAP_SHARDS is ever set to 0/1 (e.g., via SetConfig() in tests) then __builtin_ctz(0) and shifting by 64 are undefined behavior; if it is set to a non-power-of-two, the computed shard_index can exceed the allocated array bounds.
auto GetShard(const Key& key) const -> unsigned int {
const auto shard_bits = __builtin_ctz(num_shards_);
const auto hash = static_cast<uint64_t>(std::hash<Key>{}(key));
const auto dist_hash = hash * FIBONACCI_MULTIPLIER;
const auto top_bits = 64 - shard_bits;
| // Use GetConfig() rather than configs[] directly: configs has hidden | ||
| // visibility and is not accessible from external translation units under LTO. | ||
| ShardedMap() : num_shards_(config::GetConfig(config::MAP_SHARDS)) { | ||
| pd_map_arr_ = std::make_unique<PaddedMapType[]>(num_shards_); | ||
| } |
| trySetConfig(MAP_SHARDS, 65536, 2, | ||
| [](uint32_t v) { return (v & (v - 1)) == 0; }); |
| map_shards | ||
| - Values: 1-65536. Default 64 | ||
| - Sets the size of every thread safe concurrent hash map array used by different streams. | ||
| - It must be a power of two, so Fibonacci hashing can be used to calculate uniformly distributed shard indexes. |
LTO causes global constructors to run before __attribute__((constructor)), so the three ShardedMap globals were always allocated with the default 64 shards. Add ShardedMap::Init() and call it from a new InitStreamRegistries() after LoadConfigFile in init_zlib_accel. Also stop treating a missing config file as fatal, and fix the README map_shards description (range 1-65536 → 2-65536, clarify description).
A summary of the code changes in this PR is provided below.
ShardedMapthat uses concurrent hash maps from oneTBB.With these modifications, the shim layer now has the option of using an optimized thread safe map data structure provided by oneTBB instead of one composed with a shared mutex and an unordered map. Additionally, the switch to Fibonacci hashing means that threads are less likely to collide on the map shard indexes.
Finally, no additional test failures were observed.