mirror of
https://github.com/greg7mdp/parallel-hashmap.git
synced 2026-08-31 09:30:38 +08:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 48f4c5fb05 | |||
| 01c838b76a | |||
| 8442f1c82c | |||
| bed0081382 | |||
| fa5a89fe79 | |||
| 4e410c7af3 | |||
| a95befa318 | |||
| 8d29d95536 | |||
| 8bd66dc8ed | |||
| 88123934b4 | |||
| ec36fa8b96 | |||
| 75196c4dd8 | |||
| def20389e8 | |||
| c29681a777 | |||
| b56fe781f1 | |||
| ea305283f3 | |||
| 0cd57d29a9 | |||
| 896f1a03e4 | |||
| 40780b29bd | |||
| 154c63489e | |||
| 2b6ef17a53 | |||
| 2ec7990176 | |||
| f358021bcc | |||
| 2d062fc539 | |||
| a4f5730495 | |||
| 640aef361d | |||
| 0bad0987f1 | |||
| 53aa8a800e | |||
| a6ce0835ca | |||
| debe606647 | |||
| 2d0b499273 | |||
| 22a7f6ca72 | |||
| c9520f1867 | |||
| 4817a6d3b8 | |||
| feb600c4e7 | |||
| 60acfa4690 |
Vendored
+1
@@ -8,5 +8,6 @@ build*
|
||||
.cache
|
||||
.gdb_history
|
||||
compile_commands.json
|
||||
.emacs.desktop*
|
||||
**/.vscode
|
||||
TAGS
|
||||
|
||||
Vendored
+2
-2
@@ -6,5 +6,5 @@ authors:
|
||||
orcid: https://orcid.org/0009-0005-7245-5930
|
||||
title: "The Parallel Hashmap C++ library"
|
||||
license: Apache-2.0
|
||||
version: 1.3.12
|
||||
date-released: 2024-01-28
|
||||
version: 2.0.0
|
||||
date-released: 2025-01-21
|
||||
|
||||
Vendored
+31
-12
@@ -8,7 +8,7 @@
|
||||
FetchContent_Declare(
|
||||
parallel-hashmap
|
||||
GIT_REPOSITORY https://github.com/greg7mdp/parallel-hashmap.git
|
||||
GIT_TAG v1.3.12 # adjust tag/branch/commit as needed
|
||||
GIT_TAG v2.0.0 # adjust tag/branch/commit as needed
|
||||
)
|
||||
FetchContent_MakeAvailable(parallel-hashmap)
|
||||
|
||||
@@ -84,6 +84,7 @@ endif()
|
||||
## ------------------------- building tests and examples -------------
|
||||
option(PHMAP_BUILD_TESTS "Whether or not to build the tests" ${PHMAP_MASTER_PROJECT})
|
||||
option(PHMAP_BUILD_EXAMPLES "Whether or not to build the examples" ${PHMAP_MASTER_PROJECT})
|
||||
option(PHMAP_DOWNLOAD_GTEST "Whether to download gtest or use installed version" ON)
|
||||
|
||||
if(MSVC)
|
||||
add_compile_options("$<$<COMPILE_LANGUAGE:CXX>:/bigobj>")
|
||||
@@ -94,14 +95,23 @@ if (PHMAP_BUILD_TESTS OR PHMAP_BUILD_EXAMPLES)
|
||||
endif()
|
||||
|
||||
if (PHMAP_BUILD_TESTS)
|
||||
|
||||
if (NOT PHMAP_GTEST_LIBS)
|
||||
include(cmake/DownloadGTest.cmake)
|
||||
if (NOT PHMAP_DOWNLOAD_GTEST)
|
||||
find_package(GTest CONFIG)
|
||||
if (GTest_FOUND)
|
||||
set(PHMAP_GTEST_LIBS GTest::gmock_main)
|
||||
else()
|
||||
set(PHMAP_DOWNLOAD_GTEST ON)
|
||||
endif()
|
||||
endif()
|
||||
if (PHMAP_DOWNLOAD_GTEST)
|
||||
include(cmake/DownloadGTest.cmake)
|
||||
|
||||
check_target(gtest)
|
||||
check_target(gtest_main)
|
||||
check_target(gmock)
|
||||
set(PHMAP_GTEST_LIBS gmock_main)
|
||||
check_target(gtest)
|
||||
check_target(gtest_main)
|
||||
check_target(gmock)
|
||||
set(PHMAP_GTEST_LIBS gmock_main)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
enable_testing()
|
||||
@@ -206,9 +216,12 @@ if (PHMAP_BUILD_EXAMPLES)
|
||||
|
||||
# llil4map.cc - see https://www.perlmonks.com/?node_id=11149643
|
||||
# -------------------------------------------------------------
|
||||
find_package(OpenMP)
|
||||
find_package(Boost 1.70.0)
|
||||
if (OPENMP_FOUND AND Boost_FOUND AND "cxx_std_20" IN_LIST CMAKE_CXX_COMPILE_FEATURES)
|
||||
if(POLICY CMP0167)
|
||||
cmake_policy(SET CMP0167 NEW)
|
||||
endif()
|
||||
find_package(OpenMP QUIET)
|
||||
find_package(Boost 1.70.0 QUIET)
|
||||
if (OpenMP_FOUND AND Boost_FOUND AND "cxx_std_20" IN_LIST CMAKE_CXX_COMPILE_FEATURES)
|
||||
add_executable(ex_llil4map examples/llil4map.cc phmap.natvis)
|
||||
target_include_directories(ex_llil4map PRIVATE ${Boost_INCLUDE_DIRS})
|
||||
target_compile_features(ex_llil4map PUBLIC cxx_std_20)
|
||||
@@ -219,11 +232,17 @@ if (PHMAP_BUILD_EXAMPLES)
|
||||
target_link_libraries(ex_llil4map PRIVATE OpenMP::OpenMP_CXX)
|
||||
target_compile_options(ex_llil4map PRIVATE "${OpenMP_CXX_FLAGS}")
|
||||
|
||||
file(COPY examples/llil_utils DESTINATION "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
endif()
|
||||
|
||||
if (Boost_FOUND)
|
||||
add_executable(ex_custom_pointer examples/custom_pointer.cc phmap.natvis)
|
||||
target_include_directories(ex_custom_pointer PRIVATE ${Boost_INCLUDE_DIRS})
|
||||
|
||||
add_executable(ex_llil examples/llil.cc phmap.natvis)
|
||||
target_include_directories(ex_llil PRIVATE ${Boost_INCLUDE_DIRS})
|
||||
target_compile_features(ex_llil PUBLIC cxx_std_20)
|
||||
|
||||
file(COPY examples/llil_utils DESTINATION "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
target_link_libraries(ex_llil Threads::Threads)
|
||||
endif()
|
||||
|
||||
target_link_libraries(ex_knucleotide Threads::Threads)
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
|
||||
[](https://opensource.org/licenses/Apache-2.0) [](https://github.com/greg7mdp/parallel-hashmap/actions/workflows/linux.yml) [](https://github.com/greg7mdp/parallel-hashmap/actions/workflows/macos.yml) [](https://github.com/greg7mdp/parallel-hashmap/actions/workflows/windows.yml)
|
||||
|
||||
## Moving to GTL
|
||||
|
||||
We encourage phmap users to switch to [gtl](https://github.com/greg7mdp/gtl) if possible. [gtl](https://github.com/greg7mdp/gtl) provides the same functionality as this repository, but requires C++20 or above.
|
||||
|
||||
Eventually, support for issues and new features will move exclusively to [gtl](https://github.com/greg7mdp/gtl).
|
||||
|
||||
## Overview
|
||||
|
||||
This repository aims to provide a set of excellent **hash map** implementations, as well as a **btree** alternative to std::map and std::set, with the following characteristics:
|
||||
@@ -34,6 +40,12 @@ This repository aims to provide a set of excellent **hash map** implementations,
|
||||
@byronhe kindly provided this [Chinese translation](https://byronhe.com/post/2020/11/10/parallel-hashmap-btree-fast-multi-thread-intro/) of the README.md.
|
||||
|
||||
|
||||
## Parallel-hashmap or GTL?
|
||||
|
||||
The observant among us may have noticed that I have two github repos, [parallel-hashmap](https://github.com/greg7mdp/parallel-hashmap) and [gtl](https://github.com/greg7mdp/gtl), which both provide very similar functionality. Indeed the hash tables in both are equivalent and the code mostly the same. The main difference is that [parallel-hashmap](https://github.com/greg7mdp/parallel-hashmap) only requires a C++11 compiler, while [gtl](https://github.com/greg7mdp/gtl) requires a C++20 compiler.
|
||||
|
||||
My recommendation would be to use [gtl](https://github.com/greg7mdp/gtl) if you are compiling with C++20 or higher, and [parallel-hashmap](https://github.com/greg7mdp/parallel-hashmap) otherwise. While the included hash maps are equivalent, [gtl](https://github.com/greg7mdp/gtl) is where new development occurs, and it will include useful new classes.
|
||||
|
||||
## Fast *and* memory friendly
|
||||
|
||||
Click here [For a full writeup explaining the design and benefits of the Parallel Hashmap](https://greg7mdp.github.io/parallel-hashmap/).
|
||||
@@ -147,7 +159,7 @@ When an ordering is not needed, a hash container is typically a better choice th
|
||||
|
||||
- The Abseil hash tables internally randomize a hash seed, so that the table iteration order is non-deterministic. This can be useful to prevent *Denial Of Service* attacks when a hash table is used for a customer facing web service, but it can make debugging more difficult. The *phmap* hashmaps by default do **not** implement this randomization, but it can be enabled by adding `#define PHMAP_NON_DETERMINISTIC 1` before including the header `phmap.h` (as is done in raw_hash_set_test.cc).
|
||||
|
||||
- Unlike the Abseil hash maps, we do an internal mixing of the hash value provided. This prevents serious degradation of the hash table performance when the hash function provided by the user has poor entropy distribution. The cost in performance is very minimal, and this helps provide reliable performance even with *imperfect* hash functions.
|
||||
- Unlike the Abseil hash maps, we do an internal mixing of the hash value provided. This prevents serious degradation of the hash table performance when the hash function provided by the user has poor entropy distribution. The cost in performance is very minimal, and this helps provide reliable performance even with *imperfect* hash functions. Disabling this mixing is possible by defining the preprocessor macro `PHMAP_DISABLE_MIX=1` before `phmap.h` is included, but it is not recommended.
|
||||
|
||||
|
||||
## Memory usage
|
||||
|
||||
Vendored
+24
-27
@@ -1,41 +1,38 @@
|
||||
- update version in phmap_config.h
|
||||
- update version in comment on top of CMakeLists.txt
|
||||
- update version in `phmap_config.h`
|
||||
- update version in `CITATION.cff`
|
||||
- update version in comment on top of `CMakeLists.txt`
|
||||
- update all versions # in `doc/new_release.md`.
|
||||
- git commit
|
||||
- git push
|
||||
- create the new release on github (tag `v1.3.8` - use semantic versioning)
|
||||
- download the tar.gz from github, and use `sha256sum parallel-hashmap-1.3.8.tar.gz` on linux to get the sha256
|
||||
- create the new release on github (tag `v2.0.0` - use semantic versioning)
|
||||
- download the tar.gz from github, and use `sha256sum parallel-hashmap-2.0.0.tar.gz` on linux to get the sha256
|
||||
|
||||
## conan
|
||||
|
||||
- fork and clone [conan-center repo](https://github.com/conan-io/conan-center-index)
|
||||
(or sync + git pull)
|
||||
- git checkout master
|
||||
- git checkout -b phmap_1.3.8
|
||||
- use [forked repo](https://github.com/greg7mdp/conan-center-index)
|
||||
- sync fork in github
|
||||
- git checkout conan-io:master
|
||||
- git checkout -b phmap_2.0.0
|
||||
- update: `recipes/parallel-hashmap/all/conandata.yml` and `recipes/parallel-hashmap/config.yml`
|
||||
- sudo pip install conan -U
|
||||
- cd recipes/parallel-hashmap/all
|
||||
- conan create conanfile.py parallel-hashmap/1.3.8@ -pr:b=default -pr:h=default
|
||||
- *does not work* conan create conanfile.py parallel-hashmap/2.0.0@ -pr:b=default -pr:h=default
|
||||
*no version??* update version in `recipes/parallel-hashmap/all/conanfile.py`
|
||||
- git diff
|
||||
- git commit -am "[parallel-hashmap] Bump version to 1.3.8"
|
||||
- git push origin phmap_1.3.8
|
||||
- git commit -am "[parallel-hashmap] Bump version to 2.0.0"
|
||||
- git push origin phmap_2.0.0
|
||||
- create PR like [this](https://github.com/conan-io/conan-center-index/pull/13161)
|
||||
|
||||
|
||||
## vcpkg
|
||||
|
||||
- fork and clone [vcpkg repo](https://github.com/microsoft/vcpkg)
|
||||
(or sync + git pull)
|
||||
- git checkout -b phmap_1.3.8
|
||||
- update ports/parallel-hashmap/portfile.cmake and ports/parallel-hashmap/vcpkg.json
|
||||
|
||||
in windows, non-cygwin console
|
||||
|
||||
- set VCPKG_ROOT=
|
||||
- vcpkg install parallel-hashmap --triplet x64-windows
|
||||
- # update sha in portfile.cmake - run `sha512sum parallel-hashmap-1.3.8.tar.gz` on linux
|
||||
- git diff
|
||||
- git commit -am "[parallel-hashmap] Bump version to 1.3.8"
|
||||
- vcpkg x-add-version --all --overwrite-version ## (or ./vcpkg.exe --no-dry-run upgrade )
|
||||
- git diff -am "[parallel-hashmap] run x-add-version"
|
||||
- git commit ...
|
||||
- git push origin phmap_1.3.8
|
||||
- use [forked repo](https://github.com/greg7mdp/vcpkg)
|
||||
- sync fork in github
|
||||
- `git checkout -b phmap_2.0.0`
|
||||
- update ports/parallel-hashmap/portfile.cmake (the sha512) and ports/parallel-hashmap/vcpkg.json (the version #)
|
||||
- commit
|
||||
- `./bootstrap-vcpkg.sh`
|
||||
- `vcpkg x-add-version --all --overwrite-version` ## (or `./vcpkg.exe --no-dry-run upgrade` )
|
||||
- commit
|
||||
- push
|
||||
- create PR
|
||||
|
||||
@@ -337,6 +337,21 @@ Timer _lookup(vector<T> &v, HT &hash, size_t &num_present)
|
||||
return timer;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
template <class HT>
|
||||
Timer _traverse_empty(int64_t capacity, HT& hash)
|
||||
{
|
||||
hash.clear();
|
||||
hash.reserve(capacity);
|
||||
Timer timer(true);
|
||||
|
||||
for (auto& pair : hash)
|
||||
if (pair.second)
|
||||
fprintf(stderr, "should not be here");
|
||||
|
||||
return timer;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
template <class T, class HT>
|
||||
Timer _delete(vector<T> &v, HT &hash)
|
||||
@@ -441,6 +456,10 @@ int main(int argc, char ** argv)
|
||||
timer = _lookup(v, hash, num_present);
|
||||
//fprintf(stderr, "found %zu\n", num_present);
|
||||
}
|
||||
else if (!strcmp(bench_name, "traverse_empty"))
|
||||
{
|
||||
timer = _traverse_empty(num_keys, hash);
|
||||
}
|
||||
else if(!strcmp(bench_name, "delete"))
|
||||
{
|
||||
vector<int64_t> v(num_keys);
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
#include <boost/interprocess/managed_mapped_file.hpp>
|
||||
#include <scoped_allocator>
|
||||
#include <parallel_hashmap/phmap.h>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
|
||||
using mmap_file_t = boost::interprocess::managed_mapped_file;
|
||||
|
||||
template <typename T>
|
||||
using bi_alloc_t = boost::interprocess::allocator<T, boost::interprocess::managed_mapped_file::segment_manager>;
|
||||
|
||||
template <typename T>
|
||||
using scoped_alloc_t = std::scoped_allocator_adaptor<T>;
|
||||
|
||||
void simple_map() {
|
||||
struct LatpLon {
|
||||
int32_t latp;
|
||||
int32_t lon;
|
||||
};
|
||||
|
||||
using nodestore_pair_t = std::pair<const uint64_t, LatpLon>;
|
||||
using map_t = phmap::flat_hash_map<const uint64_t, LatpLon, std::hash<uint64_t>, std::equal_to<uint64_t>,
|
||||
bi_alloc_t<nodestore_pair_t>>;
|
||||
|
||||
auto mmap_file =
|
||||
boost::interprocess::managed_mapped_file(boost::interprocess::open_or_create, "map_iv.dat", 1000000);
|
||||
map_t* map = mmap_file.find_or_construct<map_t>("node_store")(mmap_file.get_segment_manager());
|
||||
|
||||
for (unsigned int i = 0; i < 1000; ++i) {
|
||||
LatpLon p = {10, 10};
|
||||
map->emplace(i, p);
|
||||
}
|
||||
|
||||
std::cout << map->at(10).latp << " " << map->at(10).lon << std::endl;
|
||||
}
|
||||
|
||||
void scoped_map() {
|
||||
using way_t = std::vector<uint64_t, bi_alloc_t<uint64_t>>;
|
||||
using waystore_pair_t = std::pair<const uint64_t, way_t>;
|
||||
using map_t = phmap::flat_hash_map<const uint64_t, way_t, std::hash<uint64_t>, std::equal_to<uint64_t>,
|
||||
std::scoped_allocator_adaptor<bi_alloc_t<waystore_pair_t>>>;
|
||||
|
||||
auto mmap_file =
|
||||
boost::interprocess::managed_mapped_file(boost::interprocess::open_or_create, "map_iv.dat", 1000000);
|
||||
map_t* map = mmap_file.find_or_construct<map_t>("ways_store")(mmap_file.get_segment_manager());
|
||||
|
||||
for (unsigned int i = 0; i < 1000; ++i) {
|
||||
std::vector<uint64_t> init = {1, 2, 3, 4};
|
||||
map->emplace(std::piecewise_construct, std::forward_as_tuple(i), std::forward_as_tuple(init.begin(), init.end()));
|
||||
}
|
||||
|
||||
std::cout << map->at(10).size() << std::endl;
|
||||
for (auto const& i : map->at(10))
|
||||
std::cout << i << " ";
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
simple_map();
|
||||
scoped_map();
|
||||
return 0;
|
||||
}
|
||||
@@ -62,7 +62,7 @@ public:
|
||||
uint64_t operator()(uint64_t boundExcluded) noexcept {
|
||||
#ifdef PHMAP_HAS_UMUL128
|
||||
uint64_t h;
|
||||
(void)umul128(operator()(), boundExcluded, &h);
|
||||
(void)phmap::umul128(operator()(), boundExcluded, &h);
|
||||
return h;
|
||||
#else
|
||||
return 0;
|
||||
|
||||
+3
-2
@@ -63,7 +63,6 @@
|
||||
|
||||
#include <utility>
|
||||
#include <iterator>
|
||||
#include <execution>
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
|
||||
@@ -234,7 +233,7 @@ namespace lf = boost::lockfree;
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// ideas
|
||||
// -----
|
||||
// do a first pass that
|
||||
// * do a first pass that
|
||||
//
|
||||
// - gather string length counts (each thread has an array `size_t lengths[256]` which are
|
||||
// merged at the end)
|
||||
@@ -258,6 +257,8 @@ namespace lf = boost::lockfree;
|
||||
//
|
||||
// and the `string_cnt` used to do the find() in the map should be pointing to the string
|
||||
// in the block (no copy), so no string alloc for duplicate strings
|
||||
//
|
||||
// * use arena allocator (no need to free until the end)
|
||||
// ------------------------------------------------------------------------------------------
|
||||
template<size_t num_consumers>
|
||||
class llil_t {
|
||||
|
||||
Vendored
+84
-58
@@ -329,11 +329,17 @@ static_assert(kDeleted == -2,
|
||||
// A single block of empty control bytes for tables without any slots allocated.
|
||||
// This enables removing a branch in the hot path of find().
|
||||
// --------------------------------------------------------------------------
|
||||
template <class std_alloc_t>
|
||||
inline ctrl_t* EmptyGroup() {
|
||||
alignas(16) static constexpr ctrl_t empty_group[] = {
|
||||
kSentinel, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty,
|
||||
kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty};
|
||||
return const_cast<ctrl_t*>(empty_group);
|
||||
PHMAP_IF_CONSTEXPR (std_alloc_t::value) {
|
||||
alignas(16) static constexpr ctrl_t empty_group[] = {
|
||||
kSentinel, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty,
|
||||
kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty};
|
||||
|
||||
return const_cast<ctrl_t*>(empty_group);
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
@@ -549,7 +555,7 @@ inline bool IsValidCapacity(size_t n) { return ((n + 1) & n) == 0 && n > 0; }
|
||||
// FULL -> DELETED
|
||||
// --------------------------------------------------------------------------
|
||||
inline void ConvertDeletedToEmptyAndFullToDeleted(
|
||||
ctrl_t* ctrl, size_t capacity)
|
||||
ctrl_t* PHMAP_RESTRICT ctrl, size_t capacity)
|
||||
{
|
||||
assert(ctrl[capacity] == kSentinel);
|
||||
assert(IsValidCapacity(capacity));
|
||||
@@ -869,6 +875,8 @@ public:
|
||||
template <class K>
|
||||
using key_arg = typename KeyArgImpl::template type<K, key_type>;
|
||||
|
||||
using std_alloc_t = std::is_same<typename std::decay<Alloc>::type, phmap::priv::Allocator<value_type>>;
|
||||
|
||||
private:
|
||||
// Give an early error when key_type is not hashable/eq.
|
||||
auto KeyTypeCanBeHashed(const Hash& h, const key_type& k) -> decltype(h(k));
|
||||
@@ -918,12 +926,7 @@ private:
|
||||
using IsDecomposable = IsDecomposable<void, PolicyTraits, Hash, Eq, Ts...>;
|
||||
|
||||
public:
|
||||
static_assert(std::is_same<pointer, value_type*>::value,
|
||||
"Allocators with custom pointer types are not supported");
|
||||
static_assert(std::is_same<const_pointer, const value_type*>::value,
|
||||
"Allocators with custom pointer types are not supported");
|
||||
|
||||
class iterator
|
||||
class iterator
|
||||
{
|
||||
friend class raw_hash_set;
|
||||
|
||||
@@ -989,6 +992,11 @@ public:
|
||||
iterator(ctrl_t* ctrl, slot_type* slot) : ctrl_(ctrl), slot_(slot) {}
|
||||
|
||||
void skip_empty_or_deleted() {
|
||||
PHMAP_IF_CONSTEXPR (!std_alloc_t::value) {
|
||||
// ctrl_ could be nullptr
|
||||
if (!ctrl_)
|
||||
return;
|
||||
}
|
||||
while (IsEmptyOrDeleted(*ctrl_)) {
|
||||
// ctrl is not necessarily aligned to Group::kWidth. It is also likely
|
||||
// to read past the space for ctrl bytes and into slots. This is ok
|
||||
@@ -1057,7 +1065,7 @@ public:
|
||||
explicit raw_hash_set(size_t bucket_cnt, const hasher& hashfn = hasher(),
|
||||
const key_equal& eq = key_equal(),
|
||||
const allocator_type& alloc = allocator_type())
|
||||
: ctrl_(EmptyGroup()), settings_(0, hashfn, eq, alloc) {
|
||||
: ctrl_(EmptyGroup<std_alloc_t>()), settings_(0, hashfn, eq, alloc) {
|
||||
if (bucket_cnt) {
|
||||
size_t new_capacity = NormalizeCapacity(bucket_cnt);
|
||||
reset_growth_left(new_capacity);
|
||||
@@ -1180,7 +1188,7 @@ public:
|
||||
std::is_nothrow_copy_constructible<hasher>::value&&
|
||||
std::is_nothrow_copy_constructible<key_equal>::value&&
|
||||
std::is_nothrow_copy_constructible<allocator_type>::value)
|
||||
: ctrl_(phmap::exchange(that.ctrl_, EmptyGroup())),
|
||||
: ctrl_(phmap::exchange(that.ctrl_, EmptyGroup<std_alloc_t>())),
|
||||
slots_(phmap::exchange(that.slots_, nullptr)),
|
||||
size_(phmap::exchange(that.size_, 0)),
|
||||
capacity_(phmap::exchange(that.capacity_, 0)),
|
||||
@@ -1194,7 +1202,7 @@ public:
|
||||
}
|
||||
|
||||
raw_hash_set(raw_hash_set&& that, const allocator_type& a)
|
||||
: ctrl_(EmptyGroup()),
|
||||
: ctrl_(EmptyGroup<std_alloc_t>()),
|
||||
slots_(nullptr),
|
||||
size_(0),
|
||||
capacity_(0),
|
||||
@@ -1237,6 +1245,7 @@ public:
|
||||
~raw_hash_set() { destroy_slots(); }
|
||||
|
||||
iterator begin() {
|
||||
if (empty()) return end();
|
||||
auto it = iterator_at(0);
|
||||
it.skip_empty_or_deleted();
|
||||
return it;
|
||||
@@ -1615,6 +1624,7 @@ public:
|
||||
// This overload is necessary because otherwise erase<K>(const K&) would be
|
||||
// a better match if non-const iterator is passed as an argument.
|
||||
iterator erase(iterator it) {
|
||||
assert(it != end());
|
||||
auto res = it;
|
||||
++res;
|
||||
_erase(it);
|
||||
@@ -1738,7 +1748,8 @@ public:
|
||||
|
||||
template <class K = key_type>
|
||||
void prefetch(const key_arg<K>& key) const {
|
||||
prefetch_hash(this->hash(key));
|
||||
PHMAP_IF_CONSTEXPR (std_alloc_t::value)
|
||||
prefetch_hash(this->hash(key));
|
||||
}
|
||||
|
||||
// The API of find() has two extensions.
|
||||
@@ -1847,7 +1858,12 @@ private:
|
||||
friend struct phmap::priv::hashtable_debug_internal::HashtableDebugAccess;
|
||||
|
||||
template <class K = key_type>
|
||||
bool find_impl(const key_arg<K>& key, size_t hashval, size_t& offset) {
|
||||
bool find_impl(const key_arg<K>& PHMAP_RESTRICT key, size_t hashval, size_t& PHMAP_RESTRICT offset) {
|
||||
PHMAP_IF_CONSTEXPR (!std_alloc_t::value) {
|
||||
// ctrl_ could be nullptr
|
||||
if (!ctrl_)
|
||||
return false;
|
||||
}
|
||||
auto seq = probe(hashval);
|
||||
while (true) {
|
||||
Group g{ ctrl_ + seq.offset() };
|
||||
@@ -1877,7 +1893,11 @@ private:
|
||||
{
|
||||
template <class K, class... Args>
|
||||
size_t operator()(const K& key, Args&&...) const {
|
||||
#if PHMAP_DISABLE_MIX
|
||||
return h(key);
|
||||
#else
|
||||
return phmap_mix<sizeof(size_t)>()(h(key));
|
||||
#endif
|
||||
}
|
||||
const hasher& h;
|
||||
};
|
||||
@@ -2015,7 +2035,7 @@ private:
|
||||
std::is_same<typename Policy::is_flat, std::false_type>::value)) {
|
||||
// node map, or not trivially destructible... we need to iterate and destroy values one by one
|
||||
// std::cout << "either this is a node map or " << type_name<typename PolicyTraits::value_type>() << " is not trivially_destructible\n";
|
||||
for (size_t i = 0; i != capacity_; ++i) {
|
||||
for (size_t i = 0, cnt = capacity_; i != cnt; ++i) {
|
||||
if (IsFull(ctrl_[i])) {
|
||||
PolicyTraits::destroy(&alloc_ref(), slots_ + i);
|
||||
}
|
||||
@@ -2025,7 +2045,7 @@ private:
|
||||
// Unpoison before returning the memory to the allocator.
|
||||
SanitizerUnpoisonMemoryRegion(slots_, sizeof(slot_type) * capacity_);
|
||||
Deallocate<Layout::Alignment()>(&alloc_ref(), ctrl_, layout.AllocSize());
|
||||
ctrl_ = EmptyGroup();
|
||||
ctrl_ = EmptyGroup<std_alloc_t>();
|
||||
slots_ = nullptr;
|
||||
size_ = 0;
|
||||
capacity_ = 0;
|
||||
@@ -2134,7 +2154,12 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
bool has_element(const value_type& elem, size_t hashval) const {
|
||||
bool has_element(const value_type& PHMAP_RESTRICT elem, size_t hashval) const {
|
||||
PHMAP_IF_CONSTEXPR (!std_alloc_t::value) {
|
||||
// ctrl_ could be nullptr
|
||||
if (!ctrl_)
|
||||
return false;
|
||||
}
|
||||
auto seq = probe(hashval);
|
||||
while (true) {
|
||||
Group g{ctrl_ + seq.offset()};
|
||||
@@ -2196,7 +2221,12 @@ private:
|
||||
|
||||
protected:
|
||||
template <class K>
|
||||
size_t _find_key(const K& key, size_t hashval) {
|
||||
size_t _find_key(const K& PHMAP_RESTRICT key, size_t hashval) {
|
||||
PHMAP_IF_CONSTEXPR (!std_alloc_t::value) {
|
||||
// ctrl_ could be nullptr
|
||||
if (!ctrl_)
|
||||
return (size_t)-1;
|
||||
}
|
||||
auto seq = probe(hashval);
|
||||
while (true) {
|
||||
Group g{ctrl_ + seq.offset()};
|
||||
@@ -2221,7 +2251,12 @@ protected:
|
||||
}
|
||||
|
||||
size_t prepare_insert(size_t hashval) PHMAP_ATTRIBUTE_NOINLINE {
|
||||
auto target = find_first_non_full(hashval);
|
||||
PHMAP_IF_CONSTEXPR (!std_alloc_t::value) {
|
||||
// ctrl_ could be nullptr
|
||||
if (!ctrl_)
|
||||
rehash_and_grow_if_necessary();
|
||||
}
|
||||
FindInfo target = find_first_non_full(hashval);
|
||||
if (PHMAP_PREDICT_FALSE(growth_left() == 0 &&
|
||||
!IsDeleted(ctrl_[target.offset]))) {
|
||||
rehash_and_grow_if_necessary();
|
||||
@@ -2335,10 +2370,10 @@ private:
|
||||
// TODO(alkis): Investigate removing some of these fields:
|
||||
// - ctrl/slots can be derived from each other
|
||||
// - size can be moved into the slot array
|
||||
ctrl_t* ctrl_ = EmptyGroup(); // [(capacity + 1) * ctrl_t]
|
||||
slot_type* slots_ = nullptr; // [capacity * slot_type]
|
||||
size_t size_ = 0; // number of full slots
|
||||
size_t capacity_ = 0; // total number of slots
|
||||
ctrl_t* ctrl_ = EmptyGroup<std_alloc_t>(); // [(capacity + 1) * ctrl_t]
|
||||
slot_type* slots_ = nullptr; // [capacity * slot_type]
|
||||
size_t size_ = 0; // number of full slots
|
||||
size_t capacity_ = 0; // total number of slots
|
||||
HashtablezInfoHandle infoz_;
|
||||
std::tuple<size_t /* growth_left */, hasher, key_equal, allocator_type>
|
||||
settings_{0, hasher{}, key_equal{}, allocator_type{}};
|
||||
@@ -2582,7 +2617,6 @@ protected:
|
||||
using UniqueLock = typename Lockable::UniqueLock;
|
||||
using SharedLock = typename Lockable::SharedLock;
|
||||
using ReadWriteLock = typename Lockable::ReadWriteLock;
|
||||
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
struct Inner : public Lockable
|
||||
@@ -3126,8 +3160,8 @@ public:
|
||||
Inner& inner = sets_[subidx(hashval)];
|
||||
auto& set = inner.set_;
|
||||
UniqueLock m(inner);
|
||||
typename EmbeddedSet::template InsertSlotWithHash<true> f { inner, std::move(*slot), hashval };
|
||||
return make_rv(PolicyTraits::apply(f, elem));
|
||||
typename EmbeddedSet::template InsertSlotWithHash<true> f { inner.set_, std::move(*slot), hashval };
|
||||
return make_rv(&inner, PolicyTraits::apply(std::move(f), elem));
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
@@ -3144,14 +3178,9 @@ public:
|
||||
{
|
||||
Inner& inner = sets_[subidx(hashval)];
|
||||
auto& set = inner.set_;
|
||||
ReadWriteLock m(inner);
|
||||
UniqueLock m(inner);
|
||||
|
||||
size_t offset = set._find_key(key, hashval);
|
||||
if (offset == (size_t)-1 && m.switch_to_unique()) {
|
||||
// we did an unlock/lock, and another thread could have inserted the same key, so we need to
|
||||
// do a find() again.
|
||||
offset = set._find_key(key, hashval);
|
||||
}
|
||||
if (offset == (size_t)-1) {
|
||||
offset = set.prepare_insert(hashval);
|
||||
set.emplace_at(offset, std::forward<Args>(args)...);
|
||||
@@ -3199,15 +3228,15 @@ public:
|
||||
std::pair<iterator, bool> emplace(Args&&... args) {
|
||||
typename phmap::aligned_storage<sizeof(slot_type), alignof(slot_type)>::type raw;
|
||||
slot_type* slot = reinterpret_cast<slot_type*>(&raw);
|
||||
size_t hashval = this->hash(PolicyTraits::key(slot));
|
||||
|
||||
PolicyTraits::construct(&alloc_ref(), slot, std::forward<Args>(args)...);
|
||||
|
||||
const auto& elem = PolicyTraits::element(slot);
|
||||
size_t hashval = this->hash(PolicyTraits::key(slot));
|
||||
Inner& inner = sets_[subidx(hashval)];
|
||||
auto& set = inner.set_;
|
||||
UniqueLock m(inner);
|
||||
typename EmbeddedSet::template InsertSlotWithHash<true> f { inner, std::move(*slot), hashval };
|
||||
return make_rv(PolicyTraits::apply(f, elem));
|
||||
typename EmbeddedSet::template InsertSlotWithHash<true> f { inner.set_, std::move(*slot), hashval };
|
||||
return make_rv(&inner, PolicyTraits::apply(std::move(f), elem));
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
@@ -3234,13 +3263,8 @@ public:
|
||||
iterator lazy_emplace_with_hash(const key_arg<K>& key, size_t hashval, F&& f) {
|
||||
Inner& inner = sets_[subidx(hashval)];
|
||||
auto& set = inner.set_;
|
||||
ReadWriteLock m(inner);
|
||||
UniqueLock m(inner);
|
||||
size_t offset = set._find_key(key, hashval);
|
||||
if (offset == (size_t)-1 && m.switch_to_unique()) {
|
||||
// we did an unlock/lock, and another thread could have inserted the same key, so we need to
|
||||
// do a find() again.
|
||||
offset = set._find_key(key, hashval);
|
||||
}
|
||||
if (offset == (size_t)-1) {
|
||||
offset = set.prepare_insert(hashval);
|
||||
set.lazy_emplace_at(offset, std::forward<F>(f));
|
||||
@@ -3355,7 +3379,7 @@ public:
|
||||
template <class K = key_type, class FExists, class FEmplace>
|
||||
bool lazy_emplace_l(const key_arg<K>& key, FExists&& fExists, FEmplace&& fEmplace) {
|
||||
size_t hashval = this->hash(key);
|
||||
ReadWriteLock m;
|
||||
UniqueLock m;
|
||||
auto res = this->find_or_prepare_insert_with_hash(hashval, key, m);
|
||||
Inner* inner = std::get<0>(res);
|
||||
if (std::get<2>(res)) {
|
||||
@@ -3536,7 +3560,8 @@ public:
|
||||
class K = key_type,
|
||||
typename std::enable_if<!std::is_same<K, iterator>::value, int>::type = 0>
|
||||
node_type extract(const key_arg<K>& key) {
|
||||
auto it = find(key);
|
||||
UniqueLock m;
|
||||
auto it = this->template find<K, UniqueLock>(key, this->hash(key), m);
|
||||
return it == end() ? node_type() : extract(const_iterator{it});
|
||||
}
|
||||
|
||||
@@ -3730,7 +3755,11 @@ private:
|
||||
{
|
||||
template <class K, class... Args>
|
||||
size_t operator()(const K& key, Args&&...) const {
|
||||
#if PHMAP_DISABLE_MIX
|
||||
return h(key);
|
||||
#else
|
||||
return phmap_mix<sizeof(size_t)>()(h(key));
|
||||
#endif
|
||||
}
|
||||
const hasher& h;
|
||||
};
|
||||
@@ -3809,16 +3838,11 @@ protected:
|
||||
|
||||
template <class K>
|
||||
std::tuple<Inner*, size_t, bool>
|
||||
find_or_prepare_insert_with_hash(size_t hashval, const K& key, ReadWriteLock &mutexlock) {
|
||||
find_or_prepare_insert_with_hash(size_t hashval, const K& key, UniqueLock &mutexlock) {
|
||||
Inner& inner = sets_[subidx(hashval)];
|
||||
auto& set = inner.set_;
|
||||
mutexlock = std::move(ReadWriteLock(inner));
|
||||
mutexlock = std::move(UniqueLock(inner));
|
||||
size_t offset = set._find_key(key, hashval);
|
||||
if (offset == (size_t)-1 && mutexlock.switch_to_unique()) {
|
||||
// we did an unlock/lock, and another thread could have inserted the same key, so we need to
|
||||
// do a find() again.
|
||||
offset = set._find_key(key, hashval);
|
||||
}
|
||||
if (offset == (size_t)-1) {
|
||||
offset = set.prepare_insert(hashval);
|
||||
return std::make_tuple(&inner, offset, true);
|
||||
@@ -3828,7 +3852,7 @@ protected:
|
||||
|
||||
template <class K>
|
||||
std::tuple<Inner*, size_t, bool>
|
||||
find_or_prepare_insert(const K& key, ReadWriteLock &mutexlock) {
|
||||
find_or_prepare_insert(const K& key, UniqueLock &mutexlock) {
|
||||
return find_or_prepare_insert_with_hash<K>(this->hash(key), key, mutexlock);
|
||||
}
|
||||
|
||||
@@ -4050,7 +4074,7 @@ public:
|
||||
template <class K = key_type, class F, class... Args>
|
||||
bool try_emplace_l(K&& k, F&& f, Args&&... args) {
|
||||
size_t hashval = this->hash(k);
|
||||
ReadWriteLock m;
|
||||
UniqueLock m;
|
||||
auto res = this->find_or_prepare_insert_with_hash(hashval, k, m);
|
||||
typename Base::Inner *inner = std::get<0>(res);
|
||||
if (std::get<2>(res)) {
|
||||
@@ -4071,7 +4095,7 @@ public:
|
||||
template <class K = key_type, class... Args>
|
||||
std::pair<typename parallel_hash_map::parallel_hash_set::pointer, bool> try_emplace_p(K&& k, Args&&... args) {
|
||||
size_t hashval = this->hash(k);
|
||||
ReadWriteLock m;
|
||||
UniqueLock m;
|
||||
auto res = this->find_or_prepare_insert_with_hash(hashval, k, m);
|
||||
typename Base::Inner *inner = std::get<0>(res);
|
||||
if (std::get<2>(res)) {
|
||||
@@ -4101,7 +4125,7 @@ private:
|
||||
template <class K, class V>
|
||||
std::pair<iterator, bool> insert_or_assign_impl(K&& k, V&& v) {
|
||||
size_t hashval = this->hash(k);
|
||||
ReadWriteLock m;
|
||||
UniqueLock m;
|
||||
auto res = this->find_or_prepare_insert_with_hash(hashval, k, m);
|
||||
typename Base::Inner *inner = std::get<0>(res);
|
||||
if (std::get<2>(res)) {
|
||||
@@ -4121,7 +4145,7 @@ private:
|
||||
|
||||
template <class K = key_type, class... Args>
|
||||
std::pair<iterator, bool> try_emplace_impl_with_hash(size_t hashval, K&& k, Args&&... args) {
|
||||
ReadWriteLock m;
|
||||
UniqueLock m;
|
||||
auto res = this->find_or_prepare_insert_with_hash(hashval, k, m);
|
||||
typename Base::Inner *inner = std::get<0>(res);
|
||||
if (std::get<2>(res)) {
|
||||
@@ -4576,6 +4600,8 @@ struct HashtableDebugAccess<Set, typename std::enable_if<has_member_type_raw_has
|
||||
|
||||
static size_t GetNumProbes(const Set& set,
|
||||
const typename Set::key_type& key) {
|
||||
if (!set.ctrl_)
|
||||
return 0;
|
||||
size_t num_probes = 0;
|
||||
size_t hashval = set.hash(key);
|
||||
auto seq = set.probe(hashval);
|
||||
|
||||
Vendored
+40
-28
@@ -47,6 +47,7 @@
|
||||
#include <utility>
|
||||
#include <memory>
|
||||
#include <mutex> // for std::lock
|
||||
#include <cstdlib>
|
||||
|
||||
#include "phmap_config.h"
|
||||
|
||||
@@ -423,18 +424,30 @@ namespace priv {
|
||||
template <class Policy, class = void>
|
||||
struct hash_policy_traits
|
||||
{
|
||||
// The type of the keys stored in the hashtable.
|
||||
using key_type = typename Policy::key_type;
|
||||
private:
|
||||
struct ReturnKey
|
||||
{
|
||||
// We return `Key` here.
|
||||
// When Key=T&, we forward the lvalue reference.
|
||||
// When Key=T, we return by value to avoid a dangling reference.
|
||||
// eg, for string_hash_map.
|
||||
template <class Key, class... Args>
|
||||
Key operator()(Key&& k, const Args&...) const {
|
||||
return std::forward<Key>(k);
|
||||
}
|
||||
};
|
||||
struct ReturnKey {
|
||||
template <class Key,
|
||||
phmap::enable_if_t<std::is_lvalue_reference<Key>::value, int> = 0>
|
||||
static key_type& Impl(Key&& k, int) {
|
||||
return *const_cast<key_type*>(std::addressof(std::forward<Key>(k)));
|
||||
}
|
||||
|
||||
template <class Key>
|
||||
static Key Impl(Key&& k, char) {
|
||||
return std::forward<Key>(k);
|
||||
}
|
||||
|
||||
// When Key=T&, we forward the lvalue reference.
|
||||
// When Key=T, we return by value to avoid a dangling reference.
|
||||
// eg, for string_hash_map.
|
||||
template <class Key, class... Args>
|
||||
auto operator()(Key&& k, const Args&...) const
|
||||
-> decltype(Impl(std::forward<Key>(k), 0)) {
|
||||
return Impl(std::forward<Key>(k), 0);
|
||||
}
|
||||
};
|
||||
|
||||
template <class P = Policy, class = void>
|
||||
struct ConstantIteratorsImpl : std::false_type {};
|
||||
@@ -447,9 +460,6 @@ public:
|
||||
// The actual object stored in the hash table.
|
||||
using slot_type = typename Policy::slot_type;
|
||||
|
||||
// The type of the keys stored in the hashtable.
|
||||
using key_type = typename Policy::key_type;
|
||||
|
||||
// The argument type for insertions into the hashtable. This is different
|
||||
// from value_type for increased performance. See initializer_list constructor
|
||||
// and insert() member functions for more details.
|
||||
@@ -1233,36 +1243,36 @@ using ExtractOrT = typename ExtractOr<Extract, Obj, Default, void>::type;
|
||||
|
||||
// Extractors for the features of allocators.
|
||||
template <typename T>
|
||||
using GetPointer = typename T::pointer;
|
||||
using GetPointer = typename std::allocator_traits<T>::pointer;
|
||||
|
||||
template <typename T>
|
||||
using GetConstPointer = typename T::const_pointer;
|
||||
using GetConstPointer = typename std::allocator_traits<T>::const_pointer;
|
||||
|
||||
template <typename T>
|
||||
using GetVoidPointer = typename T::void_pointer;
|
||||
using GetVoidPointer = typename std::allocator_traits<T>::void_pointer;
|
||||
|
||||
template <typename T>
|
||||
using GetConstVoidPointer = typename T::const_void_pointer;
|
||||
using GetConstVoidPointer = typename std::allocator_traits<T>::const_void_pointer;
|
||||
|
||||
template <typename T>
|
||||
using GetDifferenceType = typename T::difference_type;
|
||||
using GetDifferenceType = typename std::allocator_traits<T>::difference_type;
|
||||
|
||||
template <typename T>
|
||||
using GetSizeType = typename T::size_type;
|
||||
using GetSizeType = typename std::allocator_traits<T>::size_type;
|
||||
|
||||
template <typename T>
|
||||
using GetPropagateOnContainerCopyAssignment =
|
||||
typename T::propagate_on_container_copy_assignment;
|
||||
typename std::allocator_traits<T>::propagate_on_container_copy_assignment;
|
||||
|
||||
template <typename T>
|
||||
using GetPropagateOnContainerMoveAssignment =
|
||||
typename T::propagate_on_container_move_assignment;
|
||||
typename std::allocator_traits<T>::propagate_on_container_move_assignment;
|
||||
|
||||
template <typename T>
|
||||
using GetPropagateOnContainerSwap = typename T::propagate_on_container_swap;
|
||||
using GetPropagateOnContainerSwap = typename std::allocator_traits<T>::propagate_on_container_swap;
|
||||
|
||||
template <typename T>
|
||||
using GetIsAlwaysEqual = typename T::is_always_equal;
|
||||
using GetIsAlwaysEqual = typename std::allocator_traits<T>::is_always_equal;
|
||||
|
||||
template <typename T>
|
||||
struct GetFirstArg;
|
||||
@@ -2774,7 +2784,9 @@ public:
|
||||
node_handle_base& operator=(node_handle_base&& other) noexcept {
|
||||
destroy();
|
||||
if (!other.empty()) {
|
||||
alloc_ = other.alloc_;
|
||||
if (other.alloc_) {
|
||||
alloc_.emplace(other.alloc_.value());
|
||||
}
|
||||
PolicyTraits::transfer(alloc(), slot(), other.slot());
|
||||
other.reset();
|
||||
}
|
||||
@@ -4189,7 +4201,7 @@ void* Allocate(Alloc* alloc, size_t n) {
|
||||
using A = typename phmap::allocator_traits<Alloc>::template rebind_alloc<M>;
|
||||
using AT = typename phmap::allocator_traits<Alloc>::template rebind_traits<M>;
|
||||
A mem_alloc(*alloc);
|
||||
void* p = AT::allocate(mem_alloc, (n + sizeof(M) - 1) / sizeof(M));
|
||||
void* p = &*AT::allocate(mem_alloc, (n + sizeof(M) - 1) / sizeof(M)); // `&*` to support custom pointers such as boost offset_ptr.
|
||||
assert(reinterpret_cast<uintptr_t>(p) % Alignment == 0 &&
|
||||
"allocator does not respect alignment");
|
||||
return p;
|
||||
@@ -5005,10 +5017,10 @@ public:
|
||||
{
|
||||
void lock() ABSL_EXCLUSIVE_LOCK_FUNCTION() { this->Lock(); }
|
||||
void unlock() ABSL_UNLOCK_FUNCTION() { this->Unlock(); }
|
||||
void try_lock() ABSL_EXCLUSIVE_TRYLOCK_FUNCTION(true) { this->TryLock(); }
|
||||
bool try_lock() ABSL_EXCLUSIVE_TRYLOCK_FUNCTION(true) { return this->TryLock(); }
|
||||
void lock_shared() ABSL_SHARED_LOCK_FUNCTION() { this->ReaderLock(); }
|
||||
void unlock_shared() ABSL_UNLOCK_FUNCTION() { this->ReaderUnlock(); }
|
||||
void try_lock_shared() ABSL_SHARED_TRYLOCK_FUNCTION(true) { this->ReaderTryLock(); }
|
||||
bool try_lock_shared() ABSL_SHARED_TRYLOCK_FUNCTION(true) { return this->ReaderTryLock(); }
|
||||
};
|
||||
|
||||
template <>
|
||||
|
||||
Vendored
+34
-33
@@ -176,35 +176,6 @@ inline void UnalignedStore64(void *p, uint64_t v) { memcpy(p, &v, sizeof v); }
|
||||
#define PHMAP_BLOCK_TAIL_CALL_OPTIMIZATION() if (volatile int x = 0) { (void)x; }
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wpedantic"
|
||||
#endif
|
||||
|
||||
#ifdef PHMAP_HAVE_INTRINSIC_INT128
|
||||
__extension__ typedef unsigned __int128 phmap_uint128;
|
||||
inline uint64_t umul128(uint64_t a, uint64_t b, uint64_t* high)
|
||||
{
|
||||
auto result = static_cast<phmap_uint128>(a) * static_cast<phmap_uint128>(b);
|
||||
*high = static_cast<uint64_t>(result >> 64);
|
||||
return static_cast<uint64_t>(result);
|
||||
}
|
||||
#define PHMAP_HAS_UMUL128 1
|
||||
#elif (defined(_MSC_VER))
|
||||
#if defined(_M_X64)
|
||||
#pragma intrinsic(_umul128)
|
||||
inline uint64_t umul128(uint64_t a, uint64_t b, uint64_t* high)
|
||||
{
|
||||
return _umul128(a, b, high);
|
||||
}
|
||||
#define PHMAP_HAS_UMUL128 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__)
|
||||
// Cache line alignment
|
||||
#if defined(__i386__) || defined(__x86_64__)
|
||||
@@ -268,6 +239,36 @@ inline void UnalignedStore64(void *p, uint64_t v) { memcpy(p, &v, sizeof v); }
|
||||
|
||||
|
||||
namespace phmap {
|
||||
|
||||
#if defined(__GNUC__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wpedantic"
|
||||
#endif
|
||||
|
||||
#ifdef PHMAP_HAVE_INTRINSIC_INT128
|
||||
__extension__ typedef unsigned __int128 phmap_uint128;
|
||||
inline uint64_t umul128(uint64_t a, uint64_t b, uint64_t* high)
|
||||
{
|
||||
auto result = static_cast<phmap_uint128>(a) * static_cast<phmap_uint128>(b);
|
||||
*high = static_cast<uint64_t>(result >> 64);
|
||||
return static_cast<uint64_t>(result);
|
||||
}
|
||||
#define PHMAP_HAS_UMUL128 1
|
||||
#elif (defined(_MSC_VER))
|
||||
#if defined(_M_X64)
|
||||
#pragma intrinsic(_umul128)
|
||||
inline uint64_t umul128(uint64_t a, uint64_t b, uint64_t* high)
|
||||
{
|
||||
return _umul128(a, b, high);
|
||||
}
|
||||
#define PHMAP_HAS_UMUL128 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
namespace base_internal {
|
||||
|
||||
PHMAP_BASE_INTERNAL_FORCEINLINE uint32_t CountLeadingZeros64Slow(uint64_t n) {
|
||||
@@ -320,7 +321,7 @@ PHMAP_BASE_INTERNAL_FORCEINLINE uint32_t CountLeadingZeros32Slow(uint64_t n) {
|
||||
if (n >> 16) zeroes -= 16, n >>= 16;
|
||||
if (n >> 8) zeroes -= 8, n >>= 8;
|
||||
if (n >> 4) zeroes -= 4, n >>= 4;
|
||||
return "\4\3\2\2\1\1\1\1\0\0\0\0\0\0\0"[n] + zeroes;
|
||||
return static_cast<uint32_t>("\4\3\2\2\1\1\1\1\0\0\0\0\0\0\0"[n]) + zeroes;
|
||||
}
|
||||
|
||||
PHMAP_BASE_INTERNAL_FORCEINLINE uint32_t CountLeadingZeros32(uint32_t n) {
|
||||
@@ -342,7 +343,7 @@ PHMAP_BASE_INTERNAL_FORCEINLINE uint32_t CountLeadingZeros32(uint32_t n) {
|
||||
if (n == 0) {
|
||||
return 32;
|
||||
}
|
||||
return __builtin_clz(n);
|
||||
return static_cast<uint32_t>(__builtin_clz(n));
|
||||
#else
|
||||
return CountLeadingZeros32Slow(n);
|
||||
#endif
|
||||
@@ -376,7 +377,7 @@ PHMAP_BASE_INTERNAL_FORCEINLINE uint32_t CountTrailingZerosNonZero64(uint64_t n)
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
static_assert(sizeof(unsigned long long) == sizeof(n), // NOLINT(runtime/int)
|
||||
"__builtin_ctzll does not take 64-bit arg");
|
||||
return __builtin_ctzll(n);
|
||||
return static_cast<uint32_t>(__builtin_ctzll(n));
|
||||
#else
|
||||
return CountTrailingZerosNonZero64Slow(n);
|
||||
#endif
|
||||
@@ -401,7 +402,7 @@ PHMAP_BASE_INTERNAL_FORCEINLINE uint32_t CountTrailingZerosNonZero32(uint32_t n)
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
static_assert(sizeof(int) == sizeof(n),
|
||||
"__builtin_ctz does not take 32-bit arg");
|
||||
return __builtin_ctz(n);
|
||||
return static_cast<uint32_t>(__builtin_ctz(n));
|
||||
#else
|
||||
return CountTrailingZerosNonZero32Slow(n);
|
||||
#endif
|
||||
|
||||
Vendored
+19
-4
@@ -34,8 +34,8 @@
|
||||
// limitations under the License.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define PHMAP_VERSION_MAJOR 1
|
||||
#define PHMAP_VERSION_MINOR 4
|
||||
#define PHMAP_VERSION_MAJOR 2
|
||||
#define PHMAP_VERSION_MINOR 0
|
||||
#define PHMAP_VERSION_PATCH 0
|
||||
|
||||
// Included for the __GLIBC__ macro (or similar macros on other systems).
|
||||
@@ -182,11 +182,15 @@
|
||||
// Checks whether the __int128 compiler extension for a 128-bit
|
||||
// integral type is supported.
|
||||
// ------------------------------------------------------------
|
||||
#if defined(__arm__) && !defined(__aarch64__)
|
||||
#define PHMAP_ARM_32
|
||||
#endif
|
||||
|
||||
#ifdef PHMAP_HAVE_INTRINSIC_INT128
|
||||
#error PHMAP_HAVE_INTRINSIC_INT128 cannot be directly set
|
||||
#elif defined(__SIZEOF_INT128__)
|
||||
#if (defined(__clang__) && !defined(_WIN32) && !defined(__aarch64__)) || \
|
||||
(defined(__CUDACC__) && __CUDACC_VER_MAJOR__ >= 9) || \
|
||||
#if (defined(__clang__) && !defined(_WIN32) && !(defined(PHMAP_ARM_32))) || \
|
||||
(defined(__CUDACC__) && __CUDACC_VER_MAJOR__ >= 9) || \
|
||||
(defined(__GNUC__) && !defined(__clang__) && !defined(__CUDACC__))
|
||||
#define PHMAP_HAVE_INTRINSIC_INT128 1
|
||||
#elif defined(__CUDACC__)
|
||||
@@ -661,6 +665,17 @@
|
||||
#define PHMAP_BUILTIN_UNREACHABLE() (void)0
|
||||
#endif
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// RESTRICT
|
||||
// ----------------------------------------------------------------------
|
||||
#if (defined(__GNUC__) && (__GNUC__ > 3)) || defined(__clang__)
|
||||
#define PHMAP_RESTRICT __restrict__
|
||||
#elif defined(_MSC_VER) && _MSC_VER >= 1400
|
||||
#define PHMAP_RESTRICT __restrict
|
||||
#else
|
||||
#define PHMAP_RESTRICT
|
||||
#endif
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// base/macros.h
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
Vendored
+34
-11
@@ -21,7 +21,7 @@
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <functional>
|
||||
#include "phmap.h"
|
||||
namespace phmap
|
||||
{
|
||||
@@ -94,6 +94,8 @@ bool raw_hash_set<Policy, Hash, Eq, Alloc>::phmap_load(InputArchive& ar) {
|
||||
if (version >= s_version_base) {
|
||||
// growth_left should be restored after calling initialize_slots() which resets it.
|
||||
ar.loadBinary(&growth_left(), sizeof(size_t));
|
||||
} else {
|
||||
drop_deletes_without_resize();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -166,22 +168,32 @@ bool parallel_hash_set<N, RefSet, Mtx_, Policy, Hash, Eq, Alloc>::phmap_load(Inp
|
||||
class BinaryOutputArchive {
|
||||
public:
|
||||
BinaryOutputArchive(const char *file_path) {
|
||||
ofs_.open(file_path, std::ofstream::out | std::ofstream::trunc | std::ofstream::binary);
|
||||
os_ = new std::ofstream(file_path, std::ofstream::out |
|
||||
std::ofstream::trunc |
|
||||
std::ofstream::binary);
|
||||
destruct_ = [this]() { delete os_; };
|
||||
}
|
||||
|
||||
~BinaryOutputArchive() = default;
|
||||
BinaryOutputArchive(std::ostream &os) : os_(&os) {}
|
||||
|
||||
~BinaryOutputArchive() {
|
||||
if (destruct_) {
|
||||
destruct_();
|
||||
}
|
||||
}
|
||||
|
||||
BinaryOutputArchive(const BinaryOutputArchive&) = delete;
|
||||
BinaryOutputArchive& operator=(const BinaryOutputArchive&) = delete;
|
||||
|
||||
bool saveBinary(const void *p, size_t sz) {
|
||||
ofs_.write(reinterpret_cast<const char*>(p), (std::streamsize)sz);
|
||||
os_->write(reinterpret_cast<const char*>(p), (std::streamsize)sz);
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename V>
|
||||
typename std::enable_if<type_traits_internal::IsTriviallyCopyable<V>::value, bool>::type
|
||||
saveBinary(const V& v) {
|
||||
ofs_.write(reinterpret_cast<const char *>(&v), sizeof(V));
|
||||
os_->write(reinterpret_cast<const char *>(&v), sizeof(V));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -192,29 +204,39 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
std::ofstream ofs_;
|
||||
std::ostream* os_;
|
||||
std::function<void()> destruct_;
|
||||
};
|
||||
|
||||
|
||||
class BinaryInputArchive {
|
||||
public:
|
||||
BinaryInputArchive(const char * file_path) {
|
||||
ifs_.open(file_path, std::ofstream::in | std::ofstream::binary);
|
||||
is_ = new std::ifstream(file_path,
|
||||
std::ifstream::in | std::ifstream::binary);
|
||||
destruct_ = [this]() { delete is_; };
|
||||
}
|
||||
|
||||
BinaryInputArchive(std::istream& is) : is_(&is) {}
|
||||
|
||||
~BinaryInputArchive() = default;
|
||||
~BinaryInputArchive() {
|
||||
if (destruct_) {
|
||||
destruct_();
|
||||
}
|
||||
}
|
||||
|
||||
BinaryInputArchive(const BinaryInputArchive&) = delete;
|
||||
BinaryInputArchive& operator=(const BinaryInputArchive&) = delete;
|
||||
|
||||
bool loadBinary(void* p, size_t sz) {
|
||||
ifs_.read(reinterpret_cast<char*>(p), (std::streamsize)sz);
|
||||
is_->read(reinterpret_cast<char*>(p), (std::streamsize)sz);
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename V>
|
||||
typename std::enable_if<type_traits_internal::IsTriviallyCopyable<V>::value, bool>::type
|
||||
loadBinary(V* v) {
|
||||
ifs_.read(reinterpret_cast<char *>(v), sizeof(V));
|
||||
is_->read(reinterpret_cast<char *>(v), sizeof(V));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -225,7 +247,8 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
std::ifstream ifs_;
|
||||
std::istream* is_;
|
||||
std::function<void()> destruct_;
|
||||
};
|
||||
|
||||
} // namespace phmap
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <parallel_hashmap/phmap.h>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
@@ -22,6 +26,16 @@ TEST(DumpLoad, FlatHashSet_uint32) {
|
||||
EXPECT_TRUE(st2.phmap_load(ar_in));
|
||||
}
|
||||
EXPECT_TRUE(st1 == st2);
|
||||
|
||||
{
|
||||
std::stringstream ss;
|
||||
phmap::BinaryOutputArchive ar_out(ss);
|
||||
EXPECT_TRUE(st1.phmap_dump(ar_out));
|
||||
phmap::flat_hash_set<uint32_t> st3;
|
||||
phmap::BinaryInputArchive ar_in(ss);
|
||||
EXPECT_TRUE(st3.phmap_load(ar_in));
|
||||
EXPECT_TRUE(st1 == st3);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DumpLoad, FlatHashMap_uint64_uint32) {
|
||||
@@ -39,6 +53,16 @@ TEST(DumpLoad, FlatHashMap_uint64_uint32) {
|
||||
EXPECT_TRUE(mp2.phmap_load(ar_in));
|
||||
}
|
||||
|
||||
{
|
||||
std::stringstream ss;
|
||||
phmap::BinaryOutputArchive ar_out(ss);
|
||||
EXPECT_TRUE(mp1.phmap_dump(ar_out));
|
||||
phmap::flat_hash_map<uint64_t, uint32_t> mp3;
|
||||
phmap::BinaryInputArchive ar_in(ss);
|
||||
EXPECT_TRUE(mp3.phmap_load(ar_in));
|
||||
EXPECT_TRUE(mp1 == mp3);
|
||||
}
|
||||
|
||||
EXPECT_TRUE(mp1 == mp2);
|
||||
}
|
||||
|
||||
@@ -57,6 +81,24 @@ TEST(DumpLoad, ParallelFlatHashMap_uint64_uint32) {
|
||||
EXPECT_TRUE(mp2.phmap_load(ar_in));
|
||||
}
|
||||
EXPECT_TRUE(mp1 == mp2);
|
||||
|
||||
// test stringstream and dump/load in the middle of the stream
|
||||
{
|
||||
char hello[] = "Hello";
|
||||
std::stringstream ss;
|
||||
ss.write(hello, 5);
|
||||
phmap::BinaryOutputArchive ar_out(ss);
|
||||
EXPECT_TRUE(mp1.phmap_dump(ar_out));
|
||||
phmap::parallel_flat_hash_map<uint64_t, uint32_t> mp3;
|
||||
phmap::BinaryInputArchive ar_in(ss);
|
||||
char s[5];
|
||||
ss.read(s, 5);
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
EXPECT_EQ(hello[i], s[i]);
|
||||
}
|
||||
EXPECT_TRUE(mp3.phmap_load(ar_in));
|
||||
EXPECT_TRUE(mp1 == mp3);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Vendored
+7
-17
@@ -76,7 +76,7 @@ std::mt19937_64* GetSharedRng() {
|
||||
}
|
||||
|
||||
|
||||
enum Enum {
|
||||
enum Enum : uint64_t {
|
||||
kEnumEmpty,
|
||||
kEnumDeleted,
|
||||
};
|
||||
@@ -96,34 +96,24 @@ struct Generator;
|
||||
template <class T>
|
||||
struct Generator<T, typename std::enable_if<std::is_integral<T>::value>::type> {
|
||||
T operator()() const {
|
||||
std::uniform_int_distribution<T> dist;
|
||||
return dist(*GetSharedRng());
|
||||
std::uniform_int_distribution<T> dist;
|
||||
return dist(*GetSharedRng());
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Generator<Enum> {
|
||||
Enum operator()() const {
|
||||
std::uniform_int_distribution<typename std::underlying_type<Enum>::type> dist;
|
||||
|
||||
while (true) {
|
||||
auto variate = dist(*GetSharedRng());
|
||||
if (variate != kEnumEmpty && variate != kEnumDeleted)
|
||||
return static_cast<Enum>(variate);
|
||||
}
|
||||
std::uniform_int_distribution<typename std::underlying_type<Enum>::type> dist;
|
||||
return static_cast<Enum>(dist(*GetSharedRng()));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Generator<EnumClass> {
|
||||
EnumClass operator()() const {
|
||||
std::uniform_int_distribution<
|
||||
typename std::underlying_type<EnumClass>::type> dist;
|
||||
while (true) {
|
||||
EnumClass variate = static_cast<EnumClass>(dist(*GetSharedRng()));
|
||||
if (variate != EnumClass::kEmpty && variate != EnumClass::kDeleted)
|
||||
return static_cast<EnumClass>(variate);
|
||||
}
|
||||
std::uniform_int_distribution<typename std::underlying_type<EnumClass>::type> dist;
|
||||
return static_cast<EnumClass>(dist(*GetSharedRng()));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -105,6 +105,14 @@ TEST(THIS_TEST_NAME, MergeExtractInsert) {
|
||||
EXPECT_THAT(set2, UnorderedElementsAre(Pointee(7), Pointee(23)));
|
||||
}
|
||||
|
||||
TEST(THIS_TEST_NAME, Emplace) {
|
||||
using Thing = std::tuple<size_t, double>;
|
||||
phmap::THIS_HASH_SET<Thing> hs;
|
||||
hs.emplace(Thing(0, 1.25));
|
||||
hs.emplace(0, 1.3);
|
||||
EXPECT_TRUE(hs.find(Thing(0, 1.3)) != hs.end());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace priv
|
||||
} // namespace phmap
|
||||
|
||||
@@ -1,4 +1,52 @@
|
||||
#define THIS_HASH_MAP parallel_flat_hash_map
|
||||
#define THIS_TEST_NAME ParallelFlatHashMap
|
||||
#include <thread>
|
||||
|
||||
#include "parallel_hash_map_test.cc"
|
||||
|
||||
|
||||
#if PHMAP_HAVE_SHARED_MUTEX
|
||||
#include <shared_mutex>
|
||||
|
||||
template <typename K> using HashEqual = phmap::priv::hash_default_eq<K>;
|
||||
template <typename V> using HashFn = phmap::priv::hash_default_hash<V>;
|
||||
template <typename K> using Allocator = phmap::priv::Allocator<K>;
|
||||
|
||||
template <typename K, typename V, size_t N>
|
||||
using parallel_flat_hash_map =
|
||||
phmap::parallel_flat_hash_map<K, V, HashFn<K>, HashEqual<K>,
|
||||
Allocator<phmap::priv::Pair<K, V>>, N,
|
||||
std::shared_mutex>;
|
||||
|
||||
using Table = parallel_flat_hash_map<int, int, 10>;
|
||||
|
||||
TEST(THIS_TEST_NAME, ConcurrencyCheck) {
|
||||
static constexpr int THREADS = 10;
|
||||
static constexpr int EPOCH = 1000;
|
||||
static constexpr int KEY = 12345;
|
||||
|
||||
auto Incr = [](Table *table) {
|
||||
auto exist_fn = [](typename Table::value_type &value) { value.second += 1; };
|
||||
auto emplace_fn = [](const typename Table::constructor &ctor) {
|
||||
ctor(KEY, 1);
|
||||
};
|
||||
for (int i = 0; i < EPOCH; ++i) {
|
||||
(void)table->lazy_emplace_l(KEY, exist_fn, emplace_fn);
|
||||
}
|
||||
};
|
||||
|
||||
Table table;
|
||||
std::vector<std::thread> threads;
|
||||
threads.reserve(THREADS);
|
||||
for (int i = 0; i < THREADS; ++i) {
|
||||
threads.emplace_back([&]() { Incr(&table); });
|
||||
}
|
||||
|
||||
for (auto &thread : threads) {
|
||||
thread.join();
|
||||
}
|
||||
|
||||
EXPECT_EQ(table[KEY], 10000);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -162,7 +162,7 @@ TEST(BitMask, LeadingTrailing) {
|
||||
}
|
||||
|
||||
TEST(Group, EmptyGroup) {
|
||||
for (h2_t h = 0; h != 128; ++h) EXPECT_FALSE(Group{EmptyGroup()}.Match(h));
|
||||
for (h2_t h = 0; h != 128; ++h) EXPECT_FALSE(Group{EmptyGroup<std::true_type>()}.Match(h));
|
||||
}
|
||||
|
||||
TEST(Group, Match) {
|
||||
|
||||
Reference in New Issue
Block a user