Skip to content

Enhance implementation of ShardedMap class#59

Open
missa-prime wants to merge 10 commits into
intel:mainfrom
missa-prime:user/missa-prime/concurrency
Open

Enhance implementation of ShardedMap class#59
missa-prime wants to merge 10 commits into
intel:mainfrom
missa-prime:user/missa-prime/concurrency

Conversation

@missa-prime

@missa-prime missa-prime commented Jun 29, 2026

Copy link
Copy Markdown

A summary of the code changes in this PR is provided below.

  1. Add a new scheme in ShardedMap that uses concurrent hash maps from oneTBB.
  2. Enable the new scheme if QAT, IAA, or IGZIP is used.
  3. Use Fibonacci hashing instead of modulo to generate shard indexes from stream keys.
  4. Expose the number of map shards as a configuration parameter.
  5. Ensure the each map instance is 64-byte aligned to mitigate the effects of false sharing within cache lines.

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.

@asonje

asonje commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

sharded_map.h line 9 — TBB header portability

#include <oneapi/tbb/concurrent_hash_map.h> and oneapi::tbb:: are only available in oneTBB 2021.x+. Systems with older Intel TBB (Ubuntu 20.04 ships libtbb-dev 2020.3, RHEL 8 similar) will fail to build. tbb/concurrent_hash_map.h and the tbb:: namespace work on both old and new TBB — oneTBB provides the tbb/ header as a compatibility wrapper, so tbb::concurrent_hash_map is an alias for oneapi::tbb::concurrent_hash_map on modern installs. Suggest:

#include <tbb/concurrent_hash_map.h>
using MapType = tbb::concurrent_hash_map<Key, Value, HashCompare>;

@asonje

asonje commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

CMakeLists.txt lines 11–12 — TBB as unconditional dependency

Making TBB unconditionally required (find_package(TBB REQUIRED) in the root CMakeLists.txt) means libtbb.so becomes a runtime dependency of libzlib-accel.so even for builds with no hardware acceleration enabled. This differs from how QATzip, QPL, and ISA-L are handled — those only link when their respective USE_QAT / USE_IAA / USE_IGZIP flag is set.

Suggest auto-enabling TBB when any hardware path is active (since ShardedMap is on the hot path for all streams, and users enabling hardware are already in Intel oneAPI territory where TBB is available):

# in common.cmake, after USE_QAT/USE_IAA/USE_IGZIP blocks
if(USE_QAT OR USE_IAA OR USE_IGZIP)
  find_package(TBB REQUIRED COMPONENTS tbb)
  link_libraries(TBB::tbb)
  add_compile_definitions(USE_TBB)
endif()

Then guard sharded_map.h with #ifdef USE_TBB, falling back to the existing std::shared_mutex implementation (enhanced with Fibonacci hashing and alignas(64)) when TBB is absent. This keeps pure-zlib builds dependency-free.

@asonje

asonje commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

sharded_map.h line 15 — using namespace config; in a header

using namespace config; at file scope in a header pollutes the global namespace in every translation unit that includes sharded_map.h. Suggest replacing with a qualified reference to the config value: pass num_shards as a constructor parameter, or use config::configs[config::MAP_SHARDS] directly.

@asonje

asonje commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

sharded_map.h GetShardnum_shards re-read on every call

GetShard reads configs[MAP_SHARDS] on every invocation, but the array was sized once in the constructor. If the config value diverged after construction (e.g. in a test that calls SetConfig), GetShard would produce an out-of-bounds shard index silently. Suggest caching it as a const member set in the constructor:

const unsigned int num_shards_;
// constructor: num_shards_(configs[MAP_SHARDS])

Then GetShard references num_shards_ rather than calling into configs[].

@asonje

asonje commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

config/config.cpp ~line 92 — Power-of-2 not validated at runtime

trySetConfig(MAP_SHARDS, 65536, 1) accepts any value in [1, 65536], but GetShard requires a power of 2 (uses __builtin_ctz for the shift). The assert in GetShard is compiled away by NDEBUG in Release builds — a user who sets map_shards=100 gets a silently skewed distribution (only 4 shards used out of 100) with no error. Suggest validating at config load time:

trySetConfig(MAP_SHARDS, 65536, 1);
if (configs[MAP_SHARDS] & (configs[MAP_SHARDS] - 1)) {
    Log(LogLevel::LOG_ERROR,
        "map_shards must be a power of 2, ignoring value ",
        configs[MAP_SHARDS], ", using default 64\n");
    configs[MAP_SHARDS] = 64;
}

@asonje

asonje commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

CMakeLists.txt lines 11–12 — CI failure / unconditional TBB

This is the root cause of all three CI check failures. The workflow installs build-essential, clang, cmake, libgtest-dev, zlib1g-dev — no libtbb-dev — so find_package(TBB REQUIRED) fails at cmake configure and the build dies in ~21 seconds before any code compiles.

Adding libtbb-dev to the workflow would not fully fix this either, because Ubuntu's apt repo ships the old TBB 2020.x package, and #include <oneapi/tbb/concurrent_hash_map.h> does not exist in that version (see separate comment on sharded_map.h).

The deeper issue is that TBB is now a hard runtime dependency of libzlib-accel.so unconditionally, even for builds with no hardware acceleration enabled. All other library dependencies (QATzip, QPL, ISA-L) only link when their respective hardware flag is set.

Suggest: auto-enable TBB when any hardware path is active. Since ShardedMap is on the hot path of every deflate()/inflate() call, and users enabling hardware acceleration are already in Intel oneAPI territory where TBB is available, this is a natural coupling. Keep a stdlib fallback (std::shared_mutex + std::unordered_map, enhanced with Fibonacci hashing and alignas(64)) for the no-hardware case:

In common.cmake:

# ShardedMap uses TBB when any hardware acceleration path is enabled.
# Users building with hardware support are already in Intel oneAPI territory.
if(USE_QAT OR USE_IAA OR USE_IGZIP)
  find_package(TBB REQUIRED COMPONENTS tbb)
  link_libraries(TBB::tbb)
  add_compile_definitions(USE_TBB)
endif()

In sharded_map.h:

#ifdef USE_TBB
  #include <tbb/concurrent_hash_map.h>  // portable: old TBB + oneTBB
  // tbb::concurrent_hash_map implementation
#else
  #include <shared_mutex>
  #include <unordered_map>
  // stdlib implementation with Fibonacci hashing + alignas(64)
#endif

With this change the CI build (no hardware flags) passes with zero workflow changes. A separate CI job with libtbb-dev and a hardware flag set would be needed for TBB path coverage.> # ShardedMap uses TBB when any hardware acceleration path is enabled.

Users building with hardware support are already in Intel oneAPI territory.

if(USE_QAT OR USE_IAA OR USE_IGZIP)
find_package(TBB REQUIRED COMPONENTS tbb)
link_libraries(TBB::tbb)
add_compile_definitions(USE_TBB)
endif()


In `sharded_map.h`:
```cpp
#ifdef USE_TBB
  #include <tbb/concurrent_hash_map.h>  // portable: old TBB + oneTBB
  // tbb::concurrent_hash_map implementation
#else
  #include <shared_mutex>
  #include <unordered_map>
  // stdlib implementation with Fibonacci hashing + alignas(64)
#endif

With this change the CI build (no hardware flags) passes with zero workflow changes. A separate CI job with libtbb-dev and a hardware flag set would be needed for TBB path coverage.

@missa-prime

Copy link
Copy Markdown
Author

config/config.cpp ~line 92 — Power-of-2 not validated at runtime

trySetConfig(MAP_SHARDS, 65536, 1) accepts any value in [1, 65536], but GetShard requires a power of 2 (uses __builtin_ctz for the shift). The assert in GetShard is compiled away by NDEBUG in Release builds — a user who sets map_shards=100 gets a silently skewed distribution (only 4 shards used out of 100) with no error. Suggest validating at config load time:

trySetConfig(MAP_SHARDS, 65536, 1);
if (configs[MAP_SHARDS] & (configs[MAP_SHARDS] - 1)) {
    Log(LogLevel::LOG_ERROR,
        "map_shards must be a power of 2, ignoring value ",
        configs[MAP_SHARDS], ", using default 64\n");
    configs[MAP_SHARDS] = 64;
}

I opted to make this case unrecoverable by returning false instead. I think the user should be fully aware what value is being used.

missa-prime and others added 2 commits July 21, 2026 18:54
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.
@asonje
asonje requested review from asonje and Copilot July 23, 2026 22:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_shards is 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.

Comment thread sharded_map.h
Comment on lines +109 to +117
// 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;
}
Comment thread sharded_map.h Outdated
// 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;
Comment thread config/config.cpp Outdated
#include <filesystem>
#include <string>

#include "../logging.h"
Comment thread config/config.cpp Outdated
Comment on lines +95 to +98
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;
Comment thread sharded_map.h
Comment on lines +53 to +60
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);
Comment thread README.md Outdated
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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;

Comment thread sharded_map.h
Comment on lines +26 to +30
// 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_);
}
Comment thread config/config.cpp
Comment on lines +93 to +94
trySetConfig(MAP_SHARDS, 65536, 2,
[](uint32_t v) { return (v & (v - 1)) == 0; });
Comment thread README.md
Comment on lines +229 to +232
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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants