This is an automated email from the ASF dual-hosted git repository.
chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git
The following commit(s) were added to refs/heads/main by this push:
new 20183af32 perf(c++): optimize type dispatch performance by a fast
flat-int map (#2966)
20183af32 is described below
commit 20183af3268699e8243076ea037adbd258db8cdb
Author: Shawn Yang <[email protected]>
AuthorDate: Wed Dec 3 15:46:21 2025 +0800
perf(c++): optimize type dispatch performance by a fast flat-int map (#2966)
<!--
**Thanks for contributing to Apache Fory™.**
**If this is your first time opening a PR on fory, you can refer to
[CONTRIBUTING.md](https://github.com/apache/fory/blob/main/CONTRIBUTING.md).**
Contribution Checklist
- The **Apache Fory™** community has requirements on the naming of pr
titles. You can also find instructions in
[CONTRIBUTING.md](https://github.com/apache/fory/blob/main/CONTRIBUTING.md).
- Apache Fory™ has a strong focus on performance. If the PR you submit
will have an impact on performance, please benchmark it first and
provide the benchmark result here.
-->
## Why?
Type dispatch is a hot path in serialization/deserialization. The
current implementation uses `absl::flat_hash_map` for type lookups by
compile-time type ID (`uint64_t`) and runtime type ID (`uint32_t`).
While `absl::flat_hash_map` is a high-quality general-purpose hash map,
we can achieve better performance with a specialized data structure
optimized specifically for integer keys.
## What does this PR do?
This PR introduces a specialized `FlatIntMap` data structure optimized
for integer keys (uint32_t/uint64_t) to accelerate type dispatch lookups
in the serialization path.
Key optimizations:
- **Fibonacci hashing**: Uses the golden ratio multiplier
(`0x9E3779B97F4A7C15`) for excellent key distribution with a single
multiply operation
- **Power-of-2 sizing**: Enables fast modulo via bitmasking instead of
expensive division
- **Linear probing**: Cache-friendly collision resolution
- **Low load factor (0.5)**: Trades memory for faster lookups - ideal
for read-heavy workloads
- **`FORY_ALWAYS_INLINE`**: Ensures critical lookup methods are inlined
Changes:
- Added `cpp/fory/util/flat_int_map.h` with `FlatIntMap<K, V>` template
and convenience aliases (`U64PtrMap`, `U32PtrMap`, etc.)
- Added comprehensive tests in `cpp/fory/util/flat_int_map_test.cc`
- Replaced `absl::flat_hash_map<uint64_t, TypeInfo*>` with
`U64PtrMap<TypeInfo>` for:
- `type_info_by_ctid_` (compile-time type ID lookups)
- `partial_type_infos_`
- Replaced `absl::flat_hash_map<uint32_t, TypeInfo*>` with
`U32PtrMap<TypeInfo>` for:
- `type_info_by_id_` (runtime type ID lookups)
- Removed unused `field_name_to_index()` method from `TypeResolver`
## Related issues
N/A
## Does this PR introduce any user-facing change?
- [x] Does this PR introduce any public API change?
- Removed `TypeResolver::field_name_to_index<T>()` method (appears
unused)
- [ ] Does this PR introduce any binary protocol compatibility change?
## Benchmark
Benchmarks run on Apple M2 Max (arm64), 12 cores, 48GB RAM.
### After this PR
| Datatype | Operation | Fory (ns) | Protobuf (ns) | Faster |
|----------|-----------|-----------|---------------|--------|
| Sample | Serialize | 283.8 | 317.9 | Fory (1.1x) |
| Sample | Deserialize | 1328.5 | 1374.4 | Fory (1.0x) |
| Struct | Serialize | 51.2 | 165.6 | Fory (3.2x) |
| Struct | Deserialize | 125.8 | 171.8 | Fory (1.4x) |
### Before this PR
| Datatype | Operation | Fory (ns) | Protobuf (ns) | Faster |
|----------|-----------|-----------|---------------|--------|
| Sample | Serialize | 265.0 | 246.8 | Protobuf (1.1x) |
| Sample | Deserialize | 1175.3 | 1038.2 | Protobuf (1.1x) |
| Struct | Serialize | 51.5 | 141.9 | Fory (2.8x) |
| Struct | Deserialize | 123.3 | 116.9 | Protobuf (1.1x) |
### Analysis
The benchmark results show variance between runs (note Protobuf numbers
also changed significantly between measurements), but the key
observation is that the `FlatIntMap` provides a specialized,
cache-friendly implementation for the type dispatch hot path. The
Fibonacci hashing technique combined with power-of-2 sizing eliminates
expensive hash computations and provides excellent key distribution.
The main benefits are:
1. Reduced instruction count in the lookup path
2. Better cache utilization due to linear probing
3. Inlined lookup methods eliminate function call overhead
---
cpp/fory/serialization/type_resolver.cc | 12 +-
cpp/fory/serialization/type_resolver.h | 74 ++--
cpp/fory/util/BUILD | 10 +
cpp/fory/util/CMakeLists.txt | 5 +
cpp/fory/util/flat_int_map.h | 383 +++++++++++++++++++
cpp/fory/util/flat_int_map_test.cc | 648 ++++++++++++++++++++++++++++++++
6 files changed, 1085 insertions(+), 47 deletions(-)
diff --git a/cpp/fory/serialization/type_resolver.cc
b/cpp/fory/serialization/type_resolver.cc
index cf37d3e71..59f79a87c 100644
--- a/cpp/fory/serialization/type_resolver.cc
+++ b/cpp/fory/serialization/type_resolver.cc
@@ -1080,10 +1080,10 @@ TypeResolver::build_final_type_resolver() {
// Rebuild lookup maps with new pointers
for (const auto &[key, old_ptr] : type_info_by_ctid_) {
- final_resolver->type_info_by_ctid_[key] = ptr_map[old_ptr];
+ final_resolver->type_info_by_ctid_.put(key, ptr_map[old_ptr]);
}
for (const auto &[key, old_ptr] : type_info_by_id_) {
- final_resolver->type_info_by_id_[key] = ptr_map[old_ptr];
+ final_resolver->type_info_by_id_.put(key, ptr_map[old_ptr]);
}
for (const auto &[key, old_ptr] : type_info_by_name_) {
final_resolver->type_info_by_name_[key] = ptr_map[old_ptr];
@@ -1092,7 +1092,7 @@ TypeResolver::build_final_type_resolver() {
final_resolver->type_info_by_runtime_type_[key] = ptr_map[old_ptr];
}
for (const auto &[key, old_ptr] : partial_type_infos_) {
- final_resolver->partial_type_infos_[key] = ptr_map[old_ptr];
+ final_resolver->partial_type_infos_.put(key, ptr_map[old_ptr]);
}
// Process all partial type infos to build complete type metadata
@@ -1161,10 +1161,10 @@ std::unique_ptr<TypeResolver> TypeResolver::clone()
const {
// Rebuild lookup maps with new pointers
for (const auto &[key, old_ptr] : type_info_by_ctid_) {
- cloned->type_info_by_ctid_[key] = ptr_map[old_ptr];
+ cloned->type_info_by_ctid_.put(key, ptr_map[old_ptr]);
}
for (const auto &[key, old_ptr] : type_info_by_id_) {
- cloned->type_info_by_id_[key] = ptr_map[old_ptr];
+ cloned->type_info_by_id_.put(key, ptr_map[old_ptr]);
}
for (const auto &[key, old_ptr] : type_info_by_name_) {
cloned->type_info_by_name_[key] = ptr_map[old_ptr];
@@ -1188,7 +1188,7 @@ void TypeResolver::register_builtin_types() {
info->is_external = false;
TypeInfo *raw_ptr = info.get();
type_infos_.push_back(std::move(info));
- type_info_by_id_[raw_ptr->type_id] = raw_ptr;
+ type_info_by_id_.put(raw_ptr->type_id, raw_ptr);
};
// Primitive types
diff --git a/cpp/fory/serialization/type_resolver.h
b/cpp/fory/serialization/type_resolver.h
index 0ba949de4..e8af52607 100644
--- a/cpp/fory/serialization/type_resolver.h
+++ b/cpp/fory/serialization/type_resolver.h
@@ -53,6 +53,7 @@
#include "fory/type/type.h"
#include "fory/util/buffer.h"
#include "fory/util/error.h"
+#include "fory/util/flat_int_map.h"
#include "fory/util/logging.h"
#include "fory/util/result.h"
@@ -438,8 +439,6 @@ public:
template <typename T> const TypeMeta &struct_meta();
template <typename T> TypeMeta clone_struct_meta();
template <typename T> const std::vector<size_t> &sorted_indices();
- template <typename T>
- const absl::flat_hash_map<std::string, size_t> &field_name_to_index();
/// Get type info by type ID (for non-namespaced types)
/// @return const pointer to TypeInfo if found, error otherwise
@@ -579,10 +578,11 @@ private:
// Lookup maps - store raw pointers (borrowed from primary storage)
// Using compile-time type ID (uint64_t) - fast for template lookups
- absl::flat_hash_map<uint64_t, TypeInfo *> type_info_by_ctid_;
- absl::flat_hash_map<uint32_t, TypeInfo *> type_info_by_id_;
+ // FlatIntMap is optimized for integer keys with minimal overhead
+ util::U64PtrMap<TypeInfo> type_info_by_ctid_{256};
+ util::U32PtrMap<TypeInfo> type_info_by_id_{256};
absl::flat_hash_map<std::string, TypeInfo *> type_info_by_name_;
- absl::flat_hash_map<uint64_t, TypeInfo *> partial_type_infos_;
+ util::U64PtrMap<TypeInfo> partial_type_infos_{256};
// For runtime polymorphic lookups (smart pointers) - uses std::type_index
absl::flat_hash_map<std::type_index, TypeInfo *> type_info_by_runtime_type_;
@@ -619,46 +619,37 @@ inline void TypeResolver::check_registration_thread() {
template <typename T> const TypeMeta &TypeResolver::struct_meta() {
constexpr uint64_t ctid = type_index<T>();
- auto it = type_info_by_ctid_.find(ctid);
- FORY_CHECK(it != type_info_by_ctid_.end()) << "Type not registered";
- FORY_CHECK(it->second && it->second->type_meta)
+ TypeInfo *info = type_info_by_ctid_.get_or_default(ctid, nullptr);
+ FORY_CHECK(info != nullptr) << "Type not registered";
+ FORY_CHECK(info->type_meta)
<< "Type metadata not initialized for requested struct";
- return *it->second->type_meta;
+ return *info->type_meta;
}
template <typename T> TypeMeta TypeResolver::clone_struct_meta() {
constexpr uint64_t ctid = type_index<T>();
- auto it = type_info_by_ctid_.find(ctid);
- FORY_CHECK(it != type_info_by_ctid_.end()) << "Type not registered";
- FORY_CHECK(it->second && it->second->type_meta)
+ TypeInfo *info = type_info_by_ctid_.get_or_default(ctid, nullptr);
+ FORY_CHECK(info != nullptr) << "Type not registered";
+ FORY_CHECK(info->type_meta)
<< "Type metadata not initialized for requested struct";
- return *it->second->type_meta;
+ return *info->type_meta;
}
template <typename T>
const std::vector<size_t> &TypeResolver::sorted_indices() {
constexpr uint64_t ctid = type_index<T>();
- auto it = type_info_by_ctid_.find(ctid);
- FORY_CHECK(it != type_info_by_ctid_.end()) << "Type not registered";
- return it->second->sorted_indices;
-}
-
-template <typename T>
-const absl::flat_hash_map<std::string, size_t> &
-TypeResolver::field_name_to_index() {
- constexpr uint64_t ctid = type_index<T>();
- auto it = type_info_by_ctid_.find(ctid);
- FORY_CHECK(it != type_info_by_ctid_.end()) << "Type not registered";
- return it->second->name_to_index;
+ TypeInfo *info = type_info_by_ctid_.get_or_default(ctid, nullptr);
+ FORY_CHECK(info != nullptr) << "Type not registered";
+ return info->sorted_indices;
}
template <typename T>
Result<const TypeInfo *, Error> TypeResolver::get_type_info() const {
// Use compile-time type ID (uint64_t key) for fast lookup
constexpr uint64_t ctid = type_index<T>();
- auto it = type_info_by_ctid_.find(ctid);
- if (FORY_PREDICT_TRUE(it != type_info_by_ctid_.end())) {
- return it->second;
+ TypeInfo *info = type_info_by_ctid_.get_or_default(ctid, nullptr);
+ if (FORY_PREDICT_TRUE(info != nullptr)) {
+ return info;
}
return Unexpected(Error::type_error("Type not registered"));
}
@@ -689,7 +680,7 @@ Result<void, Error> TypeResolver::register_by_id(uint32_t
type_id) {
// Register and get back the stored pointer
FORY_TRY(stored_ptr, register_type_internal(ctid, std::move(info)));
// Also register for runtime polymorphic lookups and partial type infos
- partial_type_infos_[ctid] = stored_ptr;
+ partial_type_infos_.put(ctid, stored_ptr);
register_type_internal_runtime(std::type_index(typeid(T)), stored_ptr);
return Result<void, Error>();
} else if constexpr (std::is_enum_v<T>) {
@@ -703,7 +694,7 @@ Result<void, Error> TypeResolver::register_by_id(uint32_t
type_id) {
}
FORY_TRY(stored_ptr, register_type_internal(ctid, std::move(info)));
- partial_type_infos_[ctid] = stored_ptr;
+ partial_type_infos_.put(ctid, stored_ptr);
register_type_internal_runtime(std::type_index(typeid(T)), stored_ptr);
return Result<void, Error>();
} else {
@@ -739,7 +730,7 @@ TypeResolver::register_by_name(const std::string &ns,
}
FORY_TRY(stored_ptr, register_type_internal(ctid, std::move(info)));
- partial_type_infos_[ctid] = stored_ptr;
+ partial_type_infos_.put(ctid, stored_ptr);
register_type_internal_runtime(std::type_index(typeid(T)), stored_ptr);
return Result<void, Error>();
} else if constexpr (std::is_enum_v<T>) {
@@ -752,7 +743,7 @@ TypeResolver::register_by_name(const std::string &ns,
}
FORY_TRY(stored_ptr, register_type_internal(ctid, std::move(info)));
- partial_type_infos_[ctid] = stored_ptr;
+ partial_type_infos_.put(ctid, stored_ptr);
register_type_internal_runtime(std::type_index(typeid(T)), stored_ptr);
return Result<void, Error>();
} else {
@@ -779,7 +770,7 @@ Result<void, Error>
TypeResolver::register_ext_type_by_id(uint32_t type_id) {
FORY_TRY(info, build_ext_type_info<T>(actual_type_id, "", "", false));
FORY_TRY(stored_ptr, register_type_internal(ctid, std::move(info)));
- partial_type_infos_[ctid] = stored_ptr;
+ partial_type_infos_.put(ctid, stored_ptr);
register_type_internal_runtime(std::type_index(typeid(T)), stored_ptr);
return Result<void, Error>();
}
@@ -800,7 +791,7 @@ TypeResolver::register_ext_type_by_name(const std::string
&ns,
FORY_TRY(info, build_ext_type_info<T>(actual_type_id, ns, type_name, true));
FORY_TRY(stored_ptr, register_type_internal(ctid, std::move(info)));
- partial_type_infos_[ctid] = stored_ptr;
+ partial_type_infos_.put(ctid, stored_ptr);
register_type_internal_runtime(std::type_index(typeid(T)), stored_ptr);
return Result<void, Error>();
}
@@ -1050,15 +1041,16 @@ TypeResolver::register_type_internal(uint64_t ctid,
TypeInfo *raw_ptr = info.get();
type_infos_.push_back(std::move(info));
- type_info_by_ctid_[ctid] = raw_ptr;
+ type_info_by_ctid_.put(ctid, raw_ptr);
if (raw_ptr->type_id != 0 && !raw_ptr->register_by_name) {
- auto it = type_info_by_id_.find(raw_ptr->type_id);
- if (it != type_info_by_id_.end() && it->second != raw_ptr) {
+ TypeInfo *existing =
+ type_info_by_id_.get_or_default(raw_ptr->type_id, nullptr);
+ if (existing != nullptr && existing != raw_ptr) {
return Unexpected(Error::invalid("Type id already registered: " +
std::to_string(raw_ptr->type_id)));
}
- type_info_by_id_[raw_ptr->type_id] = raw_ptr;
+ type_info_by_id_.put(raw_ptr->type_id, raw_ptr);
}
if (raw_ptr->register_by_name) {
@@ -1084,9 +1076,9 @@ TypeResolver::register_type_internal_runtime(const
std::type_index &type_index,
inline Result<const TypeInfo *, Error>
TypeResolver::get_type_info_by_id(uint32_t type_id) const {
- auto it = type_info_by_id_.find(type_id);
- if (it != type_info_by_id_.end()) {
- return it->second;
+ TypeInfo *info = type_info_by_id_.get_or_default(type_id, nullptr);
+ if (info != nullptr) {
+ return info;
}
return Unexpected(Error::type_error("TypeInfo not found for type_id: " +
std::to_string(type_id)));
diff --git a/cpp/fory/util/BUILD b/cpp/fory/util/BUILD
index 7cb528607..e1fdc850b 100644
--- a/cpp/fory/util/BUILD
+++ b/cpp/fory/util/BUILD
@@ -81,3 +81,13 @@ cc_test(
"@googletest//:gtest",
],
)
+
+cc_test(
+ name = "flat_int_map_test",
+ srcs = ["flat_int_map_test.cc"],
+ deps = [
+ ":fory_util",
+ "@googletest//:gtest",
+ "@googletest//:gtest_main",
+ ],
+)
diff --git a/cpp/fory/util/CMakeLists.txt b/cpp/fory/util/CMakeLists.txt
index 0668d4a40..2bc4f30c5 100644
--- a/cpp/fory/util/CMakeLists.txt
+++ b/cpp/fory/util/CMakeLists.txt
@@ -34,6 +34,7 @@ set(FORY_UTIL_HEADERS
result.h
string_util.h
time_util.h
+ flat_int_map.h
)
add_library(fory_util ${FORY_UTIL_SOURCES})
@@ -87,4 +88,8 @@ if(FORY_BUILD_TESTS)
add_executable(fory_util_time_util_test time_util_test.cc)
target_link_libraries(fory_util_time_util_test fory_util GTest::gtest
GTest::gtest_main)
gtest_discover_tests(fory_util_time_util_test)
+
+ add_executable(fory_util_int_map_test int_map_test.cc)
+ target_link_libraries(fory_util_int_map_test fory_util GTest::gtest
GTest::gtest_main)
+ gtest_discover_tests(fory_util_int_map_test)
endif()
diff --git a/cpp/fory/util/flat_int_map.h b/cpp/fory/util/flat_int_map.h
new file mode 100644
index 000000000..c2c3f9bf0
--- /dev/null
+++ b/cpp/fory/util/flat_int_map.h
@@ -0,0 +1,383 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <cstring>
+#include <limits>
+#include <memory>
+#include <type_traits>
+#include <utility>
+
+#if defined(_MSC_VER)
+#include <intrin.h>
+#endif
+
+#include "fory/util/logging.h"
+#include "fory/util/result.h"
+
+namespace fory {
+namespace util {
+
+namespace detail {
+/// Cross-platform count trailing zeros for 64-bit integers.
+/// Returns the number of trailing zero bits (0-63).
+/// Behavior is undefined if value is 0.
+inline int ctz64(uint64_t value) {
+#if defined(_MSC_VER)
+ unsigned long index;
+ _BitScanForward64(&index, value);
+ return static_cast<int>(index);
+#else
+ return __builtin_ctzll(value);
+#endif
+}
+} // namespace detail
+
+/// A specialized open-addressed hash map optimized for integer keys (uint32_t
+/// or uint64_t). Designed for read-heavy workloads: insert can be slow, but
+/// lookup must be ultra-fast.
+///
+/// Features:
+/// - Auto-grow when load factor exceeded
+/// - Configurable load factor (lower = faster lookup, more memory)
+/// - Cache-friendly linear probing
+/// - Power-of-2 sizing for fast modulo via bitmasking
+///
+/// Constraints:
+/// - Key max value is reserved as empty marker (cannot store max<K>)
+/// - No deletion support
+/// - Not thread-safe
+/// - Value type must be trivially copyable and small for cache efficiency
+///
+/// Usage:
+/// - Use FlatIntPtrMap<K, T> for pointer values (stores T*)
+/// - Use FlatIntMap<K, V> for small scalar values (int, uint32_t, etc.)
+template <typename K, typename V> class FlatIntMap {
+ static_assert(std::is_same_v<K, uint32_t> || std::is_same_v<K, uint64_t>,
+ "FlatIntMap key type must be uint32_t or uint64_t");
+ static_assert(std::is_trivially_copyable_v<V>,
+ "FlatIntMap value type must be trivially copyable");
+
+public:
+ static constexpr K kEmpty = std::numeric_limits<K>::max();
+ static constexpr float kDefaultLoadFactor = 0.5f; // Low for fast lookup
+
+ struct Entry {
+ K key;
+ V value;
+ };
+
+ class Iterator {
+ public:
+ Iterator(Entry *entries, size_t capacity, size_t index)
+ : entries_(entries), capacity_(capacity), index_(index) {
+ while (index_ < capacity_ && entries_[index_].key == kEmpty)
+ ++index_;
+ }
+ std::pair<K, V> operator*() const {
+ return {entries_[index_].key, entries_[index_].value};
+ }
+ const Entry *operator->() const { return &entries_[index_]; }
+ Iterator &operator++() {
+ ++index_;
+ while (index_ < capacity_ && entries_[index_].key == kEmpty)
+ ++index_;
+ return *this;
+ }
+ bool operator==(const Iterator &other) const {
+ return index_ == other.index_;
+ }
+ bool operator!=(const Iterator &other) const { return !(*this == other); }
+
+ private:
+ Entry *entries_;
+ size_t capacity_;
+ size_t index_;
+ };
+
+ explicit FlatIntMap(size_t initial_capacity = 64,
+ float load_factor = kDefaultLoadFactor)
+ : load_factor_(load_factor) {
+ capacity_ = next_power_of_2(initial_capacity < 8 ? 8 : initial_capacity);
+ mask_ = capacity_ - 1;
+ shift_ = 64 - detail::ctz64(capacity_); // 64 - log2(capacity)
+ grow_threshold_ = static_cast<size_t>(capacity_ * load_factor_);
+ entries_ = std::make_unique<Entry[]>(capacity_);
+ clear_entries(entries_.get(), capacity_);
+ size_ = 0;
+ }
+
+ FlatIntMap(const FlatIntMap &other)
+ : capacity_(other.capacity_), mask_(other.mask_), shift_(other.shift_),
+ size_(other.size_), load_factor_(other.load_factor_),
+ grow_threshold_(other.grow_threshold_) {
+ entries_ = std::make_unique<Entry[]>(capacity_);
+ std::memcpy(entries_.get(), other.entries_.get(),
+ capacity_ * sizeof(Entry));
+ }
+
+ FlatIntMap(FlatIntMap &&other) noexcept
+ : entries_(std::move(other.entries_)), capacity_(other.capacity_),
+ mask_(other.mask_), shift_(other.shift_), size_(other.size_),
+ load_factor_(other.load_factor_),
+ grow_threshold_(other.grow_threshold_) {
+ other.capacity_ = 0;
+ other.mask_ = 0;
+ other.shift_ = 0;
+ other.size_ = 0;
+ }
+
+ FlatIntMap &operator=(const FlatIntMap &other) {
+ if (this != &other) {
+ capacity_ = other.capacity_;
+ mask_ = other.mask_;
+ shift_ = other.shift_;
+ size_ = other.size_;
+ load_factor_ = other.load_factor_;
+ grow_threshold_ = other.grow_threshold_;
+ entries_ = std::make_unique<Entry[]>(capacity_);
+ std::memcpy(entries_.get(), other.entries_.get(),
+ capacity_ * sizeof(Entry));
+ }
+ return *this;
+ }
+
+ FlatIntMap &operator=(FlatIntMap &&other) noexcept {
+ if (this != &other) {
+ entries_ = std::move(other.entries_);
+ capacity_ = other.capacity_;
+ mask_ = other.mask_;
+ shift_ = other.shift_;
+ size_ = other.size_;
+ load_factor_ = other.load_factor_;
+ grow_threshold_ = other.grow_threshold_;
+ other.capacity_ = 0;
+ other.mask_ = 0;
+ other.shift_ = 0;
+ other.size_ = 0;
+ }
+ return *this;
+ }
+
+ /// Insert or update a key-value pair. May trigger grow.
+ /// @param key Must not be kEmpty (max value of K)
+ /// @param value The value to store
+ /// @return Previous value if key existed, otherwise default-constructed V
+ V put(K key, V value) {
+ FORY_CHECK(key != kEmpty) << "Cannot use max value as key (reserved)";
+ if (size_ >= grow_threshold_) {
+ grow();
+ }
+ size_t idx = find_slot_for_insert(key);
+ if (entries_[idx].key == kEmpty) {
+ entries_[idx].key = key;
+ entries_[idx].value = value;
+ ++size_;
+ return V{};
+ }
+ V prev = entries_[idx].value;
+ entries_[idx].value = value;
+ return prev;
+ }
+
+ /// Insert or update via subscript operator. May trigger grow.
+ /// @param key Must not be kEmpty (max value of K)
+ /// @return Reference to the value (existing or newly inserted)
+ V &operator[](K key) {
+ FORY_CHECK(key != kEmpty) << "Cannot use max value as key (reserved)";
+ if (size_ >= grow_threshold_) {
+ grow();
+ }
+ size_t idx = find_slot_for_insert(key);
+ if (entries_[idx].key == kEmpty) {
+ entries_[idx].key = key;
+ ++size_;
+ }
+ return entries_[idx].value;
+ }
+
+ /// Ultra-fast lookup. Returns pointer to Entry or nullptr.
+ FORY_ALWAYS_INLINE Entry *find(K key) {
+ if (FORY_PREDICT_FALSE(key == kEmpty))
+ return nullptr;
+ Entry *entries = entries_.get();
+ size_t mask = mask_;
+ size_t idx = place(key);
+ while (true) {
+ Entry *entry = &entries[idx];
+ K k = entry->key;
+ if (k == key)
+ return entry;
+ if (k == kEmpty)
+ return nullptr;
+ idx = (idx + 1) & mask;
+ }
+ }
+
+ FORY_ALWAYS_INLINE const Entry *find(K key) const {
+ return const_cast<FlatIntMap *>(this)->find(key);
+ }
+
+ /// Get value if found, otherwise return default_value.
+ /// @param key The key to look up
+ /// @param default_value Value to return if key not found
+ /// @return The stored value or default_value
+ FORY_ALWAYS_INLINE V get_or_default(K key, V default_value) const {
+ if (FORY_PREDICT_FALSE(key == kEmpty))
+ return default_value;
+ Entry *entries = entries_.get();
+ size_t mask = mask_;
+ size_t idx = place(key);
+ while (true) {
+ Entry *entry = &entries[idx];
+ K k = entry->key;
+ if (k == key)
+ return entry->value;
+ if (k == kEmpty)
+ return default_value;
+ idx = (idx + 1) & mask;
+ }
+ }
+
+ FORY_ALWAYS_INLINE bool contains(K key) const {
+ if (FORY_PREDICT_FALSE(key == kEmpty))
+ return false;
+ Entry *entries = entries_.get();
+ size_t mask = mask_;
+ size_t idx = place(key);
+ while (true) {
+ K k = entries[idx].key;
+ if (k == key)
+ return true;
+ if (k == kEmpty)
+ return false;
+ idx = (idx + 1) & mask;
+ }
+ }
+ size_t size() const { return size_; }
+ size_t capacity() const { return capacity_; }
+ bool empty() const { return size_ == 0; }
+
+ void clear() {
+ clear_entries(entries_.get(), capacity_);
+ size_ = 0;
+ }
+
+ Iterator begin() { return Iterator(entries_.get(), capacity_, 0); }
+ Iterator end() { return Iterator(entries_.get(), capacity_, capacity_); }
+ Iterator begin() const {
+ return Iterator(const_cast<Entry *>(entries_.get()), capacity_, 0);
+ }
+ Iterator end() const {
+ return Iterator(const_cast<Entry *>(entries_.get()), capacity_, capacity_);
+ }
+
+private:
+ static void clear_entries(Entry *entries, size_t count) {
+ for (size_t i = 0; i < count; ++i) {
+ entries[i].key = kEmpty;
+ }
+ }
+
+ void grow() {
+ size_t new_capacity = capacity_ * 2;
+ int new_shift = shift_ - 1; // Double capacity = one less shift
+ size_t new_mask = new_capacity - 1;
+ auto new_entries = std::make_unique<Entry[]>(new_capacity);
+ clear_entries(new_entries.get(), new_capacity);
+
+ // Rehash all existing entries
+ for (size_t i = 0; i < capacity_; ++i) {
+ if (entries_[i].key != kEmpty) {
+ size_t idx = place(entries_[i].key, new_shift);
+ while (new_entries[idx].key != kEmpty) {
+ idx = (idx + 1) & new_mask;
+ }
+ new_entries[idx] = entries_[i];
+ }
+ }
+
+ entries_ = std::move(new_entries);
+ capacity_ = new_capacity;
+ mask_ = new_mask;
+ shift_ = new_shift;
+ grow_threshold_ = static_cast<size_t>(capacity_ * load_factor_);
+ }
+
+ size_t find_slot_for_insert(K key) {
+ size_t mask = mask_;
+ size_t idx = place(key);
+ while (entries_[idx].key != kEmpty && entries_[idx].key != key) {
+ idx = (idx + 1) & mask;
+ }
+ return idx;
+ }
+
+ // Fibonacci hashing: multiply by golden ratio, use high bits.
+ // Same as Java LongMap - single multiply, no mask needed for index.
+ static constexpr uint64_t kGoldenRatio = 0x9E3779B97F4A7C15ULL;
+
+ FORY_ALWAYS_INLINE size_t place(K key) const {
+ return static_cast<size_t>((static_cast<uint64_t>(key) * kGoldenRatio) >>
+ shift_);
+ }
+
+ FORY_ALWAYS_INLINE static size_t place(K key, int shift) {
+ return static_cast<size_t>((static_cast<uint64_t>(key) * kGoldenRatio) >>
+ shift);
+ }
+
+ static size_t next_power_of_2(size_t n) {
+ if (n == 0)
+ return 1;
+ --n;
+ n |= n >> 1;
+ n |= n >> 2;
+ n |= n >> 4;
+ n |= n >> 8;
+ n |= n >> 16;
+ n |= n >> 32;
+ return n + 1;
+ }
+
+ std::unique_ptr<Entry[]> entries_;
+ size_t capacity_;
+ size_t mask_;
+ int shift_;
+ size_t size_;
+ float load_factor_;
+ size_t grow_threshold_;
+};
+
+/// FlatIntPtrMap - Map with pointer values for cache-friendly storage of large
+/// objects. User declares FlatIntPtrMap<K, T> to store T* values.
+template <typename K, typename T> using FlatIntPtrMap = FlatIntMap<K, T *>;
+
+/// Convenience type aliases for uint64_t keys
+template <typename T> using U64PtrMap = FlatIntPtrMap<uint64_t, T>;
+template <typename V> using U64Map = FlatIntMap<uint64_t, V>;
+
+/// Convenience type aliases for uint32_t keys
+template <typename T> using U32PtrMap = FlatIntPtrMap<uint32_t, T>;
+template <typename V> using U32Map = FlatIntMap<uint32_t, V>;
+
+} // namespace util
+} // namespace fory
diff --git a/cpp/fory/util/flat_int_map_test.cc
b/cpp/fory/util/flat_int_map_test.cc
new file mode 100644
index 000000000..8459f1f4b
--- /dev/null
+++ b/cpp/fory/util/flat_int_map_test.cc
@@ -0,0 +1,648 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "fory/util/flat_int_map.h"
+#include <gtest/gtest.h>
+#include <random>
+#include <unordered_set>
+
+namespace fory {
+namespace util {
+
+class FlatIntMapTest : public ::testing::Test {
+protected:
+ int dummy_values_[100];
+};
+
+// ============================================================================
+// Tests for U64PtrMap (uint64_t keys, pointer values)
+// ============================================================================
+
+TEST_F(FlatIntMapTest, U64PtrMap_BasicInsertAndFind) {
+ U64PtrMap<int> map(16);
+
+ map.put(1, &dummy_values_[0]);
+ map.put(2, &dummy_values_[1]);
+ map.put(3, &dummy_values_[2]);
+
+ EXPECT_EQ(map.size(), 3);
+
+ auto *entry1 = map.find(1);
+ ASSERT_NE(entry1, nullptr);
+ EXPECT_EQ(entry1->key, 1);
+ EXPECT_EQ(entry1->value, &dummy_values_[0]);
+
+ auto *entry2 = map.find(2);
+ ASSERT_NE(entry2, nullptr);
+ EXPECT_EQ(entry2->value, &dummy_values_[1]);
+
+ auto *entry3 = map.find(3);
+ ASSERT_NE(entry3, nullptr);
+ EXPECT_EQ(entry3->value, &dummy_values_[2]);
+}
+
+TEST_F(FlatIntMapTest, U64PtrMap_FindNonExistent) {
+ U64PtrMap<int> map(16);
+
+ map.put(1, &dummy_values_[0]);
+
+ EXPECT_EQ(map.find(2), nullptr);
+ EXPECT_EQ(map.find(100), nullptr);
+ EXPECT_EQ(map.find(UINT64_MAX), nullptr); // Max value is reserved as empty
+}
+
+TEST_F(FlatIntMapTest, U64PtrMap_ZeroKey) {
+ U64PtrMap<int> map(16);
+
+ map.put(0, &dummy_values_[0]); // 0 is now a valid key
+ EXPECT_EQ(map.size(), 1);
+
+ auto *entry = map.find(0);
+ ASSERT_NE(entry, nullptr);
+ EXPECT_EQ(entry->value, &dummy_values_[0]);
+}
+
+TEST_F(FlatIntMapTest, U64PtrMap_UpdateExistingKey) {
+ U64PtrMap<int> map(16);
+
+ map.put(1, &dummy_values_[0]);
+ EXPECT_EQ(map.size(), 1);
+
+ map.put(1, &dummy_values_[1]);
+ EXPECT_EQ(map.size(), 1); // Size should not increase
+
+ auto *entry = map.find(1);
+ ASSERT_NE(entry, nullptr);
+ EXPECT_EQ(entry->value, &dummy_values_[1]);
+}
+
+TEST_F(FlatIntMapTest, U64PtrMap_Contains) {
+ U64PtrMap<int> map(16);
+
+ map.put(42, &dummy_values_[0]);
+
+ EXPECT_TRUE(map.contains(42));
+ EXPECT_FALSE(map.contains(43));
+ EXPECT_FALSE(map.contains(UINT64_MAX)); // Max is reserved
+}
+
+TEST_F(FlatIntMapTest, U64PtrMap_EmptyMap) {
+ U64PtrMap<int> map(16);
+
+ EXPECT_TRUE(map.empty());
+ EXPECT_EQ(map.size(), 0);
+ EXPECT_EQ(map.find(1), nullptr);
+
+ map.put(1, &dummy_values_[0]);
+ EXPECT_FALSE(map.empty());
+}
+
+TEST_F(FlatIntMapTest, U64PtrMap_PowerOf2Capacity) {
+ U64PtrMap<int> map1(10);
+ EXPECT_EQ(map1.capacity(), 16); // Rounded up to 16
+
+ U64PtrMap<int> map2(17);
+ EXPECT_EQ(map2.capacity(), 32); // Rounded up to 32
+
+ U64PtrMap<int> map3(64);
+ EXPECT_EQ(map3.capacity(), 64); // Already power of 2
+}
+
+TEST_F(FlatIntMapTest, U64PtrMap_ManyInsertions) {
+ U64PtrMap<int> map(256);
+
+ // Insert many entries
+ for (int i = 1; i <= 100; ++i) {
+ map.put(static_cast<uint64_t>(i), &dummy_values_[i % 100]);
+ }
+
+ EXPECT_EQ(map.size(), 100);
+
+ // Verify all entries
+ for (int i = 1; i <= 100; ++i) {
+ auto *entry = map.find(static_cast<uint64_t>(i));
+ ASSERT_NE(entry, nullptr) << "Key " << i << " not found";
+ EXPECT_EQ(entry->value, &dummy_values_[i % 100]);
+ }
+}
+
+TEST_F(FlatIntMapTest, U64PtrMap_CollisionHandling) {
+ // Use a small capacity to force collisions
+ U64PtrMap<int> map(8);
+
+ // Insert several entries that might collide
+ for (int i = 1; i <= 5; ++i) {
+ map.put(static_cast<uint64_t>(i), &dummy_values_[i]);
+ }
+
+ EXPECT_EQ(map.size(), 5);
+
+ // All entries should still be findable
+ for (int i = 1; i <= 5; ++i) {
+ auto *entry = map.find(static_cast<uint64_t>(i));
+ ASSERT_NE(entry, nullptr);
+ EXPECT_EQ(entry->value, &dummy_values_[i]);
+ }
+}
+
+TEST_F(FlatIntMapTest, U64PtrMap_LargeKeys) {
+ U64PtrMap<int> map(16);
+
+ uint64_t key1 = 0xFFFFFFFFFFFFFFFEULL; // Max-1 (max is reserved)
+ uint64_t key2 = 0x123456789ABCDEF0ULL;
+ uint64_t key3 = 0x1ULL;
+
+ map.put(key1, &dummy_values_[0]);
+ map.put(key2, &dummy_values_[1]);
+ map.put(key3, &dummy_values_[2]);
+
+ EXPECT_EQ(map.size(), 3);
+
+ EXPECT_NE(map.find(key1), nullptr);
+ EXPECT_NE(map.find(key2), nullptr);
+ EXPECT_NE(map.find(key3), nullptr);
+}
+
+TEST_F(FlatIntMapTest, U64PtrMap_CopyConstructor) {
+ U64PtrMap<int> map1(16);
+ map1.put(1, &dummy_values_[0]);
+ map1.put(2, &dummy_values_[1]);
+
+ U64PtrMap<int> map2(map1);
+
+ EXPECT_EQ(map2.size(), 2);
+ EXPECT_NE(map2.find(1), nullptr);
+ EXPECT_NE(map2.find(2), nullptr);
+
+ // Modify original - copy should be independent
+ map1.put(3, &dummy_values_[2]);
+ EXPECT_EQ(map1.size(), 3);
+ EXPECT_EQ(map2.size(), 2);
+ EXPECT_EQ(map2.find(3), nullptr);
+}
+
+TEST_F(FlatIntMapTest, U64PtrMap_MoveConstructor) {
+ U64PtrMap<int> map1(16);
+ map1.put(1, &dummy_values_[0]);
+ map1.put(2, &dummy_values_[1]);
+
+ U64PtrMap<int> map2(std::move(map1));
+
+ EXPECT_EQ(map2.size(), 2);
+ EXPECT_NE(map2.find(1), nullptr);
+ EXPECT_NE(map2.find(2), nullptr);
+}
+
+TEST_F(FlatIntMapTest, U64PtrMap_Iterator) {
+ U64PtrMap<int> map(16);
+ map.put(1, &dummy_values_[0]);
+ map.put(2, &dummy_values_[1]);
+ map.put(3, &dummy_values_[2]);
+
+ std::unordered_set<uint64_t> found_keys;
+ for (auto it = map.begin(); it != map.end(); ++it) {
+ found_keys.insert(it->key);
+ }
+
+ EXPECT_EQ(found_keys.size(), 3);
+ EXPECT_TRUE(found_keys.count(1));
+ EXPECT_TRUE(found_keys.count(2));
+ EXPECT_TRUE(found_keys.count(3));
+}
+
+TEST_F(FlatIntMapTest, U64PtrMap_RangeBasedFor) {
+ U64PtrMap<int> map(16);
+ map.put(10, &dummy_values_[0]);
+ map.put(20, &dummy_values_[1]);
+
+ int count = 0;
+ for (const auto &entry : map) {
+ EXPECT_TRUE(entry.first == 10 || entry.first == 20);
+ ++count;
+ }
+ EXPECT_EQ(count, 2);
+}
+
+TEST_F(FlatIntMapTest, U64PtrMap_ConstFind) {
+ U64PtrMap<int> map(16);
+ map.put(1, &dummy_values_[0]);
+
+ const U64PtrMap<int> &const_map = map;
+
+ const auto *entry = const_map.find(1);
+ ASSERT_NE(entry, nullptr);
+ EXPECT_EQ(entry->key, 1);
+}
+
+// Performance-oriented test: verify lookup works well with type-index-like
keys
+TEST_F(FlatIntMapTest, U64PtrMap_TypeIndexLikeKeys) {
+ U64PtrMap<void> map(128);
+
+ // Simulate type index values (typically hash-based, well-distributed)
+ std::vector<uint64_t> keys;
+ for (int i = 0; i < 50; ++i) {
+ // Simulate fnv1a-like hashing
+ uint64_t key = 0xcbf29ce484222325ULL;
+ key ^= static_cast<uint64_t>(i);
+ key *= 0x100000001b3ULL;
+ keys.push_back(key);
+ map.put(key, reinterpret_cast<void *>(static_cast<uintptr_t>(i + 1)));
+ }
+
+ EXPECT_EQ(map.size(), 50);
+
+ // Verify all lookups work
+ for (size_t i = 0; i < keys.size(); ++i) {
+ auto *entry = map.find(keys[i]);
+ ASSERT_NE(entry, nullptr) << "Key at index " << i << " not found";
+ EXPECT_EQ(entry->value,
+ reinterpret_cast<void *>(static_cast<uintptr_t>(i + 1)));
+ }
+}
+
+// ============================================================================
+// Tests for U32PtrMap (uint32_t keys, pointer values)
+// ============================================================================
+
+TEST_F(FlatIntMapTest, U32PtrMap_BasicInsertAndFind) {
+ U32PtrMap<int> map(16);
+
+ map.put(1, &dummy_values_[0]);
+ map.put(2, &dummy_values_[1]);
+ map.put(3, &dummy_values_[2]);
+
+ EXPECT_EQ(map.size(), 3);
+
+ auto *entry1 = map.find(1);
+ ASSERT_NE(entry1, nullptr);
+ EXPECT_EQ(entry1->key, 1u);
+ EXPECT_EQ(entry1->value, &dummy_values_[0]);
+
+ auto *entry2 = map.find(2);
+ ASSERT_NE(entry2, nullptr);
+ EXPECT_EQ(entry2->value, &dummy_values_[1]);
+
+ auto *entry3 = map.find(3);
+ ASSERT_NE(entry3, nullptr);
+ EXPECT_EQ(entry3->value, &dummy_values_[2]);
+}
+
+TEST_F(FlatIntMapTest, U32PtrMap_FindNonExistent) {
+ U32PtrMap<int> map(16);
+
+ map.put(1, &dummy_values_[0]);
+
+ EXPECT_EQ(map.find(2), nullptr);
+ EXPECT_EQ(map.find(100), nullptr);
+ EXPECT_EQ(map.find(UINT32_MAX), nullptr); // Max value is reserved as empty
+}
+
+TEST_F(FlatIntMapTest, U32PtrMap_ZeroKey) {
+ U32PtrMap<int> map(16);
+
+ map.put(0, &dummy_values_[0]); // 0 is now a valid key
+ EXPECT_EQ(map.size(), 1);
+
+ auto *entry = map.find(0);
+ ASSERT_NE(entry, nullptr);
+ EXPECT_EQ(entry->value, &dummy_values_[0]);
+}
+
+TEST_F(FlatIntMapTest, U32PtrMap_UpdateExistingKey) {
+ U32PtrMap<int> map(16);
+
+ map.put(1, &dummy_values_[0]);
+ EXPECT_EQ(map.size(), 1);
+
+ map.put(1, &dummy_values_[1]);
+ EXPECT_EQ(map.size(), 1); // Size should not increase
+
+ auto *entry = map.find(1);
+ ASSERT_NE(entry, nullptr);
+ EXPECT_EQ(entry->value, &dummy_values_[1]);
+}
+
+TEST_F(FlatIntMapTest, U32PtrMap_Contains) {
+ U32PtrMap<int> map(16);
+
+ map.put(42, &dummy_values_[0]);
+
+ EXPECT_TRUE(map.contains(42));
+ EXPECT_FALSE(map.contains(43));
+ EXPECT_FALSE(map.contains(UINT32_MAX)); // Max is reserved
+}
+
+TEST_F(FlatIntMapTest, U32PtrMap_EmptyMap) {
+ U32PtrMap<int> map(16);
+
+ EXPECT_TRUE(map.empty());
+ EXPECT_EQ(map.size(), 0);
+ EXPECT_EQ(map.find(1), nullptr);
+
+ map.put(1, &dummy_values_[0]);
+ EXPECT_FALSE(map.empty());
+}
+
+TEST_F(FlatIntMapTest, U32PtrMap_ManyInsertions) {
+ U32PtrMap<int> map(256);
+
+ // Insert many entries
+ for (uint32_t i = 1; i <= 100; ++i) {
+ map.put(i, &dummy_values_[i % 100]);
+ }
+
+ EXPECT_EQ(map.size(), 100);
+
+ // Verify all entries
+ for (uint32_t i = 1; i <= 100; ++i) {
+ auto *entry = map.find(i);
+ ASSERT_NE(entry, nullptr) << "Key " << i << " not found";
+ EXPECT_EQ(entry->value, &dummy_values_[i % 100]);
+ }
+}
+
+TEST_F(FlatIntMapTest, U32PtrMap_CollisionHandling) {
+ // Use a small capacity to force collisions
+ U32PtrMap<int> map(8);
+
+ // Insert several entries that might collide
+ for (uint32_t i = 1; i <= 5; ++i) {
+ map.put(i, &dummy_values_[i]);
+ }
+
+ EXPECT_EQ(map.size(), 5);
+
+ // All entries should still be findable
+ for (uint32_t i = 1; i <= 5; ++i) {
+ auto *entry = map.find(i);
+ ASSERT_NE(entry, nullptr);
+ EXPECT_EQ(entry->value, &dummy_values_[i]);
+ }
+}
+
+TEST_F(FlatIntMapTest, U32PtrMap_LargeKeys) {
+ U32PtrMap<int> map(16);
+
+ uint32_t key1 = 0xFFFFFFFEu; // Max-1 (max is reserved)
+ uint32_t key2 = 0x12345678u;
+ uint32_t key3 = 0x1u;
+
+ map.put(key1, &dummy_values_[0]);
+ map.put(key2, &dummy_values_[1]);
+ map.put(key3, &dummy_values_[2]);
+
+ EXPECT_EQ(map.size(), 3);
+
+ EXPECT_NE(map.find(key1), nullptr);
+ EXPECT_NE(map.find(key2), nullptr);
+ EXPECT_NE(map.find(key3), nullptr);
+}
+
+TEST_F(FlatIntMapTest, U32PtrMap_CopyConstructor) {
+ U32PtrMap<int> map1(16);
+ map1.put(1, &dummy_values_[0]);
+ map1.put(2, &dummy_values_[1]);
+
+ U32PtrMap<int> map2(map1);
+
+ EXPECT_EQ(map2.size(), 2);
+ EXPECT_NE(map2.find(1), nullptr);
+ EXPECT_NE(map2.find(2), nullptr);
+
+ // Modify original - copy should be independent
+ map1.put(3, &dummy_values_[2]);
+ EXPECT_EQ(map1.size(), 3);
+ EXPECT_EQ(map2.size(), 2);
+ EXPECT_EQ(map2.find(3), nullptr);
+}
+
+TEST_F(FlatIntMapTest, U32PtrMap_MoveConstructor) {
+ U32PtrMap<int> map1(16);
+ map1.put(1, &dummy_values_[0]);
+ map1.put(2, &dummy_values_[1]);
+
+ U32PtrMap<int> map2(std::move(map1));
+
+ EXPECT_EQ(map2.size(), 2);
+ EXPECT_NE(map2.find(1), nullptr);
+ EXPECT_NE(map2.find(2), nullptr);
+}
+
+TEST_F(FlatIntMapTest, U32PtrMap_Iterator) {
+ U32PtrMap<int> map(16);
+ map.put(1, &dummy_values_[0]);
+ map.put(2, &dummy_values_[1]);
+ map.put(3, &dummy_values_[2]);
+
+ std::unordered_set<uint32_t> found_keys;
+ for (auto it = map.begin(); it != map.end(); ++it) {
+ found_keys.insert(it->key);
+ }
+
+ EXPECT_EQ(found_keys.size(), 3);
+ EXPECT_TRUE(found_keys.count(1));
+ EXPECT_TRUE(found_keys.count(2));
+ EXPECT_TRUE(found_keys.count(3));
+}
+
+TEST_F(FlatIntMapTest, U32PtrMap_RangeBasedFor) {
+ U32PtrMap<int> map(16);
+ map.put(10, &dummy_values_[0]);
+ map.put(20, &dummy_values_[1]);
+
+ int count = 0;
+ for (const auto &entry : map) {
+ EXPECT_TRUE(entry.first == 10 || entry.first == 20);
+ ++count;
+ }
+ EXPECT_EQ(count, 2);
+}
+
+TEST_F(FlatIntMapTest, U32PtrMap_ConstFind) {
+ U32PtrMap<int> map(16);
+ map.put(1, &dummy_values_[0]);
+
+ const U32PtrMap<int> &const_map = map;
+
+ const auto *entry = const_map.find(1);
+ ASSERT_NE(entry, nullptr);
+ EXPECT_EQ(entry->key, 1u);
+}
+
+// Test for type_id lookups (simulates type_info_by_id_ usage)
+TEST_F(FlatIntMapTest, U32PtrMap_TypeIdLookups) {
+ U32PtrMap<void> map(256);
+
+ // Simulate type ID values used in TypeResolver
+ std::vector<uint32_t> type_ids;
+ for (uint32_t i = 1; i <= 50; ++i) {
+ // Simulate encoded type_id: (user_id << 8) + TypeId
+ uint32_t type_id = (i << 8) + 100; // 100 = some TypeId enum value
+ type_ids.push_back(type_id);
+ map.put(type_id, reinterpret_cast<void *>(static_cast<uintptr_t>(i)));
+ }
+
+ EXPECT_EQ(map.size(), 50);
+
+ // Verify all lookups work
+ for (size_t i = 0; i < type_ids.size(); ++i) {
+ auto *entry = map.find(type_ids[i]);
+ ASSERT_NE(entry, nullptr) << "Type ID at index " << i << " not found";
+ EXPECT_EQ(entry->value,
+ reinterpret_cast<void *>(static_cast<uintptr_t>(i + 1)));
+ }
+}
+
+// ============================================================================
+// Tests for auto-grow functionality
+// ============================================================================
+
+TEST_F(FlatIntMapTest, AutoGrow_U64PtrMap) {
+ // Start with small capacity, default load factor 0.5
+ U64PtrMap<int> map(8);
+ EXPECT_EQ(map.capacity(), 8);
+
+ // Insert more than capacity * load_factor (8 * 0.5 = 4)
+ for (int i = 1; i <= 10; ++i) {
+ map.put(static_cast<uint64_t>(i), &dummy_values_[i % 100]);
+ }
+
+ EXPECT_EQ(map.size(), 10);
+ EXPECT_GT(map.capacity(), 8); // Should have grown
+
+ // Verify all entries still accessible after grow
+ for (int i = 1; i <= 10; ++i) {
+ auto *entry = map.find(static_cast<uint64_t>(i));
+ ASSERT_NE(entry, nullptr) << "Key " << i << " not found after grow";
+ EXPECT_EQ(entry->value, &dummy_values_[i % 100]);
+ }
+}
+
+TEST_F(FlatIntMapTest, AutoGrow_U32PtrMap) {
+ U32PtrMap<int> map(8);
+ EXPECT_EQ(map.capacity(), 8);
+
+ for (uint32_t i = 1; i <= 10; ++i) {
+ map.put(i, &dummy_values_[i % 100]);
+ }
+
+ EXPECT_EQ(map.size(), 10);
+ EXPECT_GT(map.capacity(), 8);
+
+ for (uint32_t i = 1; i <= 10; ++i) {
+ auto *entry = map.find(i);
+ ASSERT_NE(entry, nullptr);
+ EXPECT_EQ(entry->value, &dummy_values_[i % 100]);
+ }
+}
+
+TEST_F(FlatIntMapTest, CustomLoadFactor) {
+ // Use higher load factor (0.75) - more memory efficient but slower lookup
+ U64PtrMap<int> map(16, 0.75f);
+ EXPECT_EQ(map.capacity(), 16);
+
+ // Can insert up to 16 * 0.75 = 12 before grow
+ for (int i = 1; i <= 12; ++i) {
+ map.put(static_cast<uint64_t>(i), &dummy_values_[i % 100]);
+ }
+ EXPECT_EQ(map.capacity(), 16); // Should not have grown yet
+
+ // One more should trigger grow
+ map.put(13, &dummy_values_[13]);
+ EXPECT_GT(map.capacity(), 16);
+}
+
+TEST_F(FlatIntMapTest, GetOrDefault_PtrMap) {
+ U64PtrMap<int> map(16);
+ map.put(1, &dummy_values_[0]);
+ map.put(2, &dummy_values_[1]);
+
+ // Test get_or_default() returns value directly
+ int *val1 = map.get_or_default(1, nullptr);
+ EXPECT_EQ(val1, &dummy_values_[0]);
+
+ int *val2 = map.get_or_default(2, nullptr);
+ EXPECT_EQ(val2, &dummy_values_[1]);
+
+ // Non-existent key returns default
+ int *val3 = map.get_or_default(999, nullptr);
+ EXPECT_EQ(val3, nullptr);
+
+ // Can use non-null default
+ int *val4 = map.get_or_default(999, &dummy_values_[50]);
+ EXPECT_EQ(val4, &dummy_values_[50]);
+}
+
+// ============================================================================
+// Tests for U64Map (uint64_t keys, scalar values)
+// ============================================================================
+
+TEST_F(FlatIntMapTest, U64Map_ScalarValues) {
+ U64Map<int> map(8);
+
+ // Insert many entries to trigger multiple grows
+ for (int i = 1; i <= 1000; ++i) {
+ map.put(static_cast<uint64_t>(i), i * 10);
+ }
+
+ EXPECT_EQ(map.size(), 1000);
+
+ // Verify all entries using get_or_default
+ for (int i = 1; i <= 1000; ++i) {
+ int val = map.get_or_default(static_cast<uint64_t>(i), -1);
+ EXPECT_EQ(val, i * 10) << "Key " << i << " has wrong value";
+ }
+
+ // Non-existent key returns default
+ EXPECT_EQ(map.get_or_default(9999, -1), -1);
+}
+
+TEST_F(FlatIntMapTest, U64Map_GetOrDefault) {
+ U64Map<int> map(16);
+ map.put(1, 100);
+ map.put(2, 200);
+
+ EXPECT_EQ(map.get_or_default(1, -1), 100);
+ EXPECT_EQ(map.get_or_default(2, -1), 200);
+ EXPECT_EQ(map.get_or_default(3, -1), -1);
+ EXPECT_EQ(map.get_or_default(3, 999), 999);
+}
+
+TEST_F(FlatIntMapTest, U32Map_ScalarValues) {
+ U32Map<int> map(16);
+
+ for (uint32_t i = 1; i <= 100; ++i) {
+ map.put(i, static_cast<int>(i * 2));
+ }
+
+ EXPECT_EQ(map.size(), 100);
+
+ for (uint32_t i = 1; i <= 100; ++i) {
+ int val = map.get_or_default(i, -1);
+ EXPECT_EQ(val, static_cast<int>(i * 2));
+ }
+}
+
+} // namespace util
+} // namespace fory
+
+int main(int argc, char **argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]