Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8149197fa8 | ||
|
|
c2484f05f0 | ||
|
|
f7e2132bdb | ||
|
|
07a8b2b14d | ||
|
|
2eac49f0ed | ||
|
|
6a8d7857e2 | ||
|
|
ef34ec5e87 | ||
|
|
518a5d2e1e | ||
|
|
3ea8a768ca | ||
|
|
4b877d363a | ||
|
|
2395db7bb9 | ||
|
|
7327053eea | ||
|
|
af19d99b70 | ||
|
|
64f9370fff | ||
|
|
ca193de243 | ||
|
|
2914925b4d | ||
|
|
bc37851725 | ||
|
|
f89a31849c | ||
|
|
ec945810eb | ||
|
|
68ddaba57a | ||
|
|
c48c2afc56 | ||
|
|
01a5dd0dd1 | ||
|
|
300a8e61bd | ||
|
|
1d2be664a0 | ||
|
|
e6b1ef9e1e | ||
|
|
4efb55146a | ||
|
|
8877d21237 | ||
|
|
44c50cd2ea | ||
|
|
e7c1e1a56e | ||
|
|
9a22f8fbee | ||
|
|
51520de04b | ||
|
|
6e0f76f4c2 | ||
|
|
06aa4b5c19 | ||
|
|
332540c857 | ||
|
|
fdedb6c957 | ||
|
|
7aaa2e9452 | ||
|
|
950e640fcf | ||
|
|
7874625c08 | ||
|
|
2ab4225473 | ||
|
|
c5df4ec069 | ||
|
|
55d79204be | ||
|
|
f5d5299b88 | ||
|
|
253a9bccf6 | ||
|
|
61f11a58ee | ||
|
|
e78dc311e3 | ||
|
|
a5748c2e8b | ||
|
|
146c5cb6be | ||
|
|
c63a88032d | ||
|
|
3ad164267a | ||
|
|
a9203ed93c | ||
|
|
16550ded0c | ||
|
|
b4c75abca9 | ||
|
|
a696bdecf6 | ||
|
|
8865a940fc | ||
|
|
2a28698c8c | ||
|
|
96f5983f88 | ||
|
|
ddb1148a31 | ||
|
|
ffcae204ee | ||
|
|
f28527c4d8 | ||
|
|
528f7d4b12 | ||
|
|
a140de4254 | ||
|
|
f1bc948be8 | ||
|
|
3d640ac032 | ||
|
|
ff10b287e2 | ||
|
|
4a416501c8 | ||
|
|
bf06fa97e3 | ||
|
|
fcf6fee0f6 | ||
|
|
8ddfc8ec7a | ||
|
|
5f249bc681 | ||
|
|
dbd1a929e6 | ||
|
|
32ff2f145e | ||
|
|
63026fd320 | ||
|
|
6295c7f0d4 | ||
|
|
c3879e238d | ||
|
|
9260bff8f8 | ||
|
|
f48fc70f4b | ||
|
|
48f703132e | ||
|
|
69ee0039e0 | ||
|
|
add01e2dfd | ||
|
|
4b4db3dfb3 | ||
|
|
81480feeb4 | ||
|
|
d615a08f76 | ||
|
|
dacc1c8234 | ||
|
|
511e2b3272 | ||
|
|
bcf5d0cf13 | ||
|
|
80a1904d92 | ||
|
|
21afc69894 | ||
|
|
c90b72a643 | ||
|
|
c2c34f96a3 | ||
|
|
69ba1c7c00 | ||
|
|
ba25041fc8 | ||
|
|
02197674f4 | ||
|
|
f0fe62d6ab | ||
|
|
53328766b9 | ||
|
|
5b775345ba | ||
|
|
99b0868283 | ||
|
|
1c98a4a8f1 | ||
|
|
bf73366117 | ||
|
|
719394c522 | ||
|
|
03fccc1947 | ||
|
|
814264082f | ||
|
|
70e3dc4628 | ||
|
|
b72dbef1a9 | ||
|
|
26924c73b9 | ||
|
|
23e720a968 | ||
|
|
4fb7751b55 | ||
|
|
3c0fb0fa1b | ||
|
|
3fe0807ae9 | ||
|
|
dfb4f2a28a | ||
|
|
cc4cfc7ef2 | ||
|
|
a9bf367d6e | ||
|
|
2ea0dbf30e | ||
|
|
40c4d456f3 | ||
|
|
011b7a5969 | ||
|
|
c52ad849ea | ||
|
|
615ce1e9b6 | ||
|
|
bee9a3cb1a | ||
|
|
081932221f | ||
|
|
135c9586af | ||
|
|
187fd3e71e | ||
|
|
0959df1896 | ||
|
|
7833a8359d | ||
|
|
212c6a1e4d | ||
|
|
d7acb7e8b8 | ||
|
|
0bc4f2c4b9 | ||
|
|
2b612ed120 | ||
|
|
a7c15e86fc | ||
|
|
4c117ab20a | ||
|
|
e9c6a0fef5 | ||
|
|
ac216a93c8 | ||
|
|
e49fef5f9a |
@@ -6,7 +6,7 @@ local library = "unordered";
|
||||
|
||||
local triggers =
|
||||
{
|
||||
branch: [ "master", "develop", "feature/*", "bugfix/*", "fix/*", "pr/*" ]
|
||||
branch: [ "master", "develop", "bugfix/*", "fix/*", "pr/*" ]
|
||||
};
|
||||
|
||||
local ubsan = { UBSAN: '1', UBSAN_OPTIONS: 'print_stacktrace=1' };
|
||||
@@ -158,16 +158,29 @@ local windows_pipeline(name, image, environment, arch = "amd64") =
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 18.04 GCC 8 32/64",
|
||||
"Linux 18.04 GCC 8 32/64 (03,11)",
|
||||
"cppalliance/droneubuntu1804:1",
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++-8', CXXSTD: '03,11,14,17', ADDRMD: '32,64' },
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++-8', CXXSTD: '03,11', ADDRMD: '32,64' },
|
||||
"g++-8-multilib",
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 20.04 GCC 9* 32/64",
|
||||
"Linux 18.04 GCC 8 32/64 (14,17)",
|
||||
"cppalliance/droneubuntu1804:1",
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++-8', CXXSTD: '14,17', ADDRMD: '32,64' },
|
||||
"g++-8-multilib",
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 20.04 GCC 9* 32/64 (03,11,14)",
|
||||
"cppalliance/droneubuntu2004:1",
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++', CXXSTD: '03,11,14,17,2a', ADDRMD: '32,64' },
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++', CXXSTD: '03,11,14', ADDRMD: '32,64' },
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 20.04 GCC 9* 32/64 (17,2a)",
|
||||
"cppalliance/droneubuntu2004:1",
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++', CXXSTD: '17,2a', ADDRMD: '32,64' },
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
@@ -178,36 +191,77 @@ local windows_pipeline(name, image, environment, arch = "amd64") =
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 20.04 GCC 9* S390x",
|
||||
"Linux 20.04 GCC 9* S390x (03,11,14)",
|
||||
"cppalliance/droneubuntu2004:multiarch",
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++', CXXSTD: '03,11,14,17,2a' },
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++', CXXSTD: '03,11,14' },
|
||||
arch="s390x",
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 20.04 GCC 10 32/64",
|
||||
"Linux 20.04 GCC 9* S390x (17,2a)",
|
||||
"cppalliance/droneubuntu2004:multiarch",
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++', CXXSTD: '17,2a' },
|
||||
arch="s390x",
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 20.04 GCC 10 32/64 (03,11,14)",
|
||||
"cppalliance/droneubuntu2004:1",
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++-10', CXXSTD: '03,11,14,17,20', ADDRMD: '32,64' },
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++-10', CXXSTD: '03,11,14', ADDRMD: '32,64' },
|
||||
"g++-10-multilib",
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 22.04 GCC 11* 32/64",
|
||||
"Linux 20.04 GCC 10 32/64 (17,20)",
|
||||
"cppalliance/droneubuntu2004:1",
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++-10', CXXSTD: '17,20', ADDRMD: '32,64' },
|
||||
"g++-10-multilib",
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 22.04 GCC 11* 32/64 (03,11,14)",
|
||||
"cppalliance/droneubuntu2204:1",
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++', CXXSTD: '03,11,14,17,2a', ADDRMD: '32,64' },
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++', CXXSTD: '03,11,14', ADDRMD: '32,64' },
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 22.04 GCC 11* 32/64 (17,2a)",
|
||||
"cppalliance/droneubuntu2204:1",
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++', CXXSTD: '17,2a', ADDRMD: '32,64' },
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 22.04 GCC 12 32 ASAN (03,11,14)",
|
||||
"cppalliance/droneubuntu2204:1",
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '03,11,14', ADDRMD: '32' } + asan,
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '03,11', ADDRMD: '32' } + asan,
|
||||
"g++-12-multilib",
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 22.04 GCC 12 32 ASAN (17,20,2b)",
|
||||
"Linux 22.04 GCC 12 32 ASAN (14)",
|
||||
"cppalliance/droneubuntu2204:1",
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '17,20,2b', ADDRMD: '32' } + asan,
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '14', ADDRMD: '32' } + asan,
|
||||
"g++-12-multilib",
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 22.04 GCC 12 32 ASAN (17)",
|
||||
"cppalliance/droneubuntu2204:1",
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '17', ADDRMD: '32' } + asan,
|
||||
"g++-12-multilib",
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 22.04 GCC 12 32 ASAN (20)",
|
||||
"cppalliance/droneubuntu2204:1",
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '20', ADDRMD: '32' } + asan,
|
||||
"g++-12-multilib",
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 22.04 GCC 12 32 ASAN (2b)",
|
||||
"cppalliance/droneubuntu2204:1",
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '2b', ADDRMD: '32' } + asan,
|
||||
"g++-12-multilib",
|
||||
),
|
||||
|
||||
@@ -219,9 +273,23 @@ local windows_pipeline(name, image, environment, arch = "amd64") =
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 22.04 GCC 12 64 ASAN (17,20,2b)",
|
||||
"Linux 22.04 GCC 12 64 ASAN (17)",
|
||||
"cppalliance/droneubuntu2204:1",
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '17,20,2b', ADDRMD: '64' } + asan,
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '17', ADDRMD: '64' } + asan,
|
||||
"g++-12-multilib",
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 22.04 GCC 12 64 ASAN (20)",
|
||||
"cppalliance/droneubuntu2204:1",
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '20', ADDRMD: '64' } + asan,
|
||||
"g++-12-multilib",
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 22.04 GCC 12 64 ASAN (2b)",
|
||||
"cppalliance/droneubuntu2204:1",
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++-12', CXXSTD: '2b', ADDRMD: '64' } + asan,
|
||||
"g++-12-multilib",
|
||||
),
|
||||
|
||||
@@ -232,6 +300,20 @@ local windows_pipeline(name, image, environment, arch = "amd64") =
|
||||
"g++-12-multilib",
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 23.04 GCC 13 32/64 (03,11,14)",
|
||||
"cppalliance/droneubuntu2304:1",
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++-13', CXXSTD: '03,11,14', ADDRMD: '32,64' },
|
||||
"g++-13 g++-13-multilib",
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 23.04 GCC 13 32/64 (17,20,2b)",
|
||||
"cppalliance/droneubuntu2304:1",
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++-13', CXXSTD: '17,20,2b', ADDRMD: '32,64' },
|
||||
"g++-13 g++-13-multilib",
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 16.04 Clang 3.5",
|
||||
"cppalliance/droneubuntu1604:1",
|
||||
@@ -338,16 +420,30 @@ local windows_pipeline(name, image, environment, arch = "amd64") =
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 22.04 Clang 14 UBSAN",
|
||||
"Linux 22.04 Clang 14 UBSAN (03,11,14)",
|
||||
"cppalliance/droneubuntu2204:1",
|
||||
{ TOOLSET: 'clang', COMPILER: 'clang++-14', CXXSTD: '03,11,14,17,20' } + ubsan,
|
||||
{ TOOLSET: 'clang', COMPILER: 'clang++-14', CXXSTD: '03,11,14' } + ubsan,
|
||||
"clang-14",
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 22.04 Clang 14 ASAN",
|
||||
"Linux 22.04 Clang 14 UBSAN (17,20)",
|
||||
"cppalliance/droneubuntu2204:1",
|
||||
{ TOOLSET: 'clang', COMPILER: 'clang++-14', CXXSTD: '03,11,14,17,20' } + asan,
|
||||
{ TOOLSET: 'clang', COMPILER: 'clang++-14', CXXSTD: '17,20' } + ubsan,
|
||||
"clang-14",
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 22.04 Clang 14 ASAN (03,11,14)",
|
||||
"cppalliance/droneubuntu2204:1",
|
||||
{ TOOLSET: 'clang', COMPILER: 'clang++-14', CXXSTD: '03,11,14' } + asan,
|
||||
"clang-14",
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 22.04 Clang 14 ASAN (17,20)",
|
||||
"cppalliance/droneubuntu2204:1",
|
||||
{ TOOLSET: 'clang', COMPILER: 'clang++-14', CXXSTD: '17,20' } + asan,
|
||||
"clang-14",
|
||||
),
|
||||
|
||||
@@ -367,8 +463,18 @@ local windows_pipeline(name, image, environment, arch = "amd64") =
|
||||
),
|
||||
|
||||
macos_pipeline(
|
||||
"MacOS 10.15 Xcode 12.2 UBSAN",
|
||||
{ TOOLSET: 'clang', COMPILER: 'clang++', CXXSTD: '03,11,14,1z' } + ubsan,
|
||||
"MacOS 10.15 Xcode 12.2 UBSAN (03,11)",
|
||||
{ TOOLSET: 'clang', COMPILER: 'clang++', CXXSTD: '03,11' } + ubsan,
|
||||
),
|
||||
|
||||
macos_pipeline(
|
||||
"MacOS 10.15 Xcode 12.2 UBSAN (14)",
|
||||
{ TOOLSET: 'clang', COMPILER: 'clang++', CXXSTD: '14' } + ubsan,
|
||||
),
|
||||
|
||||
macos_pipeline(
|
||||
"MacOS 10.15 Xcode 12.2 UBSAN (1z)",
|
||||
{ TOOLSET: 'clang', COMPILER: 'clang++', CXXSTD: '1z' } + ubsan,
|
||||
),
|
||||
|
||||
macos_pipeline(
|
||||
|
||||
@@ -15,7 +15,6 @@ on:
|
||||
- master
|
||||
- develop
|
||||
- bugfix/**
|
||||
- feature/**
|
||||
- fix/**
|
||||
- pr/**
|
||||
|
||||
@@ -52,7 +51,7 @@ jobs:
|
||||
- { name: "gcc-12 w/ sanitizers (17,20,2b)", sanitize: yes,
|
||||
compiler: gcc-12, cxxstd: '17,20,2b', os: ubuntu-22.04, ccache_key: "san2" }
|
||||
- { name: Collect coverage, coverage: yes,
|
||||
compiler: gcc-8, cxxstd: '03,11', os: ubuntu-20.04, install: 'g++-8-multilib', address-model: '32,64', ccache_key: "cov" }
|
||||
compiler: gcc-12, cxxstd: '03,20', os: ubuntu-22.04, install: 'g++-12-multilib', address-model: '32,64', ccache_key: "cov" }
|
||||
|
||||
- { name: "cfoa tsan (gcc)", cxxstd: '11,14,17,20,2b', os: ubuntu-22.04, compiler: gcc-12,
|
||||
targets: 'libs/unordered/test//cfoa_tests', thread-sanitize: yes }
|
||||
|
||||
@@ -22,6 +22,7 @@ target_link_libraries(boost_unordered
|
||||
Boost::mp11
|
||||
Boost::predef
|
||||
Boost::preprocessor
|
||||
Boost::static_assert
|
||||
Boost::throw_exception
|
||||
Boost::tuple
|
||||
Boost::type_traits
|
||||
|
||||
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
@@ -13,8 +13,10 @@
|
||||
include::unordered/intro.adoc[]
|
||||
include::unordered/buckets.adoc[]
|
||||
include::unordered/hash_equality.adoc[]
|
||||
include::unordered/comparison.adoc[]
|
||||
include::unordered/regular.adoc[]
|
||||
include::unordered/concurrent.adoc[]
|
||||
include::unordered/compliance.adoc[]
|
||||
include::unordered/structures.adoc[]
|
||||
include::unordered/benchmarks.adoc[]
|
||||
include::unordered/rationale.adoc[]
|
||||
include::unordered/ref.adoc[]
|
||||
|
||||
@@ -431,3 +431,263 @@ h|unsuccessful lookup
|
||||
|
||||
|===
|
||||
|
||||
== boost::concurrent_flat_map
|
||||
|
||||
All benchmarks were created using:
|
||||
|
||||
* `https://spec.oneapi.io/versions/latest/elements/oneTBB/source/containers/concurrent_hash_map_cls.html[oneapi::tbb::concurrent_hash_map^]<int, int>`
|
||||
* `https://github.com/greg7mdp/gtl/blob/main/docs/phmap.md[gtl::parallel_flat_hash_map^]<int, int>` with 64 submaps
|
||||
* `boost::concurrent_flat_map<int, int>`
|
||||
|
||||
The source code can be https://github.com/boostorg/boost_unordered_benchmarks/tree/boost_concurrent_flat_map[found here^].
|
||||
|
||||
The benchmarks exercise a number of threads _T_ (between 1 and 16) concurrently performing operations
|
||||
randomly chosen among **update**, **successful lookup** and **unsuccessful lookup**. The keys used in the
|
||||
operations follow a https://en.wikipedia.org/wiki/Zipf%27s_law#Formal_definition[Zipf distribution^]
|
||||
with different _skew_ parameters: the higher the skew, the more concentrated are the keys in the lower values
|
||||
of the covered range.
|
||||
|
||||
=== GCC 12, x64
|
||||
|
||||
|
||||
[caption=]
|
||||
[cols="3*^.^a", frame=all, grid=all]
|
||||
|===
|
||||
|
||||
|image::benchmarks-concurrent_map/gcc-x64/Parallel%20workload.xlsx.500k%2C%200.01.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/gcc-x64/Parallel%20workload.xlsx.500k%2C%200.01.png]
|
||||
|image::benchmarks-concurrent_map/gcc-x64/Parallel%20workload.xlsx.500k%2C%200.5.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/gcc-x64/Parallel%20workload.xlsx.500k%2C%200.5.png]
|
||||
|image::benchmarks-concurrent_map/gcc-x64/Parallel%20workload.xlsx.500k%2C%200.99.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/gcc-x64/Parallel%20workload.xlsx.500k%2C%200.99.png]
|
||||
|
||||
h|500k updates, 4.5M lookups +
|
||||
skew=0.01
|
||||
h|500k updates, 4.5M lookups +
|
||||
skew=0.5
|
||||
h|500k updates, 4.5M lookups +
|
||||
skew=0.99
|
||||
|===
|
||||
|
||||
[caption=]
|
||||
[cols="3*^.^a", frame=all, grid=all]
|
||||
|===
|
||||
|
||||
|image::benchmarks-concurrent_map/gcc-x64/Parallel%20workload.xlsx.5M%2C%200.01.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/gcc-x64/Parallel%20workload.xlsx.5M%2C%200.01.png]
|
||||
|image::benchmarks-concurrent_map/gcc-x64/Parallel%20workload.xlsx.5M%2C%200.5.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/gcc-x64/Parallel%20workload.xlsx.5M%2C%200.5.png]
|
||||
|image::benchmarks-concurrent_map/gcc-x64/Parallel%20workload.xlsx.5M%2C%200.99.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/gcc-x64/Parallel%20workload.xlsx.5M%2C%200.99.png]
|
||||
|
||||
h|5M updates, 45M lookups +
|
||||
skew=0.01
|
||||
h|5M updates, 45M lookups +
|
||||
skew=0.5
|
||||
h|5M updates, 45M lookups +
|
||||
skew=0.99
|
||||
|===
|
||||
|
||||
=== Clang 15, x64
|
||||
|
||||
|
||||
[caption=]
|
||||
[cols="3*^.^a", frame=all, grid=all]
|
||||
|===
|
||||
|
||||
|image::benchmarks-concurrent_map/clang-x64/Parallel%20workload.xlsx.500k%2C%200.01.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/clang-x64/Parallel%20workload.xlsx.500k%2C%200.01.png]
|
||||
|image::benchmarks-concurrent_map/clang-x64/Parallel%20workload.xlsx.500k%2C%200.5.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/clang-x64/Parallel%20workload.xlsx.500k%2C%200.5.png]
|
||||
|image::benchmarks-concurrent_map/clang-x64/Parallel%20workload.xlsx.500k%2C%200.99.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/clang-x64/Parallel%20workload.xlsx.500k%2C%200.99.png]
|
||||
|
||||
h|500k updates, 4.5M lookups +
|
||||
skew=0.01
|
||||
h|500k updates, 4.5M lookups +
|
||||
skew=0.5
|
||||
h|500k updates, 4.5M lookups +
|
||||
skew=0.99
|
||||
|===
|
||||
|
||||
[caption=]
|
||||
[cols="3*^.^a", frame=all, grid=all]
|
||||
|===
|
||||
|
||||
|image::benchmarks-concurrent_map/clang-x64/Parallel%20workload.xlsx.5M%2C%200.01.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/clang-x64/Parallel%20workload.xlsx.5M%2C%200.01.png]
|
||||
|image::benchmarks-concurrent_map/clang-x64/Parallel%20workload.xlsx.5M%2C%200.5.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/clang-x64/Parallel%20workload.xlsx.5M%2C%200.5.png]
|
||||
|image::benchmarks-concurrent_map/clang-x64/Parallel%20workload.xlsx.5M%2C%200.99.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/clang-x64/Parallel%20workload.xlsx.5M%2C%200.99.png]
|
||||
|
||||
h|5M updates, 45M lookups +
|
||||
skew=0.01
|
||||
h|5M updates, 45M lookups +
|
||||
skew=0.5
|
||||
h|5M updates, 45M lookups +
|
||||
skew=0.99
|
||||
|===
|
||||
|
||||
=== Visual Studio 2022, x64
|
||||
|
||||
|
||||
[caption=]
|
||||
[cols="3*^.^a", frame=all, grid=all]
|
||||
|===
|
||||
|
||||
|image::benchmarks-concurrent_map/vs-x64/Parallel%20workload.xlsx.500k%2C%200.01.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/vs-x64/Parallel%20workload.xlsx.500k%2C%200.01.png]
|
||||
|image::benchmarks-concurrent_map/vs-x64/Parallel%20workload.xlsx.500k%2C%200.5.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/vs-x64/Parallel%20workload.xlsx.500k%2C%200.5.png]
|
||||
|image::benchmarks-concurrent_map/vs-x64/Parallel%20workload.xlsx.500k%2C%200.99.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/vs-x64/Parallel%20workload.xlsx.500k%2C%200.99.png]
|
||||
|
||||
h|500k updates, 4.5M lookups +
|
||||
skew=0.01
|
||||
h|500k updates, 4.5M lookups +
|
||||
skew=0.5
|
||||
h|500k updates, 4.5M lookups +
|
||||
skew=0.99
|
||||
|===
|
||||
|
||||
[caption=]
|
||||
[cols="3*^.^a", frame=all, grid=all]
|
||||
|===
|
||||
|
||||
|image::benchmarks-concurrent_map/vs-x64/Parallel%20workload.xlsx.5M%2C%200.01.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/vs-x64/Parallel%20workload.xlsx.5M%2C%200.01.png]
|
||||
|image::benchmarks-concurrent_map/vs-x64/Parallel%20workload.xlsx.5M%2C%200.5.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/vs-x64/Parallel%20workload.xlsx.5M%2C%200.5.png]
|
||||
|image::benchmarks-concurrent_map/vs-x64/Parallel%20workload.xlsx.5M%2C%200.99.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/vs-x64/Parallel%20workload.xlsx.5M%2C%200.99.png]
|
||||
|
||||
h|5M updates, 45M lookups +
|
||||
skew=0.01
|
||||
h|5M updates, 45M lookups +
|
||||
skew=0.5
|
||||
h|5M updates, 45M lookups +
|
||||
skew=0.99
|
||||
|===
|
||||
|
||||
=== Clang 12, ARM64
|
||||
|
||||
|
||||
[caption=]
|
||||
[cols="3*^.^a", frame=all, grid=all]
|
||||
|===
|
||||
|
||||
|image::benchmarks-concurrent_map/clang-arm64/Parallel%20workload.xlsx.500k%2C%200.01.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/clang-arm64/Parallel%20workload.xlsx.500k%2C%200.01.png]
|
||||
|image::benchmarks-concurrent_map/clang-arm64/Parallel%20workload.xlsx.500k%2C%200.5.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/clang-arm64/Parallel%20workload.xlsx.500k%2C%200.5.png]
|
||||
|image::benchmarks-concurrent_map/clang-arm64/Parallel%20workload.xlsx.500k%2C%200.99.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/clang-arm64/Parallel%20workload.xlsx.500k%2C%200.99.png]
|
||||
|
||||
h|500k updates, 4.5M lookups +
|
||||
skew=0.01
|
||||
h|500k updates, 4.5M lookups +
|
||||
skew=0.5
|
||||
h|500k updates, 4.5M lookups +
|
||||
skew=0.99
|
||||
|===
|
||||
|
||||
[caption=]
|
||||
[cols="3*^.^a", frame=all, grid=all]
|
||||
|===
|
||||
|
||||
|image::benchmarks-concurrent_map/clang-arm64/Parallel%20workload.xlsx.5M%2C%200.01.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/clang-arm64/Parallel%20workload.xlsx.5M%2C%200.01.png]
|
||||
|image::benchmarks-concurrent_map/clang-arm64/Parallel%20workload.xlsx.5M%2C%200.5.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/clang-arm64/Parallel%20workload.xlsx.5M%2C%200.5.png]
|
||||
|image::benchmarks-concurrent_map/clang-arm64/Parallel%20workload.xlsx.5M%2C%200.99.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/clang-arm64/Parallel%20workload.xlsx.5M%2C%200.99.png]
|
||||
|
||||
h|5M updates, 45M lookups +
|
||||
skew=0.01
|
||||
h|5M updates, 45M lookups +
|
||||
skew=0.5
|
||||
h|5M updates, 45M lookups +
|
||||
skew=0.99
|
||||
|===
|
||||
|
||||
=== GCC 12, x86
|
||||
|
||||
|
||||
[caption=]
|
||||
[cols="3*^.^a", frame=all, grid=all]
|
||||
|===
|
||||
|
||||
|image::benchmarks-concurrent_map/gcc-x86/Parallel%20workload.xlsx.500k%2C%200.01.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/gcc-x86/Parallel%20workload.xlsx.500k%2C%200.01.png]
|
||||
|image::benchmarks-concurrent_map/gcc-x86/Parallel%20workload.xlsx.500k%2C%200.5.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/gcc-x86/Parallel%20workload.xlsx.500k%2C%200.5.png]
|
||||
|image::benchmarks-concurrent_map/gcc-x86/Parallel%20workload.xlsx.500k%2C%200.99.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/gcc-x86/Parallel%20workload.xlsx.500k%2C%200.99.png]
|
||||
|
||||
h|500k updates, 4.5M lookups +
|
||||
skew=0.01
|
||||
h|500k updates, 4.5M lookups +
|
||||
skew=0.5
|
||||
h|500k updates, 4.5M lookups +
|
||||
skew=0.99
|
||||
|===
|
||||
|
||||
[caption=]
|
||||
[cols="3*^.^a", frame=all, grid=all]
|
||||
|===
|
||||
|
||||
|image::benchmarks-concurrent_map/gcc-x86/Parallel%20workload.xlsx.5M%2C%200.01.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/gcc-x86/Parallel%20workload.xlsx.5M%2C%200.01.png]
|
||||
|image::benchmarks-concurrent_map/gcc-x86/Parallel%20workload.xlsx.5M%2C%200.5.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/gcc-x86/Parallel%20workload.xlsx.5M%2C%200.5.png]
|
||||
|image::benchmarks-concurrent_map/gcc-x86/Parallel%20workload.xlsx.5M%2C%200.99.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/gcc-x86/Parallel%20workload.xlsx.5M%2C%200.99.png]
|
||||
|
||||
h|5M updates, 45M lookups +
|
||||
skew=0.01
|
||||
h|5M updates, 45M lookups +
|
||||
skew=0.5
|
||||
h|5M updates, 45M lookups +
|
||||
skew=0.99
|
||||
|===
|
||||
|
||||
=== Clang 15, x86
|
||||
|
||||
|
||||
[caption=]
|
||||
[cols="3*^.^a", frame=all, grid=all]
|
||||
|===
|
||||
|
||||
|image::benchmarks-concurrent_map/clang-x86/Parallel%20workload.xlsx.500k%2C%200.01.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/clang-x86/Parallel%20workload.xlsx.500k%2C%200.01.png]
|
||||
|image::benchmarks-concurrent_map/clang-x86/Parallel%20workload.xlsx.500k%2C%200.5.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/clang-x86/Parallel%20workload.xlsx.500k%2C%200.5.png]
|
||||
|image::benchmarks-concurrent_map/clang-x86/Parallel%20workload.xlsx.500k%2C%200.99.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/clang-x86/Parallel%20workload.xlsx.500k%2C%200.99.png]
|
||||
|
||||
h|500k updates, 4.5M lookups +
|
||||
skew=0.01
|
||||
h|500k updates, 4.5M lookups +
|
||||
skew=0.5
|
||||
h|500k updates, 4.5M lookups +
|
||||
skew=0.99
|
||||
|===
|
||||
|
||||
[caption=]
|
||||
[cols="3*^.^a", frame=all, grid=all]
|
||||
|===
|
||||
|
||||
|image::benchmarks-concurrent_map/clang-x86/Parallel%20workload.xlsx.5M%2C%200.01.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/clang-x86/Parallel%20workload.xlsx.5M%2C%200.01.png]
|
||||
|image::benchmarks-concurrent_map/clang-x86/Parallel%20workload.xlsx.5M%2C%200.5.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/clang-x86/Parallel%20workload.xlsx.5M%2C%200.5.png]
|
||||
|image::benchmarks-concurrent_map/clang-x86/Parallel%20workload.xlsx.5M%2C%200.99.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/clang-x86/Parallel%20workload.xlsx.5M%2C%200.99.png]
|
||||
|
||||
h|5M updates, 45M lookups +
|
||||
skew=0.01
|
||||
h|5M updates, 45M lookups +
|
||||
skew=0.5
|
||||
h|5M updates, 45M lookups +
|
||||
skew=0.99
|
||||
|===
|
||||
|
||||
=== Visual Studio 2022, x86
|
||||
|
||||
|
||||
[caption=]
|
||||
[cols="3*^.^a", frame=all, grid=all]
|
||||
|===
|
||||
|
||||
|image::benchmarks-concurrent_map/vs-x86/Parallel%20workload.xlsx.500k%2C%200.01.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/vs-x86/Parallel%20workload.xlsx.500k%2C%200.01.png]
|
||||
|image::benchmarks-concurrent_map/vs-x86/Parallel%20workload.xlsx.500k%2C%200.5.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/vs-x86/Parallel%20workload.xlsx.500k%2C%200.5.png]
|
||||
|image::benchmarks-concurrent_map/vs-x86/Parallel%20workload.xlsx.500k%2C%200.99.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/vs-x86/Parallel%20workload.xlsx.500k%2C%200.99.png]
|
||||
|
||||
h|500k updates, 4.5M lookups +
|
||||
skew=0.01
|
||||
h|500k updates, 4.5M lookups +
|
||||
skew=0.5
|
||||
h|500k updates, 4.5M lookups +
|
||||
skew=0.99
|
||||
|===
|
||||
|
||||
[caption=]
|
||||
[cols="3*^.^a", frame=all, grid=all]
|
||||
|===
|
||||
|
||||
|image::benchmarks-concurrent_map/vs-x86/Parallel%20workload.xlsx.5M%2C%200.01.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/vs-x86/Parallel%20workload.xlsx.5M%2C%200.01.png]
|
||||
|image::benchmarks-concurrent_map/vs-x86/Parallel%20workload.xlsx.5M%2C%200.5.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/vs-x86/Parallel%20workload.xlsx.5M%2C%200.5.png]
|
||||
|image::benchmarks-concurrent_map/vs-x86/Parallel%20workload.xlsx.5M%2C%200.99.png[width=250,window=_blank,link=../diagrams/benchmarks-concurrent_map/vs-x86/Parallel%20workload.xlsx.5M%2C%200.99.png]
|
||||
|
||||
h|5M updates, 45M lookups +
|
||||
skew=0.01
|
||||
h|5M updates, 45M lookups +
|
||||
skew=0.5
|
||||
h|5M updates, 45M lookups +
|
||||
skew=0.99
|
||||
|===
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
:idprefix: buckets_
|
||||
:imagesdir: ../diagrams
|
||||
|
||||
= The Data Structure
|
||||
= Basics of Hash Tables
|
||||
|
||||
The containers are made up of a number of 'buckets', each of which can contain
|
||||
The containers are made up of a number of _buckets_, each of which can contain
|
||||
any number of elements. For example, the following diagram shows a <<unordered_set,`boost::unordered_set`>> with 7 buckets containing 5 elements, `A`,
|
||||
`B`, `C`, `D` and `E` (this is just for illustration, containers will typically
|
||||
have more buckets).
|
||||
@@ -12,8 +12,7 @@ have more buckets).
|
||||
image::buckets.png[]
|
||||
|
||||
In order to decide which bucket to place an element in, the container applies
|
||||
the hash function, `Hash`, to the element's key (for `unordered_set` and
|
||||
`unordered_multiset` the key is the whole element, but is referred to as the key
|
||||
the hash function, `Hash`, to the element's key (for sets the key is the whole element, but is referred to as the key
|
||||
so that the same terminology can be used for sets and maps). This returns a
|
||||
value of type `std::size_t`. `std::size_t` has a much greater range of values
|
||||
then the number of buckets, so the container applies another transformation to
|
||||
@@ -53,8 +52,7 @@ h|*Method* h|*Description*
|
||||
|`size_type bucket_count() const`
|
||||
|The number of buckets.
|
||||
|
||||
2+^h| *Closed-addressing containers only* +
|
||||
`boost::unordered_[multi]set`, `boost::unordered_[multi]map`
|
||||
2+^h| *Closed-addressing containers only*
|
||||
h|*Method* h|*Description*
|
||||
|
||||
|`size_type max_bucket_count() const`
|
||||
@@ -80,7 +78,7 @@ h|*Method* h|*Description*
|
||||
|
||||
|===
|
||||
|
||||
== Controlling the number of buckets
|
||||
== Controlling the Number of Buckets
|
||||
|
||||
As more elements are added to an unordered associative container, the number
|
||||
of collisions will increase causing performance to degrade.
|
||||
@@ -90,8 +88,8 @@ calling `rehash`.
|
||||
|
||||
The standard leaves a lot of freedom to the implementer to decide how the
|
||||
number of buckets is chosen, but it does make some requirements based on the
|
||||
container's 'load factor', the number of elements divided by the number of buckets.
|
||||
Containers also have a 'maximum load factor' which they should try to keep the
|
||||
container's _load factor_, the number of elements divided by the number of buckets.
|
||||
Containers also have a _maximum load factor_ which they should try to keep the
|
||||
load factor below.
|
||||
|
||||
You can't control the bucket count directly but there are two ways to
|
||||
@@ -133,9 +131,7 @@ h|*Method* h|*Description*
|
||||
|`void rehash(size_type n)`
|
||||
|Changes the number of buckets so that there at least `n` buckets, and so that the load factor is less than the maximum load factor.
|
||||
|
||||
2+^h| *Open-addressing containers only* +
|
||||
`boost::unordered_flat_set`, `boost::unordered_flat_map` +
|
||||
`boost::unordered_node_set`, `boost::unordered_node_map` +
|
||||
2+^h| *Open-addressing and concurrent containers only*
|
||||
h|*Method* h|*Description*
|
||||
|
||||
|`size_type max_load() const`
|
||||
@@ -143,7 +139,7 @@ h|*Method* h|*Description*
|
||||
|
||||
|===
|
||||
|
||||
A note on `max_load` for open-addressing containers: the maximum load will be
|
||||
A note on `max_load` for open-addressing and concurrent containers: the maximum load will be
|
||||
(`max_load_factor() * bucket_count()`) right after `rehash` or on container creation, but may
|
||||
slightly decrease when erasing elements in high-load situations. For instance, if we
|
||||
have a <<unordered_flat_map,`boost::unordered_flat_map`>> with `size()` almost
|
||||
@@ -151,165 +147,4 @@ at `max_load()` level and then erase 1,000 elements, `max_load()` may decrease b
|
||||
few dozen elements. This is done internally by Boost.Unordered in order
|
||||
to keep its performance stable, and must be taken into account when planning for rehash-free insertions.
|
||||
|
||||
== Iterator Invalidation
|
||||
|
||||
It is not specified how member functions other than `rehash` and `reserve` affect
|
||||
the bucket count, although `insert` can only invalidate iterators
|
||||
when the insertion causes the container's load to be greater than the maximum allowed.
|
||||
For most implementations this means that `insert` will only
|
||||
change the number of buckets when this happens. Iterators can be
|
||||
invalidated by calls to `insert`, `rehash` and `reserve`.
|
||||
|
||||
As for pointers and references,
|
||||
they are never invalidated for node-based containers
|
||||
(`boost::unordered_[multi]set`, `boost::unordered_[multi]map`, `boost::unordered_node_set`, `boost::unordered_node_map`),
|
||||
but they will when rehashing occurs for
|
||||
`boost::unordered_flat_set` and `boost::unordered_flat_map`: this is because
|
||||
these containers store elements directly into their holding buckets, so
|
||||
when allocating a new bucket array the elements must be transferred by means of move construction.
|
||||
|
||||
In a similar manner to using `reserve` for ``vector``s, it can be a good idea
|
||||
to call `reserve` before inserting a large number of elements. This will get
|
||||
the expensive rehashing out of the way and let you store iterators, safe in
|
||||
the knowledge that they won't be invalidated. If you are inserting `n`
|
||||
elements into container `x`, you could first call:
|
||||
|
||||
```
|
||||
x.reserve(n);
|
||||
```
|
||||
|
||||
Note:: `reserve(n)` reserves space for at least `n` elements, allocating enough buckets
|
||||
so as to not exceed the maximum load factor.
|
||||
+
|
||||
Because the maximum load factor is defined as the number of elements divided by the total
|
||||
number of available buckets, this function is logically equivalent to:
|
||||
+
|
||||
```
|
||||
x.rehash(std::ceil(n / x.max_load_factor()))
|
||||
```
|
||||
+
|
||||
See the <<unordered_map_rehash,reference for more details>> on the `rehash` function.
|
||||
|
||||
== Fast Closed Addressing Implementation
|
||||
|
||||
++++
|
||||
<style>
|
||||
.imageblock > .title {
|
||||
text-align: inherit;
|
||||
}
|
||||
</style>
|
||||
++++
|
||||
|
||||
Boost.Unordered sports one of the fastest implementations of closed addressing, also commonly known as https://en.wikipedia.org/wiki/Hash_table#Separate_chaining[separate chaining]. An example figure representing the data structure is below:
|
||||
|
||||
[#img-bucket-groups,.text-center]
|
||||
.A simple bucket group approach
|
||||
image::bucket-groups.png[align=center]
|
||||
|
||||
An array of "buckets" is allocated and each bucket in turn points to its own individual linked list. This makes meeting the standard requirements of bucket iteration straight-forward. Unfortunately, iteration of the entire container is often times slow using this layout as each bucket must be examined for occupancy, yielding a time complexity of `O(bucket_count() + size())` when the standard requires complexity to be `O(size())`.
|
||||
|
||||
Canonical standard implementations will wind up looking like the diagram below:
|
||||
|
||||
[.text-center]
|
||||
.The canonical standard approach
|
||||
image::singly-linked.png[align=center,link=../diagrams/singly-linked.png,window=_blank]
|
||||
|
||||
It's worth noting that this approach is only used by pass:[libc++] and pass:[libstdc++]; the MSVC Dinkumware implementation uses a different one. A more detailed analysis of the standard containers can be found http://bannalia.blogspot.com/2013/10/implementation-of-c-unordered.html[here].
|
||||
|
||||
This unusually laid out data structure is chosen to make iteration of the entire container efficient by inter-connecting all of the nodes into a singly-linked list. One might also notice that buckets point to the node _before_ the start of the bucket's elements. This is done so that removing elements from the list can be done efficiently without introducing the need for a doubly-linked list. Unfortunately, this data structure introduces a guaranteed extra indirection. For example, to access the first element of a bucket, something like this must be done:
|
||||
|
||||
```c++
|
||||
auto const idx = get_bucket_idx(hash_function(key));
|
||||
node* p = buckets[idx]; // first load
|
||||
node* n = p->next; // second load
|
||||
if (n && is_in_bucket(n, idx)) {
|
||||
value_type const& v = *n; // third load
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
With a simple bucket group layout, this is all that must be done:
|
||||
```c++
|
||||
auto const idx = get_bucket_idx(hash_function(key));
|
||||
node* n = buckets[idx]; // first load
|
||||
if (n) {
|
||||
value_type const& v = *n; // second load
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
In practice, the extra indirection can have a dramatic performance impact to common operations such as `insert`, `find` and `erase`. But to keep iteration of the container fast, Boost.Unordered introduces a novel data structure, a "bucket group". A bucket group is a fixed-width view of a subsection of the buckets array. It contains a bitmask (a `std::size_t`) which it uses to track occupancy of buckets and contains two pointers so that it can form a doubly-linked list with non-empty groups. An example diagram is below:
|
||||
|
||||
[#img-fca-layout]
|
||||
.The new layout used by Boost
|
||||
image::fca.png[align=center]
|
||||
|
||||
Thus container-wide iteration is turned into traversing the non-empty bucket groups (an operation with constant time complexity) which reduces the time complexity back to `O(size())`. In total, a bucket group is only 4 words in size and it views `sizeof(std::size_t) * CHAR_BIT` buckets meaning that for all common implementations, there's only 4 bits of space overhead per bucket introduced by the bucket groups.
|
||||
|
||||
A more detailed description of Boost.Unordered's closed-addressing implementation is
|
||||
given in an
|
||||
https://bannalia.blogspot.com/2022/06/advancing-state-of-art-for.html[external article].
|
||||
For more information on implementation rationale, read the
|
||||
xref:#rationale_boostunordered_multiset_and_boostunordered_multimap[corresponding section].
|
||||
|
||||
== Open Addressing Implementation
|
||||
|
||||
The diagram shows the basic internal layout of `boost::unordered_flat_map`/`unordered_node_map` and
|
||||
`boost:unordered_flat_set`/`unordered_node_set`.
|
||||
|
||||
|
||||
[#img-foa-layout]
|
||||
.Open-addressing layout used by Boost.Unordered.
|
||||
image::foa.png[align=center]
|
||||
|
||||
As with all open-addressing containers, elements (or pointers to the element nodes in the case of
|
||||
`boost::unordered_node_map` and `boost::unordered_node_set`) are stored directly in the bucket array.
|
||||
This array is logically divided into 2^_n_^ _groups_ of 15 elements each.
|
||||
In addition to the bucket array, there is an associated _metadata array_ with 2^_n_^
|
||||
16-byte words.
|
||||
|
||||
[#img-foa-metadata]
|
||||
.Breakdown of a metadata word.
|
||||
image::foa-metadata.png[align=center]
|
||||
|
||||
A metadata word is divided into 15 _h_~_i_~ bytes (one for each associated
|
||||
bucket), and an _overflow byte_ (_ofw_ in the diagram). The value of _h_~_i_~ is:
|
||||
|
||||
- 0 if the corresponding bucket is empty.
|
||||
- 1 to encode a special empty bucket called a _sentinel_, which is used internally to
|
||||
stop iteration when the container has been fully traversed.
|
||||
- If the bucket is occupied, a _reduced hash value_ obtained from the hash value of
|
||||
the element.
|
||||
|
||||
When looking for an element with hash value _h_, SIMD technologies such as
|
||||
https://en.wikipedia.org/wiki/SSE2[SSE2] and
|
||||
https://en.wikipedia.org/wiki/ARM_architecture_family#Advanced_SIMD_(Neon)[Neon] allow us
|
||||
to very quickly inspect the full metadata word and look for the reduced value of _h_ among all the
|
||||
15 buckets with just a handful of CPU instructions: non-matching buckets can be
|
||||
readily discarded, and those whose reduced hash value matches need be inspected via full
|
||||
comparison with the corresponding element. If the looked-for element is not present,
|
||||
the overflow byte is inspected:
|
||||
|
||||
- If the bit in the position _h_ mod 8 is zero, lookup terminates (and the
|
||||
element is not present).
|
||||
- If the bit is set to 1 (the group has been _overflowed_), further groups are
|
||||
checked using https://en.wikipedia.org/wiki/Quadratic_probing[_quadratic probing_], and
|
||||
the process is repeated.
|
||||
|
||||
Insertion is algorithmically similar: empty buckets are located using SIMD,
|
||||
and when going past a full group its corresponding overflow bit is set to 1.
|
||||
|
||||
In architectures without SIMD support, the logical layout stays the same, but the metadata
|
||||
word is codified using a technique we call _bit interleaving_: this layout allows us
|
||||
to emulate SIMD with reasonably good performance using only standard arithmetic and
|
||||
logical operations.
|
||||
|
||||
[#img-foa-metadata-interleaving]
|
||||
.Bit-interleaved metadata word.
|
||||
image::foa-metadata-interleaving.png[align=center]
|
||||
|
||||
A more detailed description of Boost.Unordered's open-addressing implementation is
|
||||
given in an
|
||||
https://bannalia.blogspot.com/2022/11/inside-boostunorderedflatmap.html[external article].
|
||||
For more information on implementation rationale, read the
|
||||
xref:#rationale_boostunordered_flat_set_and_boostunordered_flat_map[corresponding section].
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
:github-pr-url: https://github.com/boostorg/unordered/pull
|
||||
:cpp: C++
|
||||
|
||||
== Release 1.83.0
|
||||
== Release 1.83.0 - Major update
|
||||
|
||||
* Added `boost::concurrent_flat_map`, a fast, thread-safe hashmap based on open addressing.
|
||||
* Sped up iteration of open-addressing containers.
|
||||
|
||||
== Release 1.82.0 - Major update
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
:cpp: C++
|
||||
|
||||
== Closed-addressing containers
|
||||
== Closed-addressing Containers
|
||||
|
||||
`unordered_[multi]set` and `unordered_[multi]map` are intended to provide a conformant
|
||||
implementation of the {cpp}20 standard that will work with {cpp}98 upwards.
|
||||
@@ -13,7 +13,7 @@ This wide compatibility does mean some compromises have to be made.
|
||||
With a compiler and library that fully support {cpp}11, the differences should
|
||||
be minor.
|
||||
|
||||
=== Move emulation
|
||||
=== Move Emulation
|
||||
|
||||
Support for move semantics is implemented using Boost.Move. If rvalue
|
||||
references are available it will use them, but if not it uses a close,
|
||||
@@ -25,7 +25,7 @@ but imperfect emulation. On such compilers:
|
||||
* The containers themselves are not movable.
|
||||
* Argument forwarding is not perfect.
|
||||
|
||||
=== Use of allocators
|
||||
=== Use of Allocators
|
||||
|
||||
{cpp}11 introduced a new allocator system. It's backwards compatible due to
|
||||
the lax requirements for allocators in the old standard, but might need
|
||||
@@ -58,7 +58,7 @@ Due to imperfect move emulation, some assignments might check
|
||||
`propagate_on_container_copy_assignment` on some compilers and
|
||||
`propagate_on_container_move_assignment` on others.
|
||||
|
||||
=== Construction/Destruction using allocators
|
||||
=== Construction/Destruction Using Allocators
|
||||
|
||||
The following support is required for full use of {cpp}11 style
|
||||
construction/destruction:
|
||||
@@ -117,7 +117,7 @@ Variadic constructor arguments for `emplace` are only used when both
|
||||
rvalue references and variadic template parameters are available.
|
||||
Otherwise `emplace` can only take up to 10 constructors arguments.
|
||||
|
||||
== Open-addressing containers
|
||||
== Open-addressing Containers
|
||||
|
||||
The C++ standard does not currently provide any open-addressing container
|
||||
specification to adhere to, so `boost::unordered_flat_set`/`unordered_node_set` and
|
||||
@@ -144,4 +144,61 @@ The main differences with C++ unordered associative containers are:
|
||||
** Pointer stability is not kept under rehashing.
|
||||
** There is no API for node extraction/insertion.
|
||||
|
||||
== Concurrent Containers
|
||||
|
||||
There is currently no specification in the C++ standard for this or any other concurrent
|
||||
data structure. `boost::concurrent_flat_map` takes the same template parameters as `std::unordered_map`
|
||||
and all the maps provided by Boost.Unordered, and its API is modelled after that of
|
||||
`boost::unordered_flat_map` with the crucial difference that iterators are not provided
|
||||
due to their inherent problems in concurrent scenarios (high contention, prone to deadlocking):
|
||||
so, `boost::concurrent_flat_map` is technically not a
|
||||
https://en.cppreference.com/w/cpp/named_req/Container[Container^], although
|
||||
it meets all the requirements of https://en.cppreference.com/w/cpp/named_req/AllocatorAwareContainer[AllocatorAware^]
|
||||
containers except those implying iterators.
|
||||
|
||||
In a non-concurrent unordered container, iterators serve two main purposes:
|
||||
|
||||
* Access to an element previously located via lookup.
|
||||
* Container traversal.
|
||||
|
||||
In place of iterators, `boost::concurrent_flat_map` uses _internal visitation_
|
||||
facilities as a thread-safe substitute. Classical operations returning an iterator to an
|
||||
element already existing in the container, like for instance:
|
||||
|
||||
[source,c++]
|
||||
----
|
||||
iterator find(const key_type& k);
|
||||
std::pair<iterator, bool> insert(const value_type& obj);
|
||||
----
|
||||
|
||||
are transformed to accept a _visitation function_ that is passed such element:
|
||||
|
||||
[source,c++]
|
||||
----
|
||||
template<class F> size_t visit(const key_type& k, F f);
|
||||
template<class F> bool insert_or_visit(const value_type& obj, F f);
|
||||
----
|
||||
|
||||
(In the second case `f` is only invoked if there's an equivalent element
|
||||
to `obj` in the table, not if insertion is successful). Container traversal
|
||||
is served by:
|
||||
|
||||
[source,c++]
|
||||
----
|
||||
template<class F> size_t visit_all(F f);
|
||||
----
|
||||
|
||||
of which there are parallelized versions in C++17 compilers with parallel
|
||||
algorithm support. In general, the interface of `boost::concurrent_flat_map`
|
||||
is derived from that of `boost::unordered_flat_map` by a fairly straightforward
|
||||
process of replacing iterators with visitation where applicable. If
|
||||
`iterator` and `const_iterator` provide mutable and const access to elements,
|
||||
respectively, here visitation is granted mutable or const access depending on
|
||||
the constness of the member function used (there are also `*cvisit` overloads for
|
||||
explicit const visitation).
|
||||
|
||||
The one notable operation not provided is `operator[]`/`at`, which can be
|
||||
replaced, if in a more convoluted manner, by
|
||||
xref:#concurrent_flat_map_try_emplace_or_cvisit[`try_emplace_or_visit`].
|
||||
|
||||
//-
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
[#concurrent]
|
||||
= Concurrent Containers
|
||||
|
||||
:idprefix: concurrent_
|
||||
|
||||
Boost.Unordered currently provides just one concurrent container named `boost::concurrent_flat_map`.
|
||||
`boost::concurrent_flat_map` is a hash table that allows concurrent write/read access from
|
||||
different threads without having to implement any synchronzation mechanism on the user's side.
|
||||
|
||||
[source,c++]
|
||||
----
|
||||
std::vector<int> input;
|
||||
boost::concurrent_flat_map<int,int> m;
|
||||
|
||||
...
|
||||
|
||||
// process input in parallel
|
||||
const int num_threads = 8;
|
||||
std::vector<std::jthread> threads;
|
||||
std::size_t chunk = input.size() / num_threads; // how many elements per thread
|
||||
|
||||
for (int i = 0; i < num_threads; ++i) {
|
||||
threads.emplace_back([&,i] {
|
||||
// calculate the portion of input this thread takes care of
|
||||
std::size_t start = i * chunk;
|
||||
std::size_t end = (i == num_threads - 1)? input.size(): (i + 1) * chunk;
|
||||
|
||||
for (std::size_t n = start; n < end; ++n) {
|
||||
m.emplace(input[n], calculation(input[n]));
|
||||
}
|
||||
});
|
||||
}
|
||||
----
|
||||
|
||||
In the example above, threads access `m` without synchronization, just as we'd do in a
|
||||
single-threaded scenario. In an ideal setting, if a given workload is distributed among
|
||||
_N_ threads, execution is _N_ times faster than with one thread —this limit is
|
||||
never attained in practice due to synchronization overheads and _contention_ (one thread
|
||||
waiting for another to leave a locked portion of the map), but `boost::concurrent_flat_map`
|
||||
is designed to perform with very little overhead and typically achieves _linear scaling_
|
||||
(that is, performance is proportional to the number of threads up to the number of
|
||||
logical cores in the CPU).
|
||||
|
||||
== Visitation-based API
|
||||
|
||||
The first thing a new user of `boost::concurrent_flat_map` will notice is that this
|
||||
class _does not provide iterators_ (which makes it technically
|
||||
not a https://en.cppreference.com/w/cpp/named_req/Container[Container^]
|
||||
in the C++ standard sense). The reason for this is that iterators are inherently
|
||||
thread-unsafe. Consider this hypothetical code:
|
||||
|
||||
[source,c++]
|
||||
----
|
||||
auto it = m.find(k); // A: get an iterator pointing to the element with key k
|
||||
if (it != m.end() ) {
|
||||
some_function(*it); // B: use the value of the element
|
||||
}
|
||||
----
|
||||
|
||||
In a multithreaded scenario, the iterator `it` may be invalid at point B if some other
|
||||
thread issues an `m.erase(k)` operation between A and B. There are designs that
|
||||
can remedy this by making iterators lock the element they point to, but this
|
||||
approach lends itself to high contention and can easily produce deadlocks in a program.
|
||||
`operator[]` has similar concurrency issues, and is not provided by
|
||||
`boost::concurrent_flat_map` either. Instead, element access is done through
|
||||
so-called _visitation functions_:
|
||||
|
||||
[source,c++]
|
||||
----
|
||||
m.visit(k, [](const auto& x) { // x is the element with key k (if it exists)
|
||||
some_function(x); // use it
|
||||
});
|
||||
----
|
||||
|
||||
The visitation function passed by the user (in this case, a lambda function)
|
||||
is executed internally by `boost::concurrent_flat_map` in
|
||||
a thread-safe manner, so it can access the element without worrying about other
|
||||
threads interfering in the process.
|
||||
|
||||
On the other hand, a visitation function can _not_ access the container itself:
|
||||
|
||||
[source,c++]
|
||||
----
|
||||
m.visit(k, [&](const auto& x) {
|
||||
some_function(x, m.size()); // forbidden: m can't be accessed inside visitation
|
||||
});
|
||||
----
|
||||
|
||||
Access to a different container is allowed, though:
|
||||
|
||||
[source,c++]
|
||||
----
|
||||
m.visit(k, [&](const auto& x) {
|
||||
if (some_function(x)) {
|
||||
m2.insert(x); // OK, m2 is a different boost::concurrent_flat_map
|
||||
}
|
||||
});
|
||||
----
|
||||
|
||||
But, in general, visitation functions should be as lightweight as possible to
|
||||
reduce contention and increase parallelization. In some cases, moving heavy work
|
||||
outside of visitation may be beneficial:
|
||||
|
||||
[source,c++]
|
||||
----
|
||||
std::optional<value_type> o;
|
||||
bool found = m.visit(k, [&](const auto& x) {
|
||||
o = x;
|
||||
});
|
||||
if (found) {
|
||||
some_heavy_duty_function(*o);
|
||||
}
|
||||
----
|
||||
|
||||
Visitation is prominent in the API provided by `boost::concurrent_flat_map`, and
|
||||
many classical operations have visitation-enabled variations:
|
||||
|
||||
[source,c++]
|
||||
----
|
||||
m.insert_or_visit(x, [](auto& y) {
|
||||
// if insertion failed because of an equivalent element y,
|
||||
// do something with it, for instance:
|
||||
++y.second; // increment the mapped part of the element
|
||||
});
|
||||
----
|
||||
|
||||
Note that in this last example the visitation function could actually _modify_
|
||||
the element: as a general rule, operations on a `boost::concurrent_flat_map` `m`
|
||||
will grant visitation functions const/non-const access to the element depending on whether
|
||||
`m` is const/non-const. Const access can be always be explicitly requested
|
||||
by using `cvisit` overloads (for instance, `insert_or_cvisit`) and may result
|
||||
in higher parallelization. Consult the xref:#concurrent_flat_map[reference]
|
||||
for a complete list of available operations.
|
||||
|
||||
== Whole-Table Visitation
|
||||
|
||||
In the absence of iterators, `boost::concurrent_flat_map` provides `visit_all`
|
||||
as an alternative way to process all the elements in the map:
|
||||
|
||||
[source,c++]
|
||||
----
|
||||
m.visit_all([](auto& x) {
|
||||
x.second = 0; // reset the mapped part of the element
|
||||
});
|
||||
----
|
||||
|
||||
In C++17 compilers implementing standard parallel algorithms, whole-table
|
||||
visitation can be parallelized:
|
||||
|
||||
[source,c++]
|
||||
----
|
||||
m.visit_all(std::execution::par, [](auto& x) { // run in parallel
|
||||
x.second = 0; // reset the mapped part of the element
|
||||
});
|
||||
----
|
||||
|
||||
There is another whole-table visitation operation, `erase_if`:
|
||||
|
||||
[source,c++]
|
||||
----
|
||||
m.erase_if([](auto& x) {
|
||||
return x.second == 0; // erase the elements whose mapped value is zero
|
||||
});
|
||||
----
|
||||
|
||||
`erase_if` can also be parallelized. Note that, in order to increase efficiency,
|
||||
these operations do not block the table during execution: this implies that elements
|
||||
may be inserted, modified or erased by other threads during visitation. It is
|
||||
advisable not to assume too much about the exact global state of a `boost::concurrent_flat_map`
|
||||
at any point in your program.
|
||||
|
||||
== Blocking Operations
|
||||
|
||||
``boost::concurrent_flat_map``s can be copied, assigned, cleared and merged just like any
|
||||
Boost.Unordered container. Unlike most other operations, these are _blocking_,
|
||||
that is, all other threads are prevented from accesing the tables involved while a copy, assignment,
|
||||
clear or merge operation is in progress. Blocking is taken care of automatically by the library
|
||||
and the user need not take any special precaution, but overall performance may be affected.
|
||||
|
||||
Another blocking operation is _rehashing_, which happens explicitly via `rehash`/`reserve`
|
||||
or during insertion when the table's load hits `max_load()`. As with non-concurrent containers,
|
||||
reserving space in advance of bulk insertions will generally speed up the process.
|
||||
@@ -20,14 +20,14 @@ class unordered_map;
|
||||
|
||||
The hash function comes first as you might want to change the hash function
|
||||
but not the equality predicate. For example, if you wanted to use the
|
||||
http://www.isthe.com/chongo/tech/comp/fnv/[FNV-1 hash^] you could write:
|
||||
https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV-1a_hash[FNV-1a hash^] you could write:
|
||||
|
||||
```
|
||||
boost::unordered_map<std::string, int, hash::fnv_1>
|
||||
boost::unordered_map<std::string, int, hash::fnv_1a>
|
||||
dictionary;
|
||||
```
|
||||
|
||||
There is an link:../../examples/fnv1.hpp[implementation of FNV-1^] in the examples directory.
|
||||
There is an link:../../examples/fnv1.hpp[implementation of FNV-1a^] in the examples directory.
|
||||
|
||||
If you wish to use a different equality function, you will also need to use a matching hash function. For example, to implement a case insensitive dictionary you need to define a case insensitive equality predicate and hash function:
|
||||
|
||||
|
||||
@@ -4,26 +4,65 @@
|
||||
:idprefix: intro_
|
||||
:cpp: C++
|
||||
|
||||
For accessing data based on key lookup, the {cpp} standard library offers `std::set`,
|
||||
`std::map`, `std::multiset` and `std::multimap`. These are generally
|
||||
implemented using balanced binary trees so that lookup time has
|
||||
logarithmic complexity. That is generally okay, but in many cases a
|
||||
link:https://en.wikipedia.org/wiki/Hash_table[hash table^] can perform better, as accessing data has constant complexity,
|
||||
on average. The worst case complexity is linear, but that occurs rarely and
|
||||
with some care, can be avoided.
|
||||
link:https://en.wikipedia.org/wiki/Hash_table[Hash tables^] are extremely popular
|
||||
computer data structures and can be found under one form or another in virtually any programming
|
||||
language. Whereas other associative structures such as rb-trees (used in {cpp} by `std::set` and `std::map`)
|
||||
have logarithmic-time complexity for insertion and lookup, hash tables, if configured properly,
|
||||
perform these operations in constant time on average, and are generally much faster.
|
||||
|
||||
Also, the existing containers require a 'less than' comparison object
|
||||
to order their elements. For some data types this is impossible to implement
|
||||
or isn't practical. In contrast, a hash table only needs an equality function
|
||||
and a hash function for the key.
|
||||
{cpp} introduced __unordered associative containers__ `std::unordered_set`, `std::unordered_map`,
|
||||
`std::unordered_multiset` and `std::unordered_multimap` in {cpp}11, but research on hash tables
|
||||
hasn't stopped since: advances in CPU architectures such as
|
||||
more powerful caches, link:https://en.wikipedia.org/wiki/Single_instruction,_multiple_data[SIMD] operations
|
||||
and increasingly available link:https://en.wikipedia.org/wiki/Multi-core_processor[multicore processors]
|
||||
open up possibilities for improved hash-based data structures and new use cases that
|
||||
are simply beyond reach of unordered associative containers as specified in 2011.
|
||||
|
||||
With this in mind, unordered associative containers were added to the {cpp}
|
||||
standard. Boost.Unordered provides an implementation of the containers described in {cpp}11,
|
||||
with some <<compliance,deviations from the standard>> in
|
||||
order to work with non-{cpp}11 compilers and libraries.
|
||||
Boost.Unordered offers a catalog of hash containers with different standards compliance levels,
|
||||
performances and intented usage scenarios:
|
||||
|
||||
[caption=, title='Table {counter:table-counter}. Boost.Unordered containers']
|
||||
[cols="1,1,.^1", frame=all, grid=all]
|
||||
|===
|
||||
^h|
|
||||
^h|*Node-based*
|
||||
^h|*Flat*
|
||||
|
||||
^.^h|*Closed addressing*
|
||||
^m|
|
||||
boost::unordered_set +
|
||||
boost::unordered_map +
|
||||
boost::unordered_multiset +
|
||||
boost::unordered_multimap
|
||||
^|
|
||||
|
||||
^.^h|*Open addressing*
|
||||
^m| boost::unordered_node_set +
|
||||
boost::unordered_node_map
|
||||
^m| boost::unordered_flat_set +
|
||||
boost::unordered_flat_map
|
||||
|
||||
^.^h|*Concurrent*
|
||||
^|
|
||||
^| `boost::concurrent_flat_map`
|
||||
|
||||
|===
|
||||
|
||||
* **Closed-addressing containers** are fully compliant with the C++ specification
|
||||
for unordered associative containers and feature one of the fastest implementations
|
||||
in the market within the technical constraints imposed by the required standard interface.
|
||||
* **Open-addressing containers** rely on much faster data structures and algorithms
|
||||
(more than 2 times faster in typical scenarios) while slightly diverging from the standard
|
||||
interface to accommodate the implementation.
|
||||
There are two variants: **flat** (the fastest) and **node-based**, which
|
||||
provide pointer stability under rehashing at the expense of being slower.
|
||||
* Finally, `boost::concurrent_flat_map` (the only **concurrent container** provided
|
||||
at present) is a hashmap designed and implemented to be used in high-performance
|
||||
multithreaded scenarios. Its interface is radically different from that of regular C++ containers.
|
||||
|
||||
All sets and maps in Boost.Unordered are instantiatied similarly as
|
||||
`std::unordered_set` and `std::unordered_map`, respectively:
|
||||
|
||||
`unordered_set` and `unordered_multiset` are defined in the header
|
||||
`<boost/unordered/unordered_set.hpp>`
|
||||
[source,c++]
|
||||
----
|
||||
namespace boost {
|
||||
@@ -32,178 +71,21 @@ namespace boost {
|
||||
class Hash = boost::hash<Key>,
|
||||
class Pred = std::equal_to<Key>,
|
||||
class Alloc = std::allocator<Key> >
|
||||
class unordered_set;
|
||||
class unordered_set;
|
||||
// same for unordered_multiset, unordered_flat_set, unordered_node_set
|
||||
|
||||
template<
|
||||
class Key,
|
||||
class Hash = boost::hash<Key>,
|
||||
class Pred = std::equal_to<Key>,
|
||||
class Alloc = std::allocator<Key> >
|
||||
class unordered_multiset;
|
||||
}
|
||||
----
|
||||
|
||||
`unordered_map` and `unordered_multimap` are defined in the header
|
||||
`<boost/unordered/unordered_map.hpp>`
|
||||
|
||||
[source,c++]
|
||||
----
|
||||
namespace boost {
|
||||
template <
|
||||
class Key, class Mapped,
|
||||
class Hash = boost::hash<Key>,
|
||||
class Pred = std::equal_to<Key>,
|
||||
class Alloc = std::allocator<std::pair<Key const, Mapped> > >
|
||||
class unordered_map;
|
||||
|
||||
template<
|
||||
class Key, class Mapped,
|
||||
class Hash = boost::hash<Key>,
|
||||
class Pred = std::equal_to<Key>,
|
||||
class Alloc = std::allocator<std::pair<Key const, Mapped> > >
|
||||
class unordered_multimap;
|
||||
// same for unordered_multimap, unordered_flat_map, unordered_node_map
|
||||
// and concurrent_flat_map
|
||||
}
|
||||
----
|
||||
|
||||
These containers, and all other implementations of standard unordered associative
|
||||
containers, use an approach to its internal data structure design called
|
||||
*closed addressing*. Starting in Boost 1.81, Boost.Unordered also provides containers
|
||||
`boost::unordered_flat_set` and `boost::unordered_flat_map`, which use a
|
||||
different data structure strategy commonly known as *open addressing* and depart in
|
||||
a small number of ways from the standard so as to offer much better performance
|
||||
in exchange (more than 2 times faster in typical scenarios):
|
||||
|
||||
|
||||
[source,c++]
|
||||
----
|
||||
// #include <boost/unordered/unordered_flat_set.hpp>
|
||||
//
|
||||
// Note: no multiset version
|
||||
|
||||
namespace boost {
|
||||
template <
|
||||
class Key,
|
||||
class Hash = boost::hash<Key>,
|
||||
class Pred = std::equal_to<Key>,
|
||||
class Alloc = std::allocator<Key> >
|
||||
class unordered_flat_set;
|
||||
}
|
||||
----
|
||||
|
||||
[source,c++]
|
||||
----
|
||||
// #include <boost/unordered/unordered_flat_map.hpp>
|
||||
//
|
||||
// Note: no multimap version
|
||||
|
||||
namespace boost {
|
||||
template <
|
||||
class Key, class Mapped,
|
||||
class Hash = boost::hash<Key>,
|
||||
class Pred = std::equal_to<Key>,
|
||||
class Alloc = std::allocator<std::pair<Key const, Mapped> > >
|
||||
class unordered_flat_map;
|
||||
}
|
||||
----
|
||||
|
||||
Starting in Boost 1.82, the containers `boost::unordered_node_set` and `boost::unordered_node_map`
|
||||
are introduced: they use open addressing like `boost::unordered_flat_set` and `boost::unordered_flat_map`,
|
||||
but internally store element _nodes_, like `boost::unordered_set` and `boost::unordered_map`,
|
||||
which provide stability of pointers and references to the elements:
|
||||
|
||||
[source,c++]
|
||||
----
|
||||
// #include <boost/unordered/unordered_node_set.hpp>
|
||||
//
|
||||
// Note: no multiset version
|
||||
|
||||
namespace boost {
|
||||
template <
|
||||
class Key,
|
||||
class Hash = boost::hash<Key>,
|
||||
class Pred = std::equal_to<Key>,
|
||||
class Alloc = std::allocator<Key> >
|
||||
class unordered_node_set;
|
||||
}
|
||||
----
|
||||
|
||||
[source,c++]
|
||||
----
|
||||
// #include <boost/unordered/unordered_node_map.hpp>
|
||||
//
|
||||
// Note: no multimap version
|
||||
|
||||
namespace boost {
|
||||
template <
|
||||
class Key, class Mapped,
|
||||
class Hash = boost::hash<Key>,
|
||||
class Pred = std::equal_to<Key>,
|
||||
class Alloc = std::allocator<std::pair<Key const, Mapped> > >
|
||||
class unordered_node_map;
|
||||
}
|
||||
----
|
||||
|
||||
These are all the containers provided by Boost.Unordered:
|
||||
|
||||
[caption=, title='Table {counter:table-counter}. Boost.Unordered containers']
|
||||
[cols="1,1,.^1", frame=all, grid=rows]
|
||||
|===
|
||||
^h|
|
||||
^h|*Node-based*
|
||||
^h|*Flat*
|
||||
|
||||
^.^h|*Closed addressing*
|
||||
^| `boost::unordered_set` +
|
||||
`boost::unordered_map` +
|
||||
`boost::unordered_multiset` +
|
||||
`boost::unordered_multimap`
|
||||
^|
|
||||
|
||||
^.^h|*Open addressing*
|
||||
^| `boost::unordered_node_set` +
|
||||
`boost::unordered_node_map`
|
||||
^| `boost::unordered_flat_set` +
|
||||
`boost::unordered_flat_map`
|
||||
|
||||
|===
|
||||
|
||||
Closed-addressing containers are pass:[C++]98-compatible. Open-addressing containers require a
|
||||
reasonably compliant pass:[C++]11 compiler.
|
||||
|
||||
Boost.Unordered containers are used in a similar manner to the normal associative
|
||||
containers:
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
typedef boost::unordered_map<std::string, int> map;
|
||||
map x;
|
||||
x["one"] = 1;
|
||||
x["two"] = 2;
|
||||
x["three"] = 3;
|
||||
|
||||
assert(x.at("one") == 1);
|
||||
assert(x.find("missing") == x.end());
|
||||
----
|
||||
|
||||
But since the elements aren't ordered, the output of:
|
||||
|
||||
[source,c++]
|
||||
----
|
||||
for(const map::value_type& i: x) {
|
||||
std::cout<<i.first<<","<<i.second<<"\n";
|
||||
}
|
||||
----
|
||||
|
||||
can be in any order. For example, it might be:
|
||||
|
||||
[source]
|
||||
----
|
||||
two,2
|
||||
one,1
|
||||
three,3
|
||||
----
|
||||
|
||||
To store an object in an unordered associative container requires both a
|
||||
Storing an object in an unordered associative container requires both a
|
||||
key equality function and a hash function. The default function objects in
|
||||
the standard containers support a few basic types including integer types,
|
||||
floating point types, pointer types, and the standard strings. Since
|
||||
@@ -213,6 +95,3 @@ you have to extend Boost.Hash to support the type or use
|
||||
your own custom equality predicates and hash functions. See the
|
||||
<<hash_equality,Equality Predicates and Hash Functions>> section
|
||||
for more details.
|
||||
|
||||
There are other differences, which are listed in the
|
||||
<<comparison,Comparison with Associative Containers>> section.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
= Implementation Rationale
|
||||
|
||||
== Closed-addressing containers
|
||||
== Closed-addressing Containers
|
||||
|
||||
`boost::unordered_[multi]set` and `boost::unordered_[multi]map`
|
||||
adhere to the standard requirements for unordered associative
|
||||
@@ -74,7 +74,7 @@ Since release 1.80.0, prime numbers are chosen for the number of buckets in
|
||||
tandem with sophisticated modulo arithmetic. This removes the need for "mixing"
|
||||
the result of the user's hash function as was used for release 1.79.0.
|
||||
|
||||
== Open-addresing containers
|
||||
== Open-addresing Containers
|
||||
|
||||
The C++ standard specification of unordered associative containers impose
|
||||
severe limitations on permissible implementations, the most important being
|
||||
@@ -86,7 +86,7 @@ The design of `boost::unordered_flat_set`/`unordered_node_set` and `boost::unord
|
||||
guided by Peter Dimov's https://pdimov.github.io/articles/unordered_dev_plan.html[Development Plan for Boost.Unordered^].
|
||||
We discuss here the most relevant principles.
|
||||
|
||||
=== Hash function
|
||||
=== Hash Function
|
||||
|
||||
Given its rich functionality and cross-platform interoperability,
|
||||
`boost::hash` remains the default hash function of open-addressing containers.
|
||||
@@ -105,10 +105,10 @@ whereas in 32 bits _C_ = 0xE817FB2Du has been obtained from https://arxiv.org/ab
|
||||
When using a hash function directly suitable for open addressing, post-mixing can be opted out by via a dedicated <<hash_traits_hash_is_avalanching,`hash_is_avalanching`>>trait.
|
||||
`boost::hash` specializations for string types are marked as avalanching.
|
||||
|
||||
=== Platform interoperability
|
||||
=== Platform Interoperability
|
||||
|
||||
The observable behavior of `boost::unordered_flat_set`/`unordered_node_set` and `boost::unordered_flat_map`/`unordered_node_map` is deterministically
|
||||
identical across different compilers as long as their ``std::size_type``s are the same size and the user-provided
|
||||
identical across different compilers as long as their ``std::size_t``s are the same size and the user-provided
|
||||
hash function and equality predicate are also interoperable
|
||||
—this includes elements being ordered in exactly the same way for the same sequence of
|
||||
operations.
|
||||
@@ -117,3 +117,25 @@ Although the implementation internally uses SIMD technologies, such as https://e
|
||||
and https://en.wikipedia.org/wiki/ARM_architecture_family#Advanced_SIMD_(NEON)[Neon^], when available,
|
||||
this does not affect interoperatility. For instance, the behavior is the same
|
||||
for Visual Studio on an x64-mode Intel CPU with SSE2 and for GCC on an IBM s390x without any supported SIMD technology.
|
||||
|
||||
== Concurrent Containers
|
||||
|
||||
The same data structure used by Boost.Unordered open-addressing containers has been chosen
|
||||
also as the foundation of `boost::concurrent_flat_map`:
|
||||
|
||||
* Open-addressing is faster than closed-addressing alternatives, both in non-concurrent and
|
||||
concurrent scenarios.
|
||||
* Open-addressing layouts are eminently suitable for concurrent access and modification
|
||||
with minimal locking. In particular, the metadata array can be used for implementations of
|
||||
lookup that are lock-free up to the last step of actual element comparison.
|
||||
* Layout compatibility with Boost.Unordered flat containers allows for fast transfer
|
||||
of all elements between `boost::concurrent_flat_map` and `boost::unordered_flat_map`.
|
||||
(This feature has not been implemented yet.)
|
||||
|
||||
=== Hash Function and Platform Interoperability
|
||||
|
||||
`boost::concurrent_flat_map` makes the same decisions and provides the same guarantees
|
||||
as Boost.Unordered open-addressing containers with regards to
|
||||
xref:#rationale_hash_function[hash function defaults] and
|
||||
xref:#rationale_platform_interoperability[platform interoperability].
|
||||
|
||||
|
||||
@@ -10,3 +10,4 @@ include::unordered_flat_map.adoc[]
|
||||
include::unordered_flat_set.adoc[]
|
||||
include::unordered_node_map.adoc[]
|
||||
include::unordered_node_set.adoc[]
|
||||
include::concurrent_flat_map.adoc[]
|
||||
|
||||
@@ -1,8 +1,99 @@
|
||||
[#regular]
|
||||
= Regular Containers
|
||||
|
||||
:idprefix: regular_
|
||||
|
||||
Boost.Unordered closed-addressing containers (`boost::unordered_set`, `boost::unordered_map`,
|
||||
`boost::unordered_multiset` and `boost::unordered_multimap`) are fully conformant with the
|
||||
C++ specification for unordered associative containers, so for those who know how to use
|
||||
`std::unordered_set`, `std::unordered_map`, etc., their homonyms in Boost.Unordered are
|
||||
drop-in replacements. The interface of open-addressing containers (`boost::unordered_node_set`,
|
||||
`boost::unordered_node_map`, `boost::unordered_flat_set` and `boost::unordered_flat_map`)
|
||||
is very similar, but they present some minor differences listed in the dedicated
|
||||
xref:#compliance_open_addressing_containers[standard compliance section].
|
||||
|
||||
|
||||
For readers without previous experience with hash containers but familiar
|
||||
with normal associative containers (`std::set`, `std::map`,
|
||||
`std::multiset` and `std::multimap`), Boost.Unordered containers are used in a similar manner:
|
||||
|
||||
[source,cpp]
|
||||
----
|
||||
typedef boost::unordered_map<std::string, int> map;
|
||||
map x;
|
||||
x["one"] = 1;
|
||||
x["two"] = 2;
|
||||
x["three"] = 3;
|
||||
|
||||
assert(x.at("one") == 1);
|
||||
assert(x.find("missing") == x.end());
|
||||
----
|
||||
|
||||
But since the elements aren't ordered, the output of:
|
||||
|
||||
[source,c++]
|
||||
----
|
||||
for(const map::value_type& i: x) {
|
||||
std::cout<<i.first<<","<<i.second<<"\n";
|
||||
}
|
||||
----
|
||||
|
||||
can be in any order. For example, it might be:
|
||||
|
||||
[source]
|
||||
----
|
||||
two,2
|
||||
one,1
|
||||
three,3
|
||||
----
|
||||
|
||||
There are other differences, which are listed in the
|
||||
<<comparison,Comparison with Associative Containers>> section.
|
||||
|
||||
== Iterator Invalidation
|
||||
|
||||
It is not specified how member functions other than `rehash` and `reserve` affect
|
||||
the bucket count, although `insert` can only invalidate iterators
|
||||
when the insertion causes the container's load to be greater than the maximum allowed.
|
||||
For most implementations this means that `insert` will only
|
||||
change the number of buckets when this happens. Iterators can be
|
||||
invalidated by calls to `insert`, `rehash` and `reserve`.
|
||||
|
||||
As for pointers and references,
|
||||
they are never invalidated for node-based containers
|
||||
(`boost::unordered_[multi]set`, `boost::unordered_[multi]map`, `boost::unordered_node_set`, `boost::unordered_node_map`),
|
||||
but they will be when rehashing occurs for
|
||||
`boost::unordered_flat_set` and `boost::unordered_flat_map`: this is because
|
||||
these containers store elements directly into their holding buckets, so
|
||||
when allocating a new bucket array the elements must be transferred by means of move construction.
|
||||
|
||||
In a similar manner to using `reserve` for ``vector``s, it can be a good idea
|
||||
to call `reserve` before inserting a large number of elements. This will get
|
||||
the expensive rehashing out of the way and let you store iterators, safe in
|
||||
the knowledge that they won't be invalidated. If you are inserting `n`
|
||||
elements into container `x`, you could first call:
|
||||
|
||||
```
|
||||
x.reserve(n);
|
||||
```
|
||||
|
||||
Note:: `reserve(n)` reserves space for at least `n` elements, allocating enough buckets
|
||||
so as to not exceed the maximum load factor.
|
||||
+
|
||||
Because the maximum load factor is defined as the number of elements divided by the total
|
||||
number of available buckets, this function is logically equivalent to:
|
||||
+
|
||||
```
|
||||
x.rehash(std::ceil(n / x.max_load_factor()))
|
||||
```
|
||||
+
|
||||
See the <<unordered_map_rehash,reference for more details>> on the `rehash` function.
|
||||
|
||||
[#comparison]
|
||||
|
||||
:idprefix: comparison_
|
||||
|
||||
= Comparison with Associative Containers
|
||||
== Comparison with Associative Containers
|
||||
|
||||
[caption=, title='Table {counter:table-counter} Interface differences']
|
||||
[cols="1,1", frame=all, grid=rows]
|
||||
@@ -32,7 +123,7 @@
|
||||
|`iterator`, `const_iterator` are of at least the forward category.
|
||||
|
||||
|Iterators, pointers and references to the container's elements are never invalidated.
|
||||
|<<buckets_iterator_invalidation,Iterators can be invalidated by calls to insert or rehash>>. +
|
||||
|<<regular_iterator_invalidation,Iterators can be invalidated by calls to insert or rehash>>. +
|
||||
**Node-based containers:** Pointers and references to the container's elements are never invalidated. +
|
||||
**Flat containers:** Pointers and references to the container's elements are invalidated when rehashing occurs.
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
[#structures]
|
||||
= Data Structures
|
||||
|
||||
:idprefix: structures_
|
||||
|
||||
== Closed-addressing Containers
|
||||
|
||||
++++
|
||||
<style>
|
||||
.imageblock > .title {
|
||||
text-align: inherit;
|
||||
}
|
||||
</style>
|
||||
++++
|
||||
|
||||
Boost.Unordered sports one of the fastest implementations of closed addressing, also commonly known as https://en.wikipedia.org/wiki/Hash_table#Separate_chaining[separate chaining]. An example figure representing the data structure is below:
|
||||
|
||||
[#img-bucket-groups,.text-center]
|
||||
.A simple bucket group approach
|
||||
image::bucket-groups.png[align=center]
|
||||
|
||||
An array of "buckets" is allocated and each bucket in turn points to its own individual linked list. This makes meeting the standard requirements of bucket iteration straight-forward. Unfortunately, iteration of the entire container is often times slow using this layout as each bucket must be examined for occupancy, yielding a time complexity of `O(bucket_count() + size())` when the standard requires complexity to be `O(size())`.
|
||||
|
||||
Canonical standard implementations will wind up looking like the diagram below:
|
||||
|
||||
[.text-center]
|
||||
.The canonical standard approach
|
||||
image::singly-linked.png[align=center,link=../diagrams/singly-linked.png,window=_blank]
|
||||
|
||||
It's worth noting that this approach is only used by pass:[libc++] and pass:[libstdc++]; the MSVC Dinkumware implementation uses a different one. A more detailed analysis of the standard containers can be found http://bannalia.blogspot.com/2013/10/implementation-of-c-unordered.html[here].
|
||||
|
||||
This unusually laid out data structure is chosen to make iteration of the entire container efficient by inter-connecting all of the nodes into a singly-linked list. One might also notice that buckets point to the node _before_ the start of the bucket's elements. This is done so that removing elements from the list can be done efficiently without introducing the need for a doubly-linked list. Unfortunately, this data structure introduces a guaranteed extra indirection. For example, to access the first element of a bucket, something like this must be done:
|
||||
|
||||
```c++
|
||||
auto const idx = get_bucket_idx(hash_function(key));
|
||||
node* p = buckets[idx]; // first load
|
||||
node* n = p->next; // second load
|
||||
if (n && is_in_bucket(n, idx)) {
|
||||
value_type const& v = *n; // third load
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
With a simple bucket group layout, this is all that must be done:
|
||||
```c++
|
||||
auto const idx = get_bucket_idx(hash_function(key));
|
||||
node* n = buckets[idx]; // first load
|
||||
if (n) {
|
||||
value_type const& v = *n; // second load
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
In practice, the extra indirection can have a dramatic performance impact to common operations such as `insert`, `find` and `erase`. But to keep iteration of the container fast, Boost.Unordered introduces a novel data structure, a "bucket group". A bucket group is a fixed-width view of a subsection of the buckets array. It contains a bitmask (a `std::size_t`) which it uses to track occupancy of buckets and contains two pointers so that it can form a doubly-linked list with non-empty groups. An example diagram is below:
|
||||
|
||||
[#img-fca-layout]
|
||||
.The new layout used by Boost
|
||||
image::fca.png[align=center]
|
||||
|
||||
Thus container-wide iteration is turned into traversing the non-empty bucket groups (an operation with constant time complexity) which reduces the time complexity back to `O(size())`. In total, a bucket group is only 4 words in size and it views `sizeof(std::size_t) * CHAR_BIT` buckets meaning that for all common implementations, there's only 4 bits of space overhead per bucket introduced by the bucket groups.
|
||||
|
||||
A more detailed description of Boost.Unordered's closed-addressing implementation is
|
||||
given in an
|
||||
https://bannalia.blogspot.com/2022/06/advancing-state-of-art-for.html[external article].
|
||||
For more information on implementation rationale, read the
|
||||
xref:#rationale_closed_addressing_containers[corresponding section].
|
||||
|
||||
== Open-addressing Containers
|
||||
|
||||
The diagram shows the basic internal layout of `boost::unordered_flat_map`/`unordered_node_map` and
|
||||
`boost:unordered_flat_set`/`unordered_node_set`.
|
||||
|
||||
|
||||
[#img-foa-layout]
|
||||
.Open-addressing layout used by Boost.Unordered.
|
||||
image::foa.png[align=center]
|
||||
|
||||
As with all open-addressing containers, elements (or pointers to the element nodes in the case of
|
||||
`boost::unordered_node_map` and `boost::unordered_node_set`) are stored directly in the bucket array.
|
||||
This array is logically divided into 2^_n_^ _groups_ of 15 elements each.
|
||||
In addition to the bucket array, there is an associated _metadata array_ with 2^_n_^
|
||||
16-byte words.
|
||||
|
||||
[#img-foa-metadata]
|
||||
.Breakdown of a metadata word.
|
||||
image::foa-metadata.png[align=center]
|
||||
|
||||
A metadata word is divided into 15 _h_~_i_~ bytes (one for each associated
|
||||
bucket), and an _overflow byte_ (_ofw_ in the diagram). The value of _h_~_i_~ is:
|
||||
|
||||
- 0 if the corresponding bucket is empty.
|
||||
- 1 to encode a special empty bucket called a _sentinel_, which is used internally to
|
||||
stop iteration when the container has been fully traversed.
|
||||
- If the bucket is occupied, a _reduced hash value_ obtained from the hash value of
|
||||
the element.
|
||||
|
||||
When looking for an element with hash value _h_, SIMD technologies such as
|
||||
https://en.wikipedia.org/wiki/SSE2[SSE2] and
|
||||
https://en.wikipedia.org/wiki/ARM_architecture_family#Advanced_SIMD_(Neon)[Neon] allow us
|
||||
to very quickly inspect the full metadata word and look for the reduced value of _h_ among all the
|
||||
15 buckets with just a handful of CPU instructions: non-matching buckets can be
|
||||
readily discarded, and those whose reduced hash value matches need be inspected via full
|
||||
comparison with the corresponding element. If the looked-for element is not present,
|
||||
the overflow byte is inspected:
|
||||
|
||||
- If the bit in the position _h_ mod 8 is zero, lookup terminates (and the
|
||||
element is not present).
|
||||
- If the bit is set to 1 (the group has been _overflowed_), further groups are
|
||||
checked using https://en.wikipedia.org/wiki/Quadratic_probing[_quadratic probing_], and
|
||||
the process is repeated.
|
||||
|
||||
Insertion is algorithmically similar: empty buckets are located using SIMD,
|
||||
and when going past a full group its corresponding overflow bit is set to 1.
|
||||
|
||||
In architectures without SIMD support, the logical layout stays the same, but the metadata
|
||||
word is codified using a technique we call _bit interleaving_: this layout allows us
|
||||
to emulate SIMD with reasonably good performance using only standard arithmetic and
|
||||
logical operations.
|
||||
|
||||
[#img-foa-metadata-interleaving]
|
||||
.Bit-interleaved metadata word.
|
||||
image::foa-metadata-interleaving.png[align=center]
|
||||
|
||||
A more detailed description of Boost.Unordered's open-addressing implementation is
|
||||
given in an
|
||||
https://bannalia.blogspot.com/2022/11/inside-boostunorderedflatmap.html[external article].
|
||||
For more information on implementation rationale, read the
|
||||
xref:#rationale_open_addresing_containers[corresponding section].
|
||||
|
||||
== Concurrent Containers
|
||||
|
||||
`boost::concurrent_flat_map` uses the basic
|
||||
xref:#structures_open_addressing_containers[open-addressing layout] described above
|
||||
augmented with synchronization mechanisms.
|
||||
|
||||
|
||||
[#img-cfoa-layout]
|
||||
.Concurrent open-addressing layout used by Boost.Unordered.
|
||||
image::cfoa.png[align=center]
|
||||
|
||||
Two levels of synchronization are used:
|
||||
|
||||
* Container level: A read-write mutex is used to control access from any operation
|
||||
to the container. Typically, such access is in read mode (that is, concurrent) even
|
||||
for modifying operations, so for most practical purposes there is no thread
|
||||
contention at this level. Access is only in write mode (blocking) when rehashing or
|
||||
performing container-wide operations such as swapping or assignment.
|
||||
* Group level: Each 15-slot group is equipped with an 8-byte word containing:
|
||||
** A read-write spinlock for synchronized access to any element in the group.
|
||||
** An atomic _insertion counter_ used for optimistic insertion as described
|
||||
below.
|
||||
|
||||
By using atomic operations to access the group metadata, lookup is (group-level)
|
||||
lock-free up to the point where an actual comparison needs to be done with an element
|
||||
that has been previously SIMD-matched: only then it's the group's spinlock used.
|
||||
|
||||
Insertion uses the following _optimistic algorithm_:
|
||||
|
||||
* The value of the insertion counter for the initial group in the probe
|
||||
sequence is locally recorded (let's call this value `c0`).
|
||||
* Lookup is as described above. If lookup finds no equivalent element,
|
||||
search for an available slot for insertion successively locks/unlocks
|
||||
each group in the probing sequence.
|
||||
* When an available slot is located, it is preemptively occupied (its
|
||||
reduced hash value is set) and the insertion counter is atomically
|
||||
incremented: if no other thread has incremented the counter during the
|
||||
whole operation (which is checked by comparing with `c0`), then we're
|
||||
good to go and complete the insertion, otherwise we roll back and start
|
||||
over.
|
||||
|
||||
This algorithm has very low contention both at the lookup and actual
|
||||
insertion phases in exchange for the possibility that computations have
|
||||
to be started over if some other thread interferes in the process by
|
||||
performing a succesful insertion beginning at the same group. In
|
||||
practice, the start-over frequency is extremely small, measured in the range
|
||||
of parts per million for some of our benchmarks.
|
||||
|
||||
For more information on implementation rationale, read the
|
||||
xref:#rationale_concurrent_containers[corresponding section].
|
||||
@@ -1,5 +1,5 @@
|
||||
[#unordered_flat_map]
|
||||
== Class template unordered_flat_map
|
||||
== Class Template unordered_flat_map
|
||||
|
||||
:idprefix: unordered_flat_map_
|
||||
|
||||
@@ -280,6 +280,7 @@ namespace boost {
|
||||
unordered_flat_map<Key, T, Hash, Pred, Alloc>& y)
|
||||
noexcept(noexcept(x.swap(y)));
|
||||
|
||||
// Erasure
|
||||
template<class K, class T, class H, class P, class A, class Predicate>
|
||||
typename unordered_flat_map<K, T, H, P, A>::size_type
|
||||
xref:#unordered_flat_map_erase_if[erase_if](unordered_flat_map<K, T, H, P, A>& c, Predicate pred);
|
||||
@@ -859,7 +860,7 @@ void insert(std::initializer_list<value_type>);
|
||||
Inserts a range of elements into the container. Elements are inserted if and only if there is no element in the container with an equivalent key.
|
||||
|
||||
[horizontal]
|
||||
Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/EmplaceConstructible[EmplaceConstructible^] into the container from `*first`.
|
||||
Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInsertable[CopyInsertable^] into the container.
|
||||
Throws:;; When inserting a single element, if an exception is thrown by an operation other than a call to `hasher` the function has no effect.
|
||||
Notes:;; Can invalidate iterators, pointers and references, but only if the insert causes the load to be greater than the maximum load.
|
||||
|
||||
@@ -875,7 +876,7 @@ template<class K, class... Args>
|
||||
std::pair<iterator, bool> try_emplace(K&& k, Args&&... args);
|
||||
```
|
||||
|
||||
Inserts a new node into the container if there is no existing element with key `k` contained within it.
|
||||
Inserts a new element into the container if there is no existing element with key `k` contained within it.
|
||||
|
||||
If there is an existing element with key `k` this function does nothing.
|
||||
|
||||
@@ -904,7 +905,7 @@ unlike xref:#unordered_flat_map_emplace[emplace], which simply forwards all argu
|
||||
|
||||
Can invalidate iterators pointers and references, but only if the insert causes the load to be greater than the maximum load.
|
||||
|
||||
The `template <class K, class... Args>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs and neither `iterator` nor `const_iterator` are implicitly convertible from `K`. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
The `template<class K, class\... Args>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs and neither `iterator` nor `const_iterator` are implicitly convertible from `K`. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
--
|
||||
|
||||
@@ -920,7 +921,7 @@ template<class K, class... Args>
|
||||
iterator try_emplace(const_iterator hint, K&& k, Args&&... args);
|
||||
```
|
||||
|
||||
Inserts a new node into the container if there is no existing element with key `k` contained within it.
|
||||
Inserts a new element into the container if there is no existing element with key `k` contained within it.
|
||||
|
||||
If there is an existing element with key `k` this function does nothing.
|
||||
|
||||
@@ -949,7 +950,7 @@ unlike xref:#unordered_flat_map_emplace_hint[emplace_hint], which simply forward
|
||||
|
||||
Can invalidate iterators pointers and references, but only if the insert causes the load to be greater than the maximum load.
|
||||
|
||||
The `template <class K, class... Args>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs and neither `iterator` nor `const_iterator` are implicitly convertible from `K`. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
The `template<class K, class\... Args>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs and neither `iterator` nor `const_iterator` are implicitly convertible from `K`. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
--
|
||||
|
||||
@@ -1160,7 +1161,7 @@ template<class K>
|
||||
|
||||
[horizontal]
|
||||
Returns:;; An iterator pointing to an element with key equivalent to `k`, or `end()` if no such element exists.
|
||||
Notes:;; The `template <typename K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
Notes:;; The `template<class K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
---
|
||||
|
||||
@@ -1173,7 +1174,7 @@ template<class K>
|
||||
|
||||
[horizontal]
|
||||
Returns:;; The number of elements with key equivalent to `k`.
|
||||
Notes:;; The `template <typename K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
Notes:;; The `template<class K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
---
|
||||
|
||||
@@ -1186,7 +1187,7 @@ template<class K>
|
||||
|
||||
[horizontal]
|
||||
Returns:;; A boolean indicating whether or not there is an element with key equal to `key` in the container
|
||||
Notes:;; The `template <typename K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
Notes:;; The `template<class K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
---
|
||||
|
||||
@@ -1202,7 +1203,7 @@ template<class K>
|
||||
|
||||
[horizontal]
|
||||
Returns:;; A range containing all elements with key equivalent to `k`. If the container doesn't contain any such elements, returns `std::make_pair(b.end(), b.end())`.
|
||||
Notes:;; The `template <typename K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
Notes:;; The `template<class K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[#unordered_flat_set]
|
||||
== Class template unordered_flat_set
|
||||
== Class Template unordered_flat_set
|
||||
|
||||
:idprefix: unordered_flat_set_
|
||||
|
||||
@@ -234,6 +234,7 @@ namespace boost {
|
||||
unordered_flat_set<Key, T, Hash, Pred, Alloc>& y)
|
||||
noexcept(noexcept(x.swap(y)));
|
||||
|
||||
// Erasure
|
||||
template<class K, class T, class H, class P, class A, class Predicate>
|
||||
typename unordered_flat_set<K, T, H, P, A>::size_type
|
||||
xref:#unordered_flat_set_erase_if[erase_if](unordered_flat_set<K, T, H, P, A>& c, Predicate pred);
|
||||
@@ -837,7 +838,7 @@ void insert(std::initializer_list<value_type>);
|
||||
Inserts a range of elements into the container. Elements are inserted if and only if there is no element in the container with an equivalent key.
|
||||
|
||||
[horizontal]
|
||||
Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/EmplaceConstructible[EmplaceConstructible^] into the container from `*first`.
|
||||
Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInsertable[CopyInsertable^] into the container.
|
||||
Throws:;; When inserting a single element, if an exception is thrown by an operation other than a call to `hasher` the function has no effect.
|
||||
Notes:;; Can invalidate iterators, pointers and references, but only if the insert causes the load to be greater than the maximum load.
|
||||
|
||||
@@ -971,7 +972,7 @@ template<class K>
|
||||
|
||||
[horizontal]
|
||||
Returns:;; An iterator pointing to an element with key equivalent to `k`, or `end()` if no such element exists.
|
||||
Notes:;; The `template <typename K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
Notes:;; The `template<class K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
---
|
||||
|
||||
@@ -984,7 +985,7 @@ template<class K>
|
||||
|
||||
[horizontal]
|
||||
Returns:;; The number of elements with key equivalent to `k`.
|
||||
Notes:;; The `template <typename K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
Notes:;; The `template<class K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
---
|
||||
|
||||
@@ -997,7 +998,7 @@ template<class K>
|
||||
|
||||
[horizontal]
|
||||
Returns:;; A boolean indicating whether or not there is an element with key equal to `key` in the container
|
||||
Notes:;; The `template <typename K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
Notes:;; The `template<class K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
---
|
||||
|
||||
@@ -1013,7 +1014,7 @@ template<class K>
|
||||
|
||||
[horizontal]
|
||||
Returns:;; A range containing all elements with key equivalent to `k`. If the container doesn't contain any such elements, returns `std::make_pair(b.end(), b.end())`.
|
||||
Notes:;; The `template <typename K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
Notes:;; The `template<class K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[#unordered_map]
|
||||
== Class template unordered_map
|
||||
== Class Template unordered_map
|
||||
|
||||
:idprefix: unordered_map_
|
||||
|
||||
@@ -286,6 +286,7 @@ namespace boost {
|
||||
unordered_map<Key, T, Hash, Pred, Alloc>& y)
|
||||
noexcept(noexcept(x.swap(y)));
|
||||
|
||||
// Erasure
|
||||
template<class K, class T, class H, class P, class A, class Predicate>
|
||||
typename unordered_map<K, T, H, P, A>::size_type
|
||||
xref:#unordered_map_erase_if[erase_if](unordered_map<K, T, H, P, A>& c, Predicate pred);
|
||||
@@ -995,7 +996,7 @@ void insert(std::initializer_list<value_type>);
|
||||
Inserts a range of elements into the container. Elements are inserted if and only if there is no element in the container with an equivalent key.
|
||||
|
||||
[horizontal]
|
||||
Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/EmplaceConstructible[EmplaceConstructible^] into `X` from `*first`.
|
||||
Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInsertable[CopyInsertable^] into the container.
|
||||
Throws:;; When inserting a single element, if an exception is thrown by an operation other than a call to `hasher` the function has no effect.
|
||||
Notes:;; Can invalidate iterators, but only if the insert causes the load factor to be greater to or equal to the maximum load factor. +
|
||||
+
|
||||
@@ -1009,11 +1010,11 @@ template<class... Args>
|
||||
std::pair<iterator, bool> try_emplace(const key_type& k, Args&&... args);
|
||||
template<class... Args>
|
||||
std::pair<iterator, bool> try_emplace(key_type&& k, Args&&... args);
|
||||
template <class K, class... Args>
|
||||
template<class K, class... Args>
|
||||
std::pair<iterator, bool> try_emplace(K&& k, Args&&... args)
|
||||
```
|
||||
|
||||
Inserts a new node into the container if there is no existing element with key `k` contained within it.
|
||||
Inserts a new element into the container if there is no existing element with key `k` contained within it.
|
||||
|
||||
If there is an existing element with key `k` this function does nothing.
|
||||
|
||||
@@ -1043,7 +1044,7 @@ Can invalidate iterators, but only if the insert causes the load factor to be gr
|
||||
|
||||
Pointers and references to elements are never invalidated.
|
||||
|
||||
The `template <class K, class... Args>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs and neither `iterator` nor `const_iterator` are implicitly convertible from `K`. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
The `template<class K, class\... Args>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs and neither `iterator` nor `const_iterator` are implicitly convertible from `K`. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
If the compiler doesn't support variadic template arguments or rvalue references, this is emulated for up to `10` arguments, with no support for rvalue references or move semantics.
|
||||
|
||||
@@ -1062,7 +1063,7 @@ template<class K, class... Args>
|
||||
iterator try_emplace(const_iterator hint, K&& k, Args&&... args);
|
||||
```
|
||||
|
||||
Inserts a new node into the container if there is no existing element with key `k` contained within it.
|
||||
Inserts a new element into the container if there is no existing element with key `k` contained within it.
|
||||
|
||||
If there is an existing element with key `k` this function does nothing.
|
||||
|
||||
@@ -1094,7 +1095,7 @@ Can invalidate iterators, but only if the insert causes the load factor to be gr
|
||||
|
||||
Pointers and references to elements are never invalidated.
|
||||
|
||||
The `template <class K, class... Args>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs and neither `iterator` nor `const_iterator` are implicitly convertible from `K`. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
The `template<class K, class\... Args>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs and neither `iterator` nor `const_iterator` are implicitly convertible from `K`. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
If the compiler doesn't support variadic template arguments or rvalue references, this is emulated for up to `10` arguments, with no support for rvalue references or move semantics.
|
||||
|
||||
@@ -1466,7 +1467,7 @@ template<typename CompatibleKey, typename CompatibleHash, typename CompatiblePre
|
||||
Returns:;; An iterator pointing to an element with key equivalent to `k`, or `b.end()` if no such element exists.
|
||||
Notes:;; The templated overloads containing `CompatibleKey`, `CompatibleHash` and `CompatiblePredicate` are non-standard extensions which allow you to use a compatible hash function and equality predicate for a key of a different type in order to avoid an expensive type cast. In general, its use is not encouraged and instead the `K` member function templates should be used. +
|
||||
+
|
||||
The `template <typename K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
The `template<class K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
---
|
||||
|
||||
@@ -1479,7 +1480,7 @@ template<class K>
|
||||
|
||||
[horizontal]
|
||||
Returns:;; The number of elements with key equivalent to `k`.
|
||||
Notes:;; The `template <typename K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
Notes:;; The `template<class K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
---
|
||||
|
||||
@@ -1492,7 +1493,7 @@ template<class K>
|
||||
|
||||
[horizontal]
|
||||
Returns:;; A boolean indicating whether or not there is an element with key equal to `key` in the container
|
||||
Notes:;; The `template <typename K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
Notes:;; The `template<class K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
---
|
||||
|
||||
@@ -1508,7 +1509,7 @@ template<class K>
|
||||
|
||||
[horizontal]
|
||||
Returns:;; A range containing all elements with key equivalent to `k`. If the container doesn't contain any such elements, returns `std::make_pair(b.end(), b.end())`.
|
||||
Notes:;; The `template <typename K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
Notes:;; The `template<class K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[#unordered_multimap]
|
||||
== Class template unordered_multimap
|
||||
== Class Template unordered_multimap
|
||||
|
||||
:idprefix: unordered_multimap_
|
||||
|
||||
@@ -253,6 +253,7 @@ namespace boost {
|
||||
unordered_multimap<Key, T, Hash, Pred, Alloc>& y)
|
||||
noexcept(noexcept(x.swap(y)));
|
||||
|
||||
// Erasure
|
||||
template<class K, class T, class H, class P, class A, class Predicate>
|
||||
typename unordered_multimap<K, T, H, P, A>::size_type
|
||||
xref:#unordered_multimap_erase_if[erase_if](unordered_multimap<K, T, H, P, A>& c, Predicate pred);
|
||||
@@ -941,7 +942,7 @@ void insert(std::initializer_list<value_type> il);
|
||||
Inserts a range of elements into the container.
|
||||
|
||||
[horizontal]
|
||||
Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/EmplaceConstructible[EmplaceConstructible^] into `X` from `*first`.
|
||||
Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInsertable[CopyInsertable^] into the container.
|
||||
Throws:;; When inserting a single element, if an exception is thrown by an operation other than a call to `hasher` the function has no effect.
|
||||
Notes:;; Can invalidate iterators, but only if the insert causes the load factor to be greater to or equal to the maximum load factor. +
|
||||
+
|
||||
@@ -1223,7 +1224,7 @@ template<typename CompatibleKey, typename CompatibleHash, typename CompatiblePre
|
||||
Returns:;; An iterator pointing to an element with key equivalent to `k`, or `b.end()` if no such element exists.
|
||||
Notes:;; The templated overloads containing `CompatibleKey`, `CompatibleHash` and `CompatiblePredicate` are non-standard extensions which allow you to use a compatible hash function and equality predicate for a key of a different type in order to avoid an expensive type cast. In general, its use is not encouraged and instead the `K` member function templates should be used. +
|
||||
+
|
||||
The `template <typename K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
The `template<class K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
---
|
||||
|
||||
@@ -1236,7 +1237,7 @@ template<class K>
|
||||
|
||||
[horizontal]
|
||||
Returns:;; The number of elements with key equivalent to `k`.
|
||||
Notes:;; The `template <typename K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
Notes:;; The `template<class K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
---
|
||||
|
||||
@@ -1249,7 +1250,7 @@ template<class K>
|
||||
|
||||
[horizontal]
|
||||
Returns:;; A boolean indicating whether or not there is an element with key equal to `key` in the container
|
||||
Notes:;; The `template <typename K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
Notes:;; The `template<class K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
---
|
||||
|
||||
@@ -1265,7 +1266,7 @@ template<class K>
|
||||
|
||||
[horizontal]
|
||||
Returns:;; A range containing all elements with key equivalent to `k`. If the container doesn't contain any such elements, returns `std::make_pair(b.end(), b.end())`.
|
||||
Notes:;; The `template <typename K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
Notes:;; The `template<class K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[#unordered_multiset]
|
||||
== Class template unordered_multiset
|
||||
== Class Template unordered_multiset
|
||||
|
||||
:idprefix: unordered_multiset_
|
||||
|
||||
@@ -244,6 +244,7 @@ namespace boost {
|
||||
unordered_multiset<Key, Hash, Pred, Alloc>& y)
|
||||
noexcept(noexcept(x.swap(y)));
|
||||
|
||||
// Erasure
|
||||
template<class K, class H, class P, class A, class Predicate>
|
||||
typename unordered_multiset<K, H, P, A>::size_type
|
||||
xref:#unordered_multiset_erase_if[erase_if](unordered_multiset<K, H, P, A>& c, Predicate pred);
|
||||
@@ -899,7 +900,7 @@ void insert(std::initializer_list<value_type> il);
|
||||
Inserts a range of elements into the container. Elements are inserted if and only if there is no element in the container with an equivalent key.
|
||||
|
||||
[horizontal]
|
||||
Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/EmplaceConstructible[EmplaceConstructible^] into `X` from `*first`.
|
||||
Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInsertable[CopyInsertable^] into the container.
|
||||
Throws:;; When inserting a single element, if an exception is thrown by an operation other than a call to `hasher` the function has no effect.
|
||||
Notes:;; Can invalidate iterators, but only if the insert causes the load factor to be greater to or equal to the maximum load factor. +
|
||||
+
|
||||
@@ -1181,7 +1182,7 @@ template<typename CompatibleKey, typename CompatibleHash, typename CompatiblePre
|
||||
Returns:;; An iterator pointing to an element with key equivalent to `k`, or `b.end()` if no such element exists.
|
||||
Notes:;; The templated overloads containing `CompatibleKey`, `CompatibleHash` and `CompatiblePredicate` are non-standard extensions which allow you to use a compatible hash function and equality predicate for a key of a different type in order to avoid an expensive type cast. In general, its use is not encouraged and instead the `K` member function templates should be used. +
|
||||
+
|
||||
The `template <typename K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
The `template<class K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
---
|
||||
|
||||
@@ -1194,7 +1195,7 @@ template<class K>
|
||||
|
||||
[horizontal]
|
||||
Returns:;; The number of elements with key equivalent to `k`.
|
||||
Notes:;; The `template <typename K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
Notes:;; The `template<class K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
---
|
||||
|
||||
@@ -1207,7 +1208,7 @@ template<class K>
|
||||
|
||||
[horizontal]
|
||||
Returns:;; A boolean indicating whether or not there is an element with key equal to `key` in the container
|
||||
Notes:;; The `template <typename K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
Notes:;; The `template<class K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
---
|
||||
|
||||
@@ -1223,7 +1224,7 @@ template<class K>
|
||||
|
||||
[horizontal]
|
||||
Returns:;; A range containing all elements with key equivalent to `k`. If the container doesn't contain any such elements, returns `std::make_pair(b.end(), b.end())`.
|
||||
Notes:;; The `template <typename K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
Notes:;; The `template<class K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[#unordered_node_map]
|
||||
== Class template unordered_node_map
|
||||
== Class Template unordered_node_map
|
||||
|
||||
:idprefix: unordered_node_map_
|
||||
|
||||
@@ -284,6 +284,7 @@ namespace boost {
|
||||
unordered_node_map<Key, T, Hash, Pred, Alloc>& y)
|
||||
noexcept(noexcept(x.swap(y)));
|
||||
|
||||
// Erasure
|
||||
template<class K, class T, class H, class P, class A, class Predicate>
|
||||
typename unordered_node_map<K, T, H, P, A>::size_type
|
||||
xref:#unordered_node_map_erase_if[erase_if](unordered_node_map<K, T, H, P, A>& c, Predicate pred);
|
||||
@@ -893,7 +894,7 @@ void insert(std::initializer_list<value_type>);
|
||||
Inserts a range of elements into the container. Elements are inserted if and only if there is no element in the container with an equivalent key.
|
||||
|
||||
[horizontal]
|
||||
Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/EmplaceConstructible[EmplaceConstructible^] into the container from `*first`.
|
||||
Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInsertable[CopyInsertable^] into the container.
|
||||
Throws:;; When inserting a single element, if an exception is thrown by an operation other than a call to `hasher` the function has no effect.
|
||||
Notes:;; Can invalidate iterators, but only if the insert causes the load to be greater than the maximum load.
|
||||
|
||||
@@ -945,7 +946,7 @@ template<class K, class... Args>
|
||||
std::pair<iterator, bool> try_emplace(K&& k, Args&&... args);
|
||||
```
|
||||
|
||||
Inserts a new node into the container if there is no existing element with key `k` contained within it.
|
||||
Inserts a new element into the container if there is no existing element with key `k` contained within it.
|
||||
|
||||
If there is an existing element with key `k` this function does nothing.
|
||||
|
||||
@@ -974,7 +975,7 @@ unlike xref:#unordered_node_map_emplace[emplace], which simply forwards all argu
|
||||
|
||||
Can invalidate iterators, but only if the insert causes the load to be greater than the maximum load.
|
||||
|
||||
The `template <class K, class... Args>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs and neither `iterator` nor `const_iterator` are implicitly convertible from `K`. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
The `template<class K, class\... Args>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs and neither `iterator` nor `const_iterator` are implicitly convertible from `K`. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
--
|
||||
|
||||
@@ -990,7 +991,7 @@ template<class K, class... Args>
|
||||
iterator try_emplace(const_iterator hint, K&& k, Args&&... args);
|
||||
```
|
||||
|
||||
Inserts a new node into the container if there is no existing element with key `k` contained within it.
|
||||
Inserts a new element into the container if there is no existing element with key `k` contained within it.
|
||||
|
||||
If there is an existing element with key `k` this function does nothing.
|
||||
|
||||
@@ -1019,7 +1020,7 @@ unlike xref:#unordered_node_map_emplace_hint[emplace_hint], which simply forward
|
||||
|
||||
Can invalidate iterators, but only if the insert causes the load to be greater than the maximum load.
|
||||
|
||||
The `template <class K, class... Args>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs and neither `iterator` nor `const_iterator` are implicitly convertible from `K`. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
The `template<class K, class\... Args>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs and neither `iterator` nor `const_iterator` are implicitly convertible from `K`. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
--
|
||||
|
||||
@@ -1258,7 +1259,7 @@ template<class K>
|
||||
|
||||
[horizontal]
|
||||
Returns:;; An iterator pointing to an element with key equivalent to `k`, or `end()` if no such element exists.
|
||||
Notes:;; The `template <typename K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
Notes:;; The `template<class K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
---
|
||||
|
||||
@@ -1271,7 +1272,7 @@ template<class K>
|
||||
|
||||
[horizontal]
|
||||
Returns:;; The number of elements with key equivalent to `k`.
|
||||
Notes:;; The `template <typename K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
Notes:;; The `template<class K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
---
|
||||
|
||||
@@ -1284,7 +1285,7 @@ template<class K>
|
||||
|
||||
[horizontal]
|
||||
Returns:;; A boolean indicating whether or not there is an element with key equal to `key` in the container
|
||||
Notes:;; The `template <typename K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
Notes:;; The `template<class K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
---
|
||||
|
||||
@@ -1300,7 +1301,7 @@ template<class K>
|
||||
|
||||
[horizontal]
|
||||
Returns:;; A range containing all elements with key equivalent to `k`. If the container doesn't contain any such elements, returns `std::make_pair(b.end(), b.end())`.
|
||||
Notes:;; The `template <typename K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
Notes:;; The `template<class K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[#unordered_node_set]
|
||||
== Class template unordered_node_set
|
||||
== Class Template unordered_node_set
|
||||
|
||||
:idprefix: unordered_node_set_
|
||||
|
||||
@@ -238,6 +238,7 @@ namespace boost {
|
||||
unordered_node_set<Key, T, Hash, Pred, Alloc>& y)
|
||||
noexcept(noexcept(x.swap(y)));
|
||||
|
||||
// Erasure
|
||||
template<class K, class T, class H, class P, class A, class Predicate>
|
||||
typename unordered_node_set<K, T, H, P, A>::size_type
|
||||
xref:#unordered_node_set_erase_if[erase_if](unordered_node_set<K, T, H, P, A>& c, Predicate pred);
|
||||
@@ -874,7 +875,7 @@ void insert(std::initializer_list<value_type>);
|
||||
Inserts a range of elements into the container. Elements are inserted if and only if there is no element in the container with an equivalent key.
|
||||
|
||||
[horizontal]
|
||||
Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/EmplaceConstructible[EmplaceConstructible^] into the container from `*first`.
|
||||
Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInsertable[CopyInsertable^] into the container.
|
||||
Throws:;; When inserting a single element, if an exception is thrown by an operation other than a call to `hasher` the function has no effect.
|
||||
Notes:;; Can invalidate iterators, but only if the insert causes the load to be greater than the maximum load.
|
||||
|
||||
@@ -1072,7 +1073,7 @@ template<class K>
|
||||
|
||||
[horizontal]
|
||||
Returns:;; An iterator pointing to an element with key equivalent to `k`, or `end()` if no such element exists.
|
||||
Notes:;; The `template <typename K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
Notes:;; The `template<class K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
---
|
||||
|
||||
@@ -1085,7 +1086,7 @@ template<class K>
|
||||
|
||||
[horizontal]
|
||||
Returns:;; The number of elements with key equivalent to `k`.
|
||||
Notes:;; The `template <typename K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
Notes:;; The `template<class K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
---
|
||||
|
||||
@@ -1098,7 +1099,7 @@ template<class K>
|
||||
|
||||
[horizontal]
|
||||
Returns:;; A boolean indicating whether or not there is an element with key equal to `key` in the container
|
||||
Notes:;; The `template <typename K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
Notes:;; The `template<class K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
---
|
||||
|
||||
@@ -1114,7 +1115,7 @@ template<class K>
|
||||
|
||||
[horizontal]
|
||||
Returns:;; A range containing all elements with key equivalent to `k`. If the container doesn't contain any such elements, returns `std::make_pair(b.end(), b.end())`.
|
||||
Notes:;; The `template <typename K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
Notes:;; The `template<class K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[#unordered_set]
|
||||
== Class template unordered_set
|
||||
== Class Template unordered_set
|
||||
|
||||
:idprefix: unordered_set_
|
||||
|
||||
@@ -245,6 +245,7 @@ namespace boost {
|
||||
unordered_set<Key, Hash, Pred, Alloc>& y)
|
||||
noexcept(noexcept(x.swap(y)));
|
||||
|
||||
// Erasure
|
||||
template<class K, class H, class P, class A, class Predicate>
|
||||
typename unordered_set<K, H, P, A>::size_type
|
||||
xref:#unordered_set_erase_if[erase_if](unordered_set<K, H, P, A>& c, Predicate pred);
|
||||
@@ -959,7 +960,7 @@ void insert(std::initializer_list<value_type>);
|
||||
Inserts a range of elements into the container. Elements are inserted if and only if there is no element in the container with an equivalent key.
|
||||
|
||||
[horizontal]
|
||||
Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/EmplaceConstructible[EmplaceConstructible^] into `X` from `*first`.
|
||||
Requires:;; `value_type` is https://en.cppreference.com/w/cpp/named_req/CopyInsertable[CopyInsertable^] into the container.
|
||||
Throws:;; When inserting a single element, if an exception is thrown by an operation other than a call to `hasher` the function has no effect.
|
||||
Notes:;; Can invalidate iterators, but only if the insert causes the load factor to be greater to or equal to the maximum load factor. +
|
||||
+
|
||||
@@ -1248,7 +1249,7 @@ template<typename CompatibleKey, typename CompatibleHash, typename CompatiblePre
|
||||
Returns:;; An iterator pointing to an element with key equivalent to `k`, or `b.end()` if no such element exists.
|
||||
Notes:;; The templated overloads containing `CompatibleKey`, `CompatibleHash` and `CompatiblePredicate` are non-standard extensions which allow you to use a compatible hash function and equality predicate for a key of a different type in order to avoid an expensive type cast. In general, its use is not encouraged and instead the `K` member function templates should be used. +
|
||||
+
|
||||
The `template <typename K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
The `template<class K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
---
|
||||
|
||||
@@ -1261,7 +1262,7 @@ template<class K>
|
||||
|
||||
[horizontal]
|
||||
Returns:;; The number of elements with key equivalent to `k`.
|
||||
Notes:;; The `template <typename K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
Notes:;; The `template<class K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
---
|
||||
|
||||
@@ -1274,7 +1275,7 @@ template<class K>
|
||||
|
||||
[horizontal]
|
||||
Returns:;; A boolean indicating whether or not there is an element with key equal to `key` in the container
|
||||
Notes:;; The `template <typename K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
Notes:;; The `template<class K>` overload only participates in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
---
|
||||
|
||||
@@ -1290,7 +1291,7 @@ template<class K>
|
||||
|
||||
[horizontal]
|
||||
Returns:;; A range containing all elements with key equivalent to `k`. If the container doesn't contain any such elements, returns `std::make_pair(b.end(), b.end())`.
|
||||
Notes:;; The `template <typename K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
Notes:;; The `template<class K>` overloads only participate in overload resolution if `Hash::is_transparent` and `Pred::is_transparent` are valid member typedefs. The library assumes that `Hash` is callable with both `K` and `Key` and that `Pred` is transparent. This enables heterogeneous lookup which avoids the cost of instantiating an instance of the `Key` type.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#ifndef BOOST_UNORDERED_CONCURRENT_FLAT_MAP_HPP
|
||||
#define BOOST_UNORDERED_CONCURRENT_FLAT_MAP_HPP
|
||||
|
||||
#include <boost/unordered/concurrent_flat_map_fwd.hpp>
|
||||
#include <boost/unordered/detail/foa/concurrent_table.hpp>
|
||||
#include <boost/unordered/detail/type_traits.hpp>
|
||||
|
||||
@@ -37,6 +38,24 @@
|
||||
boost::unordered::detail::is_invocable<F, value_type const&>::value, \
|
||||
"The provided Callable must be invocable with `value_type const&`");
|
||||
|
||||
#if BOOST_CXX_VERSION >= 202002L
|
||||
|
||||
#define BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(P) \
|
||||
static_assert(!std::is_base_of<std::execution::parallel_unsequenced_policy, \
|
||||
ExecPolicy>::value, \
|
||||
"ExecPolicy must be sequenced."); \
|
||||
static_assert( \
|
||||
!std::is_base_of<std::execution::unsequenced_policy, ExecPolicy>::value, \
|
||||
"ExecPolicy must be sequenced.");
|
||||
|
||||
#else
|
||||
|
||||
#define BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(P) \
|
||||
static_assert(!std::is_base_of<std::execution::parallel_unsequenced_policy, \
|
||||
ExecPolicy>::value, \
|
||||
"ExecPolicy must be sequenced.");
|
||||
#endif
|
||||
|
||||
#define BOOST_UNORDERED_COMMA ,
|
||||
|
||||
#define BOOST_UNORDERED_LAST_ARG(Arg, Args) \
|
||||
@@ -116,16 +135,22 @@ namespace boost {
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
template <class Key, class T, class Hash = boost::hash<Key>,
|
||||
class Pred = std::equal_to<Key>,
|
||||
class Allocator = std::allocator<std::pair<Key const, T> > >
|
||||
template <class Key, class T, class Hash, class Pred, class Allocator>
|
||||
class concurrent_flat_map
|
||||
{
|
||||
private:
|
||||
template <class Key2, class T2, class Hash2, class Pred2,
|
||||
class Allocator2>
|
||||
friend class concurrent_flat_map;
|
||||
|
||||
using type_policy = detail::concurrent_map_types<Key, T>;
|
||||
|
||||
detail::foa::concurrent_table<type_policy, Hash, Pred, Allocator> table_;
|
||||
|
||||
template <class K, class V, class H, class KE, class A>
|
||||
bool friend operator==(concurrent_flat_map<K, V, H, KE, A> const& lhs,
|
||||
concurrent_flat_map<K, V, H, KE, A> const& rhs);
|
||||
|
||||
public:
|
||||
using key_type = Key;
|
||||
using mapped_type = T;
|
||||
@@ -258,6 +283,21 @@ namespace boost {
|
||||
return *this;
|
||||
}
|
||||
|
||||
concurrent_flat_map& operator=(concurrent_flat_map&& rhs)
|
||||
noexcept(boost::allocator_is_always_equal<Allocator>::type::value ||
|
||||
boost::allocator_propagate_on_container_move_assignment<
|
||||
Allocator>::type::value)
|
||||
{
|
||||
table_ = std::move(rhs.table_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
concurrent_flat_map& operator=(std::initializer_list<value_type> ilist)
|
||||
{
|
||||
table_ = ilist;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Capacity
|
||||
///
|
||||
|
||||
@@ -269,21 +309,21 @@ namespace boost {
|
||||
}
|
||||
|
||||
template <class F>
|
||||
BOOST_FORCEINLINE std::size_t visit(key_type const& k, F f)
|
||||
BOOST_FORCEINLINE size_type visit(key_type const& k, F f)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F)
|
||||
return table_.visit(k, f);
|
||||
}
|
||||
|
||||
template <class F>
|
||||
BOOST_FORCEINLINE std::size_t visit(key_type const& k, F f) const
|
||||
BOOST_FORCEINLINE size_type visit(key_type const& k, F f) const
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
|
||||
return table_.visit(k, f);
|
||||
}
|
||||
|
||||
template <class F>
|
||||
BOOST_FORCEINLINE std::size_t cvisit(key_type const& k, F f) const
|
||||
BOOST_FORCEINLINE size_type cvisit(key_type const& k, F f) const
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
|
||||
return table_.visit(k, f);
|
||||
@@ -291,7 +331,7 @@ namespace boost {
|
||||
|
||||
template <class K, class F>
|
||||
BOOST_FORCEINLINE typename std::enable_if<
|
||||
detail::are_transparent<K, hasher, key_equal>::value, std::size_t>::type
|
||||
detail::are_transparent<K, hasher, key_equal>::value, size_type>::type
|
||||
visit(K&& k, F f)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F)
|
||||
@@ -300,7 +340,7 @@ namespace boost {
|
||||
|
||||
template <class K, class F>
|
||||
BOOST_FORCEINLINE typename std::enable_if<
|
||||
detail::are_transparent<K, hasher, key_equal>::value, std::size_t>::type
|
||||
detail::are_transparent<K, hasher, key_equal>::value, size_type>::type
|
||||
visit(K&& k, F f) const
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
|
||||
@@ -309,26 +349,26 @@ namespace boost {
|
||||
|
||||
template <class K, class F>
|
||||
BOOST_FORCEINLINE typename std::enable_if<
|
||||
detail::are_transparent<K, hasher, key_equal>::value, std::size_t>::type
|
||||
detail::are_transparent<K, hasher, key_equal>::value, size_type>::type
|
||||
cvisit(K&& k, F f) const
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
|
||||
return table_.visit(std::forward<K>(k), f);
|
||||
}
|
||||
|
||||
template <class F> BOOST_FORCEINLINE std::size_t visit_all(F f)
|
||||
template <class F> size_type visit_all(F f)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F)
|
||||
return table_.visit_all(f);
|
||||
}
|
||||
|
||||
template <class F> BOOST_FORCEINLINE std::size_t visit_all(F f) const
|
||||
template <class F> size_type visit_all(F f) const
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
|
||||
return table_.visit_all(f);
|
||||
}
|
||||
|
||||
template <class F> BOOST_FORCEINLINE std::size_t cvisit_all(F f) const
|
||||
template <class F> size_type cvisit_all(F f) const
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
|
||||
return table_.cvisit_all(f);
|
||||
@@ -336,32 +376,32 @@ namespace boost {
|
||||
|
||||
#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS)
|
||||
template <class ExecPolicy, class F>
|
||||
BOOST_FORCEINLINE
|
||||
typename std::enable_if<detail::is_execution_policy<ExecPolicy>::value,
|
||||
void>::type
|
||||
visit_all(ExecPolicy p, F f)
|
||||
typename std::enable_if<detail::is_execution_policy<ExecPolicy>::value,
|
||||
void>::type
|
||||
visit_all(ExecPolicy&& p, F f)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F)
|
||||
BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(ExecPolicy)
|
||||
table_.visit_all(p, f);
|
||||
}
|
||||
|
||||
template <class ExecPolicy, class F>
|
||||
BOOST_FORCEINLINE
|
||||
typename std::enable_if<detail::is_execution_policy<ExecPolicy>::value,
|
||||
void>::type
|
||||
visit_all(ExecPolicy p, F f) const
|
||||
typename std::enable_if<detail::is_execution_policy<ExecPolicy>::value,
|
||||
void>::type
|
||||
visit_all(ExecPolicy&& p, F f) const
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
|
||||
BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(ExecPolicy)
|
||||
table_.visit_all(p, f);
|
||||
}
|
||||
|
||||
template <class ExecPolicy, class F>
|
||||
BOOST_FORCEINLINE
|
||||
typename std::enable_if<detail::is_execution_policy<ExecPolicy>::value,
|
||||
void>::type
|
||||
cvisit_all(ExecPolicy p, F f) const
|
||||
typename std::enable_if<detail::is_execution_policy<ExecPolicy>::value,
|
||||
void>::type
|
||||
cvisit_all(ExecPolicy&& p, F f) const
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
|
||||
BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(ExecPolicy)
|
||||
table_.cvisit_all(p, f);
|
||||
}
|
||||
#endif
|
||||
@@ -369,33 +409,27 @@ namespace boost {
|
||||
/// Modifiers
|
||||
///
|
||||
|
||||
BOOST_FORCEINLINE bool insert(value_type const& obj)
|
||||
template <class Ty>
|
||||
BOOST_FORCEINLINE auto insert(Ty&& value)
|
||||
-> decltype(table_.insert(std::forward<Ty>(value)))
|
||||
{
|
||||
return table_.insert(obj);
|
||||
}
|
||||
BOOST_FORCEINLINE bool insert(value_type&& obj)
|
||||
{
|
||||
return table_.insert(std::move(obj));
|
||||
return table_.insert(std::forward<Ty>(value));
|
||||
}
|
||||
|
||||
BOOST_FORCEINLINE bool insert(init_type const& obj)
|
||||
{
|
||||
return table_.insert(obj);
|
||||
}
|
||||
BOOST_FORCEINLINE bool insert(init_type&& obj)
|
||||
{
|
||||
return table_.insert(std::move(obj));
|
||||
}
|
||||
|
||||
template <class InputIterator>
|
||||
BOOST_FORCEINLINE void insert(InputIterator begin, InputIterator end)
|
||||
void insert(InputIterator begin, InputIterator end)
|
||||
{
|
||||
for (auto pos = begin; pos != end; ++pos) {
|
||||
table_.insert(*pos);
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_FORCEINLINE void insert(std::initializer_list<value_type> ilist)
|
||||
void insert(std::initializer_list<value_type> ilist)
|
||||
{
|
||||
this->insert(ilist.begin(), ilist.end());
|
||||
}
|
||||
@@ -424,25 +458,11 @@ namespace boost {
|
||||
[&](value_type& m) { m.second = std::forward<M>(obj); });
|
||||
}
|
||||
|
||||
template <class F>
|
||||
BOOST_FORCEINLINE bool insert_or_visit(value_type const& obj, F f)
|
||||
template <class Ty, class F>
|
||||
BOOST_FORCEINLINE auto insert_or_visit(Ty&& value, F f)
|
||||
-> decltype(table_.insert_or_visit(std::forward<Ty>(value), f))
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F)
|
||||
return table_.insert_or_visit(obj, f);
|
||||
}
|
||||
|
||||
template <class F>
|
||||
BOOST_FORCEINLINE bool insert_or_visit(value_type&& obj, F f)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F)
|
||||
return table_.insert_or_visit(std::move(obj), f);
|
||||
}
|
||||
|
||||
template <class F>
|
||||
BOOST_FORCEINLINE bool insert_or_visit(init_type const& obj, F f)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F)
|
||||
return table_.insert_or_visit(obj, f);
|
||||
return table_.insert_or_visit(std::forward<Ty>(value), f);
|
||||
}
|
||||
|
||||
template <class F>
|
||||
@@ -453,8 +473,7 @@ namespace boost {
|
||||
}
|
||||
|
||||
template <class InputIterator, class F>
|
||||
BOOST_FORCEINLINE void insert_or_visit(
|
||||
InputIterator first, InputIterator last, F f)
|
||||
void insert_or_visit(InputIterator first, InputIterator last, F f)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F)
|
||||
for (; first != last; ++first) {
|
||||
@@ -463,32 +482,18 @@ namespace boost {
|
||||
}
|
||||
|
||||
template <class F>
|
||||
BOOST_FORCEINLINE void insert_or_visit(
|
||||
std::initializer_list<value_type> ilist, F f)
|
||||
void insert_or_visit(std::initializer_list<value_type> ilist, F f)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F)
|
||||
this->insert_or_visit(ilist.begin(), ilist.end(), f);
|
||||
}
|
||||
|
||||
template <class F>
|
||||
BOOST_FORCEINLINE bool insert_or_cvisit(value_type const& obj, F f)
|
||||
template <class Ty, class F>
|
||||
BOOST_FORCEINLINE auto insert_or_cvisit(Ty&& value, F f)
|
||||
-> decltype(table_.insert_or_cvisit(std::forward<Ty>(value), f))
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
|
||||
return table_.insert_or_cvisit(obj, f);
|
||||
}
|
||||
|
||||
template <class F>
|
||||
BOOST_FORCEINLINE bool insert_or_cvisit(value_type&& obj, F f)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
|
||||
return table_.insert_or_cvisit(std::move(obj), f);
|
||||
}
|
||||
|
||||
template <class F>
|
||||
BOOST_FORCEINLINE bool insert_or_cvisit(init_type const& obj, F f)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
|
||||
return table_.insert_or_cvisit(obj, f);
|
||||
return table_.insert_or_cvisit(std::forward<Ty>(value), f);
|
||||
}
|
||||
|
||||
template <class F>
|
||||
@@ -499,8 +504,7 @@ namespace boost {
|
||||
}
|
||||
|
||||
template <class InputIterator, class F>
|
||||
BOOST_FORCEINLINE void insert_or_cvisit(
|
||||
InputIterator first, InputIterator last, F f)
|
||||
void insert_or_cvisit(InputIterator first, InputIterator last, F f)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
|
||||
for (; first != last; ++first) {
|
||||
@@ -509,8 +513,7 @@ namespace boost {
|
||||
}
|
||||
|
||||
template <class F>
|
||||
BOOST_FORCEINLINE void insert_or_cvisit(
|
||||
std::initializer_list<value_type> ilist, F f)
|
||||
void insert_or_cvisit(std::initializer_list<value_type> ilist, F f)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F)
|
||||
this->insert_or_visit(ilist.begin(), ilist.end(), f);
|
||||
@@ -643,22 +646,77 @@ namespace boost {
|
||||
|
||||
#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS)
|
||||
template <class ExecPolicy, class F>
|
||||
BOOST_FORCEINLINE
|
||||
typename std::enable_if<detail::is_execution_policy<ExecPolicy>::value,
|
||||
void>::type
|
||||
erase_if(ExecPolicy p, F f)
|
||||
typename std::enable_if<detail::is_execution_policy<ExecPolicy>::value,
|
||||
void>::type
|
||||
erase_if(ExecPolicy&& p, F f)
|
||||
{
|
||||
BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(ExecPolicy)
|
||||
table_.erase_if(p, f);
|
||||
}
|
||||
#endif
|
||||
|
||||
template <class F> BOOST_FORCEINLINE size_type erase_if(F f)
|
||||
template <class F> size_type erase_if(F f) { return table_.erase_if(f); }
|
||||
|
||||
void swap(concurrent_flat_map& other) noexcept(
|
||||
boost::allocator_is_always_equal<Allocator>::type::value ||
|
||||
boost::allocator_propagate_on_container_swap<Allocator>::type::value)
|
||||
{
|
||||
return table_.erase_if(f);
|
||||
return table_.swap(other.table_);
|
||||
}
|
||||
|
||||
void clear() noexcept { table_.clear(); }
|
||||
|
||||
template <typename H2, typename P2>
|
||||
size_type merge(concurrent_flat_map<Key, T, H2, P2, Allocator>& x)
|
||||
{
|
||||
BOOST_ASSERT(get_allocator() == x.get_allocator());
|
||||
return table_.merge(x.table_);
|
||||
}
|
||||
|
||||
template <typename H2, typename P2>
|
||||
size_type merge(concurrent_flat_map<Key, T, H2, P2, Allocator>&& x)
|
||||
{
|
||||
return merge(x);
|
||||
}
|
||||
|
||||
BOOST_FORCEINLINE size_type count(key_type const& k) const
|
||||
{
|
||||
return table_.count(k);
|
||||
}
|
||||
|
||||
template <class K>
|
||||
BOOST_FORCEINLINE typename std::enable_if<
|
||||
detail::are_transparent<K, hasher, key_equal>::value, size_type>::type
|
||||
count(K const& k)
|
||||
{
|
||||
return table_.count(k);
|
||||
}
|
||||
|
||||
BOOST_FORCEINLINE bool contains(key_type const& k) const
|
||||
{
|
||||
return table_.contains(k);
|
||||
}
|
||||
|
||||
template <class K>
|
||||
BOOST_FORCEINLINE typename std::enable_if<
|
||||
detail::are_transparent<K, hasher, key_equal>::value, bool>::type
|
||||
contains(K const& k) const
|
||||
{
|
||||
return table_.contains(k);
|
||||
}
|
||||
|
||||
/// Hash Policy
|
||||
///
|
||||
size_type bucket_count() const noexcept { return table_.capacity(); }
|
||||
|
||||
float load_factor() const noexcept { return table_.load_factor(); }
|
||||
float max_load_factor() const noexcept
|
||||
{
|
||||
return table_.max_load_factor();
|
||||
};
|
||||
void max_load_factor(float) {}
|
||||
size_type max_load() const noexcept { return table_.max_load(); }
|
||||
|
||||
void rehash(size_type n) { table_.rehash(n); }
|
||||
void reserve(size_type n) { table_.reserve(n); }
|
||||
|
||||
@@ -672,11 +730,137 @@ namespace boost {
|
||||
hasher hash_function() const { return table_.hash_function(); }
|
||||
key_equal key_eq() const { return table_.key_eq(); }
|
||||
};
|
||||
|
||||
template <class Key, class T, class Hash, class KeyEqual, class Allocator>
|
||||
bool operator==(
|
||||
concurrent_flat_map<Key, T, Hash, KeyEqual, Allocator> const& lhs,
|
||||
concurrent_flat_map<Key, T, Hash, KeyEqual, Allocator> const& rhs)
|
||||
{
|
||||
return lhs.table_ == rhs.table_;
|
||||
}
|
||||
|
||||
template <class Key, class T, class Hash, class KeyEqual, class Allocator>
|
||||
bool operator!=(
|
||||
concurrent_flat_map<Key, T, Hash, KeyEqual, Allocator> const& lhs,
|
||||
concurrent_flat_map<Key, T, Hash, KeyEqual, Allocator> const& rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
template <class Key, class T, class Hash, class Pred, class Alloc>
|
||||
void swap(concurrent_flat_map<Key, T, Hash, Pred, Alloc>& x,
|
||||
concurrent_flat_map<Key, T, Hash, Pred, Alloc>& y)
|
||||
noexcept(noexcept(x.swap(y)))
|
||||
{
|
||||
x.swap(y);
|
||||
}
|
||||
|
||||
template <class K, class T, class H, class P, class A, class Predicate>
|
||||
typename concurrent_flat_map<K, T, H, P, A>::size_type erase_if(
|
||||
concurrent_flat_map<K, T, H, P, A>& c, Predicate pred)
|
||||
{
|
||||
return c.erase_if(pred);
|
||||
}
|
||||
|
||||
#if BOOST_UNORDERED_TEMPLATE_DEDUCTION_GUIDES
|
||||
|
||||
template <class InputIterator,
|
||||
class Hash =
|
||||
boost::hash<boost::unordered::detail::iter_key_t<InputIterator> >,
|
||||
class Pred =
|
||||
std::equal_to<boost::unordered::detail::iter_key_t<InputIterator> >,
|
||||
class Allocator = std::allocator<
|
||||
boost::unordered::detail::iter_to_alloc_t<InputIterator> >,
|
||||
class = boost::enable_if_t<detail::is_input_iterator_v<InputIterator> >,
|
||||
class = boost::enable_if_t<detail::is_hash_v<Hash> >,
|
||||
class = boost::enable_if_t<detail::is_pred_v<Pred> >,
|
||||
class = boost::enable_if_t<detail::is_allocator_v<Allocator> > >
|
||||
concurrent_flat_map(InputIterator, InputIterator,
|
||||
std::size_t = boost::unordered::detail::foa::default_bucket_count,
|
||||
Hash = Hash(), Pred = Pred(), Allocator = Allocator())
|
||||
-> concurrent_flat_map<
|
||||
boost::unordered::detail::iter_key_t<InputIterator>,
|
||||
boost::unordered::detail::iter_val_t<InputIterator>, Hash, Pred,
|
||||
Allocator>;
|
||||
|
||||
template <class Key, class T,
|
||||
class Hash = boost::hash<boost::remove_const_t<Key> >,
|
||||
class Pred = std::equal_to<boost::remove_const_t<Key> >,
|
||||
class Allocator = std::allocator<std::pair<const Key, T> >,
|
||||
class = boost::enable_if_t<detail::is_hash_v<Hash> >,
|
||||
class = boost::enable_if_t<detail::is_pred_v<Pred> >,
|
||||
class = boost::enable_if_t<detail::is_allocator_v<Allocator> > >
|
||||
concurrent_flat_map(std::initializer_list<std::pair<Key, T> >,
|
||||
std::size_t = boost::unordered::detail::foa::default_bucket_count,
|
||||
Hash = Hash(), Pred = Pred(), Allocator = Allocator())
|
||||
-> concurrent_flat_map<boost::remove_const_t<Key>, T, Hash, Pred,
|
||||
Allocator>;
|
||||
|
||||
template <class InputIterator, class Allocator,
|
||||
class = boost::enable_if_t<detail::is_input_iterator_v<InputIterator> >,
|
||||
class = boost::enable_if_t<detail::is_allocator_v<Allocator> > >
|
||||
concurrent_flat_map(InputIterator, InputIterator, std::size_t, Allocator)
|
||||
-> concurrent_flat_map<
|
||||
boost::unordered::detail::iter_key_t<InputIterator>,
|
||||
boost::unordered::detail::iter_val_t<InputIterator>,
|
||||
boost::hash<boost::unordered::detail::iter_key_t<InputIterator> >,
|
||||
std::equal_to<boost::unordered::detail::iter_key_t<InputIterator> >,
|
||||
Allocator>;
|
||||
|
||||
template <class InputIterator, class Allocator,
|
||||
class = boost::enable_if_t<detail::is_input_iterator_v<InputIterator> >,
|
||||
class = boost::enable_if_t<detail::is_allocator_v<Allocator> > >
|
||||
concurrent_flat_map(InputIterator, InputIterator, Allocator)
|
||||
-> concurrent_flat_map<
|
||||
boost::unordered::detail::iter_key_t<InputIterator>,
|
||||
boost::unordered::detail::iter_val_t<InputIterator>,
|
||||
boost::hash<boost::unordered::detail::iter_key_t<InputIterator> >,
|
||||
std::equal_to<boost::unordered::detail::iter_key_t<InputIterator> >,
|
||||
Allocator>;
|
||||
|
||||
template <class InputIterator, class Hash, class Allocator,
|
||||
class = boost::enable_if_t<detail::is_hash_v<Hash> >,
|
||||
class = boost::enable_if_t<detail::is_input_iterator_v<InputIterator> >,
|
||||
class = boost::enable_if_t<detail::is_allocator_v<Allocator> > >
|
||||
concurrent_flat_map(
|
||||
InputIterator, InputIterator, std::size_t, Hash, Allocator)
|
||||
-> concurrent_flat_map<
|
||||
boost::unordered::detail::iter_key_t<InputIterator>,
|
||||
boost::unordered::detail::iter_val_t<InputIterator>, Hash,
|
||||
std::equal_to<boost::unordered::detail::iter_key_t<InputIterator> >,
|
||||
Allocator>;
|
||||
|
||||
template <class Key, class T, class Allocator,
|
||||
class = boost::enable_if_t<detail::is_allocator_v<Allocator> > >
|
||||
concurrent_flat_map(std::initializer_list<std::pair<Key, T> >, std::size_t,
|
||||
Allocator) -> concurrent_flat_map<boost::remove_const_t<Key>, T,
|
||||
boost::hash<boost::remove_const_t<Key> >,
|
||||
std::equal_to<boost::remove_const_t<Key> >, Allocator>;
|
||||
|
||||
template <class Key, class T, class Allocator,
|
||||
class = boost::enable_if_t<detail::is_allocator_v<Allocator> > >
|
||||
concurrent_flat_map(std::initializer_list<std::pair<Key, T> >, Allocator)
|
||||
-> concurrent_flat_map<boost::remove_const_t<Key>, T,
|
||||
boost::hash<boost::remove_const_t<Key> >,
|
||||
std::equal_to<boost::remove_const_t<Key> >, Allocator>;
|
||||
|
||||
template <class Key, class T, class Hash, class Allocator,
|
||||
class = boost::enable_if_t<detail::is_hash_v<Hash> >,
|
||||
class = boost::enable_if_t<detail::is_allocator_v<Allocator> > >
|
||||
concurrent_flat_map(std::initializer_list<std::pair<Key, T> >, std::size_t,
|
||||
Hash, Allocator) -> concurrent_flat_map<boost::remove_const_t<Key>, T,
|
||||
Hash, std::equal_to<boost::remove_const_t<Key> >, Allocator>;
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace unordered
|
||||
|
||||
using unordered::concurrent_flat_map;
|
||||
} // namespace boost
|
||||
|
||||
#undef BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE
|
||||
#undef BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE
|
||||
#undef BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY
|
||||
#undef BOOST_UNORDERED_COMMA
|
||||
#undef BOOST_UNORDERED_LAST_ARG
|
||||
#undef BOOST_UNORDERED_STATIC_ASSERT_LAST_ARG_INVOCABLE
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/* Fast open-addressing concurrent hash table.
|
||||
*
|
||||
* Copyright 2023 Christian Mazakas.
|
||||
* Distributed under the Boost Software License, Version 1.0.
|
||||
* (See accompanying file LICENSE_1_0.txt or copy at
|
||||
* http://www.boost.org/LICENSE_1_0.txt)
|
||||
*
|
||||
* See https://www.boost.org/libs/unordered for library home page.
|
||||
*/
|
||||
|
||||
/* Reference:
|
||||
* https://github.com/joaquintides/concurrent_hashmap_api#proposed-synopsis
|
||||
*/
|
||||
|
||||
#ifndef BOOST_UNORDERED_CONCURRENT_FLAT_MAP_FWD_HPP
|
||||
#define BOOST_UNORDERED_CONCURRENT_FLAT_MAP_FWD_HPP
|
||||
|
||||
#include <boost/container_hash/hash_fwd.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
namespace boost {
|
||||
namespace unordered {
|
||||
|
||||
template <class Key, class T, class Hash = boost::hash<Key>,
|
||||
class Pred = std::equal_to<Key>,
|
||||
class Allocator = std::allocator<std::pair<Key const, T> > >
|
||||
class concurrent_flat_map;
|
||||
|
||||
template <class Key, class T, class Hash, class KeyEqual, class Allocator>
|
||||
bool operator==(
|
||||
concurrent_flat_map<Key, T, Hash, KeyEqual, Allocator> const& lhs,
|
||||
concurrent_flat_map<Key, T, Hash, KeyEqual, Allocator> const& rhs);
|
||||
|
||||
template <class Key, class T, class Hash, class KeyEqual, class Allocator>
|
||||
bool operator!=(
|
||||
concurrent_flat_map<Key, T, Hash, KeyEqual, Allocator> const& lhs,
|
||||
concurrent_flat_map<Key, T, Hash, KeyEqual, Allocator> const& rhs);
|
||||
|
||||
template <class Key, class T, class Hash, class Pred, class Alloc>
|
||||
void swap(concurrent_flat_map<Key, T, Hash, Pred, Alloc>& x,
|
||||
concurrent_flat_map<Key, T, Hash, Pred, Alloc>& y)
|
||||
noexcept(noexcept(x.swap(y)));
|
||||
|
||||
template <class K, class T, class H, class P, class A, class Predicate>
|
||||
typename concurrent_flat_map<K, T, H, P, A>::size_type erase_if(
|
||||
concurrent_flat_map<K, T, H, P, A>& c, Predicate pred);
|
||||
|
||||
} // namespace unordered
|
||||
|
||||
using boost::unordered::concurrent_flat_map;
|
||||
using boost::unordered::swap;
|
||||
using boost::unordered::operator==;
|
||||
using boost::unordered::operator!=;
|
||||
} // namespace boost
|
||||
|
||||
#endif // BOOST_UNORDERED_CONCURRENT_FLAT_MAP_HPP
|
||||
@@ -14,19 +14,23 @@
|
||||
#include <atomic>
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/config.hpp>
|
||||
#include <boost/core/ignore_unused.hpp>
|
||||
#include <boost/core/no_exceptions_support.hpp>
|
||||
#include <boost/cstdint.hpp>
|
||||
#include <boost/mp11/tuple.hpp>
|
||||
#include <boost/static_assert.hpp>
|
||||
#include <boost/unordered/detail/foa/core.hpp>
|
||||
#include <boost/unordered/detail/foa/rw_spinlock.hpp>
|
||||
#include <boost/unordered/detail/foa/tuple_rotate_right.hpp>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <type_traits>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include "oneapi/tbb/spin_rw_mutex.h"
|
||||
|
||||
#if !defined(BOOST_UNORDERED_DISABLE_PARALLEL_ALGORITHMS)
|
||||
#if defined(BOOST_UNORDERED_ENABLE_PARALLEL_ALGORITHMS)|| \
|
||||
@@ -62,20 +66,35 @@ using is_execution_policy=std::false_type;
|
||||
|
||||
namespace foa{
|
||||
|
||||
#if defined(BOOST_MSVC)
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable:4324) /* padded structure due to alignas */
|
||||
#endif
|
||||
static constexpr std::size_t cacheline_size=64;
|
||||
|
||||
template<typename T>
|
||||
struct alignas(64) cacheline_protected:T
|
||||
template<typename T,std::size_t N>
|
||||
class cache_aligned_array
|
||||
{
|
||||
using T::T;
|
||||
};
|
||||
public:
|
||||
cache_aligned_array(){for(std::size_t n=0;n<N;)::new (data(n++)) T();}
|
||||
~cache_aligned_array(){for(auto n=N;n>0;)data(n--)->~T();}
|
||||
cache_aligned_array(const cache_aligned_array&)=delete;
|
||||
cache_aligned_array& operator=(const cache_aligned_array&)=delete;
|
||||
|
||||
#if defined(BOOST_MSVC)
|
||||
#pragma warning(pop) /* C4324 */
|
||||
#endif
|
||||
T& operator[](std::size_t pos)noexcept{return *data(pos);}
|
||||
|
||||
private:
|
||||
static constexpr std::size_t element_offset=
|
||||
(sizeof(T)+cacheline_size-1)/cacheline_size*cacheline_size;
|
||||
|
||||
BOOST_STATIC_ASSERT(alignof(T)<=cacheline_size);
|
||||
|
||||
T* data(std::size_t pos)noexcept
|
||||
{
|
||||
return reinterpret_cast<T*>(
|
||||
(reinterpret_cast<uintptr_t>(&buf)+cacheline_size-1)/
|
||||
cacheline_size*cacheline_size
|
||||
+pos*element_offset);
|
||||
}
|
||||
|
||||
unsigned char buf[element_offset*N+cacheline_size-1];
|
||||
};
|
||||
|
||||
template<typename Mutex,std::size_t N>
|
||||
class multimutex
|
||||
@@ -93,7 +112,7 @@ public:
|
||||
void unlock()noexcept{for(auto n=N;n>0;)mutexes[--n].unlock();}
|
||||
|
||||
private:
|
||||
Mutex mutexes[N];
|
||||
cache_aligned_array<Mutex,N> mutexes;
|
||||
};
|
||||
|
||||
/* std::shared_lock is C++14 */
|
||||
@@ -193,7 +212,7 @@ struct atomic_integral
|
||||
|
||||
struct group_access
|
||||
{
|
||||
using mutex_type=rw_spinlock;
|
||||
using mutex_type=tbb::spin_rw_mutex;
|
||||
using shared_lock_guard=shared_lock<mutex_type>;
|
||||
using exclusive_lock_guard=lock_guard<mutex_type>;
|
||||
using insert_counter_type=std::atomic<boost::uint32_t>;
|
||||
@@ -282,6 +301,40 @@ struct concurrent_table_arrays:table_arrays<Value,Group,SizePolicy>
|
||||
group_access *group_accesses;
|
||||
};
|
||||
|
||||
struct atomic_size_control
|
||||
{
|
||||
static constexpr auto atomic_size_t_size=sizeof(std::atomic<std::size_t>);
|
||||
BOOST_STATIC_ASSERT(atomic_size_t_size<cacheline_size);
|
||||
|
||||
atomic_size_control(std::size_t ml_,std::size_t size_):
|
||||
pad0_{},ml{ml_},pad1_{},size{size_}{}
|
||||
atomic_size_control(atomic_size_control& x):
|
||||
pad0_{},ml{x.ml.load()},pad1_{},size{x.size.load()}{}
|
||||
|
||||
/* padding to avoid false sharing internally and with sorrounding data */
|
||||
|
||||
unsigned char pad0_[cacheline_size-atomic_size_t_size];
|
||||
std::atomic<std::size_t> ml;
|
||||
unsigned char pad1_[cacheline_size-atomic_size_t_size];
|
||||
std::atomic<std::size_t> size;
|
||||
};
|
||||
|
||||
/* std::swap can't be used on non-assignable atomics */
|
||||
|
||||
inline void
|
||||
swap_atomic_size_t(std::atomic<std::size_t>& x,std::atomic<std::size_t>& y)
|
||||
{
|
||||
std::size_t tmp=x;
|
||||
x=static_cast<std::size_t>(y);
|
||||
y=tmp;
|
||||
}
|
||||
|
||||
inline void swap(atomic_size_control& x,atomic_size_control& y)
|
||||
{
|
||||
swap_atomic_size_t(x.ml,y.ml);
|
||||
swap_atomic_size_t(x.size,y.size);
|
||||
}
|
||||
|
||||
/* foa::concurrent_table serves as the foundation for end-user concurrent
|
||||
* hash containers. The TypePolicy parameter can specify flat/node-based
|
||||
* map-like and set-like containers, though currently we're only providing
|
||||
@@ -342,7 +395,7 @@ struct concurrent_table_arrays:table_arrays<Value,Group,SizePolicy>
|
||||
template <typename TypePolicy,typename Hash,typename Pred,typename Allocator>
|
||||
using concurrent_table_core_impl=table_core<
|
||||
TypePolicy,group15<atomic_integral>,concurrent_table_arrays,
|
||||
std::atomic<std::size_t>,Hash,Pred,Allocator>;
|
||||
atomic_size_control,Hash,Pred,Allocator>;
|
||||
|
||||
#include <boost/unordered/detail/foa/ignore_wshadow.hpp>
|
||||
|
||||
@@ -361,6 +414,10 @@ class concurrent_table:
|
||||
using super::N;
|
||||
using prober=typename super::prober;
|
||||
|
||||
template<
|
||||
typename TypePolicy2,typename Hash2,typename Pred2,typename Allocator2>
|
||||
friend class concurrent_table;
|
||||
|
||||
public:
|
||||
using key_type=typename super::key_type;
|
||||
using init_type=typename super::init_type;
|
||||
@@ -410,6 +467,16 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
concurrent_table& operator=(std::initializer_list<value_type> il) {
|
||||
auto lck=exclusive_access();
|
||||
super::clear();
|
||||
super::noshrink_reserve(il.size());
|
||||
for (auto const& v : il) {
|
||||
this->unprotected_emplace(v);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
allocator_type get_allocator()const noexcept
|
||||
{
|
||||
auto lck=shared_access();
|
||||
@@ -672,17 +739,22 @@ public:
|
||||
|
||||
// TODO: should we accept different allocator too?
|
||||
template<typename Hash2,typename Pred2>
|
||||
void merge(concurrent_table<TypePolicy,Hash2,Pred2,Allocator>& x)
|
||||
size_type merge(concurrent_table<TypePolicy,Hash2,Pred2,Allocator>& x)
|
||||
{
|
||||
// TODO: consider grabbing shared access on *this at this level
|
||||
// TODO: can deadlock if x1.merge(x2) while x2.merge(x1)
|
||||
auto lck=x.shared_access();
|
||||
x.for_all_elements(
|
||||
group_exclusive{},
|
||||
using merge_table_type=concurrent_table<TypePolicy,Hash2,Pred2,Allocator>;
|
||||
using super2=typename merge_table_type::super;
|
||||
|
||||
// for clang
|
||||
boost::ignore_unused<super2>();
|
||||
|
||||
auto lck=exclusive_access(*this,x);
|
||||
size_type s=super::size();
|
||||
x.super2::for_all_elements( /* super2::for_all_elements -> unprotected */
|
||||
[&,this](group_type* pg,unsigned int n,element_type* p){
|
||||
erase_on_exit e{x,pg,n,p};
|
||||
if(!emplace_impl(type_policy::move(*p)))e.rollback();
|
||||
typename merge_table_type::erase_on_exit e{x,pg,n,p};
|
||||
if(!unprotected_emplace(type_policy::move(*p)))e.rollback();
|
||||
});
|
||||
return size_type{super::size()-s};
|
||||
}
|
||||
|
||||
template<typename Hash2,typename Pred2>
|
||||
@@ -709,7 +781,7 @@ public:
|
||||
template<typename Key>
|
||||
BOOST_FORCEINLINE bool contains(Key&& x)const
|
||||
{
|
||||
return visit(std::forward<Key>(x),[](const value_type&){});
|
||||
return visit(std::forward<Key>(x),[](const value_type&){})!=0;
|
||||
}
|
||||
|
||||
std::size_t capacity()const noexcept
|
||||
@@ -752,8 +824,19 @@ public:
|
||||
return x.erase_if(std::forward<Predicate>(pr));
|
||||
}
|
||||
|
||||
friend bool operator==(const concurrent_table& x,const concurrent_table& y)
|
||||
{
|
||||
auto lck=exclusive_access(x,y);
|
||||
return static_cast<const super&>(x)==static_cast<const super&>(y);
|
||||
}
|
||||
|
||||
friend bool operator!=(const concurrent_table& x,const concurrent_table& y)
|
||||
{
|
||||
return !(x==y);
|
||||
}
|
||||
|
||||
private:
|
||||
using mutex_type=cacheline_protected<rw_spinlock>;
|
||||
using mutex_type=rw_spinlock;
|
||||
using multimutex_type=multimutex<mutex_type,128>; // TODO: adapt 128 to the machine
|
||||
using shared_lock_guard=shared_lock<mutex_type>;
|
||||
using exclusive_lock_guard=lock_guard<multimutex_type>;
|
||||
@@ -785,12 +868,20 @@ private:
|
||||
return exclusive_lock_guard{mutexes};
|
||||
}
|
||||
|
||||
inline exclusive_bilock_guard exclusive_access(
|
||||
static inline exclusive_bilock_guard exclusive_access(
|
||||
const concurrent_table& x,const concurrent_table& y)
|
||||
{
|
||||
return {x.mutexes,y.mutexes};
|
||||
}
|
||||
|
||||
template<typename Hash2,typename Pred2>
|
||||
static inline exclusive_bilock_guard exclusive_access(
|
||||
const concurrent_table& x,
|
||||
const concurrent_table<TypePolicy,Hash2,Pred2,Allocator>& y)
|
||||
{
|
||||
return {x.mutexes,y.mutexes};
|
||||
}
|
||||
|
||||
/* Tag-dispatched shared/exclusive group access */
|
||||
|
||||
using group_shared=std::false_type;
|
||||
@@ -909,7 +1000,7 @@ private:
|
||||
auto mask=pg->match(hash);
|
||||
if(mask){
|
||||
auto p=this->arrays.elements+pos*N;
|
||||
this->prefetch_elements(p);
|
||||
BOOST_UNORDERED_PREFETCH_ELEMENTS(p,N);
|
||||
auto lck=access(access_mode,pos);
|
||||
do{
|
||||
auto n=unchecked_countr_zero(mask);
|
||||
@@ -935,8 +1026,8 @@ private:
|
||||
|
||||
std::size_t unprotected_size()const
|
||||
{
|
||||
std::size_t m=this->ml;
|
||||
std::size_t s=this->size_;
|
||||
std::size_t m=this->size_ctrl.ml;
|
||||
std::size_t s=this->size_ctrl.size;
|
||||
return s<=m?s:m;
|
||||
}
|
||||
|
||||
@@ -1043,19 +1134,37 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
BOOST_FORCEINLINE bool unprotected_emplace(Args&&... args)
|
||||
{
|
||||
const auto &k=this->key_from(std::forward<Args>(args)...);
|
||||
auto hash=this->hash_for(k);
|
||||
auto pos0=this->position_for(hash);
|
||||
|
||||
if(this->find(k,pos0,hash))return false;
|
||||
|
||||
if(BOOST_LIKELY(this->size_ctrl.size<this->size_ctrl.ml)){
|
||||
this->unchecked_emplace_at(pos0,hash,std::forward<Args>(args)...);
|
||||
}
|
||||
else{
|
||||
this->unchecked_emplace_with_rehash(hash,std::forward<Args>(args)...);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
struct reserve_size
|
||||
{
|
||||
reserve_size(concurrent_table& x_):x{x_}
|
||||
{
|
||||
size_=++x.size_;
|
||||
size_=++x.size_ctrl.size;
|
||||
}
|
||||
|
||||
~reserve_size()
|
||||
{
|
||||
if(!commit_)--x.size_;
|
||||
if(!commit_)--x.size_ctrl.size;
|
||||
}
|
||||
|
||||
bool succeeded()const{return size_<=x.ml;}
|
||||
bool succeeded()const{return size_<=x.size_ctrl.ml;}
|
||||
|
||||
void commit(){commit_=true;}
|
||||
|
||||
@@ -1089,9 +1198,9 @@ private:
|
||||
unprotected_norehash_emplace_or_visit(
|
||||
GroupAccessMode access_mode,F&& f,Args&&... args)
|
||||
{
|
||||
const auto &k=this->key_from(std::forward<Args>(args)...);
|
||||
auto hash=this->hash_for(k);
|
||||
auto pos0=this->position_for(hash);
|
||||
const auto &k=this->key_from(std::forward<Args>(args)...);
|
||||
auto hash=this->hash_for(k);
|
||||
auto pos0=this->position_for(hash);
|
||||
|
||||
for(;;){
|
||||
startover:
|
||||
@@ -1129,7 +1238,9 @@ private:
|
||||
void rehash_if_full()
|
||||
{
|
||||
auto lck=exclusive_access();
|
||||
if(this->size_==this->ml)this->unchecked_rehash_for_growth();
|
||||
if(this->size_ctrl.size==this->size_ctrl.ml){
|
||||
this->unchecked_rehash_for_growth();
|
||||
}
|
||||
}
|
||||
|
||||
template<typename GroupAccessMode,typename F>
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
@@ -85,6 +86,42 @@
|
||||
}while(0)
|
||||
#endif
|
||||
|
||||
/* We use BOOST_UNORDERED_PREFETCH[_ELEMENTS] macros rather than proper
|
||||
* functions because of https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109985
|
||||
*/
|
||||
|
||||
#if defined(BOOST_GCC)||defined(BOOST_CLANG)
|
||||
#define BOOST_UNORDERED_PREFETCH(p) __builtin_prefetch((const char*)(p))
|
||||
#elif defined(BOOST_UNORDERED_SSE2)
|
||||
#define BOOST_UNORDERED_PREFETCH(p) _mm_prefetch((const char*)(p),_MM_HINT_T0)
|
||||
#else
|
||||
#define BOOST_UNORDERED_PREFETCH(p) ((void)0)
|
||||
#endif
|
||||
|
||||
/* We have experimentally confirmed that ARM architectures get a higher
|
||||
* speedup when around the first half of the element slots in a group are
|
||||
* prefetched, whereas for Intel just the first cache line is best.
|
||||
* Please report back if you find better tunings for some particular
|
||||
* architectures.
|
||||
*/
|
||||
|
||||
#if BOOST_ARCH_ARM
|
||||
/* Cache line size can't be known at compile time, so we settle on
|
||||
* the very frequent value of 64B.
|
||||
*/
|
||||
|
||||
#define BOOST_UNORDERED_PREFETCH_ELEMENTS(p,N) \
|
||||
do{ \
|
||||
auto BOOST_UNORDERED_P=(p); \
|
||||
constexpr int cache_line=64; \
|
||||
const char *p0=reinterpret_cast<const char*>(BOOST_UNORDERED_P), \
|
||||
*p1=p0+sizeof(*BOOST_UNORDERED_P)*(N)/2; \
|
||||
for(;p0<p1;p0+=cache_line)BOOST_UNORDERED_PREFETCH(p0); \
|
||||
}while(0)
|
||||
#else
|
||||
#define BOOST_UNORDERED_PREFETCH_ELEMENTS(p,N) BOOST_UNORDERED_PREFETCH(p)
|
||||
#endif
|
||||
|
||||
#ifdef __has_feature
|
||||
#define BOOST_UNORDERED_HAS_FEATURE(x) __has_feature(x)
|
||||
#else
|
||||
@@ -111,7 +148,7 @@ static constexpr std::size_t default_bucket_count=0;
|
||||
|
||||
/* foa::table_core is the common base of foa::table and foa::concurrent_table,
|
||||
* which in their turn serve as the foundational core of
|
||||
* boost::unordered_flat_[map|set] and boost::concurrent_flat_map,
|
||||
* boost::unordered_(flat|node)_(map|set) and boost::concurrent_flat_map,
|
||||
* respectively. Its main internal design aspects are:
|
||||
*
|
||||
* - Element slots are logically split into groups of size N=15. The number
|
||||
@@ -1037,16 +1074,6 @@ void swap_if(T& x,T& y){using std::swap; swap(x,y);}
|
||||
template<bool B,typename T,typename std::enable_if<!B>::type* =nullptr>
|
||||
void swap_if(T&,T&){}
|
||||
|
||||
inline void prefetch(const void* p)
|
||||
{
|
||||
(void) p;
|
||||
#if defined(BOOST_GCC)||defined(BOOST_CLANG)
|
||||
__builtin_prefetch((const char*)p);
|
||||
#elif defined(BOOST_UNORDERED_SSE2)
|
||||
_mm_prefetch((const char*)p,_MM_HINT_T0);
|
||||
#endif
|
||||
}
|
||||
|
||||
template<typename Allocator>
|
||||
struct is_std_allocator:std::false_type{};
|
||||
|
||||
@@ -1097,9 +1124,14 @@ static constexpr float mlf=0.875f;
|
||||
template<typename Group,typename Element>
|
||||
struct table_locator
|
||||
{
|
||||
Group *pg;
|
||||
unsigned int n;
|
||||
Element *p;
|
||||
table_locator()=default;
|
||||
table_locator(Group* pg_,unsigned int n_,Element* p_):pg{pg_},n{n_},p{p_}{}
|
||||
|
||||
explicit operator bool()const noexcept{return p!=nullptr;}
|
||||
|
||||
Group *pg=nullptr;
|
||||
unsigned int n=0;
|
||||
Element *p=nullptr;
|
||||
};
|
||||
|
||||
struct try_emplace_args_t{};
|
||||
@@ -1202,7 +1234,7 @@ alloc_make_insert_type(const Allocator& al,Args&&... args)
|
||||
|
||||
template<
|
||||
typename TypePolicy,typename Group,template<typename...> class Arrays,
|
||||
typename SizeImpl,typename Hash,typename Pred,typename Allocator
|
||||
typename SizeControl,typename Hash,typename Pred,typename Allocator
|
||||
>
|
||||
class
|
||||
|
||||
@@ -1226,7 +1258,7 @@ public:
|
||||
using alloc_traits=boost::allocator_traits<Allocator>;
|
||||
using element_type=typename type_policy::element_type;
|
||||
using arrays_type=Arrays<element_type,group_type,size_policy>;
|
||||
using size_impl_type=SizeImpl;
|
||||
using size_ctrl_type=SizeControl;
|
||||
|
||||
using key_type=typename type_policy::key_type;
|
||||
using init_type=typename type_policy::init_type;
|
||||
@@ -1247,7 +1279,7 @@ public:
|
||||
const Pred& pred_=Pred(),const Allocator& al_=Allocator()):
|
||||
hash_base{empty_init,h_},pred_base{empty_init,pred_},
|
||||
allocator_base{empty_init,al_},arrays(new_arrays(n)),
|
||||
ml{initial_max_load()},size_{0}
|
||||
size_ctrl{initial_max_load(),0}
|
||||
{}
|
||||
|
||||
table_core(const table_core& x):
|
||||
@@ -1261,11 +1293,11 @@ public:
|
||||
hash_base{empty_init,std::move(x.h())},
|
||||
pred_base{empty_init,std::move(x.pred())},
|
||||
allocator_base{empty_init,std::move(x.al())},
|
||||
arrays(x.arrays),ml{std::size_t(x.ml)},size_{std::size_t(x.size_)}
|
||||
arrays(x.arrays),size_ctrl(x.size_ctrl)
|
||||
{
|
||||
x.arrays=x.new_arrays(0);
|
||||
x.ml=x.initial_max_load();
|
||||
x.size_=0;
|
||||
x.size_ctrl.ml=x.initial_max_load();
|
||||
x.size_ctrl.size=0;
|
||||
}
|
||||
|
||||
table_core(const table_core& x,const Allocator& al_):
|
||||
@@ -1275,15 +1307,12 @@ public:
|
||||
}
|
||||
|
||||
table_core(table_core&& x,const Allocator& al_):
|
||||
hash_base{empty_init,std::move(x.h())},
|
||||
pred_base{empty_init,std::move(x.pred())},
|
||||
allocator_base{empty_init,al_},arrays(new_arrays(0)),
|
||||
ml{initial_max_load()},size_{0}
|
||||
table_core{std::move(x.h()),std::move(x.pred()),al_}
|
||||
{
|
||||
if(al()==x.al()){
|
||||
std::swap(arrays,x.arrays);
|
||||
swap_size_impl(size_,x.size_);
|
||||
swap_size_impl(ml,x.ml);
|
||||
using std::swap;
|
||||
swap(arrays,x.arrays);
|
||||
swap(size_ctrl,x.size_ctrl);
|
||||
}
|
||||
else{
|
||||
reserve(x.size());
|
||||
@@ -1381,8 +1410,7 @@ public:
|
||||
reserve(0);
|
||||
move_assign_if<pocma>(al(),x.al());
|
||||
swap(arrays,x.arrays);
|
||||
swap(ml,x.ml);
|
||||
swap(size_,x.size_);
|
||||
swap(size_ctrl,x.size_ctrl);
|
||||
}
|
||||
else{
|
||||
/* noshrink: favor memory reuse over tightness */
|
||||
@@ -1408,7 +1436,7 @@ public:
|
||||
allocator_type get_allocator()const noexcept{return al();}
|
||||
|
||||
bool empty()const noexcept{return size()==0;}
|
||||
std::size_t size()const noexcept{return size_;}
|
||||
std::size_t size()const noexcept{return size_ctrl.size;}
|
||||
std::size_t max_size()const noexcept{return SIZE_MAX;}
|
||||
|
||||
BOOST_FORCEINLINE
|
||||
@@ -1425,6 +1453,52 @@ public:
|
||||
recover_slot(pc);
|
||||
}
|
||||
|
||||
template<typename Key>
|
||||
BOOST_FORCEINLINE locator find(const Key& x)const
|
||||
{
|
||||
auto hash=hash_for(x);
|
||||
return find(x,position_for(hash),hash);
|
||||
}
|
||||
|
||||
#if defined(BOOST_MSVC)
|
||||
/* warning: forcing value to bool 'true' or 'false' in bool(pred()...) */
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable:4800)
|
||||
#endif
|
||||
|
||||
template<typename Key>
|
||||
BOOST_FORCEINLINE locator find(
|
||||
const Key& x,std::size_t pos0,std::size_t hash)const
|
||||
{
|
||||
prober pb(pos0);
|
||||
do{
|
||||
auto pos=pb.get();
|
||||
auto pg=arrays.groups+pos;
|
||||
auto mask=pg->match(hash);
|
||||
if(mask){
|
||||
BOOST_UNORDERED_ASSUME(arrays.elements!=nullptr);
|
||||
auto p=arrays.elements+pos*N;
|
||||
BOOST_UNORDERED_PREFETCH_ELEMENTS(p,N);
|
||||
do{
|
||||
auto n=unchecked_countr_zero(mask);
|
||||
if(BOOST_LIKELY(bool(pred()(x,key_from(p[n]))))){
|
||||
return {pg,n,p+n};
|
||||
}
|
||||
mask&=mask-1;
|
||||
}while(mask);
|
||||
}
|
||||
if(BOOST_LIKELY(pg->is_not_overflowed(hash))){
|
||||
return {};
|
||||
}
|
||||
}
|
||||
while(BOOST_LIKELY(pb.next(arrays.groups_size_mask)));
|
||||
return {};
|
||||
}
|
||||
|
||||
#if defined(BOOST_MSVC)
|
||||
#pragma warning(pop) /* C4800 */
|
||||
#endif
|
||||
|
||||
void swap(table_core& x)
|
||||
noexcept(
|
||||
alloc_traits::propagate_on_container_swap::value||
|
||||
@@ -1447,8 +1521,7 @@ public:
|
||||
swap(h(),x.h());
|
||||
swap(pred(),x.pred());
|
||||
swap(arrays,x.arrays);
|
||||
swap(ml,x.ml);
|
||||
swap(size_,x.size_);
|
||||
swap(size_ctrl,x.size_ctrl);
|
||||
}
|
||||
|
||||
void clear()noexcept
|
||||
@@ -1466,8 +1539,8 @@ public:
|
||||
pg->initialize();
|
||||
}
|
||||
arrays.groups[arrays.groups_size_mask].set_sentinel();
|
||||
ml=initial_max_load();
|
||||
size_=0;
|
||||
size_ctrl.ml=initial_max_load();
|
||||
size_ctrl.size=0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1487,7 +1560,7 @@ public:
|
||||
|
||||
float max_load_factor()const noexcept{return mlf;}
|
||||
|
||||
std::size_t max_load()const noexcept{return ml;}
|
||||
std::size_t max_load()const noexcept{return size_ctrl.ml;}
|
||||
|
||||
void rehash(std::size_t n)
|
||||
{
|
||||
@@ -1503,6 +1576,23 @@ public:
|
||||
rehash(std::size_t(std::ceil(float(n)/mlf)));
|
||||
}
|
||||
|
||||
friend bool operator==(const table_core& x,const table_core& y)
|
||||
{
|
||||
return
|
||||
x.size()==y.size()&&
|
||||
x.for_all_elements_while([&](element_type* p){
|
||||
auto loc=y.find(key_from(*p));
|
||||
return loc&&
|
||||
const_cast<const value_type&>(type_policy::value_from(*p))==
|
||||
const_cast<const value_type&>(type_policy::value_from(*loc.p));
|
||||
});
|
||||
}
|
||||
|
||||
friend bool operator!=(const table_core& x,const table_core& y)
|
||||
{
|
||||
return !(x==y);
|
||||
}
|
||||
|
||||
struct clear_on_exit
|
||||
{
|
||||
~clear_on_exit(){x.clear();}
|
||||
@@ -1580,35 +1670,13 @@ public:
|
||||
return pg->match_occupied()&~(int(pg==last-1)<<(N-1));
|
||||
}
|
||||
|
||||
static inline void prefetch_elements(const element_type* p)
|
||||
{
|
||||
/* We have experimentally confirmed that ARM architectures get a higher
|
||||
* speedup when around the first half of the element slots in a group are
|
||||
* prefetched, whereas for Intel just the first cache line is best.
|
||||
* Please report back if you find better tunings for some particular
|
||||
* architectures.
|
||||
*/
|
||||
|
||||
#if BOOST_ARCH_ARM
|
||||
/* Cache line size can't be known at compile time, so we settle on
|
||||
* the very frequent value of 64B.
|
||||
*/
|
||||
constexpr int cache_line=64;
|
||||
const char *p0=reinterpret_cast<const char*>(p),
|
||||
*p1=p0+sizeof(value_type)*N/2;
|
||||
for(;p0<p1;p0+=cache_line)prefetch(p0);
|
||||
#else
|
||||
prefetch(p);
|
||||
#endif
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
locator unchecked_emplace_at(
|
||||
std::size_t pos0,std::size_t hash,Args&&... args)
|
||||
{
|
||||
auto res=nosize_unchecked_emplace_at(
|
||||
arrays,pos0,hash,std::forward<Args>(args)...);
|
||||
++size_;
|
||||
++size_ctrl.size;
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -1638,10 +1706,28 @@ public:
|
||||
|
||||
/* new_arrays_ lifetime taken care of by unchecked_rehash */
|
||||
unchecked_rehash(new_arrays_);
|
||||
++size_;
|
||||
++size_ctrl.size;
|
||||
return it;
|
||||
}
|
||||
|
||||
void noshrink_reserve(std::size_t n)
|
||||
{
|
||||
/* used only on assignment after element clearance */
|
||||
BOOST_ASSERT(empty());
|
||||
|
||||
if(n){
|
||||
n=std::size_t(std::ceil(float(n)/mlf)); /* elements -> slots */
|
||||
n=capacity_for(n); /* exact resulting capacity */
|
||||
|
||||
if(n>capacity()){
|
||||
auto new_arrays_=new_arrays(n);
|
||||
delete_arrays(arrays);
|
||||
arrays=new_arrays_;
|
||||
size_ctrl.ml=initial_max_load();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
void for_all_elements(F f)const
|
||||
{
|
||||
@@ -1665,39 +1751,40 @@ public:
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
void for_all_elements_while(F f)const
|
||||
bool for_all_elements_while(F f)const
|
||||
{
|
||||
for_all_elements_while(arrays,f);
|
||||
return for_all_elements_while(arrays,f);
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
static auto for_all_elements_while(const arrays_type& arrays_,F f)
|
||||
->decltype(f(nullptr),void())
|
||||
->decltype(f(nullptr),bool())
|
||||
{
|
||||
for_all_elements_while(
|
||||
return for_all_elements_while(
|
||||
arrays_,[&](group_type*,unsigned int,element_type* p){return f(p);});
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
static auto for_all_elements_while(const arrays_type& arrays_,F f)
|
||||
->decltype(f(nullptr,0,nullptr),void())
|
||||
->decltype(f(nullptr,0,nullptr),bool())
|
||||
{
|
||||
auto p=arrays_.elements;
|
||||
if(!p){return;}
|
||||
for(auto pg=arrays_.groups,last=pg+arrays_.groups_size_mask+1;
|
||||
pg!=last;++pg,p+=N){
|
||||
auto mask=match_really_occupied(pg,last);
|
||||
while(mask){
|
||||
auto n=unchecked_countr_zero(mask);
|
||||
if(!f(pg,n,p+n))return;
|
||||
mask&=mask-1;
|
||||
if(p){
|
||||
for(auto pg=arrays_.groups,last=pg+arrays_.groups_size_mask+1;
|
||||
pg!=last;++pg,p+=N){
|
||||
auto mask=match_really_occupied(pg,last);
|
||||
while(mask){
|
||||
auto n=unchecked_countr_zero(mask);
|
||||
if(!f(pg,n,p+n))return false;
|
||||
mask&=mask-1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
arrays_type arrays;
|
||||
size_impl_type ml;
|
||||
size_impl_type size_;
|
||||
size_ctrl_type size_ctrl;
|
||||
|
||||
private:
|
||||
template<
|
||||
@@ -1710,6 +1797,15 @@ private:
|
||||
using pred_base=empty_value<Pred,1>;
|
||||
using allocator_base=empty_value<Allocator,2>;
|
||||
|
||||
/* used by allocator-extended move ctor */
|
||||
|
||||
table_core(Hash&& h_,Pred&& pred_,const Allocator& al_):
|
||||
hash_base{empty_init,std::move(h_)},
|
||||
pred_base{empty_init,std::move(pred_)},
|
||||
allocator_base{empty_init,al_},arrays(new_arrays(0)),
|
||||
size_ctrl{initial_max_load(),0}
|
||||
{}
|
||||
|
||||
arrays_type new_arrays(std::size_t n)
|
||||
{
|
||||
return arrays_type::new_(al(),n);
|
||||
@@ -1777,7 +1873,7 @@ private:
|
||||
if(arrays.elements){
|
||||
copy_elements_array_from(x);
|
||||
copy_groups_array_from(x);
|
||||
size_=std::size_t(x.size_);
|
||||
size_ctrl.size=std::size_t(x.size_ctrl.size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1862,23 +1958,15 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
static inline void swap_size_impl(size_impl_type& x,size_impl_type& y)
|
||||
{
|
||||
/* std::swap can't be used on non-assignable atomics */
|
||||
std::size_t tmp=x;
|
||||
x=static_cast<std::size_t>(y);
|
||||
y=tmp;
|
||||
}
|
||||
|
||||
void recover_slot(unsigned char* pc)
|
||||
{
|
||||
/* If this slot potentially caused overflow, we decrease the maximum load so
|
||||
* that average probe length won't increase unboundedly in repeated
|
||||
* insert/erase cycles (drift).
|
||||
*/
|
||||
ml-=group_type::maybe_caused_overflow(pc);
|
||||
size_ctrl.ml-=group_type::maybe_caused_overflow(pc);
|
||||
group_type::reset(pc);
|
||||
--size_;
|
||||
--size_ctrl.size;
|
||||
}
|
||||
|
||||
void recover_slot(group_type* pg,std::size_t pos)
|
||||
@@ -1944,25 +2032,7 @@ private:
|
||||
}
|
||||
delete_arrays(arrays);
|
||||
arrays=new_arrays_;
|
||||
ml=initial_max_load();
|
||||
}
|
||||
|
||||
void noshrink_reserve(std::size_t n)
|
||||
{
|
||||
/* used only on assignment after element clearance */
|
||||
BOOST_ASSERT(empty());
|
||||
|
||||
if(n){
|
||||
n=std::size_t(std::ceil(float(n)/mlf)); /* elements -> slots */
|
||||
n=capacity_for(n); /* exact resulting capacity */
|
||||
|
||||
if(n>capacity()){
|
||||
auto new_arrays_=new_arrays(n);
|
||||
delete_arrays(arrays);
|
||||
arrays=new_arrays_;
|
||||
ml=initial_max_load();
|
||||
}
|
||||
}
|
||||
size_ctrl.ml=initial_max_load();
|
||||
}
|
||||
|
||||
template<typename Value>
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// https://www.boost.org/LICENSE_1_0.txt
|
||||
|
||||
#include <boost/smart_ptr/detail/sp_thread_pause.hpp>
|
||||
#include <boost/smart_ptr/detail/sp_thread_sleep.hpp>
|
||||
#include <boost/core/yield_primitives.hpp>
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
|
||||
@@ -64,10 +63,10 @@ public:
|
||||
if( state_.compare_exchange_weak( st, newst, std::memory_order_acquire, std::memory_order_relaxed ) ) return;
|
||||
}
|
||||
|
||||
boost::detail::sp_thread_pause();
|
||||
boost::core::sp_thread_pause();
|
||||
}
|
||||
|
||||
boost::detail::sp_thread_sleep();
|
||||
boost::core::sp_thread_sleep();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,7 +131,7 @@ public:
|
||||
state_.compare_exchange_weak( st, newst, std::memory_order_relaxed, std::memory_order_relaxed );
|
||||
}
|
||||
|
||||
boost::detail::sp_thread_pause();
|
||||
boost::core::sp_thread_pause();
|
||||
}
|
||||
|
||||
// clear writer pending bit before going to sleep
|
||||
@@ -169,7 +168,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
boost::detail::sp_thread_sleep();
|
||||
boost::core::sp_thread_sleep();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,12 @@ struct plain_integral
|
||||
Integral n;
|
||||
};
|
||||
|
||||
struct plain_size_control
|
||||
{
|
||||
std::size_t ml;
|
||||
std::size_t size;
|
||||
};
|
||||
|
||||
template<typename,typename,typename,typename>
|
||||
class table;
|
||||
|
||||
@@ -194,7 +200,7 @@ private:
|
||||
|
||||
/* foa::table interface departs in a number of ways from that of C++ unordered
|
||||
* associative containers because it's not for end-user consumption
|
||||
* (boost::unordered_[flat|node]_[map|set] wrappers complete it as
|
||||
* (boost::unordered_(flat|node)_(map|set) wrappers complete it as
|
||||
* appropriate).
|
||||
*
|
||||
* The table supports two main modes of operation: flat and node-based. In the
|
||||
@@ -215,13 +221,13 @@ private:
|
||||
*
|
||||
* try_emplace, erase and find support heterogeneous lookup by default,
|
||||
* that is, without checking for any ::is_transparent typedefs --the
|
||||
* checking is done by boost::unordered_[flat|node]_[map|set].
|
||||
* checking is done by boost::unordered_(flat|node)_(map|set).
|
||||
*/
|
||||
|
||||
template <typename TypePolicy,typename Hash,typename Pred,typename Allocator>
|
||||
using table_core_impl=
|
||||
table_core<TypePolicy,group15<plain_integral>,table_arrays,
|
||||
std::size_t,Hash,Pred,Allocator>;
|
||||
plain_size_control,Hash,Pred,Allocator>;
|
||||
|
||||
#include <boost/unordered/detail/foa/ignore_wshadow.hpp>
|
||||
|
||||
@@ -404,8 +410,7 @@ public:
|
||||
template<typename Key>
|
||||
BOOST_FORCEINLINE iterator find(const Key& x)
|
||||
{
|
||||
auto hash=this->hash_for(x);
|
||||
return find_impl(x,this->position_for(hash),hash);
|
||||
return make_iterator(super::find(x));
|
||||
}
|
||||
|
||||
template<typename Key>
|
||||
@@ -440,6 +445,13 @@ public:
|
||||
return std::size_t(s-x.size());
|
||||
}
|
||||
|
||||
friend bool operator==(const table& x,const table& y)
|
||||
{
|
||||
return static_cast<const super&>(x)==static_cast<const super&>(y);
|
||||
}
|
||||
|
||||
friend bool operator!=(const table& x,const table& y){return !(x==y);}
|
||||
|
||||
private:
|
||||
struct erase_on_exit
|
||||
{
|
||||
@@ -458,57 +470,18 @@ private:
|
||||
return {l.pg,l.n,l.p};
|
||||
}
|
||||
|
||||
#if defined(BOOST_MSVC)
|
||||
/* warning: forcing value to bool 'true' or 'false' in bool(pred()...) */
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable:4800)
|
||||
#endif
|
||||
|
||||
template<typename Key>
|
||||
BOOST_FORCEINLINE iterator find_impl(
|
||||
const Key& x,std::size_t pos0,std::size_t hash)const
|
||||
{
|
||||
prober pb(pos0);
|
||||
do{
|
||||
auto pos=pb.get();
|
||||
auto pg=this->arrays.groups+pos;
|
||||
auto mask=pg->match(hash);
|
||||
if(mask){
|
||||
BOOST_UNORDERED_ASSUME(this->arrays.elements!=nullptr);
|
||||
auto p=this->arrays.elements+pos*N;
|
||||
this->prefetch_elements(p);
|
||||
do{
|
||||
auto n=unchecked_countr_zero(mask);
|
||||
if(BOOST_LIKELY(bool(this->pred()(x,this->key_from(p[n]))))){
|
||||
return {pg,n,p+n};
|
||||
}
|
||||
mask&=mask-1;
|
||||
}while(mask);
|
||||
}
|
||||
if(BOOST_LIKELY(pg->is_not_overflowed(hash))){
|
||||
return {}; /* end() */
|
||||
}
|
||||
}
|
||||
while(BOOST_LIKELY(pb.next(this->arrays.groups_size_mask)));
|
||||
return {}; /* end() */
|
||||
}
|
||||
|
||||
#if defined(BOOST_MSVC)
|
||||
#pragma warning(pop) /* C4800 */
|
||||
#endif
|
||||
|
||||
template<typename... Args>
|
||||
BOOST_FORCEINLINE std::pair<iterator,bool> emplace_impl(Args&&... args)
|
||||
{
|
||||
const auto &k=this->key_from(std::forward<Args>(args)...);
|
||||
auto hash=this->hash_for(k);
|
||||
auto pos0=this->position_for(hash);
|
||||
auto it=find_impl(k,pos0,hash);
|
||||
auto loc=super::find(k,pos0,hash);
|
||||
|
||||
if(it!=end()){
|
||||
return {it,false};
|
||||
if(loc){
|
||||
return {make_iterator(loc),false};
|
||||
}
|
||||
if(BOOST_LIKELY(this->size_<this->ml)){
|
||||
if(BOOST_LIKELY(this->size_ctrl.size<this->size_ctrl.ml)){
|
||||
return {
|
||||
make_iterator(
|
||||
this->unchecked_emplace_at(pos0,hash,std::forward<Args>(args)...)),
|
||||
|
||||
@@ -20,6 +20,9 @@
|
||||
#include <boost/type_traits/enable_if.hpp>
|
||||
#include <boost/type_traits/is_integral.hpp>
|
||||
#include <boost/type_traits/remove_const.hpp>
|
||||
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
#endif
|
||||
|
||||
// BOOST_UNORDERED_TEMPLATE_DEDUCTION_GUIDES
|
||||
@@ -101,6 +104,16 @@ namespace boost {
|
||||
!boost::is_integral<H>::value && !is_allocator_v<H>;
|
||||
|
||||
template <class P> constexpr bool const is_pred_v = !is_allocator_v<P>;
|
||||
|
||||
template <typename T>
|
||||
using iter_key_t =
|
||||
typename std::iterator_traits<T>::value_type::first_type;
|
||||
template <typename T>
|
||||
using iter_val_t =
|
||||
typename std::iterator_traits<T>::value_type::second_type;
|
||||
template <typename T>
|
||||
using iter_to_alloc_t =
|
||||
typename std::pair<iter_key_t<T> const, iter_val_t<T> >;
|
||||
#endif
|
||||
} // namespace detail
|
||||
} // namespace unordered
|
||||
|
||||
@@ -100,6 +100,11 @@ namespace boost {
|
||||
|
||||
table_type table_;
|
||||
|
||||
template <class K, class V, class H, class KE, class A>
|
||||
bool friend operator==(
|
||||
unordered_flat_map<K, V, H, KE, A> const& lhs,
|
||||
unordered_flat_map<K, V, H, KE, A> const& rhs);
|
||||
|
||||
template <class K, class V, class H, class KE, class A, class Pred>
|
||||
typename unordered_flat_map<K, V, H, KE, A>::size_type friend erase_if(
|
||||
unordered_flat_map<K, V, H, KE, A>& set, Pred pred);
|
||||
@@ -702,19 +707,7 @@ namespace boost {
|
||||
unordered_flat_map<Key, T, Hash, KeyEqual, Allocator> const& lhs,
|
||||
unordered_flat_map<Key, T, Hash, KeyEqual, Allocator> const& rhs)
|
||||
{
|
||||
if (&lhs == &rhs) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (lhs.size() == rhs.size()) && ([&] {
|
||||
for (auto const& kvp : lhs) {
|
||||
auto pos = rhs.find(kvp.first);
|
||||
if ((pos == rhs.end()) || (*pos != kvp)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
})();
|
||||
return lhs.table_ == rhs.table_;
|
||||
}
|
||||
|
||||
template <class Key, class T, class Hash, class KeyEqual, class Allocator>
|
||||
@@ -748,18 +741,6 @@ namespace boost {
|
||||
|
||||
#if BOOST_UNORDERED_TEMPLATE_DEDUCTION_GUIDES
|
||||
|
||||
namespace detail {
|
||||
template <typename T>
|
||||
using iter_key_t =
|
||||
typename std::iterator_traits<T>::value_type::first_type;
|
||||
template <typename T>
|
||||
using iter_val_t =
|
||||
typename std::iterator_traits<T>::value_type::second_type;
|
||||
template <typename T>
|
||||
using iter_to_alloc_t =
|
||||
typename std::pair<iter_key_t<T> const, iter_val_t<T> >;
|
||||
} // namespace detail
|
||||
|
||||
template <class InputIterator,
|
||||
class Hash =
|
||||
boost::hash<boost::unordered::detail::iter_key_t<InputIterator> >,
|
||||
|
||||
@@ -69,6 +69,11 @@ namespace boost {
|
||||
|
||||
table_type table_;
|
||||
|
||||
template <class K, class H, class KE, class A>
|
||||
bool friend operator==(
|
||||
unordered_flat_set<K, H, KE, A> const& lhs,
|
||||
unordered_flat_set<K, H, KE, A> const& rhs);
|
||||
|
||||
template <class K, class H, class KE, class A, class Pred>
|
||||
typename unordered_flat_set<K, H, KE, A>::size_type friend erase_if(
|
||||
unordered_flat_set<K, H, KE, A>& set, Pred pred);
|
||||
@@ -499,19 +504,7 @@ namespace boost {
|
||||
unordered_flat_set<Key, Hash, KeyEqual, Allocator> const& lhs,
|
||||
unordered_flat_set<Key, Hash, KeyEqual, Allocator> const& rhs)
|
||||
{
|
||||
if (&lhs == &rhs) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (lhs.size() == rhs.size()) && ([&] {
|
||||
for (auto const& key : lhs) {
|
||||
auto pos = rhs.find(key);
|
||||
if ((pos == rhs.end()) || (key != *pos)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
})();
|
||||
return lhs.table_ == rhs.table_;
|
||||
}
|
||||
|
||||
template <class Key, class Hash, class KeyEqual, class Allocator>
|
||||
|
||||
@@ -1061,18 +1061,6 @@ namespace boost {
|
||||
|
||||
#if BOOST_UNORDERED_TEMPLATE_DEDUCTION_GUIDES
|
||||
|
||||
namespace detail {
|
||||
template <typename T>
|
||||
using iter_key_t =
|
||||
typename std::iterator_traits<T>::value_type::first_type;
|
||||
template <typename T>
|
||||
using iter_val_t =
|
||||
typename std::iterator_traits<T>::value_type::second_type;
|
||||
template <typename T>
|
||||
using iter_to_alloc_t =
|
||||
typename std::pair<iter_key_t<T> const, iter_val_t<T> >;
|
||||
} // namespace detail
|
||||
|
||||
template <class InputIterator,
|
||||
class Hash =
|
||||
boost::hash<boost::unordered::detail::iter_key_t<InputIterator> >,
|
||||
|
||||
@@ -187,6 +187,11 @@ namespace boost {
|
||||
|
||||
table_type table_;
|
||||
|
||||
template <class K, class V, class H, class KE, class A>
|
||||
bool friend operator==(
|
||||
unordered_node_map<K, V, H, KE, A> const& lhs,
|
||||
unordered_node_map<K, V, H, KE, A> const& rhs);
|
||||
|
||||
template <class K, class V, class H, class KE, class A, class Pred>
|
||||
typename unordered_node_map<K, V, H, KE, A>::size_type friend erase_if(
|
||||
unordered_node_map<K, V, H, KE, A>& set, Pred pred);
|
||||
@@ -854,19 +859,7 @@ namespace boost {
|
||||
unordered_node_map<Key, T, Hash, KeyEqual, Allocator> const& lhs,
|
||||
unordered_node_map<Key, T, Hash, KeyEqual, Allocator> const& rhs)
|
||||
{
|
||||
if (&lhs == &rhs) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (lhs.size() == rhs.size()) && ([&] {
|
||||
for (auto const& kvp : lhs) {
|
||||
auto pos = rhs.find(kvp.first);
|
||||
if ((pos == rhs.end()) || (*pos != kvp)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
})();
|
||||
return lhs.table_ == rhs.table_;
|
||||
}
|
||||
|
||||
template <class Key, class T, class Hash, class KeyEqual, class Allocator>
|
||||
@@ -900,18 +893,6 @@ namespace boost {
|
||||
|
||||
#if BOOST_UNORDERED_TEMPLATE_DEDUCTION_GUIDES
|
||||
|
||||
namespace detail {
|
||||
template <typename T>
|
||||
using iter_key_t =
|
||||
typename std::iterator_traits<T>::value_type::first_type;
|
||||
template <typename T>
|
||||
using iter_val_t =
|
||||
typename std::iterator_traits<T>::value_type::second_type;
|
||||
template <typename T>
|
||||
using iter_to_alloc_t =
|
||||
typename std::pair<iter_key_t<T> const, iter_val_t<T> >;
|
||||
} // namespace detail
|
||||
|
||||
template <class InputIterator,
|
||||
class Hash =
|
||||
boost::hash<boost::unordered::detail::iter_key_t<InputIterator> >,
|
||||
|
||||
@@ -143,6 +143,11 @@ namespace boost {
|
||||
|
||||
table_type table_;
|
||||
|
||||
template <class K, class H, class KE, class A>
|
||||
bool friend operator==(
|
||||
unordered_node_set<K, H, KE, A> const& lhs,
|
||||
unordered_node_set<K, H, KE, A> const& rhs);
|
||||
|
||||
template <class K, class H, class KE, class A, class Pred>
|
||||
typename unordered_node_set<K, H, KE, A>::size_type friend erase_if(
|
||||
unordered_node_set<K, H, KE, A>& set, Pred pred);
|
||||
@@ -638,19 +643,7 @@ namespace boost {
|
||||
unordered_node_set<Key, Hash, KeyEqual, Allocator> const& lhs,
|
||||
unordered_node_set<Key, Hash, KeyEqual, Allocator> const& rhs)
|
||||
{
|
||||
if (&lhs == &rhs) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (lhs.size() == rhs.size()) && ([&] {
|
||||
for (auto const& key : lhs) {
|
||||
auto pos = rhs.find(key);
|
||||
if ((pos == rhs.end()) || (key != *pos)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
})();
|
||||
return lhs.table_ == rhs.table_;
|
||||
}
|
||||
|
||||
template <class Key, class Hash, class KeyEqual, class Allocator>
|
||||
|
||||
@@ -6,12 +6,158 @@ include(BoostTestJamfile OPTIONAL RESULT_VARIABLE HAVE_BOOST_TEST)
|
||||
|
||||
if(HAVE_BOOST_TEST)
|
||||
|
||||
boost_test_jamfile(
|
||||
FILE Jamfile.v2
|
||||
LINK_LIBRARIES
|
||||
Boost::unordered
|
||||
Boost::core
|
||||
Boost::concept_check
|
||||
)
|
||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
set(BOOST_TEST_LINK_LIBRARIES Boost::unordered Boost::core Boost::concept_check)
|
||||
|
||||
function(fca_tests)
|
||||
boost_test(PREFIX boost_unordered ${ARGN})
|
||||
endfunction()
|
||||
|
||||
function(foa_tests)
|
||||
boost_test(PREFIX boost_unordered_foa COMPILE_DEFINITIONS BOOST_UNORDERED_FOA_TESTS ${ARGN})
|
||||
endfunction()
|
||||
|
||||
function(cfoa_tests)
|
||||
boost_test(PREFIX boost_unordered_cfoa LINK_LIBRARIES Threads::Threads ${ARGN})
|
||||
endfunction()
|
||||
|
||||
# FCA tests
|
||||
|
||||
fca_tests(SOURCES unordered/prime_fmod_tests.cpp)
|
||||
fca_tests(SOURCES unordered/fwd_set_test.cpp)
|
||||
fca_tests(SOURCES unordered/fwd_map_test.cpp)
|
||||
fca_tests(SOURCES unordered/allocator_traits.cpp)
|
||||
fca_tests(SOURCES unordered/minimal_allocator.cpp)
|
||||
fca_tests(SOURCES unordered/compile_set.cpp)
|
||||
fca_tests(SOURCES unordered/compile_map.cpp)
|
||||
fca_tests(SOURCES unordered/noexcept_tests.cpp)
|
||||
fca_tests(SOURCES unordered/link_test_1.cpp unordered/link_test_2.cpp)
|
||||
fca_tests(SOURCES unordered/incomplete_test.cpp)
|
||||
fca_tests(SOURCES unordered/simple_tests.cpp)
|
||||
fca_tests(SOURCES unordered/equivalent_keys_tests.cpp)
|
||||
fca_tests(SOURCES unordered/constructor_tests.cpp)
|
||||
fca_tests(SOURCES unordered/copy_tests.cpp)
|
||||
fca_tests(SOURCES unordered/move_tests.cpp)
|
||||
fca_tests(SOURCES unordered/post_move_tests.cpp)
|
||||
fca_tests(SOURCES unordered/assign_tests.cpp)
|
||||
fca_tests(SOURCES unordered/insert_tests.cpp)
|
||||
fca_tests(SOURCES unordered/insert_stable_tests.cpp)
|
||||
fca_tests(SOURCES unordered/insert_hint_tests.cpp)
|
||||
fca_tests(SOURCES unordered/emplace_tests.cpp)
|
||||
fca_tests(SOURCES unordered/unnecessary_copy_tests.cpp)
|
||||
fca_tests(SOURCES unordered/erase_tests.cpp COMPILE_DEFINITIONS BOOST_UNORDERED_SUPPRESS_DEPRECATED)
|
||||
fca_tests(SOURCES unordered/erase_equiv_tests.cpp)
|
||||
fca_tests(SOURCES unordered/extract_tests.cpp)
|
||||
fca_tests(SOURCES unordered/node_handle_tests.cpp)
|
||||
fca_tests(SOURCES unordered/merge_tests.cpp)
|
||||
fca_tests(SOURCES unordered/find_tests.cpp)
|
||||
fca_tests(SOURCES unordered/at_tests.cpp)
|
||||
fca_tests(SOURCES unordered/bucket_tests.cpp)
|
||||
fca_tests(SOURCES unordered/load_factor_tests.cpp)
|
||||
fca_tests(SOURCES unordered/rehash_tests.cpp)
|
||||
fca_tests(SOURCES unordered/equality_tests.cpp)
|
||||
fca_tests(SOURCES unordered/swap_tests.cpp)
|
||||
fca_tests(SOURCES unordered/deduction_tests.cpp)
|
||||
fca_tests(SOURCES unordered/scoped_allocator.cpp)
|
||||
fca_tests(SOURCES unordered/transparent_tests.cpp)
|
||||
fca_tests(SOURCES unordered/reserve_tests.cpp)
|
||||
fca_tests(SOURCES unordered/contains_tests.cpp)
|
||||
fca_tests(SOURCES unordered/erase_if.cpp)
|
||||
fca_tests(SOURCES unordered/scary_tests.cpp)
|
||||
fca_tests(SOURCES exception/constructor_exception_tests.cpp)
|
||||
fca_tests(SOURCES exception/copy_exception_tests.cpp)
|
||||
fca_tests(SOURCES exception/assign_exception_tests.cpp)
|
||||
fca_tests(SOURCES exception/move_assign_exception_tests.cpp)
|
||||
fca_tests(SOURCES exception/insert_exception_tests.cpp)
|
||||
fca_tests(SOURCES exception/erase_exception_tests.cpp)
|
||||
fca_tests(SOURCES exception/rehash_exception_tests.cpp)
|
||||
fca_tests(SOURCES exception/swap_exception_tests.cpp COMPILE_DEFINITIONS BOOST_UNORDERED_SWAP_METHOD=2)
|
||||
fca_tests(SOURCES exception/merge_exception_tests.cpp)
|
||||
fca_tests(SOURCES exception/less_tests.cpp)
|
||||
fca_tests(SOURCES unordered/narrow_cast_tests.cpp)
|
||||
fca_tests(SOURCES unordered/compile_set.cpp COMPILE_DEFINITIONS BOOST_UNORDERED_USE_MOVE NAME bmove_compile_set)
|
||||
fca_tests(SOURCES unordered/compile_map.cpp COMPILE_DEFINITIONS BOOST_UNORDERED_USE_MOVE NAME bmove_compile_map)
|
||||
fca_tests(SOURCES unordered/copy_tests.cpp COMPILE_DEFINITIONS BOOST_UNORDERED_USE_MOVE NAME bmove_copy)
|
||||
fca_tests(SOURCES unordered/move_tests.cpp COMPILE_DEFINITIONS BOOST_UNORDERED_USE_MOVE NAME bmove_move)
|
||||
fca_tests(SOURCES unordered/assign_tests.cpp COMPILE_DEFINITIONS BOOST_UNORDERED_USE_MOVE NAME bmove_assign)
|
||||
fca_tests(SOURCES quick.cpp)
|
||||
|
||||
fca_tests(TYPE compile-fail NAME insert_node_type_fail_map COMPILE_DEFINITIONS UNORDERED_TEST_MAP SOURCES unordered/insert_node_type_fail.cpp)
|
||||
fca_tests(TYPE compile-fail NAME insert_node_type_fail_multimap COMPILE_DEFINITIONS UNORDERED_TEST_MULTIMAP SOURCES unordered/insert_node_type_fail.cpp)
|
||||
fca_tests(TYPE compile-fail NAME insert_node_type_fail_set COMPILE_DEFINITIONS UNORDERED_TEST_SET SOURCES unordered/insert_node_type_fail.cpp)
|
||||
fca_tests(TYPE compile-fail NAME insert_node_type_fail_multiset COMPILE_DEFINITIONS UNORDERED_TEST_MULTISET SOURCES unordered/insert_node_type_fail.cpp)
|
||||
|
||||
# FOA tests
|
||||
|
||||
foa_tests(SOURCES unordered/fwd_set_test.cpp)
|
||||
foa_tests(SOURCES unordered/fwd_map_test.cpp)
|
||||
foa_tests(SOURCES unordered/compile_set.cpp)
|
||||
foa_tests(SOURCES unordered/compile_map.cpp)
|
||||
foa_tests(SOURCES unordered/noexcept_tests.cpp)
|
||||
foa_tests(SOURCES unordered/incomplete_test.cpp)
|
||||
foa_tests(SOURCES unordered/simple_tests.cpp)
|
||||
foa_tests(SOURCES unordered/equivalent_keys_tests.cpp)
|
||||
foa_tests(SOURCES unordered/constructor_tests.cpp)
|
||||
foa_tests(SOURCES unordered/copy_tests.cpp)
|
||||
foa_tests(SOURCES unordered/move_tests.cpp)
|
||||
foa_tests(SOURCES unordered/post_move_tests.cpp)
|
||||
foa_tests(SOURCES unordered/assign_tests.cpp)
|
||||
foa_tests(SOURCES unordered/insert_tests.cpp)
|
||||
foa_tests(SOURCES unordered/insert_hint_tests.cpp)
|
||||
foa_tests(SOURCES unordered/emplace_tests.cpp)
|
||||
foa_tests(SOURCES unordered/erase_tests.cpp)
|
||||
foa_tests(SOURCES unordered/merge_tests.cpp)
|
||||
foa_tests(SOURCES unordered/find_tests.cpp)
|
||||
foa_tests(SOURCES unordered/at_tests.cpp)
|
||||
foa_tests(SOURCES unordered/load_factor_tests.cpp)
|
||||
foa_tests(SOURCES unordered/rehash_tests.cpp)
|
||||
foa_tests(SOURCES unordered/equality_tests.cpp)
|
||||
foa_tests(SOURCES unordered/swap_tests.cpp)
|
||||
foa_tests(SOURCES unordered/transparent_tests.cpp)
|
||||
foa_tests(SOURCES unordered/reserve_tests.cpp)
|
||||
foa_tests(SOURCES unordered/contains_tests.cpp)
|
||||
foa_tests(SOURCES unordered/erase_if.cpp)
|
||||
foa_tests(SOURCES unordered/scary_tests.cpp)
|
||||
foa_tests(SOURCES unordered/init_type_insert_tests.cpp)
|
||||
foa_tests(SOURCES unordered/max_load_tests.cpp)
|
||||
foa_tests(SOURCES unordered/extract_tests.cpp)
|
||||
foa_tests(SOURCES unordered/node_handle_tests.cpp)
|
||||
foa_tests(SOURCES unordered/uses_allocator.cpp)
|
||||
foa_tests(SOURCES unordered/link_test_1.cpp unordered/link_test_2.cpp )
|
||||
foa_tests(SOURCES unordered/scoped_allocator.cpp)
|
||||
foa_tests(SOURCES unordered/hash_is_avalanching_test.cpp)
|
||||
foa_tests(SOURCES exception/constructor_exception_tests.cpp)
|
||||
foa_tests(SOURCES exception/copy_exception_tests.cpp)
|
||||
foa_tests(SOURCES exception/assign_exception_tests.cpp)
|
||||
foa_tests(SOURCES exception/move_assign_exception_tests.cpp)
|
||||
foa_tests(SOURCES exception/insert_exception_tests.cpp)
|
||||
foa_tests(SOURCES exception/erase_exception_tests.cpp)
|
||||
foa_tests(SOURCES exception/rehash_exception_tests.cpp)
|
||||
foa_tests(SOURCES exception/swap_exception_tests.cpp)
|
||||
foa_tests(SOURCES exception/merge_exception_tests.cpp)
|
||||
|
||||
# CFOA tests
|
||||
|
||||
cfoa_tests(SOURCES cfoa/latch_tests.cpp)
|
||||
cfoa_tests(SOURCES cfoa/insert_tests.cpp)
|
||||
cfoa_tests(SOURCES cfoa/erase_tests.cpp)
|
||||
cfoa_tests(SOURCES cfoa/try_emplace_tests.cpp)
|
||||
cfoa_tests(SOURCES cfoa/emplace_tests.cpp)
|
||||
cfoa_tests(SOURCES cfoa/visit_tests.cpp)
|
||||
cfoa_tests(SOURCES cfoa/constructor_tests.cpp)
|
||||
cfoa_tests(SOURCES cfoa/assign_tests.cpp)
|
||||
cfoa_tests(SOURCES cfoa/clear_tests.cpp)
|
||||
cfoa_tests(SOURCES cfoa/swap_tests.cpp)
|
||||
cfoa_tests(SOURCES cfoa/merge_tests.cpp)
|
||||
cfoa_tests(SOURCES cfoa/rehash_tests.cpp)
|
||||
cfoa_tests(SOURCES cfoa/equality_tests.cpp)
|
||||
cfoa_tests(SOURCES cfoa/fwd_tests.cpp)
|
||||
cfoa_tests(SOURCES cfoa/exception_insert_tests.cpp)
|
||||
cfoa_tests(SOURCES cfoa/exception_erase_tests.cpp)
|
||||
cfoa_tests(SOURCES cfoa/exception_constructor_tests.cpp)
|
||||
cfoa_tests(SOURCES cfoa/exception_assign_tests.cpp)
|
||||
cfoa_tests(SOURCES cfoa/exception_merge_tests.cpp)
|
||||
|
||||
endif()
|
||||
|
||||
@@ -176,6 +176,7 @@ alias foa_tests :
|
||||
;
|
||||
|
||||
local CFOA_TESTS =
|
||||
latch_tests
|
||||
insert_tests
|
||||
erase_tests
|
||||
try_emplace_tests
|
||||
@@ -183,12 +184,23 @@ local CFOA_TESTS =
|
||||
visit_tests
|
||||
constructor_tests
|
||||
assign_tests
|
||||
clear_tests
|
||||
swap_tests
|
||||
merge_tests
|
||||
rehash_tests
|
||||
equality_tests
|
||||
fwd_tests
|
||||
exception_insert_tests
|
||||
exception_erase_tests
|
||||
exception_constructor_tests
|
||||
exception_assign_tests
|
||||
exception_merge_tests
|
||||
;
|
||||
|
||||
for local test in $(CFOA_TESTS)
|
||||
{
|
||||
run cfoa/$(test).cpp
|
||||
: requirements $(CPP11)
|
||||
: requirements $(CPP11) <threading>multi
|
||||
: target-name cfoa_$(test)
|
||||
;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,23 @@
|
||||
|
||||
#include <boost/unordered/concurrent_flat_map.hpp>
|
||||
|
||||
#if defined(__clang__) && defined(__has_warning)
|
||||
|
||||
#if __has_warning("-Wself-assign-overloaded")
|
||||
#pragma clang diagnostic ignored "-Wself-assign-overloaded"
|
||||
#endif
|
||||
|
||||
#if __has_warning("-Wself-move")
|
||||
#pragma clang diagnostic ignored "-Wself-move"
|
||||
#endif
|
||||
|
||||
#endif /* defined(__clang__) && defined(__has_warning) */
|
||||
|
||||
#if defined(BOOST_GCC) && BOOST_GCC >= 130000
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wself-move"
|
||||
#endif
|
||||
|
||||
test::seed_t initialize_seed{2762556623};
|
||||
|
||||
using test::default_generator;
|
||||
@@ -21,6 +38,80 @@ using map_type = boost::unordered::concurrent_flat_map<raii, raii, hasher,
|
||||
|
||||
using map_value_type = typename map_type::value_type;
|
||||
|
||||
template <class T> struct pocca_allocator
|
||||
{
|
||||
using propagate_on_container_copy_assignment = std::true_type;
|
||||
|
||||
int x_ = -1;
|
||||
|
||||
using value_type = T;
|
||||
|
||||
pocca_allocator() = default;
|
||||
pocca_allocator(pocca_allocator const&) = default;
|
||||
pocca_allocator(pocca_allocator&&) = default;
|
||||
|
||||
pocca_allocator(int const x) : x_{x} {}
|
||||
|
||||
pocca_allocator& operator=(pocca_allocator const& rhs)
|
||||
{
|
||||
if (this != &rhs) {
|
||||
x_ = rhs.x_;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class U> pocca_allocator(pocca_allocator<U> const& rhs) : x_{rhs.x_}
|
||||
{
|
||||
}
|
||||
|
||||
T* allocate(std::size_t n)
|
||||
{
|
||||
return static_cast<T*>(::operator new(n * sizeof(T)));
|
||||
}
|
||||
|
||||
void deallocate(T* p, std::size_t) { ::operator delete(p); }
|
||||
|
||||
bool operator==(pocca_allocator const& rhs) const { return x_ == rhs.x_; }
|
||||
bool operator!=(pocca_allocator const& rhs) const { return x_ != rhs.x_; }
|
||||
};
|
||||
|
||||
template <class T> struct pocma_allocator
|
||||
{
|
||||
using propagate_on_container_move_assignment = std::true_type;
|
||||
|
||||
int x_ = -1;
|
||||
|
||||
using value_type = T;
|
||||
|
||||
pocma_allocator() = default;
|
||||
pocma_allocator(pocma_allocator const&) = default;
|
||||
pocma_allocator(pocma_allocator&&) = default;
|
||||
|
||||
pocma_allocator(int const x) : x_{x} {}
|
||||
|
||||
pocma_allocator& operator=(pocma_allocator const& rhs)
|
||||
{
|
||||
if (this != &rhs) {
|
||||
x_ = rhs.x_;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class U> pocma_allocator(pocma_allocator<U> const& rhs) : x_{rhs.x_}
|
||||
{
|
||||
}
|
||||
|
||||
T* allocate(std::size_t n)
|
||||
{
|
||||
return static_cast<T*>(::operator new(n * sizeof(T)));
|
||||
}
|
||||
|
||||
void deallocate(T* p, std::size_t) { ::operator delete(p); }
|
||||
|
||||
bool operator==(pocma_allocator const& rhs) const { return x_ == rhs.x_; }
|
||||
bool operator!=(pocma_allocator const& rhs) const { return x_ != rhs.x_; }
|
||||
};
|
||||
|
||||
namespace {
|
||||
template <class G> void copy_assign(G gen, test::random_generator rg)
|
||||
{
|
||||
@@ -28,30 +119,31 @@ namespace {
|
||||
auto reference_map =
|
||||
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
|
||||
|
||||
// to test:
|
||||
// self-assign
|
||||
// propagation
|
||||
//
|
||||
|
||||
// lhs empty, rhs empty
|
||||
{
|
||||
raii::reset_counts();
|
||||
|
||||
map_type x(0, hasher(1), key_equal(2), allocator_type(3));
|
||||
map_type y;
|
||||
|
||||
BOOST_TEST(x.empty());
|
||||
BOOST_TEST(y.empty());
|
||||
y = x;
|
||||
thread_runner(values, [&x](boost::span<map_value_type> s) {
|
||||
(void)s;
|
||||
|
||||
map_type y;
|
||||
|
||||
BOOST_TEST(x.empty());
|
||||
BOOST_TEST(y.empty());
|
||||
|
||||
y = x;
|
||||
|
||||
BOOST_TEST_EQ(x.hash_function(), y.hash_function());
|
||||
BOOST_TEST_EQ(x.key_eq(), y.key_eq());
|
||||
BOOST_TEST(x.get_allocator() != y.get_allocator());
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(raii::destructor, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 0u);
|
||||
|
||||
BOOST_TEST_EQ(x.hash_function(), y.hash_function());
|
||||
BOOST_TEST_EQ(x.key_eq(), y.key_eq());
|
||||
BOOST_TEST(x.get_allocator() != y.get_allocator());
|
||||
}
|
||||
|
||||
// lhs non-empty, rhs empty
|
||||
@@ -60,23 +152,30 @@ namespace {
|
||||
|
||||
map_type x(0, hasher(1), key_equal(2), allocator_type(3));
|
||||
|
||||
map_type y(values.begin(), values.end(), values.size());
|
||||
auto const old_size = reference_map.size();
|
||||
|
||||
auto const old_cc = +raii::copy_constructor;
|
||||
auto const old_size = y.size();
|
||||
thread_runner(values, [&x, &values](boost::span<map_value_type> s) {
|
||||
(void)s;
|
||||
|
||||
BOOST_TEST(x.empty());
|
||||
BOOST_TEST(!y.empty());
|
||||
y = x;
|
||||
map_type y(values.begin(), values.end(), values.size());
|
||||
|
||||
BOOST_TEST_EQ(raii::destructor, 2 * old_size);
|
||||
BOOST_TEST(x.empty());
|
||||
BOOST_TEST(!y.empty());
|
||||
|
||||
y = x;
|
||||
|
||||
BOOST_TEST_EQ(x.hash_function(), y.hash_function());
|
||||
BOOST_TEST_EQ(x.key_eq(), y.key_eq());
|
||||
BOOST_TEST(x.get_allocator() != y.get_allocator());
|
||||
|
||||
BOOST_TEST(y.empty());
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(raii::destructor, num_threads * (2 * old_size));
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
|
||||
|
||||
BOOST_TEST_EQ(x.hash_function(), y.hash_function());
|
||||
BOOST_TEST_EQ(x.key_eq(), y.key_eq());
|
||||
BOOST_TEST(x.get_allocator() != y.get_allocator());
|
||||
BOOST_TEST_EQ(
|
||||
raii::copy_constructor, num_threads * 2 * reference_map.size());
|
||||
}
|
||||
check_raii_counts();
|
||||
|
||||
@@ -87,23 +186,31 @@ namespace {
|
||||
map_type x(values.begin(), values.end(), values.size(), hasher(1),
|
||||
key_equal(2), allocator_type(3));
|
||||
|
||||
map_type y;
|
||||
auto const old_cc = +raii::copy_constructor;
|
||||
|
||||
BOOST_TEST(!x.empty());
|
||||
BOOST_TEST(y.empty());
|
||||
y = x;
|
||||
thread_runner(
|
||||
values, [&x, &reference_map](boost::span<map_value_type> s) {
|
||||
(void)s;
|
||||
|
||||
BOOST_TEST_EQ(raii::destructor, 0u);
|
||||
map_type y;
|
||||
|
||||
BOOST_TEST(!x.empty());
|
||||
BOOST_TEST(y.empty());
|
||||
|
||||
y = x;
|
||||
|
||||
BOOST_TEST_EQ(x.hash_function(), y.hash_function());
|
||||
BOOST_TEST_EQ(x.key_eq(), y.key_eq());
|
||||
BOOST_TEST(x.get_allocator() != y.get_allocator());
|
||||
|
||||
test_matches_reference(y, reference_map);
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(raii::destructor, num_threads * 2 * x.size());
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_constructor, old_cc + (2 * x.size()));
|
||||
|
||||
BOOST_TEST_EQ(x.hash_function(), y.hash_function());
|
||||
BOOST_TEST_EQ(x.key_eq(), y.key_eq());
|
||||
BOOST_TEST(x.get_allocator() != y.get_allocator());
|
||||
|
||||
test_matches_reference(y, reference_map);
|
||||
BOOST_TEST_EQ(
|
||||
raii::copy_constructor, old_cc + (num_threads * 2 * x.size()));
|
||||
}
|
||||
check_raii_counts();
|
||||
|
||||
@@ -114,27 +221,590 @@ namespace {
|
||||
map_type x(values.begin(), values.end(), values.size(), hasher(1),
|
||||
key_equal(2), allocator_type(3));
|
||||
|
||||
map_type y(values.begin(), values.end(), values.size());
|
||||
|
||||
auto const old_size = y.size();
|
||||
auto const old_size = x.size();
|
||||
auto const old_cc = +raii::copy_constructor;
|
||||
|
||||
BOOST_TEST(!x.empty());
|
||||
BOOST_TEST(!y.empty());
|
||||
y = x;
|
||||
thread_runner(values, [&x, &values](boost::span<map_value_type> s) {
|
||||
(void)s;
|
||||
|
||||
BOOST_TEST_EQ(raii::destructor, 2 * old_size);
|
||||
map_type y(values.begin(), values.end(), values.size());
|
||||
|
||||
BOOST_TEST(!x.empty());
|
||||
BOOST_TEST(!y.empty());
|
||||
|
||||
y = x;
|
||||
|
||||
BOOST_TEST_EQ(x.hash_function(), y.hash_function());
|
||||
BOOST_TEST_EQ(x.key_eq(), y.key_eq());
|
||||
BOOST_TEST(x.get_allocator() != y.get_allocator());
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(raii::destructor, 2 * num_threads * 2 * old_size);
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_constructor, old_cc + (2 * x.size()));
|
||||
BOOST_TEST_EQ(
|
||||
raii::copy_constructor, old_cc + (2 * num_threads * 2 * x.size()));
|
||||
}
|
||||
check_raii_counts();
|
||||
|
||||
BOOST_TEST_EQ(x.hash_function(), y.hash_function());
|
||||
BOOST_TEST_EQ(x.key_eq(), y.key_eq());
|
||||
BOOST_TEST(x.get_allocator() != y.get_allocator());
|
||||
// self-assign
|
||||
{
|
||||
raii::reset_counts();
|
||||
|
||||
map_type x(values.begin(), values.end(), values.size(), hasher(1),
|
||||
key_equal(2), allocator_type(3));
|
||||
|
||||
auto const old_cc = +raii::copy_constructor;
|
||||
|
||||
thread_runner(
|
||||
values, [&x, &reference_map](boost::span<map_value_type> s) {
|
||||
(void)s;
|
||||
|
||||
BOOST_TEST(!x.empty());
|
||||
|
||||
x = x;
|
||||
|
||||
BOOST_TEST_EQ(x.hash_function(), hasher(1));
|
||||
BOOST_TEST_EQ(x.key_eq(), key_equal(2));
|
||||
BOOST_TEST(x.get_allocator() == allocator_type(3));
|
||||
|
||||
test_matches_reference(x, reference_map);
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(raii::destructor, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
|
||||
}
|
||||
check_raii_counts();
|
||||
|
||||
// propagation
|
||||
{
|
||||
using pocca_allocator_type =
|
||||
pocca_allocator<std::pair<const raii, raii> >;
|
||||
|
||||
using pocca_map_type = boost::unordered::concurrent_flat_map<raii, raii,
|
||||
hasher, key_equal, pocca_allocator_type>;
|
||||
|
||||
raii::reset_counts();
|
||||
|
||||
pocca_map_type x(values.begin(), values.end(), values.size(), hasher(1),
|
||||
key_equal(2), pocca_allocator_type(3));
|
||||
|
||||
auto const old_size = x.size();
|
||||
auto const old_cc = +raii::copy_constructor;
|
||||
|
||||
thread_runner(values, [&x, &values](boost::span<map_value_type> s) {
|
||||
(void)s;
|
||||
|
||||
pocca_map_type y(values.begin(), values.end(), values.size());
|
||||
|
||||
BOOST_TEST(!x.empty());
|
||||
BOOST_TEST(!y.empty());
|
||||
|
||||
BOOST_TEST(x.get_allocator() != y.get_allocator());
|
||||
|
||||
y = x;
|
||||
|
||||
BOOST_TEST_EQ(x.hash_function(), y.hash_function());
|
||||
BOOST_TEST_EQ(x.key_eq(), y.key_eq());
|
||||
BOOST_TEST(x.get_allocator() == y.get_allocator());
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(raii::destructor, 2 * num_threads * 2 * old_size);
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
BOOST_TEST_EQ(
|
||||
raii::copy_constructor, old_cc + (2 * num_threads * 2 * x.size()));
|
||||
}
|
||||
check_raii_counts();
|
||||
}
|
||||
|
||||
template <class G> void move_assign(G gen, test::random_generator rg)
|
||||
{
|
||||
using pocma_allocator_type = pocma_allocator<std::pair<const raii, raii> >;
|
||||
|
||||
using pocma_map_type = boost::unordered::concurrent_flat_map<raii, raii,
|
||||
hasher, key_equal, pocma_allocator_type>;
|
||||
|
||||
BOOST_STATIC_ASSERT(
|
||||
std::is_nothrow_move_assignable<boost::unordered::concurrent_flat_map<int,
|
||||
int, std::hash<int>, std::equal_to<int>,
|
||||
std::allocator<std::pair<int const, int> > > >::value);
|
||||
|
||||
BOOST_STATIC_ASSERT(
|
||||
std::is_nothrow_move_assignable<boost::unordered::concurrent_flat_map<int,
|
||||
int, std::hash<int>, std::equal_to<int>,
|
||||
pocma_allocator<std::pair<int const, int> > > >::value);
|
||||
|
||||
BOOST_STATIC_ASSERT(
|
||||
!std::is_nothrow_move_assignable<boost::unordered::concurrent_flat_map<
|
||||
int, int, std::hash<int>, std::equal_to<int>,
|
||||
stateful_allocator<std::pair<int const, int> > > >::value);
|
||||
|
||||
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
|
||||
auto reference_map =
|
||||
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
|
||||
|
||||
// move assignment has more complex requirements than copying
|
||||
// equal allocators:
|
||||
// lhs empty, rhs non-empty
|
||||
// lhs non-empty, rhs empty
|
||||
// lhs non-empty, rhs non-empty
|
||||
//
|
||||
// unequal allocators:
|
||||
// lhs non-empty, rhs non-empty
|
||||
//
|
||||
// pocma
|
||||
// self move-assign
|
||||
|
||||
// lhs empty, rhs empty
|
||||
{
|
||||
raii::reset_counts();
|
||||
|
||||
map_type x(0, hasher(1), key_equal(2), allocator_type(3));
|
||||
|
||||
std::atomic<unsigned> num_transfers{0};
|
||||
|
||||
thread_runner(
|
||||
values, [&x, &num_transfers](boost::span<map_value_type> s) {
|
||||
(void)s;
|
||||
|
||||
map_type y(0, hasher(2), key_equal(1), allocator_type(3));
|
||||
|
||||
BOOST_TEST(x.empty());
|
||||
BOOST_TEST(y.empty());
|
||||
BOOST_TEST(x.get_allocator() == y.get_allocator());
|
||||
|
||||
y = std::move(x);
|
||||
if (y.hash_function() == hasher(1)) {
|
||||
++num_transfers;
|
||||
BOOST_TEST_EQ(y.key_eq(), key_equal(2));
|
||||
} else {
|
||||
BOOST_TEST_EQ(y.hash_function(), hasher(2));
|
||||
BOOST_TEST_EQ(y.key_eq(), key_equal(1));
|
||||
}
|
||||
|
||||
BOOST_TEST_EQ(x.hash_function(), hasher(2));
|
||||
BOOST_TEST_EQ(x.key_eq(), key_equal(1));
|
||||
BOOST_TEST(x.get_allocator() == y.get_allocator());
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_transfers, 1u);
|
||||
|
||||
BOOST_TEST_EQ(raii::destructor, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 0u);
|
||||
}
|
||||
|
||||
// lhs non-empty, rhs empty
|
||||
{
|
||||
raii::reset_counts();
|
||||
|
||||
map_type x(0, hasher(1), key_equal(2), allocator_type(3));
|
||||
|
||||
std::atomic<unsigned> num_transfers{0};
|
||||
|
||||
thread_runner(
|
||||
values, [&x, &values, &num_transfers](boost::span<map_value_type> s) {
|
||||
(void)s;
|
||||
|
||||
map_type y(values.begin(), values.end(), values.size(), hasher(2),
|
||||
key_equal(1), allocator_type(3));
|
||||
|
||||
BOOST_TEST(x.empty());
|
||||
BOOST_TEST(!y.empty());
|
||||
BOOST_TEST(x.get_allocator() == y.get_allocator());
|
||||
|
||||
y = std::move(x);
|
||||
if (y.hash_function() == hasher(1)) {
|
||||
++num_transfers;
|
||||
BOOST_TEST_EQ(y.key_eq(), key_equal(2));
|
||||
} else {
|
||||
BOOST_TEST_EQ(y.hash_function(), hasher(2));
|
||||
BOOST_TEST_EQ(y.key_eq(), key_equal(1));
|
||||
}
|
||||
|
||||
BOOST_TEST_EQ(x.hash_function(), hasher(2));
|
||||
BOOST_TEST_EQ(x.key_eq(), key_equal(1));
|
||||
BOOST_TEST(x.get_allocator() == y.get_allocator());
|
||||
|
||||
BOOST_TEST(y.empty());
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_transfers, 1u);
|
||||
|
||||
BOOST_TEST_EQ(raii::destructor, num_threads * 2 * reference_map.size());
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
BOOST_TEST_EQ(
|
||||
raii::copy_constructor, num_threads * 2 * reference_map.size());
|
||||
}
|
||||
check_raii_counts();
|
||||
|
||||
// lhs empty, rhs non-empty
|
||||
{
|
||||
raii::reset_counts();
|
||||
|
||||
map_type x(values.begin(), values.end(), values.size(), hasher(1),
|
||||
key_equal(2), allocator_type(3));
|
||||
|
||||
auto const old_cc = +raii::copy_constructor;
|
||||
auto const old_mc = +raii::move_constructor;
|
||||
std::atomic<unsigned> num_transfers{0};
|
||||
|
||||
thread_runner(values,
|
||||
[&x, &reference_map, &num_transfers](boost::span<map_value_type> s) {
|
||||
(void)s;
|
||||
|
||||
map_type y(allocator_type(3));
|
||||
|
||||
BOOST_TEST(y.empty());
|
||||
BOOST_TEST(x.get_allocator() == y.get_allocator());
|
||||
|
||||
y = std::move(x);
|
||||
if (!y.empty()) {
|
||||
++num_transfers;
|
||||
test_matches_reference(y, reference_map);
|
||||
|
||||
BOOST_TEST_EQ(y.hash_function(), hasher(1));
|
||||
BOOST_TEST_EQ(y.key_eq(), key_equal(2));
|
||||
} else {
|
||||
BOOST_TEST_EQ(y.hash_function(), hasher());
|
||||
BOOST_TEST_EQ(y.key_eq(), key_equal());
|
||||
}
|
||||
|
||||
BOOST_TEST(x.empty());
|
||||
|
||||
BOOST_TEST_EQ(x.hash_function(), hasher());
|
||||
BOOST_TEST_EQ(x.key_eq(), key_equal());
|
||||
BOOST_TEST(x.get_allocator() == y.get_allocator());
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_transfers, 1u);
|
||||
|
||||
BOOST_TEST_EQ(raii::destructor, 2 * reference_map.size());
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
|
||||
BOOST_TEST_EQ(raii::move_constructor, old_mc);
|
||||
}
|
||||
check_raii_counts();
|
||||
|
||||
// lhs non-empty, rhs non-empty
|
||||
{
|
||||
raii::reset_counts();
|
||||
|
||||
map_type x(values.begin(), values.end(), values.size(), hasher(1),
|
||||
key_equal(2), allocator_type(3));
|
||||
|
||||
auto const old_size = x.size();
|
||||
auto const old_cc = +raii::copy_constructor;
|
||||
auto const old_mc = +raii::move_constructor;
|
||||
|
||||
std::atomic<unsigned> num_transfers{0};
|
||||
|
||||
thread_runner(values, [&x, &values, &num_transfers, &reference_map](
|
||||
boost::span<map_value_type> s) {
|
||||
(void)s;
|
||||
|
||||
map_type y(values.begin(), values.end(), values.size(), hasher(2),
|
||||
key_equal(1), allocator_type(3));
|
||||
|
||||
BOOST_TEST(!y.empty());
|
||||
BOOST_TEST(x.get_allocator() == y.get_allocator());
|
||||
|
||||
y = std::move(x);
|
||||
if (y.hash_function() == hasher(1)) {
|
||||
++num_transfers;
|
||||
test_matches_reference(y, reference_map);
|
||||
|
||||
BOOST_TEST_EQ(y.key_eq(), key_equal(2));
|
||||
} else {
|
||||
BOOST_TEST_EQ(y.hash_function(), hasher(2));
|
||||
BOOST_TEST_EQ(y.key_eq(), key_equal(1));
|
||||
}
|
||||
|
||||
BOOST_TEST(x.empty());
|
||||
|
||||
BOOST_TEST_EQ(x.hash_function(), hasher(2));
|
||||
BOOST_TEST_EQ(x.key_eq(), key_equal(1));
|
||||
BOOST_TEST(x.get_allocator() == y.get_allocator());
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_transfers, 1u);
|
||||
|
||||
BOOST_TEST_EQ(
|
||||
raii::destructor, 2 * old_size + num_threads * 2 * old_size);
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_constructor, old_mc);
|
||||
BOOST_TEST_EQ(raii::copy_constructor,
|
||||
old_cc + (num_threads * 2 * reference_map.size()));
|
||||
}
|
||||
check_raii_counts();
|
||||
|
||||
// lhs non-empty, rhs non-empty, unequal allocators, no propagation
|
||||
{
|
||||
raii::reset_counts();
|
||||
|
||||
map_type x(values.begin(), values.end(), values.size(), hasher(1),
|
||||
key_equal(2), allocator_type(3));
|
||||
|
||||
auto const old_size = x.size();
|
||||
auto const old_cc = +raii::copy_constructor;
|
||||
auto const old_mc = +raii::move_constructor;
|
||||
|
||||
std::atomic<unsigned> num_transfers{0};
|
||||
|
||||
thread_runner(values, [&x, &values, &num_transfers, &reference_map](
|
||||
boost::span<map_value_type> s) {
|
||||
(void)s;
|
||||
|
||||
map_type y(values.begin(), values.end(), values.size(), hasher(2),
|
||||
key_equal(1), allocator_type(13));
|
||||
|
||||
BOOST_TEST(
|
||||
!boost::allocator_is_always_equal<allocator_type>::type::value);
|
||||
|
||||
BOOST_TEST(!boost::allocator_propagate_on_container_move_assignment<
|
||||
allocator_type>::type::value);
|
||||
|
||||
BOOST_TEST(!y.empty());
|
||||
BOOST_TEST(x.get_allocator() != y.get_allocator());
|
||||
|
||||
y = std::move(x);
|
||||
if (y.hash_function() == hasher(1)) {
|
||||
++num_transfers;
|
||||
test_matches_reference(y, reference_map);
|
||||
|
||||
BOOST_TEST_EQ(y.key_eq(), key_equal(2));
|
||||
} else {
|
||||
BOOST_TEST_EQ(y.hash_function(), hasher(2));
|
||||
BOOST_TEST_EQ(y.key_eq(), key_equal(1));
|
||||
}
|
||||
|
||||
BOOST_TEST(x.empty());
|
||||
|
||||
BOOST_TEST_EQ(x.hash_function(), hasher(2));
|
||||
BOOST_TEST_EQ(x.key_eq(), key_equal(1));
|
||||
BOOST_TEST(x.get_allocator() != y.get_allocator());
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_transfers, 1u);
|
||||
|
||||
BOOST_TEST_EQ(
|
||||
raii::destructor, 2 * 2 * old_size + num_threads * 2 * old_size);
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_constructor, old_mc + 2 * old_size);
|
||||
BOOST_TEST_EQ(raii::copy_constructor,
|
||||
old_cc + (num_threads * 2 * reference_map.size()));
|
||||
}
|
||||
check_raii_counts();
|
||||
|
||||
// lhs non-empty, rhs non-empty, pocma
|
||||
{
|
||||
raii::reset_counts();
|
||||
|
||||
pocma_map_type x(values.begin(), values.end(), values.size(), hasher(1),
|
||||
key_equal(2), pocma_allocator_type(3));
|
||||
|
||||
auto const old_size = x.size();
|
||||
auto const old_cc = +raii::copy_constructor;
|
||||
auto const old_mc = +raii::move_constructor;
|
||||
|
||||
std::atomic<unsigned> num_transfers{0};
|
||||
|
||||
thread_runner(values, [&x, &values, &num_transfers, &reference_map](
|
||||
boost::span<map_value_type> s) {
|
||||
(void)s;
|
||||
|
||||
pocma_map_type y(values.begin(), values.end(), values.size(), hasher(2),
|
||||
key_equal(1), pocma_allocator_type(13));
|
||||
|
||||
BOOST_TEST(!y.empty());
|
||||
BOOST_TEST(x.get_allocator() != y.get_allocator());
|
||||
|
||||
y = std::move(x);
|
||||
if (y.hash_function() == hasher(1)) {
|
||||
++num_transfers;
|
||||
test_matches_reference(y, reference_map);
|
||||
|
||||
BOOST_TEST_EQ(y.key_eq(), key_equal(2));
|
||||
} else {
|
||||
BOOST_TEST_EQ(y.hash_function(), hasher(2));
|
||||
BOOST_TEST_EQ(y.key_eq(), key_equal(1));
|
||||
}
|
||||
|
||||
BOOST_TEST(x.empty());
|
||||
|
||||
BOOST_TEST_EQ(x.hash_function(), hasher(2));
|
||||
BOOST_TEST_EQ(x.key_eq(), key_equal(1));
|
||||
BOOST_TEST(x.get_allocator() == y.get_allocator());
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_transfers, 1u);
|
||||
|
||||
BOOST_TEST_EQ(
|
||||
raii::destructor, 2 * old_size + num_threads * 2 * old_size);
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_constructor, old_mc);
|
||||
BOOST_TEST_EQ(raii::copy_constructor,
|
||||
old_cc + (num_threads * 2 * reference_map.size()));
|
||||
}
|
||||
check_raii_counts();
|
||||
|
||||
// self-assign
|
||||
{
|
||||
raii::reset_counts();
|
||||
|
||||
map_type x(values.begin(), values.end(), values.size(), hasher(1),
|
||||
key_equal(2), allocator_type(3));
|
||||
|
||||
auto const old_cc = +raii::copy_constructor;
|
||||
auto const old_mc = +raii::move_constructor;
|
||||
|
||||
thread_runner(
|
||||
values, [&x, &reference_map](boost::span<map_value_type> s) {
|
||||
(void)s;
|
||||
|
||||
x = std::move(x);
|
||||
|
||||
BOOST_TEST(!x.empty());
|
||||
|
||||
BOOST_TEST_EQ(x.hash_function(), hasher(1));
|
||||
BOOST_TEST_EQ(x.key_eq(), key_equal(2));
|
||||
BOOST_TEST(x.get_allocator() == allocator_type(3));
|
||||
|
||||
test_matches_reference(x, reference_map);
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(raii::destructor, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_constructor, old_mc);
|
||||
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
|
||||
}
|
||||
check_raii_counts();
|
||||
}
|
||||
|
||||
UNORDERED_AUTO_TEST (initializer_list_assignment) {
|
||||
std::initializer_list<map_value_type> values{
|
||||
map_value_type{raii{0}, raii{0}},
|
||||
map_value_type{raii{1}, raii{1}},
|
||||
map_value_type{raii{2}, raii{2}},
|
||||
map_value_type{raii{3}, raii{3}},
|
||||
map_value_type{raii{4}, raii{4}},
|
||||
map_value_type{raii{5}, raii{5}},
|
||||
map_value_type{raii{6}, raii{6}},
|
||||
map_value_type{raii{6}, raii{6}},
|
||||
map_value_type{raii{7}, raii{7}},
|
||||
map_value_type{raii{8}, raii{8}},
|
||||
map_value_type{raii{9}, raii{9}},
|
||||
map_value_type{raii{10}, raii{10}},
|
||||
map_value_type{raii{9}, raii{9}},
|
||||
map_value_type{raii{8}, raii{8}},
|
||||
map_value_type{raii{7}, raii{7}},
|
||||
map_value_type{raii{6}, raii{6}},
|
||||
map_value_type{raii{5}, raii{5}},
|
||||
map_value_type{raii{4}, raii{4}},
|
||||
map_value_type{raii{3}, raii{3}},
|
||||
map_value_type{raii{2}, raii{2}},
|
||||
map_value_type{raii{1}, raii{1}},
|
||||
map_value_type{raii{0}, raii{0}},
|
||||
};
|
||||
|
||||
auto reference_map =
|
||||
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
|
||||
auto v = std::vector<map_value_type>(values.begin(), values.end());
|
||||
|
||||
{
|
||||
raii::reset_counts();
|
||||
map_type x(0, hasher(1), key_equal(2), allocator_type(3));
|
||||
|
||||
thread_runner(v, [&x, &values](boost::span<map_value_type> s) {
|
||||
(void)s;
|
||||
x = values;
|
||||
});
|
||||
|
||||
test_matches_reference(x, reference_map);
|
||||
BOOST_TEST_EQ(x.hash_function(), hasher(1));
|
||||
BOOST_TEST_EQ(x.key_eq(), key_equal(2));
|
||||
BOOST_TEST(x.get_allocator() == allocator_type(3));
|
||||
|
||||
BOOST_TEST_EQ(raii::copy_constructor, num_threads * 2 * x.size());
|
||||
BOOST_TEST_EQ(raii::destructor, (num_threads - 1) * 2 * x.size());
|
||||
BOOST_TEST_EQ(raii::move_constructor, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
}
|
||||
check_raii_counts();
|
||||
}
|
||||
|
||||
template <class G> void insert_and_assign(G gen, test::random_generator rg)
|
||||
{
|
||||
|
||||
std::thread t1, t2, t3;
|
||||
|
||||
boost::latch start_latch(2), end_latch(2);
|
||||
|
||||
auto v1 = make_random_values(1024 * 16, [&] { return gen(rg); });
|
||||
auto v2 = v1;
|
||||
shuffle_values(v2);
|
||||
|
||||
auto reference_map =
|
||||
boost::unordered_flat_map<raii, raii>(v1.begin(), v1.end());
|
||||
|
||||
raii::reset_counts();
|
||||
{
|
||||
map_type map1(v1.size(), hasher(1), key_equal(2), allocator_type(3));
|
||||
map_type map2(v2.size(), hasher(1), key_equal(2), allocator_type(3));
|
||||
|
||||
t1 = std::thread([&v1, &map1, &start_latch, &end_latch] {
|
||||
start_latch.arrive_and_wait();
|
||||
for (auto const& v : v1) {
|
||||
map1.insert(v);
|
||||
}
|
||||
end_latch.arrive_and_wait();
|
||||
});
|
||||
|
||||
t2 = std::thread([&v2, &map2, &end_latch, &start_latch] {
|
||||
start_latch.arrive_and_wait();
|
||||
for (auto const& v : v2) {
|
||||
map2.insert(v);
|
||||
}
|
||||
end_latch.arrive_and_wait();
|
||||
});
|
||||
|
||||
std::atomic<unsigned> num_assignments{0};
|
||||
t3 = std::thread([&map1, &map2, &end_latch, &num_assignments] {
|
||||
while (map1.empty() && map2.empty()) {
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(10));
|
||||
}
|
||||
|
||||
do {
|
||||
map1 = map2;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
map2 = map1;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
++num_assignments;
|
||||
} while (!end_latch.try_wait());
|
||||
});
|
||||
|
||||
t1.join();
|
||||
t2.join();
|
||||
t3.join();
|
||||
|
||||
BOOST_TEST_GT(num_assignments, 0u);
|
||||
|
||||
test_fuzzy_matches_reference(map1, reference_map, rg);
|
||||
test_fuzzy_matches_reference(map2, reference_map, rg);
|
||||
}
|
||||
check_raii_counts();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// clang-format off
|
||||
@@ -142,6 +812,16 @@ UNORDERED_TEST(
|
||||
copy_assign,
|
||||
((value_type_generator))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
|
||||
UNORDERED_TEST(
|
||||
move_assign,
|
||||
((value_type_generator))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
|
||||
UNORDERED_TEST(
|
||||
insert_and_assign,
|
||||
((init_type_generator))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
// clang-format on
|
||||
|
||||
RUN_TESTS()
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
// Copyright (C) 2023 Christian Mazakas
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
#include <boost/unordered/concurrent_flat_map.hpp>
|
||||
|
||||
test::seed_t initialize_seed{674140082};
|
||||
|
||||
using test::default_generator;
|
||||
using test::limited_range;
|
||||
using test::sequential;
|
||||
|
||||
using hasher = stateful_hash;
|
||||
using key_equal = stateful_key_equal;
|
||||
using allocator_type = stateful_allocator<std::pair<raii const, raii> >;
|
||||
|
||||
using map_type = boost::unordered::concurrent_flat_map<raii, raii, hasher,
|
||||
key_equal, allocator_type>;
|
||||
|
||||
using map_value_type = typename map_type::value_type;
|
||||
|
||||
namespace {
|
||||
template <class G> void clear_tests(G gen, test::random_generator rg)
|
||||
{
|
||||
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
|
||||
|
||||
raii::reset_counts();
|
||||
|
||||
map_type x(values.begin(), values.end(), values.size(), hasher(1),
|
||||
key_equal(2), allocator_type(3));
|
||||
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 2 * x.size());
|
||||
BOOST_TEST_EQ(raii::destructor, 0u);
|
||||
|
||||
thread_runner(values, [&x](boost::span<map_value_type> s) {
|
||||
(void)s;
|
||||
x.clear();
|
||||
});
|
||||
|
||||
BOOST_TEST(x.empty());
|
||||
|
||||
check_raii_counts();
|
||||
}
|
||||
|
||||
template <class G> void insert_and_clear(G gen, test::random_generator rg)
|
||||
{
|
||||
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
|
||||
auto reference_map =
|
||||
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
|
||||
|
||||
raii::reset_counts();
|
||||
|
||||
std::thread t1, t2;
|
||||
|
||||
{
|
||||
map_type x(0, hasher(1), key_equal(2), allocator_type(3));
|
||||
|
||||
std::mutex m;
|
||||
std::condition_variable cv;
|
||||
std::atomic<bool> done{false};
|
||||
std::atomic<unsigned> num_clears{0};
|
||||
|
||||
bool ready = false;
|
||||
|
||||
t1 = std::thread([&x, &values, &cv, &done, &m, &ready] {
|
||||
for (auto i = 0u; i < values.size(); ++i) {
|
||||
x.insert(values[i]);
|
||||
if (i % (values.size() / 128) == 0) {
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m);
|
||||
ready = true;
|
||||
}
|
||||
cv.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
done = true;
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m);
|
||||
ready = true;
|
||||
}
|
||||
cv.notify_all();
|
||||
});
|
||||
|
||||
t2 = std::thread([&x, &m, &cv, &done, &ready, &num_clears] {
|
||||
do {
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m);
|
||||
cv.wait(lk, [&ready] { return ready; });
|
||||
ready = false;
|
||||
}
|
||||
x.clear();
|
||||
++num_clears;
|
||||
} while (!done);
|
||||
});
|
||||
|
||||
t1.join();
|
||||
t2.join();
|
||||
|
||||
BOOST_TEST_GE(num_clears, 1u);
|
||||
|
||||
if (!x.empty()) {
|
||||
test_fuzzy_matches_reference(x, reference_map, rg);
|
||||
}
|
||||
}
|
||||
|
||||
check_raii_counts();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// clang-format off
|
||||
UNORDERED_TEST(
|
||||
clear_tests,
|
||||
((value_type_generator))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
|
||||
UNORDERED_TEST(insert_and_clear,
|
||||
((value_type_generator))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
// clang-format on
|
||||
|
||||
RUN_TESTS()
|
||||
@@ -0,0 +1,142 @@
|
||||
// Copyright (C) 2023 Christian Mazakas
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
#include <boost/unordered/concurrent_flat_map.hpp>
|
||||
|
||||
test::seed_t initialize_seed{1634048962};
|
||||
|
||||
using test::default_generator;
|
||||
using test::limited_range;
|
||||
using test::sequential;
|
||||
|
||||
using hasher = stateful_hash;
|
||||
using key_equal = stateful_key_equal;
|
||||
using allocator_type = stateful_allocator<std::pair<raii const, raii> >;
|
||||
|
||||
using map_type = boost::unordered::concurrent_flat_map<raii, raii, hasher,
|
||||
key_equal, allocator_type>;
|
||||
|
||||
using map_value_type = typename map_type::value_type;
|
||||
|
||||
namespace {
|
||||
|
||||
UNORDERED_AUTO_TEST (simple_equality) {
|
||||
{
|
||||
map_type x1(
|
||||
{{1, 11}, {2, 22}}, 0, hasher(1), key_equal(2), allocator_type(3));
|
||||
|
||||
map_type x2(
|
||||
{{1, 11}, {2, 22}}, 0, hasher(2), key_equal(2), allocator_type(3));
|
||||
|
||||
map_type x3(
|
||||
{{1, 11}, {2, 23}}, 0, hasher(2), key_equal(2), allocator_type(3));
|
||||
|
||||
map_type x4({{1, 11}}, 0, hasher(2), key_equal(2), allocator_type(3));
|
||||
|
||||
BOOST_TEST_EQ(x1.size(), x2.size());
|
||||
BOOST_TEST(x1 == x2);
|
||||
BOOST_TEST(!(x1 != x2));
|
||||
|
||||
BOOST_TEST_EQ(x1.size(), x3.size());
|
||||
BOOST_TEST(!(x1 == x3));
|
||||
BOOST_TEST(x1 != x3);
|
||||
|
||||
BOOST_TEST(x1.size() != x4.size());
|
||||
BOOST_TEST(!(x1 == x4));
|
||||
BOOST_TEST(x1 != x4);
|
||||
}
|
||||
}
|
||||
|
||||
template <class G> void insert_and_compare(G gen, test::random_generator rg)
|
||||
{
|
||||
auto vals1 = make_random_values(1024 * 8, [&] { return gen(rg); });
|
||||
boost::unordered_flat_map<raii, raii> reference_map(
|
||||
vals1.begin(), vals1.end());
|
||||
|
||||
{
|
||||
raii::reset_counts();
|
||||
|
||||
map_type x1(vals1.size(), hasher(1), key_equal(2), allocator_type(3));
|
||||
map_type x2(vals1.begin(), vals1.end(), vals1.size(), hasher(2),
|
||||
key_equal(2), allocator_type(3));
|
||||
|
||||
std::thread t1, t2;
|
||||
|
||||
std::mutex m;
|
||||
std::condition_variable cv;
|
||||
std::atomic_bool done{false};
|
||||
std::atomic<unsigned> num_compares{0};
|
||||
bool ready = false;
|
||||
|
||||
BOOST_TEST(x1.empty());
|
||||
|
||||
t1 = std::thread([&x1, &m, &cv, &vals1, &done, &ready] {
|
||||
for (std::size_t idx = 0; idx < vals1.size(); ++idx) {
|
||||
auto const& v = vals1[idx];
|
||||
x1.insert(v);
|
||||
|
||||
if (idx % (vals1.size() / 128) == 0) {
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m);
|
||||
ready = true;
|
||||
}
|
||||
cv.notify_all();
|
||||
}
|
||||
std::this_thread::yield();
|
||||
}
|
||||
|
||||
done = true;
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m);
|
||||
ready = true;
|
||||
}
|
||||
cv.notify_all();
|
||||
});
|
||||
|
||||
t2 = std::thread([&x1, &x2, &m, &cv, &done, &num_compares, &ready] {
|
||||
do {
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m);
|
||||
cv.wait(lk, [&ready] { return ready; });
|
||||
ready = false;
|
||||
}
|
||||
|
||||
volatile bool b = false;
|
||||
|
||||
b = x1 == x2;
|
||||
b = x1 != x2;
|
||||
|
||||
b;
|
||||
|
||||
++num_compares;
|
||||
std::this_thread::yield();
|
||||
} while (!done);
|
||||
|
||||
BOOST_TEST(done);
|
||||
});
|
||||
|
||||
t1.join();
|
||||
t2.join();
|
||||
|
||||
BOOST_TEST_GE(num_compares, 1u);
|
||||
|
||||
BOOST_TEST(x1 == x2);
|
||||
BOOST_TEST(!(x1 != x2));
|
||||
|
||||
test_matches_reference(x1, reference_map);
|
||||
}
|
||||
check_raii_counts();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// clang-format off
|
||||
UNORDERED_TEST(
|
||||
insert_and_compare,
|
||||
((value_type_generator))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
// clang-format on
|
||||
|
||||
RUN_TESTS()
|
||||
@@ -225,14 +225,14 @@ namespace {
|
||||
}
|
||||
});
|
||||
|
||||
thread_runner(values, [&num_erased, &x, threshold](boost::span<T> s) {
|
||||
for (auto const& k : s) {
|
||||
(void)k;
|
||||
auto count = x.erase_if(
|
||||
[threshold](value_type& v) { return v.second.x_ > threshold; });
|
||||
num_erased += count;
|
||||
}
|
||||
});
|
||||
thread_runner(
|
||||
values, [&num_erased, &x, threshold](boost::span<T> /* s */) {
|
||||
for (std::size_t i = 0; i < 128; ++i) {
|
||||
auto count = x.erase_if(
|
||||
[threshold](value_type& v) { return v.second.x_ > threshold; });
|
||||
num_erased += count;
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_erased, expected_erasures);
|
||||
BOOST_TEST_EQ(x.size(), old_size - num_erased);
|
||||
@@ -245,6 +245,58 @@ namespace {
|
||||
}
|
||||
} erase_if;
|
||||
|
||||
struct free_fn_erase_if_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
using value_type = typename X::value_type;
|
||||
|
||||
std::atomic<std::uint64_t> num_erased{0};
|
||||
|
||||
auto const old_size = x.size();
|
||||
|
||||
auto const old_dc = +raii::default_constructor;
|
||||
auto const old_cc = +raii::copy_constructor;
|
||||
auto const old_mc = +raii::move_constructor;
|
||||
|
||||
auto const old_d = +raii::destructor;
|
||||
|
||||
auto max = 0;
|
||||
x.visit_all([&max](value_type const& v) {
|
||||
if (v.second.x_ > max) {
|
||||
max = v.second.x_;
|
||||
}
|
||||
});
|
||||
|
||||
auto threshold = max / 2;
|
||||
|
||||
auto expected_erasures = 0u;
|
||||
x.visit_all([&expected_erasures, threshold](value_type const& v) {
|
||||
if (v.second.x_ > threshold) {
|
||||
++expected_erasures;
|
||||
}
|
||||
});
|
||||
|
||||
thread_runner(
|
||||
values, [&num_erased, &x, threshold](boost::span<T> /* s */) {
|
||||
for (std::size_t i = 0; i < 128; ++i) {
|
||||
auto count = boost::unordered::erase_if(x,
|
||||
[threshold](value_type& v) { return v.second.x_ > threshold; });
|
||||
num_erased += count;
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(num_erased, expected_erasures);
|
||||
BOOST_TEST_EQ(x.size(), old_size - num_erased);
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, old_dc);
|
||||
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
|
||||
BOOST_TEST_EQ(raii::move_constructor, old_mc);
|
||||
|
||||
BOOST_TEST_EQ(raii::destructor, old_d + 2 * num_erased);
|
||||
}
|
||||
} free_fn_erase_if;
|
||||
|
||||
struct erase_if_exec_policy_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
@@ -281,7 +333,7 @@ namespace {
|
||||
thread_runner(values, [&num_invokes, &x, threshold](boost::span<T> s) {
|
||||
(void)s;
|
||||
x.erase_if(
|
||||
std::execution::par_unseq, [&num_invokes, threshold](value_type& v) {
|
||||
std::execution::par, [&num_invokes, threshold](value_type& v) {
|
||||
++num_invokes;
|
||||
return v.second.x_ > threshold;
|
||||
});
|
||||
@@ -317,25 +369,13 @@ namespace {
|
||||
|
||||
BOOST_TEST_EQ(x.size(), reference_map.size());
|
||||
|
||||
using value_type = typename X::value_type;
|
||||
BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) {
|
||||
BOOST_TEST(reference_map.contains(kv.first));
|
||||
if (rg == test::sequential) {
|
||||
BOOST_TEST_EQ(kv.second, reference_map[kv.first]);
|
||||
}
|
||||
}));
|
||||
test_fuzzy_matches_reference(x, reference_map, rg);
|
||||
|
||||
eraser(values, x);
|
||||
test_fuzzy_matches_reference(x, reference_map, rg);
|
||||
}
|
||||
|
||||
BOOST_TEST_GE(raii::default_constructor, 0u);
|
||||
BOOST_TEST_GE(raii::copy_constructor, 0u);
|
||||
BOOST_TEST_GE(raii::move_constructor, 0u);
|
||||
BOOST_TEST_GT(raii::destructor, 0u);
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor +
|
||||
raii::move_constructor,
|
||||
raii::destructor);
|
||||
check_raii_counts();
|
||||
}
|
||||
|
||||
boost::unordered::concurrent_flat_map<raii, raii>* map;
|
||||
@@ -353,7 +393,7 @@ UNORDERED_TEST(
|
||||
erase,
|
||||
((map))
|
||||
((value_type_generator)(init_type_generator))
|
||||
((lvalue_eraser)(lvalue_eraser_if)(erase_if)(erase_if_exec_policy))
|
||||
((lvalue_eraser)(lvalue_eraser_if)(erase_if)(free_fn_erase_if)(erase_if_exec_policy))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
|
||||
UNORDERED_TEST(
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
// Copyright (C) 2023 Christian Mazakas
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#include "exception_helpers.hpp"
|
||||
|
||||
#include <boost/unordered/concurrent_flat_map.hpp>
|
||||
|
||||
using allocator_type = stateful_allocator<std::pair<raii const, raii> >;
|
||||
|
||||
using hasher = stateful_hash;
|
||||
using key_equal = stateful_key_equal;
|
||||
|
||||
using map_type = boost::unordered::concurrent_flat_map<raii, raii, hasher,
|
||||
key_equal, allocator_type>;
|
||||
|
||||
namespace {
|
||||
test::seed_t initialize_seed(1794114520);
|
||||
|
||||
template <class G> void copy_assign(G gen, test::random_generator rg)
|
||||
{
|
||||
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
|
||||
|
||||
{
|
||||
raii::reset_counts();
|
||||
|
||||
unsigned num_throws = 0;
|
||||
|
||||
auto begin = values.begin();
|
||||
auto mid =
|
||||
values.begin() + static_cast<std::ptrdiff_t>(values.size() / 2);
|
||||
auto end = values.end();
|
||||
|
||||
auto reference_map = boost::unordered_flat_map<raii, raii>(begin, mid);
|
||||
|
||||
map_type x(
|
||||
begin, mid, values.size(), hasher(1), key_equal(2), allocator_type(3));
|
||||
|
||||
map_type y(
|
||||
mid, end, values.size(), hasher(2), key_equal(1), allocator_type(4));
|
||||
|
||||
BOOST_TEST(!y.empty());
|
||||
|
||||
enable_exceptions();
|
||||
for (std::size_t i = 0; i < 2 * alloc_throw_threshold; ++i) {
|
||||
try {
|
||||
y = x;
|
||||
} catch (...) {
|
||||
++num_throws;
|
||||
}
|
||||
}
|
||||
|
||||
disable_exceptions();
|
||||
|
||||
BOOST_TEST_GT(num_throws, 0u);
|
||||
test_fuzzy_matches_reference(y, reference_map, rg);
|
||||
}
|
||||
check_raii_counts();
|
||||
}
|
||||
|
||||
template <class G> void move_assign(G gen, test::random_generator rg)
|
||||
{
|
||||
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
|
||||
|
||||
{
|
||||
raii::reset_counts();
|
||||
|
||||
unsigned num_throws = 0;
|
||||
|
||||
auto begin = values.begin();
|
||||
auto mid =
|
||||
values.begin() + static_cast<std::ptrdiff_t>(values.size() / 2);
|
||||
auto end = values.end();
|
||||
|
||||
auto reference_map = boost::unordered_flat_map<raii, raii>(begin, mid);
|
||||
|
||||
BOOST_TEST(
|
||||
!boost::allocator_is_always_equal<allocator_type>::type::value);
|
||||
|
||||
BOOST_TEST(!boost::allocator_propagate_on_container_move_assignment<
|
||||
allocator_type>::type::value);
|
||||
|
||||
for (std::size_t i = 0; i < 2 * alloc_throw_threshold; ++i) {
|
||||
disable_exceptions();
|
||||
|
||||
map_type x(begin, mid, values.size(), hasher(1), key_equal(2),
|
||||
allocator_type(3));
|
||||
|
||||
map_type y(
|
||||
mid, end, values.size(), hasher(2), key_equal(1), allocator_type(4));
|
||||
|
||||
enable_exceptions();
|
||||
try {
|
||||
y = std::move(x);
|
||||
} catch (...) {
|
||||
++num_throws;
|
||||
}
|
||||
disable_exceptions();
|
||||
test_fuzzy_matches_reference(y, reference_map, rg);
|
||||
}
|
||||
|
||||
BOOST_TEST_GT(num_throws, 0u);
|
||||
}
|
||||
check_raii_counts();
|
||||
}
|
||||
|
||||
UNORDERED_AUTO_TEST (intializer_list_assign) {
|
||||
using value_type = typename map_type::value_type;
|
||||
|
||||
std::initializer_list<value_type> values{
|
||||
value_type{raii{0}, raii{0}},
|
||||
value_type{raii{1}, raii{1}},
|
||||
value_type{raii{2}, raii{2}},
|
||||
value_type{raii{3}, raii{3}},
|
||||
value_type{raii{4}, raii{4}},
|
||||
value_type{raii{5}, raii{5}},
|
||||
value_type{raii{6}, raii{6}},
|
||||
value_type{raii{6}, raii{6}},
|
||||
value_type{raii{7}, raii{7}},
|
||||
value_type{raii{8}, raii{8}},
|
||||
value_type{raii{9}, raii{9}},
|
||||
value_type{raii{10}, raii{10}},
|
||||
value_type{raii{9}, raii{9}},
|
||||
value_type{raii{8}, raii{8}},
|
||||
value_type{raii{7}, raii{7}},
|
||||
value_type{raii{6}, raii{6}},
|
||||
value_type{raii{5}, raii{5}},
|
||||
value_type{raii{4}, raii{4}},
|
||||
value_type{raii{3}, raii{3}},
|
||||
value_type{raii{2}, raii{2}},
|
||||
value_type{raii{1}, raii{1}},
|
||||
value_type{raii{0}, raii{0}},
|
||||
};
|
||||
|
||||
{
|
||||
raii::reset_counts();
|
||||
unsigned num_throws = 0;
|
||||
|
||||
for (std::size_t i = 0; i < throw_threshold; ++i) {
|
||||
map_type x(0, hasher(1), key_equal(2), allocator_type(3));
|
||||
enable_exceptions();
|
||||
try {
|
||||
x = values;
|
||||
} catch (...) {
|
||||
++num_throws;
|
||||
}
|
||||
disable_exceptions();
|
||||
}
|
||||
|
||||
BOOST_TEST_GT(num_throws, 0u);
|
||||
check_raii_counts();
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
using test::default_generator;
|
||||
using test::limited_range;
|
||||
using test::sequential;
|
||||
|
||||
// clang-format off
|
||||
UNORDERED_TEST(
|
||||
copy_assign,
|
||||
((exception_value_type_generator))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
|
||||
UNORDERED_TEST(
|
||||
move_assign,
|
||||
((exception_value_type_generator))
|
||||
((default_generator)(sequential)))
|
||||
// clang-format on
|
||||
|
||||
RUN_TESTS()
|
||||
@@ -0,0 +1,304 @@
|
||||
// Copyright (C) 2023 Christian Mazakas
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#include "exception_helpers.hpp"
|
||||
|
||||
#include <boost/unordered/concurrent_flat_map.hpp>
|
||||
|
||||
using allocator_type = stateful_allocator<std::pair<raii const, raii> >;
|
||||
|
||||
using hasher = stateful_hash;
|
||||
using key_equal = stateful_key_equal;
|
||||
|
||||
using map_type = boost::unordered::concurrent_flat_map<raii, raii, hasher,
|
||||
key_equal, allocator_type>;
|
||||
|
||||
namespace {
|
||||
test::seed_t initialize_seed(795610904);
|
||||
|
||||
UNORDERED_AUTO_TEST (bucket_constructor) {
|
||||
raii::reset_counts();
|
||||
|
||||
bool was_thrown = false;
|
||||
|
||||
enable_exceptions();
|
||||
for (std::size_t i = 0; i < alloc_throw_threshold; ++i) {
|
||||
try {
|
||||
map_type m(128);
|
||||
} catch (...) {
|
||||
was_thrown = true;
|
||||
}
|
||||
}
|
||||
disable_exceptions();
|
||||
|
||||
BOOST_TEST(was_thrown);
|
||||
}
|
||||
|
||||
template <class G> void iterator_range(G gen, test::random_generator rg)
|
||||
{
|
||||
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
|
||||
|
||||
{
|
||||
raii::reset_counts();
|
||||
|
||||
bool was_thrown = false;
|
||||
|
||||
enable_exceptions();
|
||||
try {
|
||||
map_type x(values.begin(), values.end(), 0, hasher(1), key_equal(2),
|
||||
allocator_type(3));
|
||||
} catch (...) {
|
||||
was_thrown = true;
|
||||
}
|
||||
disable_exceptions();
|
||||
|
||||
BOOST_TEST(was_thrown);
|
||||
check_raii_counts();
|
||||
}
|
||||
|
||||
{
|
||||
raii::reset_counts();
|
||||
|
||||
bool was_thrown = false;
|
||||
|
||||
enable_exceptions();
|
||||
try {
|
||||
map_type x(values.begin(), values.end(), allocator_type(3));
|
||||
} catch (...) {
|
||||
was_thrown = true;
|
||||
}
|
||||
disable_exceptions();
|
||||
|
||||
BOOST_TEST(was_thrown);
|
||||
check_raii_counts();
|
||||
}
|
||||
|
||||
{
|
||||
raii::reset_counts();
|
||||
|
||||
bool was_thrown = false;
|
||||
|
||||
enable_exceptions();
|
||||
try {
|
||||
map_type x(
|
||||
values.begin(), values.end(), values.size(), allocator_type(3));
|
||||
} catch (...) {
|
||||
was_thrown = true;
|
||||
}
|
||||
disable_exceptions();
|
||||
|
||||
BOOST_TEST(was_thrown);
|
||||
check_raii_counts();
|
||||
}
|
||||
|
||||
{
|
||||
raii::reset_counts();
|
||||
|
||||
bool was_thrown = false;
|
||||
|
||||
enable_exceptions();
|
||||
try {
|
||||
map_type x(values.begin(), values.end(), values.size(), hasher(1),
|
||||
allocator_type(3));
|
||||
} catch (...) {
|
||||
was_thrown = true;
|
||||
}
|
||||
disable_exceptions();
|
||||
|
||||
BOOST_TEST(was_thrown);
|
||||
check_raii_counts();
|
||||
}
|
||||
}
|
||||
|
||||
template <class G> void copy_constructor(G gen, test::random_generator rg)
|
||||
{
|
||||
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
|
||||
|
||||
{
|
||||
raii::reset_counts();
|
||||
|
||||
bool was_thrown = false;
|
||||
|
||||
try {
|
||||
map_type x(values.begin(), values.end(), 0);
|
||||
|
||||
enable_exceptions();
|
||||
map_type y(x);
|
||||
} catch (...) {
|
||||
was_thrown = true;
|
||||
}
|
||||
disable_exceptions();
|
||||
|
||||
BOOST_TEST(was_thrown);
|
||||
check_raii_counts();
|
||||
}
|
||||
|
||||
{
|
||||
raii::reset_counts();
|
||||
|
||||
bool was_thrown = false;
|
||||
|
||||
try {
|
||||
map_type x(values.begin(), values.end(), 0);
|
||||
|
||||
enable_exceptions();
|
||||
map_type y(x, allocator_type(4));
|
||||
} catch (...) {
|
||||
was_thrown = true;
|
||||
}
|
||||
disable_exceptions();
|
||||
|
||||
BOOST_TEST(was_thrown);
|
||||
check_raii_counts();
|
||||
}
|
||||
}
|
||||
|
||||
template <class G> void move_constructor(G gen, test::random_generator rg)
|
||||
{
|
||||
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
|
||||
|
||||
{
|
||||
raii::reset_counts();
|
||||
|
||||
bool was_thrown = false;
|
||||
|
||||
try {
|
||||
map_type x(values.begin(), values.end(), 0);
|
||||
|
||||
enable_exceptions();
|
||||
map_type y(std::move(x), allocator_type(4));
|
||||
} catch (...) {
|
||||
was_thrown = true;
|
||||
}
|
||||
disable_exceptions();
|
||||
|
||||
BOOST_TEST(was_thrown);
|
||||
check_raii_counts();
|
||||
}
|
||||
}
|
||||
|
||||
UNORDERED_AUTO_TEST (initializer_list_bucket_count) {
|
||||
using value_type = typename map_type::value_type;
|
||||
|
||||
std::initializer_list<value_type> values{
|
||||
value_type{raii{0}, raii{0}},
|
||||
value_type{raii{1}, raii{1}},
|
||||
value_type{raii{2}, raii{2}},
|
||||
value_type{raii{3}, raii{3}},
|
||||
value_type{raii{4}, raii{4}},
|
||||
value_type{raii{5}, raii{5}},
|
||||
value_type{raii{6}, raii{6}},
|
||||
value_type{raii{6}, raii{6}},
|
||||
value_type{raii{7}, raii{7}},
|
||||
value_type{raii{8}, raii{8}},
|
||||
value_type{raii{9}, raii{9}},
|
||||
value_type{raii{10}, raii{10}},
|
||||
value_type{raii{9}, raii{9}},
|
||||
value_type{raii{8}, raii{8}},
|
||||
value_type{raii{7}, raii{7}},
|
||||
value_type{raii{6}, raii{6}},
|
||||
value_type{raii{5}, raii{5}},
|
||||
value_type{raii{4}, raii{4}},
|
||||
value_type{raii{3}, raii{3}},
|
||||
value_type{raii{2}, raii{2}},
|
||||
value_type{raii{1}, raii{1}},
|
||||
value_type{raii{0}, raii{0}},
|
||||
};
|
||||
|
||||
{
|
||||
raii::reset_counts();
|
||||
unsigned num_throws = 0;
|
||||
|
||||
enable_exceptions();
|
||||
for (std::size_t i = 0; i < throw_threshold; ++i) {
|
||||
try {
|
||||
map_type x(values, 0, hasher(1), key_equal(2), allocator_type(3));
|
||||
} catch (...) {
|
||||
++num_throws;
|
||||
}
|
||||
}
|
||||
disable_exceptions();
|
||||
|
||||
BOOST_TEST_GT(num_throws, 0u);
|
||||
check_raii_counts();
|
||||
}
|
||||
|
||||
{
|
||||
raii::reset_counts();
|
||||
unsigned num_throws = 0;
|
||||
|
||||
enable_exceptions();
|
||||
for (std::size_t i = 0; i < alloc_throw_threshold * 2; ++i) {
|
||||
try {
|
||||
map_type x(values, allocator_type(3));
|
||||
} catch (...) {
|
||||
++num_throws;
|
||||
}
|
||||
}
|
||||
disable_exceptions();
|
||||
|
||||
BOOST_TEST_GT(num_throws, 0u);
|
||||
check_raii_counts();
|
||||
}
|
||||
|
||||
{
|
||||
raii::reset_counts();
|
||||
unsigned num_throws = 0;
|
||||
|
||||
enable_exceptions();
|
||||
for (std::size_t i = 0; i < alloc_throw_threshold * 2; ++i) {
|
||||
try {
|
||||
map_type x(values, values.size() * 2, allocator_type(3));
|
||||
} catch (...) {
|
||||
++num_throws;
|
||||
}
|
||||
}
|
||||
disable_exceptions();
|
||||
|
||||
BOOST_TEST_GT(num_throws, 0u);
|
||||
check_raii_counts();
|
||||
}
|
||||
|
||||
{
|
||||
raii::reset_counts();
|
||||
unsigned num_throws = 0;
|
||||
|
||||
enable_exceptions();
|
||||
for (std::size_t i = 0; i < throw_threshold; ++i) {
|
||||
try {
|
||||
map_type x(values, values.size() * 2, hasher(1), allocator_type(3));
|
||||
} catch (...) {
|
||||
++num_throws;
|
||||
}
|
||||
}
|
||||
disable_exceptions();
|
||||
|
||||
BOOST_TEST_GT(num_throws, 0u);
|
||||
check_raii_counts();
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
using test::default_generator;
|
||||
using test::limited_range;
|
||||
using test::sequential;
|
||||
|
||||
// clang-format off
|
||||
UNORDERED_TEST(
|
||||
iterator_range,
|
||||
((exception_value_type_generator))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
|
||||
UNORDERED_TEST(
|
||||
copy_constructor,
|
||||
((exception_value_type_generator))
|
||||
((default_generator)(sequential)))
|
||||
|
||||
UNORDERED_TEST(
|
||||
move_constructor,
|
||||
((exception_value_type_generator))
|
||||
((default_generator)(sequential)))
|
||||
// clang-format on
|
||||
|
||||
RUN_TESTS()
|
||||
@@ -0,0 +1,263 @@
|
||||
// Copyright (C) 2023 Christian Mazakas
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#include "exception_helpers.hpp"
|
||||
|
||||
#include <boost/unordered/concurrent_flat_map.hpp>
|
||||
|
||||
#include <boost/core/ignore_unused.hpp>
|
||||
|
||||
namespace {
|
||||
test::seed_t initialize_seed(3202923);
|
||||
|
||||
struct lvalue_eraser_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
std::atomic<std::uint64_t> num_erased{0};
|
||||
|
||||
auto const old_size = x.size();
|
||||
|
||||
auto const old_dc = +raii::default_constructor;
|
||||
auto const old_cc = +raii::copy_constructor;
|
||||
auto const old_mc = +raii::move_constructor;
|
||||
|
||||
auto const old_d = +raii::destructor;
|
||||
|
||||
enable_exceptions();
|
||||
thread_runner(values, [&values, &num_erased, &x](boost::span<T>) {
|
||||
for (auto const& k : values) {
|
||||
try {
|
||||
auto count = x.erase(k.first);
|
||||
BOOST_TEST_LE(count, 1u);
|
||||
BOOST_TEST_GE(count, 0u);
|
||||
|
||||
num_erased += count;
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
});
|
||||
disable_exceptions();
|
||||
|
||||
BOOST_TEST_EQ(x.size(), old_size - num_erased);
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, old_dc);
|
||||
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
|
||||
BOOST_TEST_EQ(raii::move_constructor, old_mc);
|
||||
|
||||
BOOST_TEST_EQ(raii::destructor, old_d + 2 * num_erased);
|
||||
}
|
||||
} lvalue_eraser;
|
||||
|
||||
struct lvalue_eraser_if_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
using value_type = typename X::value_type;
|
||||
|
||||
std::atomic<std::uint64_t> num_erased{0};
|
||||
|
||||
auto const old_size = x.size();
|
||||
|
||||
auto const old_dc = +raii::default_constructor;
|
||||
auto const old_cc = +raii::copy_constructor;
|
||||
auto const old_mc = +raii::move_constructor;
|
||||
|
||||
auto const old_d = +raii::destructor;
|
||||
|
||||
auto max = 0;
|
||||
x.visit_all([&max](value_type const& v) {
|
||||
if (v.second.x_ > max) {
|
||||
max = v.second.x_;
|
||||
}
|
||||
});
|
||||
|
||||
auto threshold = max / 2;
|
||||
|
||||
auto expected_erasures = 0u;
|
||||
x.visit_all([&expected_erasures, threshold](value_type const& v) {
|
||||
if (v.second.x_ > threshold) {
|
||||
++expected_erasures;
|
||||
}
|
||||
});
|
||||
|
||||
enable_exceptions();
|
||||
thread_runner(values, [&num_erased, &x, threshold](boost::span<T> s) {
|
||||
for (auto const& k : s) {
|
||||
try {
|
||||
auto count = x.erase_if(k.first,
|
||||
[threshold](value_type& v) { return v.second.x_ > threshold; });
|
||||
num_erased += count;
|
||||
BOOST_TEST_LE(count, 1u);
|
||||
BOOST_TEST_GE(count, 0u);
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
});
|
||||
disable_exceptions();
|
||||
|
||||
BOOST_TEST_LE(num_erased, expected_erasures);
|
||||
BOOST_TEST_EQ(x.size(), old_size - num_erased);
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, old_dc);
|
||||
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
|
||||
BOOST_TEST_EQ(raii::move_constructor, old_mc);
|
||||
|
||||
BOOST_TEST_EQ(raii::destructor, old_d + 2 * num_erased);
|
||||
}
|
||||
} lvalue_eraser_if;
|
||||
|
||||
struct erase_if_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
using value_type = typename X::value_type;
|
||||
|
||||
auto const old_size = x.size();
|
||||
|
||||
auto const old_dc = +raii::default_constructor;
|
||||
auto const old_cc = +raii::copy_constructor;
|
||||
auto const old_mc = +raii::move_constructor;
|
||||
|
||||
auto const old_d = +raii::destructor;
|
||||
|
||||
auto max = 0;
|
||||
x.visit_all([&max](value_type const& v) {
|
||||
if (v.second.x_ > max) {
|
||||
max = v.second.x_;
|
||||
}
|
||||
});
|
||||
|
||||
auto threshold = max / 2;
|
||||
|
||||
auto expected_erasures = 0u;
|
||||
x.visit_all([&expected_erasures, threshold](value_type const& v) {
|
||||
if (v.second.x_ > threshold) {
|
||||
++expected_erasures;
|
||||
}
|
||||
});
|
||||
|
||||
enable_exceptions();
|
||||
thread_runner(values, [&x, threshold](boost::span<T> /* s */) {
|
||||
for (std::size_t i = 0; i < 256; ++i) {
|
||||
try {
|
||||
x.erase_if([threshold](value_type& v) {
|
||||
static std::atomic<std::uint32_t> c{0};
|
||||
auto t = ++c;
|
||||
if (should_throw && (t % throw_threshold == 0)) {
|
||||
throw exception_tag{};
|
||||
}
|
||||
|
||||
return v.second.x_ > threshold;
|
||||
});
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
});
|
||||
disable_exceptions();
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, old_dc);
|
||||
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
|
||||
BOOST_TEST_EQ(raii::move_constructor, old_mc);
|
||||
|
||||
BOOST_TEST_EQ(raii::destructor, old_d + 2 * (old_size - x.size()));
|
||||
}
|
||||
} erase_if;
|
||||
|
||||
struct free_fn_erase_if_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
using value_type = typename X::value_type;
|
||||
|
||||
auto const old_size = x.size();
|
||||
|
||||
auto const old_dc = +raii::default_constructor;
|
||||
auto const old_cc = +raii::copy_constructor;
|
||||
auto const old_mc = +raii::move_constructor;
|
||||
|
||||
auto const old_d = +raii::destructor;
|
||||
|
||||
auto max = 0;
|
||||
x.visit_all([&max](value_type const& v) {
|
||||
if (v.second.x_ > max) {
|
||||
max = v.second.x_;
|
||||
}
|
||||
});
|
||||
|
||||
auto threshold = max / 2;
|
||||
|
||||
enable_exceptions();
|
||||
thread_runner(values, [&x, threshold](boost::span<T> /* s */) {
|
||||
for (std::size_t i = 0; i < 256; ++i) {
|
||||
try {
|
||||
boost::unordered::erase_if(x, [threshold](value_type& v) {
|
||||
static std::atomic<std::uint32_t> c{0};
|
||||
auto t = ++c;
|
||||
if (should_throw && (t % throw_threshold == 0)) {
|
||||
throw exception_tag{};
|
||||
}
|
||||
|
||||
return v.second.x_ > threshold;
|
||||
});
|
||||
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
});
|
||||
disable_exceptions();
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, old_dc);
|
||||
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
|
||||
BOOST_TEST_EQ(raii::move_constructor, old_mc);
|
||||
|
||||
BOOST_TEST_EQ(raii::destructor, old_d + 2 * (old_size - x.size()));
|
||||
}
|
||||
} free_fn_erase_if;
|
||||
|
||||
template <class X, class G, class F>
|
||||
void erase(X*, G gen, F eraser, test::random_generator rg)
|
||||
{
|
||||
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
|
||||
auto reference_map =
|
||||
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
|
||||
|
||||
raii::reset_counts();
|
||||
|
||||
{
|
||||
X x(values.size());
|
||||
x.insert(values.begin(), values.end());
|
||||
|
||||
BOOST_TEST_EQ(x.size(), reference_map.size());
|
||||
BOOST_TEST_EQ(raii::destructor, 0u);
|
||||
|
||||
test_fuzzy_matches_reference(x, reference_map, rg);
|
||||
|
||||
eraser(values, x);
|
||||
test_fuzzy_matches_reference(x, reference_map, rg);
|
||||
}
|
||||
|
||||
check_raii_counts();
|
||||
}
|
||||
|
||||
boost::unordered::concurrent_flat_map<raii, raii, stateful_hash,
|
||||
stateful_key_equal, stateful_allocator<std::pair<raii const, raii> > >* map;
|
||||
|
||||
} // namespace
|
||||
|
||||
using test::default_generator;
|
||||
using test::limited_range;
|
||||
using test::sequential;
|
||||
|
||||
// clang-format off
|
||||
UNORDERED_TEST(
|
||||
erase,
|
||||
((map))
|
||||
((exception_value_type_generator)(exception_init_type_generator))
|
||||
((lvalue_eraser)(lvalue_eraser_if)(erase_if)(free_fn_erase_if))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
|
||||
// clang-format on
|
||||
|
||||
RUN_TESTS()
|
||||
@@ -0,0 +1,445 @@
|
||||
// Copyright (C) 2023 Christian Mazakas
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#include "latch.hpp"
|
||||
|
||||
#include "../helpers/generators.hpp"
|
||||
#include "../helpers/test.hpp"
|
||||
|
||||
#include <boost/container_hash/hash.hpp>
|
||||
#include <boost/core/span.hpp>
|
||||
#include <boost/unordered/unordered_flat_map.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <random>
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
static std::size_t const num_threads =
|
||||
std::max(2u, std::thread::hardware_concurrency());
|
||||
|
||||
std::atomic_bool should_throw{false};
|
||||
|
||||
constexpr std::uint32_t throw_threshold = 2500;
|
||||
constexpr std::uint32_t alloc_throw_threshold = 10;
|
||||
|
||||
void enable_exceptions() { should_throw = true; }
|
||||
void disable_exceptions() { should_throw = false; }
|
||||
|
||||
struct exception_tag
|
||||
{
|
||||
};
|
||||
|
||||
struct stateful_hash
|
||||
{
|
||||
int x_ = -1;
|
||||
|
||||
static std::atomic<std::uint32_t> c;
|
||||
|
||||
void throw_helper() const
|
||||
{
|
||||
auto n = ++c;
|
||||
if (should_throw && ((n + 1) % throw_threshold == 0)) {
|
||||
throw exception_tag{};
|
||||
}
|
||||
}
|
||||
|
||||
stateful_hash() {}
|
||||
stateful_hash(stateful_hash const& rhs) : x_(rhs.x_) {}
|
||||
stateful_hash(stateful_hash&& rhs) noexcept
|
||||
{
|
||||
auto tmp = x_;
|
||||
x_ = rhs.x_;
|
||||
rhs.x_ = tmp;
|
||||
}
|
||||
|
||||
stateful_hash(int const x) : x_{x} {}
|
||||
|
||||
template <class T> std::size_t operator()(T const& t) const
|
||||
{
|
||||
throw_helper();
|
||||
std::size_t h = static_cast<std::size_t>(x_);
|
||||
boost::hash_combine(h, t);
|
||||
return h;
|
||||
}
|
||||
|
||||
bool operator==(stateful_hash const& rhs) const { return x_ == rhs.x_; }
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, stateful_hash const& rhs)
|
||||
{
|
||||
os << "{ x_: " << rhs.x_ << " }";
|
||||
return os;
|
||||
}
|
||||
|
||||
friend void swap(stateful_hash& lhs, stateful_hash& rhs) noexcept
|
||||
{
|
||||
if (&lhs != &rhs) {
|
||||
std::swap(lhs.x_, rhs.x_);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::atomic<std::uint32_t> stateful_hash::c{0};
|
||||
|
||||
struct stateful_key_equal
|
||||
{
|
||||
int x_ = -1;
|
||||
static std::atomic<std::uint32_t> c;
|
||||
|
||||
void throw_helper() const
|
||||
{
|
||||
auto n = ++c;
|
||||
if (should_throw && ((n + 1) % throw_threshold == 0)) {
|
||||
throw exception_tag{};
|
||||
}
|
||||
}
|
||||
|
||||
stateful_key_equal() = default;
|
||||
stateful_key_equal(stateful_key_equal const&) = default;
|
||||
stateful_key_equal(stateful_key_equal&& rhs) noexcept
|
||||
{
|
||||
auto tmp = x_;
|
||||
x_ = rhs.x_;
|
||||
rhs.x_ = tmp;
|
||||
}
|
||||
|
||||
stateful_key_equal(int const x) : x_{x} {}
|
||||
|
||||
template <class T, class U> bool operator()(T const& t, U const& u) const
|
||||
{
|
||||
throw_helper();
|
||||
return t == u;
|
||||
}
|
||||
|
||||
bool operator==(stateful_key_equal const& rhs) const { return x_ == rhs.x_; }
|
||||
|
||||
friend std::ostream& operator<<(
|
||||
std::ostream& os, stateful_key_equal const& rhs)
|
||||
{
|
||||
os << "{ x_: " << rhs.x_ << " }";
|
||||
return os;
|
||||
}
|
||||
|
||||
friend void swap(stateful_key_equal& lhs, stateful_key_equal& rhs) noexcept
|
||||
{
|
||||
if (&lhs != &rhs) {
|
||||
std::swap(lhs.x_, rhs.x_);
|
||||
}
|
||||
}
|
||||
};
|
||||
std::atomic<std::uint32_t> stateful_key_equal::c{0};
|
||||
|
||||
static std::atomic<std::uint32_t> allocator_c = {};
|
||||
|
||||
template <class T> struct stateful_allocator
|
||||
{
|
||||
int x_ = -1;
|
||||
|
||||
void throw_helper() const
|
||||
{
|
||||
auto n = ++allocator_c;
|
||||
if (should_throw && ((n + 1) % alloc_throw_threshold == 0)) {
|
||||
throw exception_tag{};
|
||||
}
|
||||
}
|
||||
|
||||
using value_type = T;
|
||||
|
||||
stateful_allocator() = default;
|
||||
stateful_allocator(stateful_allocator const&) = default;
|
||||
stateful_allocator(stateful_allocator&&) = default;
|
||||
|
||||
stateful_allocator(int const x) : x_{x} {}
|
||||
|
||||
template <class U>
|
||||
stateful_allocator(stateful_allocator<U> const& rhs) : x_{rhs.x_}
|
||||
{
|
||||
}
|
||||
|
||||
T* allocate(std::size_t n)
|
||||
{
|
||||
throw_helper();
|
||||
return static_cast<T*>(::operator new(n * sizeof(T)));
|
||||
}
|
||||
|
||||
void deallocate(T* p, std::size_t) { ::operator delete(p); }
|
||||
|
||||
bool operator==(stateful_allocator const& rhs) const { return x_ == rhs.x_; }
|
||||
bool operator!=(stateful_allocator const& rhs) const { return x_ != rhs.x_; }
|
||||
};
|
||||
|
||||
struct raii
|
||||
{
|
||||
static std::atomic<std::uint32_t> default_constructor;
|
||||
static std::atomic<std::uint32_t> copy_constructor;
|
||||
static std::atomic<std::uint32_t> move_constructor;
|
||||
static std::atomic<std::uint32_t> destructor;
|
||||
|
||||
static std::atomic<std::uint32_t> copy_assignment;
|
||||
static std::atomic<std::uint32_t> move_assignment;
|
||||
|
||||
static std::atomic<std::uint32_t> c;
|
||||
void throw_helper() const
|
||||
{
|
||||
auto n = ++c;
|
||||
if (should_throw && ((n + 1) % throw_threshold == 0)) {
|
||||
throw exception_tag{};
|
||||
}
|
||||
}
|
||||
|
||||
int x_ = -1;
|
||||
|
||||
raii()
|
||||
{
|
||||
throw_helper();
|
||||
++default_constructor;
|
||||
}
|
||||
|
||||
raii(int const x) : x_{x}
|
||||
{
|
||||
throw_helper();
|
||||
++default_constructor;
|
||||
}
|
||||
|
||||
raii(raii const& rhs) : x_{rhs.x_}
|
||||
{
|
||||
throw_helper();
|
||||
++copy_constructor;
|
||||
}
|
||||
raii(raii&& rhs) noexcept : x_{rhs.x_}
|
||||
{
|
||||
rhs.x_ = -1;
|
||||
++move_constructor;
|
||||
}
|
||||
~raii() { ++destructor; }
|
||||
|
||||
raii& operator=(raii const& rhs)
|
||||
{
|
||||
throw_helper();
|
||||
++copy_assignment;
|
||||
if (this != &rhs) {
|
||||
x_ = rhs.x_;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
raii& operator=(raii&& rhs) noexcept
|
||||
{
|
||||
++move_assignment;
|
||||
if (this != &rhs) {
|
||||
x_ = rhs.x_;
|
||||
rhs.x_ = -1;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend bool operator==(raii const& lhs, raii const& rhs)
|
||||
{
|
||||
return lhs.x_ == rhs.x_;
|
||||
}
|
||||
|
||||
friend bool operator!=(raii const& lhs, raii const& rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
friend bool operator==(raii const& lhs, int const x) { return lhs.x_ == x; }
|
||||
friend bool operator!=(raii const& lhs, int const x)
|
||||
{
|
||||
return !(lhs.x_ == x);
|
||||
}
|
||||
|
||||
friend bool operator==(int const x, raii const& rhs) { return rhs.x_ == x; }
|
||||
|
||||
friend bool operator!=(int const x, raii const& rhs)
|
||||
{
|
||||
return !(rhs.x_ == x);
|
||||
}
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, raii const& rhs)
|
||||
{
|
||||
os << "{ x_: " << rhs.x_ << " }";
|
||||
return os;
|
||||
}
|
||||
|
||||
friend std::ostream& operator<<(
|
||||
std::ostream& os, std::pair<raii const, raii> const& rhs)
|
||||
{
|
||||
os << "pair<" << rhs.first << ", " << rhs.second << ">";
|
||||
return os;
|
||||
}
|
||||
|
||||
static void reset_counts()
|
||||
{
|
||||
default_constructor = 0;
|
||||
copy_constructor = 0;
|
||||
move_constructor = 0;
|
||||
destructor = 0;
|
||||
copy_assignment = 0;
|
||||
move_assignment = 0;
|
||||
c = 0;
|
||||
|
||||
stateful_hash::c = 0;
|
||||
stateful_key_equal::c = 0;
|
||||
allocator_c = 0;
|
||||
}
|
||||
|
||||
friend void swap(raii& lhs, raii& rhs) { std::swap(lhs.x_, rhs.x_); }
|
||||
};
|
||||
|
||||
std::atomic<std::uint32_t> raii::default_constructor{0};
|
||||
std::atomic<std::uint32_t> raii::copy_constructor{0};
|
||||
std::atomic<std::uint32_t> raii::move_constructor{0};
|
||||
std::atomic<std::uint32_t> raii::destructor{0};
|
||||
std::atomic<std::uint32_t> raii::copy_assignment{0};
|
||||
std::atomic<std::uint32_t> raii::move_assignment{0};
|
||||
std::atomic<std::uint32_t> raii::c{0};
|
||||
|
||||
std::size_t hash_value(raii const& r) noexcept
|
||||
{
|
||||
boost::hash<int> hasher;
|
||||
return hasher(r.x_);
|
||||
}
|
||||
|
||||
struct exception_value_type_generator_type
|
||||
{
|
||||
std::pair<raii const, raii> operator()(test::random_generator rg)
|
||||
{
|
||||
int* p = nullptr;
|
||||
int a = generate(p, rg);
|
||||
int b = generate(p, rg);
|
||||
return std::make_pair(raii{a}, raii{b});
|
||||
}
|
||||
} exception_value_type_generator;
|
||||
|
||||
struct exception_init_type_generator_type
|
||||
{
|
||||
std::pair<raii, raii> operator()(test::random_generator rg)
|
||||
{
|
||||
int* p = nullptr;
|
||||
int a = generate(p, rg);
|
||||
int b = generate(p, rg);
|
||||
return std::make_pair(raii{a}, raii{b});
|
||||
}
|
||||
} exception_init_type_generator;
|
||||
|
||||
template <class T>
|
||||
std::vector<boost::span<T> > split(
|
||||
boost::span<T> s, std::size_t const nt /* num threads*/)
|
||||
{
|
||||
std::vector<boost::span<T> > subslices;
|
||||
subslices.reserve(nt);
|
||||
|
||||
auto a = s.size() / nt;
|
||||
auto b = a;
|
||||
if (s.size() % nt != 0) {
|
||||
++b;
|
||||
}
|
||||
|
||||
auto num_a = nt;
|
||||
auto num_b = std::size_t{0};
|
||||
|
||||
if (nt * b > s.size()) {
|
||||
num_a = nt * b - s.size();
|
||||
num_b = nt - num_a;
|
||||
}
|
||||
|
||||
auto sub_b = s.subspan(0, num_b * b);
|
||||
auto sub_a = s.subspan(num_b * b);
|
||||
|
||||
for (std::size_t i = 0; i < num_b; ++i) {
|
||||
subslices.push_back(sub_b.subspan(i * b, b));
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < num_a; ++i) {
|
||||
auto const is_last = i == (num_a - 1);
|
||||
subslices.push_back(
|
||||
sub_a.subspan(i * a, is_last ? boost::dynamic_extent : a));
|
||||
}
|
||||
|
||||
return subslices;
|
||||
}
|
||||
|
||||
template <class T, class F> void thread_runner(std::vector<T>& values, F f)
|
||||
{
|
||||
boost::latch latch(static_cast<std::ptrdiff_t>(num_threads));
|
||||
|
||||
std::vector<std::thread> threads;
|
||||
auto subslices = split<T>(values, num_threads);
|
||||
|
||||
for (std::size_t i = 0; i < num_threads; ++i) {
|
||||
threads.emplace_back([&f, &subslices, i, &latch] {
|
||||
latch.arrive_and_wait();
|
||||
|
||||
auto s = subslices[i];
|
||||
f(s);
|
||||
});
|
||||
}
|
||||
|
||||
for (auto& t : threads) {
|
||||
t.join();
|
||||
}
|
||||
}
|
||||
|
||||
template <class X, class Y>
|
||||
void test_matches_reference(X const& x, Y const& reference_map)
|
||||
{
|
||||
using value_type = typename X::value_type;
|
||||
BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) {
|
||||
BOOST_TEST(reference_map.contains(kv.first));
|
||||
BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second);
|
||||
}));
|
||||
}
|
||||
|
||||
template <class X, class Y>
|
||||
void test_fuzzy_matches_reference(
|
||||
X const& x, Y const& reference_map, test::random_generator rg)
|
||||
{
|
||||
using value_type = typename X::value_type;
|
||||
BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) {
|
||||
BOOST_TEST(reference_map.contains(kv.first));
|
||||
if (rg == test::sequential) {
|
||||
BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
template <class T> using span_value_type = typename T::value_type;
|
||||
|
||||
void check_raii_counts()
|
||||
{
|
||||
BOOST_TEST_GT(raii::destructor, 0u);
|
||||
|
||||
BOOST_TEST_EQ(
|
||||
raii::default_constructor + raii::copy_constructor + raii::move_constructor,
|
||||
raii::destructor);
|
||||
}
|
||||
|
||||
template <class T> void shuffle_values(std::vector<T>& v)
|
||||
{
|
||||
std::random_device rd;
|
||||
std::mt19937 g(rd());
|
||||
|
||||
std::shuffle(v.begin(), v.end(), g);
|
||||
}
|
||||
|
||||
template <class F>
|
||||
auto make_random_values(std::size_t count, F f) -> std::vector<decltype(f())>
|
||||
{
|
||||
using vector_type = std::vector<decltype(f())>;
|
||||
|
||||
vector_type v;
|
||||
v.reserve(count);
|
||||
for (std::size_t i = 0; i < count; ++i) {
|
||||
v.emplace_back(f());
|
||||
}
|
||||
return v;
|
||||
}
|
||||
@@ -0,0 +1,431 @@
|
||||
// Copyright (C) 2023 Christian Mazakas
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#include "exception_helpers.hpp"
|
||||
|
||||
#include <boost/unordered/concurrent_flat_map.hpp>
|
||||
|
||||
#include <boost/core/ignore_unused.hpp>
|
||||
|
||||
namespace {
|
||||
test::seed_t initialize_seed(73987);
|
||||
|
||||
struct lvalue_inserter_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
enable_exceptions();
|
||||
|
||||
std::atomic<std::uint64_t> num_inserts{0};
|
||||
thread_runner(values, [&x, &num_inserts](boost::span<T> s) {
|
||||
for (auto const& r : s) {
|
||||
try {
|
||||
bool b = x.insert(r);
|
||||
if (b) {
|
||||
++num_inserts;
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
disable_exceptions();
|
||||
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
}
|
||||
} lvalue_inserter;
|
||||
|
||||
struct norehash_lvalue_inserter_type : public lvalue_inserter_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
x.reserve(values.size());
|
||||
lvalue_inserter_type::operator()(values, x);
|
||||
BOOST_TEST_GT(raii::copy_constructor, 0u);
|
||||
BOOST_TEST_EQ(raii::move_constructor, 0u);
|
||||
}
|
||||
} norehash_lvalue_inserter;
|
||||
|
||||
struct rvalue_inserter_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 0u);
|
||||
|
||||
enable_exceptions();
|
||||
|
||||
std::atomic<std::uint64_t> num_inserts{0};
|
||||
thread_runner(values, [&x, &num_inserts](boost::span<T> s) {
|
||||
for (auto& r : s) {
|
||||
try {
|
||||
bool b = x.insert(std::move(r));
|
||||
if (b) {
|
||||
++num_inserts;
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
disable_exceptions();
|
||||
|
||||
if (!std::is_same<T, typename X::value_type>::value) {
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 0u);
|
||||
}
|
||||
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
}
|
||||
} rvalue_inserter;
|
||||
|
||||
struct norehash_rvalue_inserter_type : public rvalue_inserter_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
x.reserve(values.size());
|
||||
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 0u);
|
||||
BOOST_TEST_EQ(raii::move_constructor, 0u);
|
||||
|
||||
rvalue_inserter_type::operator()(values, x);
|
||||
|
||||
if (std::is_same<T, typename X::value_type>::value) {
|
||||
BOOST_TEST_EQ(raii::copy_constructor, x.size());
|
||||
BOOST_TEST_EQ(raii::move_constructor, x.size());
|
||||
} else {
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 0u);
|
||||
BOOST_TEST_EQ(raii::move_constructor, 2 * x.size());
|
||||
}
|
||||
}
|
||||
} norehash_rvalue_inserter;
|
||||
|
||||
struct iterator_range_inserter_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
for (std::size_t i = 0; i < 10; ++i) {
|
||||
x.insert(values[i]);
|
||||
}
|
||||
|
||||
enable_exceptions();
|
||||
thread_runner(values, [&x](boost::span<T> s) {
|
||||
try {
|
||||
x.insert(s.begin(), s.end());
|
||||
} catch (...) {
|
||||
}
|
||||
});
|
||||
disable_exceptions();
|
||||
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
}
|
||||
} iterator_range_inserter;
|
||||
|
||||
struct lvalue_insert_or_assign_copy_assign_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
enable_exceptions();
|
||||
thread_runner(values, [&x](boost::span<T> s) {
|
||||
for (auto& r : s) {
|
||||
try {
|
||||
x.insert_or_assign(r.first, r.second);
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
});
|
||||
disable_exceptions();
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, 0u);
|
||||
BOOST_TEST_GT(raii::copy_constructor, 0u);
|
||||
BOOST_TEST_GT(raii::move_constructor, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
}
|
||||
} lvalue_insert_or_assign_copy_assign;
|
||||
|
||||
struct lvalue_insert_or_assign_move_assign_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
enable_exceptions();
|
||||
thread_runner(values, [&x](boost::span<T> s) {
|
||||
for (auto& r : s) {
|
||||
try {
|
||||
|
||||
x.insert_or_assign(r.first, std::move(r.second));
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
});
|
||||
disable_exceptions();
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, 0u);
|
||||
BOOST_TEST_GT(raii::copy_constructor, 0u);
|
||||
BOOST_TEST_GT(raii::move_constructor, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
}
|
||||
} lvalue_insert_or_assign_move_assign;
|
||||
|
||||
struct rvalue_insert_or_assign_copy_assign_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
enable_exceptions();
|
||||
thread_runner(values, [&x](boost::span<T> s) {
|
||||
for (auto& r : s) {
|
||||
try {
|
||||
x.insert_or_assign(std::move(r.first), r.second);
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
});
|
||||
disable_exceptions();
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, 0u);
|
||||
BOOST_TEST_GT(raii::copy_constructor, 0u);
|
||||
BOOST_TEST_GT(raii::move_constructor, x.size()); // rehashing
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
}
|
||||
} rvalue_insert_or_assign_copy_assign;
|
||||
|
||||
struct rvalue_insert_or_assign_move_assign_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
enable_exceptions();
|
||||
thread_runner(values, [&x](boost::span<T> s) {
|
||||
for (auto& r : s) {
|
||||
try {
|
||||
x.insert_or_assign(std::move(r.first), std::move(r.second));
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
});
|
||||
disable_exceptions();
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 0u);
|
||||
BOOST_TEST_GT(raii::move_constructor, 0u);
|
||||
BOOST_TEST_EQ(raii::copy_assignment, 0u);
|
||||
}
|
||||
} rvalue_insert_or_assign_move_assign;
|
||||
|
||||
struct lvalue_insert_or_cvisit_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
std::atomic<std::uint64_t> num_inserts{0};
|
||||
|
||||
enable_exceptions();
|
||||
thread_runner(values, [&x, &num_inserts](boost::span<T> s) {
|
||||
for (auto& r : s) {
|
||||
try {
|
||||
bool b = x.insert_or_cvisit(
|
||||
r, [](typename X::value_type const& v) { (void)v; });
|
||||
|
||||
if (b) {
|
||||
++num_inserts;
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
});
|
||||
disable_exceptions();
|
||||
|
||||
BOOST_TEST_GT(num_inserts, 0u);
|
||||
BOOST_TEST_EQ(raii::default_constructor, 0u);
|
||||
// don't check move construction count here because of rehashing
|
||||
BOOST_TEST_GT(raii::move_constructor, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
}
|
||||
} lvalue_insert_or_cvisit;
|
||||
|
||||
struct lvalue_insert_or_visit_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
std::atomic<std::uint64_t> num_inserts{0};
|
||||
|
||||
enable_exceptions();
|
||||
thread_runner(values, [&x, &num_inserts](boost::span<T> s) {
|
||||
for (auto& r : s) {
|
||||
try {
|
||||
bool b =
|
||||
x.insert_or_visit(r, [](typename X::value_type& v) { (void)v; });
|
||||
|
||||
if (b) {
|
||||
++num_inserts;
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
});
|
||||
disable_exceptions();
|
||||
|
||||
BOOST_TEST_GT(num_inserts, 0u);
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, 0u);
|
||||
|
||||
// don't check move construction count here because of rehashing
|
||||
BOOST_TEST_GT(raii::move_constructor, 0u);
|
||||
BOOST_TEST_EQ(raii::move_assignment, 0u);
|
||||
}
|
||||
} lvalue_insert_or_visit;
|
||||
|
||||
struct rvalue_insert_or_cvisit_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
std::atomic<std::uint64_t> num_inserts{0};
|
||||
|
||||
enable_exceptions();
|
||||
thread_runner(values, [&x, &num_inserts](boost::span<T> s) {
|
||||
for (auto& r : s) {
|
||||
try {
|
||||
bool b = x.insert_or_cvisit(
|
||||
std::move(r), [](typename X::value_type const& v) { (void)v; });
|
||||
|
||||
if (b) {
|
||||
++num_inserts;
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
});
|
||||
disable_exceptions();
|
||||
|
||||
BOOST_TEST_GT(num_inserts, 0u);
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, 0u);
|
||||
}
|
||||
} rvalue_insert_or_cvisit;
|
||||
|
||||
struct rvalue_insert_or_visit_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
std::atomic<std::uint64_t> num_inserts{0};
|
||||
|
||||
enable_exceptions();
|
||||
thread_runner(values, [&x, &num_inserts](boost::span<T> s) {
|
||||
for (auto& r : s) {
|
||||
try {
|
||||
bool b = x.insert_or_visit(
|
||||
std::move(r), [](typename X::value_type& v) { (void)v; });
|
||||
|
||||
if (b) {
|
||||
++num_inserts;
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
});
|
||||
disable_exceptions();
|
||||
|
||||
BOOST_TEST_GT(num_inserts, 0u);
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, 0u);
|
||||
if (!std::is_same<T, typename X::value_type>::value) {
|
||||
BOOST_TEST_EQ(raii::copy_constructor, 0u);
|
||||
}
|
||||
}
|
||||
} rvalue_insert_or_visit;
|
||||
|
||||
struct iterator_range_insert_or_cvisit_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
for (std::size_t i = 0; i < 10; ++i) {
|
||||
x.insert(values[i]);
|
||||
}
|
||||
|
||||
enable_exceptions();
|
||||
thread_runner(values, [&x](boost::span<T> s) {
|
||||
try {
|
||||
x.insert_or_cvisit(s.begin(), s.end(),
|
||||
[](typename X::value_type const& v) { (void)v; });
|
||||
} catch (...) {
|
||||
}
|
||||
});
|
||||
disable_exceptions();
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, 0u);
|
||||
}
|
||||
} iterator_range_insert_or_cvisit;
|
||||
|
||||
struct iterator_range_insert_or_visit_type
|
||||
{
|
||||
template <class T, class X> void operator()(std::vector<T>& values, X& x)
|
||||
{
|
||||
for (std::size_t i = 0; i < 10; ++i) {
|
||||
x.insert(values[i]);
|
||||
}
|
||||
|
||||
enable_exceptions();
|
||||
thread_runner(values, [&x](boost::span<T> s) {
|
||||
try {
|
||||
x.insert_or_visit(s.begin(), s.end(),
|
||||
[](typename X::value_type const& v) { (void)v; });
|
||||
} catch (...) {
|
||||
}
|
||||
});
|
||||
disable_exceptions();
|
||||
|
||||
BOOST_TEST_EQ(raii::default_constructor, 0u);
|
||||
}
|
||||
} iterator_range_insert_or_visit;
|
||||
|
||||
template <class X, class G, class F>
|
||||
void insert(X*, G gen, F inserter, test::random_generator rg)
|
||||
{
|
||||
disable_exceptions();
|
||||
|
||||
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
|
||||
auto reference_map =
|
||||
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
|
||||
|
||||
raii::reset_counts();
|
||||
{
|
||||
X x;
|
||||
|
||||
inserter(values, x);
|
||||
|
||||
test_fuzzy_matches_reference(x, reference_map, rg);
|
||||
}
|
||||
check_raii_counts();
|
||||
}
|
||||
|
||||
boost::unordered::concurrent_flat_map<raii, raii, stateful_hash,
|
||||
stateful_key_equal, stateful_allocator<std::pair<raii const, raii> > >* map;
|
||||
|
||||
} // namespace
|
||||
|
||||
using test::default_generator;
|
||||
using test::limited_range;
|
||||
using test::sequential;
|
||||
|
||||
// clang-format off
|
||||
UNORDERED_TEST(
|
||||
insert,
|
||||
((map))
|
||||
((exception_value_type_generator)(exception_init_type_generator))
|
||||
((lvalue_inserter)(rvalue_inserter)(iterator_range_inserter)
|
||||
(norehash_lvalue_inserter)(norehash_rvalue_inserter)
|
||||
(lvalue_insert_or_cvisit)(lvalue_insert_or_visit)
|
||||
(rvalue_insert_or_cvisit)(rvalue_insert_or_visit)
|
||||
(iterator_range_insert_or_cvisit)(iterator_range_insert_or_visit))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
|
||||
UNORDERED_TEST(
|
||||
insert,
|
||||
((map))
|
||||
((exception_init_type_generator))
|
||||
((lvalue_insert_or_assign_copy_assign)(lvalue_insert_or_assign_move_assign)
|
||||
(rvalue_insert_or_assign_copy_assign)(rvalue_insert_or_assign_move_assign))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
|
||||
// clang-format on
|
||||
|
||||
RUN_TESTS()
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright (C) 2023 Christian Mazakas
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#include "exception_helpers.hpp"
|
||||
|
||||
#include <boost/unordered/concurrent_flat_map.hpp>
|
||||
|
||||
#include <boost/core/ignore_unused.hpp>
|
||||
|
||||
using allocator_type = stateful_allocator<std::pair<raii const, raii> >;
|
||||
|
||||
using hasher = stateful_hash;
|
||||
using key_equal = stateful_key_equal;
|
||||
|
||||
using map_type = boost::unordered::concurrent_flat_map<raii, raii, hasher,
|
||||
key_equal, allocator_type>;
|
||||
|
||||
namespace {
|
||||
test::seed_t initialize_seed(223333016);
|
||||
|
||||
template <class G> void merge(G gen, test::random_generator rg)
|
||||
{
|
||||
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
|
||||
auto reference_map =
|
||||
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
|
||||
|
||||
raii::reset_counts();
|
||||
|
||||
auto begin = values.begin();
|
||||
auto mid = begin + static_cast<std::ptrdiff_t>(values.size() / 2);
|
||||
auto end = values.end();
|
||||
|
||||
{
|
||||
unsigned num_throws = 0;
|
||||
|
||||
for (unsigned i = 0; i < 5 * alloc_throw_threshold; ++i) {
|
||||
disable_exceptions();
|
||||
|
||||
map_type x1(0, hasher(1), key_equal(2), allocator_type(3));
|
||||
x1.insert(begin, mid);
|
||||
|
||||
map_type x2(0, hasher(2), key_equal(1), allocator_type(3));
|
||||
x2.insert(mid, end);
|
||||
|
||||
enable_exceptions();
|
||||
try {
|
||||
x1.merge(x2);
|
||||
} catch (...) {
|
||||
++num_throws;
|
||||
}
|
||||
|
||||
disable_exceptions();
|
||||
test_fuzzy_matches_reference(x1, reference_map, rg);
|
||||
test_fuzzy_matches_reference(x2, reference_map, rg);
|
||||
}
|
||||
|
||||
BOOST_TEST_GT(num_throws, 0u);
|
||||
}
|
||||
|
||||
check_raii_counts();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
using test::default_generator;
|
||||
using test::limited_range;
|
||||
using test::sequential;
|
||||
|
||||
// clang-format off
|
||||
UNORDERED_TEST(
|
||||
merge,
|
||||
((exception_value_type_generator))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
|
||||
// clang-format on
|
||||
|
||||
RUN_TESTS()
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright (C) 2023 Christian Mazakas
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#include "helpers.hpp"
|
||||
#include <boost/config/workaround.hpp>
|
||||
#include <boost/unordered/concurrent_flat_map_fwd.hpp>
|
||||
|
||||
test::seed_t initialize_seed{32304628};
|
||||
|
||||
using test::default_generator;
|
||||
using test::limited_range;
|
||||
using test::sequential;
|
||||
|
||||
template <class T>
|
||||
void swap_call(boost::unordered::concurrent_flat_map<T, T>& x1,
|
||||
boost::unordered::concurrent_flat_map<T, T>& x2)
|
||||
{
|
||||
swap(x1, x2);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool equal_call(boost::unordered::concurrent_flat_map<T, T>& x1,
|
||||
boost::unordered::concurrent_flat_map<T, T>& x2)
|
||||
{
|
||||
return x1 == x2;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool unequal_call(boost::unordered::concurrent_flat_map<T, T>& x1,
|
||||
boost::unordered::concurrent_flat_map<T, T>& x2)
|
||||
{
|
||||
return x1 != x2;
|
||||
}
|
||||
|
||||
#include <boost/unordered/concurrent_flat_map.hpp>
|
||||
|
||||
using map_type = boost::unordered::concurrent_flat_map<int, int>;
|
||||
|
||||
#if !defined(BOOST_CLANG_VERSION) || \
|
||||
BOOST_WORKAROUND(BOOST_CLANG_VERSION, < 30700) || \
|
||||
BOOST_WORKAROUND(BOOST_CLANG_VERSION, >= 30800)
|
||||
// clang-3.7 seems to have a codegen bug here so we workaround it
|
||||
UNORDERED_AUTO_TEST (fwd_swap_call) {
|
||||
map_type x1, x2;
|
||||
swap_call(x1, x2);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
UNORDERED_AUTO_TEST (fwd_equal_call) {
|
||||
map_type x1, x2;
|
||||
BOOST_TEST(equal_call(x1, x2));
|
||||
}
|
||||
|
||||
UNORDERED_AUTO_TEST (fwd_unequal_call) {
|
||||
map_type x1, x2;
|
||||
BOOST_TEST_NOT(unequal_call(x1, x2));
|
||||
}
|
||||
|
||||
RUN_TESTS()
|
||||
@@ -1,6 +1,12 @@
|
||||
// Copyright (C) 2023 Christian Mazakas
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#ifndef BOOST_UNORDERED_TEST_CFOA_HELPERS_HPP
|
||||
#define BOOST_UNORDERED_TEST_CFOA_HELPERS_HPP
|
||||
|
||||
#include "latch.hpp"
|
||||
|
||||
#include "../helpers/generators.hpp"
|
||||
#include "../helpers/test.hpp"
|
||||
|
||||
@@ -8,15 +14,20 @@
|
||||
#include <boost/core/span.hpp>
|
||||
#include <boost/unordered/unordered_flat_map.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <random>
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
constexpr std::size_t const num_threads = 16;
|
||||
static std::size_t const num_threads =
|
||||
std::max(2u, std::thread::hardware_concurrency());
|
||||
|
||||
struct transp_hash
|
||||
{
|
||||
@@ -24,7 +35,6 @@ struct transp_hash
|
||||
|
||||
template <class T> std::size_t operator()(T const& t) const noexcept
|
||||
{
|
||||
std::this_thread::yield();
|
||||
return boost::hash<T>()(t);
|
||||
}
|
||||
};
|
||||
@@ -35,7 +45,6 @@ struct transp_key_equal
|
||||
|
||||
template <class T, class U> bool operator()(T const& lhs, U const& rhs) const
|
||||
{
|
||||
std::this_thread::yield();
|
||||
return lhs == rhs;
|
||||
}
|
||||
};
|
||||
@@ -59,7 +68,6 @@ struct stateful_hash
|
||||
{
|
||||
std::size_t h = static_cast<std::size_t>(x_);
|
||||
boost::hash_combine(h, t);
|
||||
std::this_thread::yield();
|
||||
return h;
|
||||
}
|
||||
|
||||
@@ -96,7 +104,6 @@ struct stateful_key_equal
|
||||
|
||||
template <class T, class U> bool operator()(T const& t, U const& u) const
|
||||
{
|
||||
std::this_thread::yield();
|
||||
return t == u;
|
||||
}
|
||||
|
||||
@@ -231,6 +238,8 @@ struct raii
|
||||
copy_assignment = 0;
|
||||
move_assignment = 0;
|
||||
}
|
||||
|
||||
friend void swap(raii& lhs, raii& rhs) { std::swap(lhs.x_, rhs.x_); }
|
||||
};
|
||||
|
||||
std::atomic<std::uint32_t> raii::default_constructor{0};
|
||||
@@ -246,6 +255,16 @@ std::size_t hash_value(raii const& r) noexcept
|
||||
return hasher(r.x_);
|
||||
}
|
||||
|
||||
namespace std {
|
||||
template <> struct hash<raii>
|
||||
{
|
||||
std::size_t operator()(raii const& r) const noexcept
|
||||
{
|
||||
return hash_value(r);
|
||||
}
|
||||
};
|
||||
} // namespace std
|
||||
|
||||
template <class F>
|
||||
auto make_random_values(std::size_t count, F f) -> std::vector<decltype(f())>
|
||||
{
|
||||
@@ -320,25 +339,14 @@ std::vector<boost::span<T> > split(
|
||||
|
||||
template <class T, class F> void thread_runner(std::vector<T>& values, F f)
|
||||
{
|
||||
std::mutex m;
|
||||
std::condition_variable cv;
|
||||
std::size_t c = 0;
|
||||
boost::latch latch(static_cast<std::ptrdiff_t>(num_threads));
|
||||
|
||||
std::vector<std::thread> threads;
|
||||
auto subslices = split<T>(values, num_threads);
|
||||
|
||||
for (std::size_t i = 0; i < num_threads; ++i) {
|
||||
threads.emplace_back([&f, &subslices, i, &m, &cv, &c] {
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m);
|
||||
++c;
|
||||
if (c == num_threads) {
|
||||
lk.unlock();
|
||||
cv.notify_all();
|
||||
} else {
|
||||
cv.wait(lk, [&] { return c == num_threads; });
|
||||
}
|
||||
}
|
||||
threads.emplace_back([&f, &subslices, i, &latch] {
|
||||
latch.arrive_and_wait();
|
||||
|
||||
auto s = subslices[i];
|
||||
f(s);
|
||||
@@ -377,9 +385,6 @@ template <class T> using span_value_type = typename T::value_type;
|
||||
|
||||
void check_raii_counts()
|
||||
{
|
||||
BOOST_TEST_GE(raii::default_constructor, 0u);
|
||||
BOOST_TEST_GE(raii::copy_constructor, 0u);
|
||||
BOOST_TEST_GE(raii::move_constructor, 0u);
|
||||
BOOST_TEST_GT(raii::destructor, 0u);
|
||||
|
||||
BOOST_TEST_EQ(
|
||||
@@ -387,4 +392,203 @@ void check_raii_counts()
|
||||
raii::destructor);
|
||||
}
|
||||
|
||||
template <class T> void shuffle_values(std::vector<T>& v)
|
||||
{
|
||||
std::random_device rd;
|
||||
std::mt19937 g(rd());
|
||||
|
||||
std::shuffle(v.begin(), v.end(), g);
|
||||
}
|
||||
|
||||
template <class T> class ptr;
|
||||
template <class T> class const_ptr;
|
||||
template <class T> class fancy_allocator;
|
||||
|
||||
struct void_ptr
|
||||
{
|
||||
template <typename T> friend class ptr;
|
||||
|
||||
private:
|
||||
void* ptr_;
|
||||
|
||||
public:
|
||||
void_ptr() : ptr_(0) {}
|
||||
|
||||
template <typename T> explicit void_ptr(ptr<T> const& x) : ptr_(x.ptr_) {}
|
||||
|
||||
// I'm not using the safe bool idiom because the containers should be
|
||||
// able to cope with bool conversions.
|
||||
operator bool() const { return !!ptr_; }
|
||||
|
||||
bool operator==(void_ptr const& x) const { return ptr_ == x.ptr_; }
|
||||
bool operator!=(void_ptr const& x) const { return ptr_ != x.ptr_; }
|
||||
};
|
||||
|
||||
class void_const_ptr
|
||||
{
|
||||
template <typename T> friend class const_ptr;
|
||||
|
||||
private:
|
||||
void* ptr_;
|
||||
|
||||
public:
|
||||
void_const_ptr() : ptr_(0) {}
|
||||
|
||||
template <typename T>
|
||||
explicit void_const_ptr(const_ptr<T> const& x) : ptr_(x.ptr_)
|
||||
{
|
||||
}
|
||||
|
||||
// I'm not using the safe bool idiom because the containers should be
|
||||
// able to cope with bool conversions.
|
||||
operator bool() const { return !!ptr_; }
|
||||
|
||||
bool operator==(void_const_ptr const& x) const { return ptr_ == x.ptr_; }
|
||||
bool operator!=(void_const_ptr const& x) const { return ptr_ != x.ptr_; }
|
||||
};
|
||||
|
||||
template <class T> class ptr
|
||||
{
|
||||
friend class fancy_allocator<T>;
|
||||
friend class const_ptr<T>;
|
||||
friend struct void_ptr;
|
||||
|
||||
T* ptr_;
|
||||
|
||||
ptr(T* x) : ptr_(x) {}
|
||||
|
||||
public:
|
||||
ptr() : ptr_(0) {}
|
||||
explicit ptr(void_ptr const& x) : ptr_((T*)x.ptr_) {}
|
||||
|
||||
T& operator*() const { return *ptr_; }
|
||||
T* operator->() const { return ptr_; }
|
||||
ptr& operator++()
|
||||
{
|
||||
++ptr_;
|
||||
return *this;
|
||||
}
|
||||
ptr operator++(int)
|
||||
{
|
||||
ptr tmp(*this);
|
||||
++ptr_;
|
||||
return tmp;
|
||||
}
|
||||
ptr operator+(std::ptrdiff_t s) const { return ptr<T>(ptr_ + s); }
|
||||
friend ptr operator+(std::ptrdiff_t s, ptr p) { return ptr<T>(s + p.ptr_); }
|
||||
|
||||
std::ptrdiff_t operator-(ptr p) const { return ptr_ - p.ptr_; }
|
||||
ptr operator-(std::ptrdiff_t s) const { return ptr(ptr_ - s); }
|
||||
T& operator[](std::ptrdiff_t s) const { return ptr_[s]; }
|
||||
bool operator!() const { return !ptr_; }
|
||||
|
||||
static ptr pointer_to(T& p) { return ptr(boost::addressof(p)); }
|
||||
|
||||
// I'm not using the safe bool idiom because the containers should be
|
||||
// able to cope with bool conversions.
|
||||
operator bool() const { return !!ptr_; }
|
||||
|
||||
bool operator==(ptr const& x) const { return ptr_ == x.ptr_; }
|
||||
bool operator!=(ptr const& x) const { return ptr_ != x.ptr_; }
|
||||
bool operator<(ptr const& x) const { return ptr_ < x.ptr_; }
|
||||
bool operator>(ptr const& x) const { return ptr_ > x.ptr_; }
|
||||
bool operator<=(ptr const& x) const { return ptr_ <= x.ptr_; }
|
||||
bool operator>=(ptr const& x) const { return ptr_ >= x.ptr_; }
|
||||
};
|
||||
|
||||
template <class T> class const_ptr
|
||||
{
|
||||
friend class fancy_allocator<T>;
|
||||
friend struct const_void_ptr;
|
||||
|
||||
T const* ptr_;
|
||||
|
||||
const_ptr(T const* ptr) : ptr_(ptr) {}
|
||||
|
||||
public:
|
||||
const_ptr() : ptr_(0) {}
|
||||
const_ptr(ptr<T> const& x) : ptr_(x.ptr_) {}
|
||||
explicit const_ptr(void_const_ptr const& x) : ptr_((T const*)x.ptr_) {}
|
||||
|
||||
T const& operator*() const { return *ptr_; }
|
||||
T const* operator->() const { return ptr_; }
|
||||
const_ptr& operator++()
|
||||
{
|
||||
++ptr_;
|
||||
return *this;
|
||||
}
|
||||
const_ptr operator++(int)
|
||||
{
|
||||
const_ptr tmp(*this);
|
||||
++ptr_;
|
||||
return tmp;
|
||||
}
|
||||
const_ptr operator+(std::ptrdiff_t s) const { return const_ptr(ptr_ + s); }
|
||||
friend const_ptr operator+(std::ptrdiff_t s, const_ptr p)
|
||||
{
|
||||
return ptr<T>(s + p.ptr_);
|
||||
}
|
||||
T const& operator[](int s) const { return ptr_[s]; }
|
||||
bool operator!() const { return !ptr_; }
|
||||
operator bool() const { return !!ptr_; }
|
||||
|
||||
bool operator==(const_ptr const& x) const { return ptr_ == x.ptr_; }
|
||||
bool operator!=(const_ptr const& x) const { return ptr_ != x.ptr_; }
|
||||
bool operator<(const_ptr const& x) const { return ptr_ < x.ptr_; }
|
||||
bool operator>(const_ptr const& x) const { return ptr_ > x.ptr_; }
|
||||
bool operator<=(const_ptr const& x) const { return ptr_ <= x.ptr_; }
|
||||
bool operator>=(const_ptr const& x) const { return ptr_ >= x.ptr_; }
|
||||
};
|
||||
|
||||
template <class T> class fancy_allocator
|
||||
{
|
||||
public:
|
||||
typedef std::size_t size_type;
|
||||
typedef std::ptrdiff_t difference_type;
|
||||
typedef void_ptr void_pointer;
|
||||
typedef void_const_ptr const_void_pointer;
|
||||
typedef ptr<T> pointer;
|
||||
typedef const_ptr<T> const_pointer;
|
||||
typedef T& reference;
|
||||
typedef T const& const_reference;
|
||||
typedef T value_type;
|
||||
|
||||
template <class U> struct rebind
|
||||
{
|
||||
typedef fancy_allocator<U> other;
|
||||
};
|
||||
|
||||
fancy_allocator() {}
|
||||
template <class Y> fancy_allocator(fancy_allocator<Y> const&) {}
|
||||
fancy_allocator(fancy_allocator const&) {}
|
||||
~fancy_allocator() {}
|
||||
|
||||
pointer address(reference r) { return pointer(&r); }
|
||||
const_pointer address(const_reference r) { return const_pointer(&r); }
|
||||
|
||||
pointer allocate(size_type n)
|
||||
{
|
||||
return pointer(static_cast<T*>(::operator new(n * sizeof(T))));
|
||||
}
|
||||
|
||||
template <class Y> pointer allocate(size_type n, const_ptr<Y>)
|
||||
{
|
||||
return pointer(static_cast<T*>(::operator new(n * sizeof(T))));
|
||||
}
|
||||
|
||||
void deallocate(pointer p, size_type) { ::operator delete((void*)p.ptr_); }
|
||||
|
||||
template <class U, class... Args> void construct(U* p, Args&&... args)
|
||||
{
|
||||
new ((void*)p) U(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class U> void destroy(U* p) { p->~U(); }
|
||||
|
||||
size_type max_size() const { return 1000; }
|
||||
|
||||
public:
|
||||
fancy_allocator& operator=(fancy_allocator const&) { return *this; }
|
||||
};
|
||||
|
||||
#endif // BOOST_UNORDERED_TEST_CFOA_HELPERS_HPP
|
||||
@@ -540,9 +540,24 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
UNORDERED_AUTO_TEST (insert_sfinae_test) {
|
||||
// mostly a compile-time tests to ensure that there's no ambiguity when a
|
||||
// user does this
|
||||
using value_type =
|
||||
typename boost::unordered::concurrent_flat_map<raii, raii>::value_type;
|
||||
boost::unordered::concurrent_flat_map<raii, raii> x;
|
||||
x.insert({1, 2});
|
||||
|
||||
x.insert_or_visit({2, 3}, [](value_type&) {});
|
||||
x.insert_or_cvisit({3, 4}, [](value_type const&) {});
|
||||
}
|
||||
|
||||
boost::unordered::concurrent_flat_map<raii, raii>* map;
|
||||
boost::unordered::concurrent_flat_map<raii, raii, transp_hash,
|
||||
transp_key_equal>* trans_map;
|
||||
boost::unordered::concurrent_flat_map<raii, raii, boost::hash<raii>,
|
||||
std::equal_to<raii>, fancy_allocator<std::pair<raii const, raii> > >*
|
||||
fancy_map;
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -557,7 +572,7 @@ UNORDERED_TEST(
|
||||
|
||||
UNORDERED_TEST(
|
||||
insert,
|
||||
((map))
|
||||
((map)(fancy_map))
|
||||
((value_type_generator)(init_type_generator))
|
||||
((lvalue_inserter)(rvalue_inserter)(iterator_range_inserter)
|
||||
(norehash_lvalue_inserter)(norehash_rvalue_inserter)
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
// Copyright (C) 2023 Christian Mazakas
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#ifndef BOOST_UNORDERED_TEST_CFOA_LATCH_HPP
|
||||
#define BOOST_UNORDERED_TEST_CFOA_LATCH_HPP
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
|
||||
#include <climits>
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
#include <mutex>
|
||||
|
||||
namespace boost {
|
||||
class latch
|
||||
{
|
||||
private:
|
||||
std::ptrdiff_t n_;
|
||||
mutable std::mutex m_;
|
||||
mutable std::condition_variable cv_;
|
||||
|
||||
public:
|
||||
explicit latch(std::ptrdiff_t expected) : n_{expected}, m_{}, cv_{}
|
||||
{
|
||||
BOOST_ASSERT(n_ >= 0);
|
||||
BOOST_ASSERT(n_ <= max());
|
||||
}
|
||||
|
||||
latch(latch const&) = delete;
|
||||
latch& operator=(latch const&) = delete;
|
||||
|
||||
~latch() = default;
|
||||
|
||||
void count_down(std::ptrdiff_t n = 1)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_);
|
||||
count_down_and_notify(lk, n);
|
||||
}
|
||||
|
||||
bool try_wait() const noexcept
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_);
|
||||
return is_ready();
|
||||
}
|
||||
|
||||
void wait() const
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_);
|
||||
wait_impl(lk);
|
||||
}
|
||||
|
||||
void arrive_and_wait(std::ptrdiff_t n = 1)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_);
|
||||
bool should_wait = count_down_and_notify(lk, n);
|
||||
if (should_wait) {
|
||||
wait_impl(lk);
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr std::ptrdiff_t max() noexcept { return INT_MAX; }
|
||||
|
||||
private:
|
||||
bool is_ready() const { return n_ == 0; }
|
||||
|
||||
bool count_down_and_notify(
|
||||
std::unique_lock<std::mutex>& lk, std::ptrdiff_t n)
|
||||
{
|
||||
n_ -= n;
|
||||
if (n_ == 0) {
|
||||
lk.unlock();
|
||||
cv_.notify_all();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void wait_impl(std::unique_lock<std::mutex>& lk) const
|
||||
{
|
||||
cv_.wait(lk, [this] { return this->is_ready(); });
|
||||
}
|
||||
};
|
||||
} // namespace boost
|
||||
|
||||
#endif // BOOST_UNORDERED_TEST_CFOA_LATCH_HPP
|
||||
@@ -0,0 +1,155 @@
|
||||
// Copyright (C) 2023 Christian Mazakas
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#define BOOST_ENABLE_ASSERT_HANDLER
|
||||
|
||||
#include "latch.hpp"
|
||||
|
||||
#include <boost/core/lightweight_test.hpp>
|
||||
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
struct exception
|
||||
{
|
||||
};
|
||||
|
||||
namespace boost {
|
||||
void assertion_failed(
|
||||
char const* expr, char const* function, char const* file, long line)
|
||||
{
|
||||
(void)expr;
|
||||
(void)function;
|
||||
(void)file;
|
||||
(void)line;
|
||||
throw exception{};
|
||||
}
|
||||
} // namespace boost
|
||||
|
||||
namespace {
|
||||
void test_max() { BOOST_TEST_EQ(boost::latch::max(), INT_MAX); }
|
||||
|
||||
void test_constructor()
|
||||
{
|
||||
{
|
||||
auto const f = [] {
|
||||
boost::latch l(-1);
|
||||
(void)l;
|
||||
};
|
||||
BOOST_TEST_THROWS(f(), exception);
|
||||
}
|
||||
|
||||
{
|
||||
std::ptrdiff_t n = 0;
|
||||
|
||||
boost::latch l(n);
|
||||
BOOST_TEST(l.try_wait());
|
||||
}
|
||||
|
||||
{
|
||||
std::ptrdiff_t n = 16;
|
||||
|
||||
boost::latch l(n);
|
||||
BOOST_TEST_NOT(l.try_wait());
|
||||
|
||||
l.count_down(16);
|
||||
BOOST_TEST(l.try_wait());
|
||||
}
|
||||
|
||||
#if PTRDIFF_MAX > INT_MAX
|
||||
{
|
||||
auto const f = [] {
|
||||
std::ptrdiff_t n = INT_MAX;
|
||||
n += 10;
|
||||
boost::latch l(n);
|
||||
(void)l;
|
||||
};
|
||||
BOOST_TEST_THROWS(f(), exception);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void test_count_down_and_wait()
|
||||
{
|
||||
constexpr std::ptrdiff_t n = 1024;
|
||||
|
||||
boost::latch l(2 * n);
|
||||
|
||||
bool bs[] = {false, false};
|
||||
|
||||
std::thread t1([&] {
|
||||
l.wait();
|
||||
BOOST_TEST(bs[0]);
|
||||
BOOST_TEST(bs[1]);
|
||||
});
|
||||
|
||||
std::thread t2([&] {
|
||||
for (int i = 0; i < n; ++i) {
|
||||
if (i == (n - 1)) {
|
||||
bs[0] = true;
|
||||
} else {
|
||||
BOOST_TEST_NOT(l.try_wait());
|
||||
}
|
||||
|
||||
l.count_down(1);
|
||||
}
|
||||
});
|
||||
|
||||
for (int i = 0; i < n; ++i) {
|
||||
if (i == (n - 1)) {
|
||||
bs[1] = true;
|
||||
} else {
|
||||
BOOST_TEST_NOT(l.try_wait());
|
||||
}
|
||||
|
||||
l.count_down(1);
|
||||
}
|
||||
|
||||
t1.join();
|
||||
t2.join();
|
||||
|
||||
BOOST_TEST(l.try_wait());
|
||||
}
|
||||
|
||||
void test_arrive_and_wait()
|
||||
{
|
||||
std::ptrdiff_t const n = 16;
|
||||
|
||||
boost::latch l(2 * n);
|
||||
|
||||
int xs[n] = {0};
|
||||
|
||||
std::vector<std::thread> threads;
|
||||
for (int i = 0; i < n; ++i) {
|
||||
threads.emplace_back([&l, &xs, i, n] {
|
||||
(void)n;
|
||||
for (int j = 0; j < n; ++j) {
|
||||
BOOST_TEST_EQ(xs[j], 0);
|
||||
}
|
||||
|
||||
l.arrive_and_wait(2);
|
||||
|
||||
xs[i] = 1;
|
||||
});
|
||||
}
|
||||
|
||||
for (auto& t : threads) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
for (int i = 0; i < n; ++i) {
|
||||
BOOST_TEST_EQ(xs[i], 1);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
{
|
||||
test_max();
|
||||
test_constructor();
|
||||
test_count_down_and_wait();
|
||||
test_arrive_and_wait();
|
||||
|
||||
return boost::report_errors();
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
// Copyright (C) 2023 Christian Mazakas
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
#include <boost/unordered/concurrent_flat_map.hpp>
|
||||
|
||||
test::seed_t initialize_seed{402031699};
|
||||
|
||||
using test::default_generator;
|
||||
using test::limited_range;
|
||||
using test::sequential;
|
||||
|
||||
using hasher = stateful_hash;
|
||||
using key_equal = stateful_key_equal;
|
||||
using allocator_type = stateful_allocator<std::pair<raii const, raii> >;
|
||||
|
||||
using map_type = boost::unordered::concurrent_flat_map<raii, raii, hasher,
|
||||
key_equal, allocator_type>;
|
||||
|
||||
using map_value_type = typename map_type::value_type;
|
||||
|
||||
struct
|
||||
{
|
||||
template <class X1, class X2>
|
||||
std::size_t operator()(X1& x1, X2& x2) const noexcept
|
||||
{
|
||||
return x1.merge(x2);
|
||||
}
|
||||
} lvalue_merge;
|
||||
|
||||
struct
|
||||
{
|
||||
template <class X1, class X2>
|
||||
std::size_t operator()(X1& x1, X2& x2) const noexcept
|
||||
{
|
||||
return x1.merge(std::move(x2));
|
||||
}
|
||||
} rvalue_merge;
|
||||
|
||||
namespace {
|
||||
template <class F, class G>
|
||||
void merge_tests(F merger, G gen, test::random_generator rg)
|
||||
{
|
||||
auto values = make_random_values(1024 * 8, [&] { return gen(rg); });
|
||||
|
||||
auto ref_map =
|
||||
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
|
||||
|
||||
{
|
||||
raii::reset_counts();
|
||||
|
||||
map_type x(values.size(), hasher(1), key_equal(2), allocator_type(3));
|
||||
|
||||
auto const old_cc = +raii::copy_constructor;
|
||||
|
||||
std::atomic<unsigned long long> expected_copies{0};
|
||||
std::atomic<unsigned long long> num_merged{0};
|
||||
|
||||
thread_runner(values, [&x, &expected_copies, &num_merged, merger](
|
||||
boost::span<map_value_type> s) {
|
||||
using map2_type = boost::unordered::concurrent_flat_map<raii, raii,
|
||||
std::hash<raii>, std::equal_to<raii>, allocator_type>;
|
||||
|
||||
map2_type y(s.begin(), s.end(), s.size(), allocator_type(3));
|
||||
expected_copies += 2 * y.size();
|
||||
|
||||
BOOST_TEST(x.get_allocator() == y.get_allocator());
|
||||
num_merged += merger(x, y);
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(raii::copy_constructor, old_cc + expected_copies);
|
||||
BOOST_TEST_EQ(raii::move_constructor, 2 * ref_map.size());
|
||||
BOOST_TEST_EQ(+num_merged, ref_map.size());
|
||||
|
||||
test_fuzzy_matches_reference(x, ref_map, rg);
|
||||
}
|
||||
check_raii_counts();
|
||||
}
|
||||
|
||||
template <class G>
|
||||
void insert_and_merge_tests(G gen, test::random_generator rg)
|
||||
{
|
||||
using map2_type = boost::unordered::concurrent_flat_map<raii, raii,
|
||||
std::hash<raii>, std::equal_to<raii>, allocator_type>;
|
||||
|
||||
auto vals1 = make_random_values(1024 * 8, [&] { return gen(rg); });
|
||||
auto vals2 = make_random_values(1024 * 4, [&] { return gen(rg); });
|
||||
|
||||
auto ref_map = boost::unordered_flat_map<raii, raii>();
|
||||
ref_map.insert(vals1.begin(), vals1.end());
|
||||
ref_map.insert(vals2.begin(), vals2.end());
|
||||
|
||||
{
|
||||
raii::reset_counts();
|
||||
|
||||
map_type x1(2 * vals1.size(), hasher(1), key_equal(2), allocator_type(3));
|
||||
|
||||
map2_type x2(2 * vals1.size(), allocator_type(3));
|
||||
|
||||
std::thread t1, t2, t3;
|
||||
boost::latch l(2);
|
||||
|
||||
std::mutex m;
|
||||
std::condition_variable cv;
|
||||
std::atomic_bool done1{false}, done2{false};
|
||||
std::atomic<unsigned long long> num_merges{0};
|
||||
std::atomic<unsigned long long> call_count{0};
|
||||
bool ready = false;
|
||||
|
||||
auto const old_mc = +raii::move_constructor;
|
||||
BOOST_TEST_EQ(old_mc, 0u);
|
||||
|
||||
t1 = std::thread([&x1, &vals1, &l, &done1, &cv, &ready, &m] {
|
||||
l.arrive_and_wait();
|
||||
|
||||
for (std::size_t idx = 0; idx < vals1.size(); ++idx) {
|
||||
auto const& val = vals1[idx];
|
||||
x1.insert(val);
|
||||
|
||||
if (idx % (vals1.size() / 128) == 0) {
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m);
|
||||
ready = true;
|
||||
}
|
||||
cv.notify_all();
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
|
||||
done1 = true;
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m);
|
||||
ready = true;
|
||||
}
|
||||
cv.notify_all();
|
||||
});
|
||||
|
||||
t2 = std::thread([&x2, &vals2, &l, &done2, &cv, &m, &ready] {
|
||||
l.arrive_and_wait();
|
||||
|
||||
for (std::size_t idx = 0; idx < vals2.size(); ++idx) {
|
||||
auto const& val = vals2[idx];
|
||||
x2.insert(val);
|
||||
if (idx % 100 == 0) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
|
||||
done2 = true;
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m);
|
||||
ready = true;
|
||||
}
|
||||
cv.notify_all();
|
||||
});
|
||||
|
||||
t3 = std::thread(
|
||||
[&x1, &x2, &m, &cv, &done1, &done2, &num_merges, &call_count, &ready] {
|
||||
while (x1.empty() && x2.empty()) {
|
||||
}
|
||||
|
||||
do {
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m);
|
||||
cv.wait(lk, [&ready] { return ready; });
|
||||
ready = false;
|
||||
}
|
||||
|
||||
num_merges += x1.merge(x2);
|
||||
std::this_thread::yield();
|
||||
num_merges += x2.merge(x1);
|
||||
|
||||
call_count += 1;
|
||||
|
||||
} while (!done1 || !done2);
|
||||
|
||||
BOOST_TEST(done1);
|
||||
BOOST_TEST(done2);
|
||||
});
|
||||
|
||||
t1.join();
|
||||
t2.join();
|
||||
t3.join();
|
||||
|
||||
if (num_merges > 0) {
|
||||
// num merges is 0 most commonly in the cast of the limited_range
|
||||
// generator as both maps will contains keys from 0 to 99
|
||||
BOOST_TEST_EQ(+raii::move_constructor, 2 * num_merges);
|
||||
BOOST_TEST_GE(call_count, 1u);
|
||||
}
|
||||
|
||||
x1.merge(x2);
|
||||
test_fuzzy_matches_reference(x1, ref_map, rg);
|
||||
}
|
||||
|
||||
check_raii_counts();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// clang-format off
|
||||
UNORDERED_TEST(
|
||||
merge_tests,
|
||||
((lvalue_merge)(rvalue_merge))
|
||||
((value_type_generator))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
|
||||
UNORDERED_TEST(
|
||||
insert_and_merge_tests,
|
||||
((value_type_generator))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
// clang-format on
|
||||
|
||||
RUN_TESTS()
|
||||
@@ -0,0 +1,178 @@
|
||||
// Copyright (C) 2023 Christian Mazakas
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
#include <boost/unordered/concurrent_flat_map.hpp>
|
||||
|
||||
using test::default_generator;
|
||||
using test::limited_range;
|
||||
using test::sequential;
|
||||
|
||||
using hasher = stateful_hash;
|
||||
using key_equal = stateful_key_equal;
|
||||
using allocator_type = stateful_allocator<std::pair<raii const, raii> >;
|
||||
|
||||
using map_type = boost::unordered::concurrent_flat_map<raii, raii, hasher,
|
||||
key_equal, allocator_type>;
|
||||
|
||||
using map_value_type = typename map_type::value_type;
|
||||
|
||||
namespace {
|
||||
test::seed_t initialize_seed{748775921};
|
||||
|
||||
UNORDERED_AUTO_TEST (rehash_no_insert) {
|
||||
map_type x(0, hasher(1), key_equal(2), allocator_type(3));
|
||||
BOOST_TEST_EQ(x.bucket_count(), 0u);
|
||||
|
||||
x.rehash(1024);
|
||||
BOOST_TEST_GE(x.bucket_count(), 1024u);
|
||||
|
||||
x.rehash(512);
|
||||
BOOST_TEST_GE(x.bucket_count(), 512u);
|
||||
BOOST_TEST_LT(x.bucket_count(), 1024u);
|
||||
|
||||
x.rehash(0);
|
||||
BOOST_TEST_EQ(x.bucket_count(), 0u);
|
||||
}
|
||||
|
||||
UNORDERED_AUTO_TEST (reserve_no_insert) {
|
||||
using size_type = map_type::size_type;
|
||||
|
||||
map_type x(0, hasher(1), key_equal(2), allocator_type(3));
|
||||
|
||||
auto f = [&x](double c) {
|
||||
return static_cast<size_type>(std::ceil(c / x.max_load_factor()));
|
||||
};
|
||||
|
||||
BOOST_TEST_EQ(x.bucket_count(), f(0.0));
|
||||
|
||||
x.reserve(1024);
|
||||
BOOST_TEST_GE(x.bucket_count(), f(1024.0));
|
||||
|
||||
x.reserve(512);
|
||||
BOOST_TEST_GE(x.bucket_count(), f(512.0));
|
||||
BOOST_TEST_LT(x.bucket_count(), f(1024.0));
|
||||
|
||||
x.reserve(0);
|
||||
BOOST_TEST_EQ(x.bucket_count(), f(0.0));
|
||||
}
|
||||
|
||||
template <class G>
|
||||
void insert_and_erase_with_rehash(G gen, test::random_generator rg)
|
||||
{
|
||||
auto vals1 = make_random_values(1024 * 8, [&] { return gen(rg); });
|
||||
|
||||
auto erase_indices = std::vector<std::size_t>(vals1.size());
|
||||
for (std::size_t idx = 0; idx < erase_indices.size(); ++idx) {
|
||||
erase_indices[idx] = idx;
|
||||
}
|
||||
shuffle_values(erase_indices);
|
||||
|
||||
auto ref_map = boost::unordered_flat_map<raii, raii>();
|
||||
ref_map.insert(vals1.begin(), vals1.end());
|
||||
|
||||
{
|
||||
raii::reset_counts();
|
||||
|
||||
map_type x(0, hasher(1), key_equal(2), allocator_type(3));
|
||||
|
||||
std::thread t1, t2, t3;
|
||||
boost::latch l(2);
|
||||
|
||||
std::mutex m;
|
||||
std::condition_variable cv;
|
||||
std::atomic_bool done1{false}, done2{false};
|
||||
std::atomic<unsigned long long> call_count{0};
|
||||
bool ready = false;
|
||||
|
||||
auto const old_mc = +raii::move_constructor;
|
||||
BOOST_TEST_EQ(old_mc, 0u);
|
||||
|
||||
t1 = std::thread([&x, &vals1, &l, &done1, &cv, &ready, &m] {
|
||||
l.arrive_and_wait();
|
||||
|
||||
for (std::size_t idx = 0; idx < vals1.size(); ++idx) {
|
||||
auto const& val = vals1[idx];
|
||||
x.insert(val);
|
||||
|
||||
if (idx % (vals1.size() / 128) == 0) {
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m);
|
||||
ready = true;
|
||||
}
|
||||
cv.notify_all();
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
|
||||
done1 = true;
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m);
|
||||
ready = true;
|
||||
}
|
||||
cv.notify_all();
|
||||
});
|
||||
|
||||
t2 =
|
||||
std::thread([&x, &vals1, &erase_indices, &l, &done2, &cv, &m, &ready] {
|
||||
l.arrive_and_wait();
|
||||
|
||||
for (std::size_t idx = 0; idx < erase_indices.size(); ++idx) {
|
||||
auto const& val = vals1[erase_indices[idx]];
|
||||
x.erase(val.first);
|
||||
if (idx % 100 == 0) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
|
||||
done2 = true;
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m);
|
||||
ready = true;
|
||||
}
|
||||
cv.notify_all();
|
||||
});
|
||||
|
||||
t3 =
|
||||
std::thread([&x, &vals1, &m, &cv, &done1, &done2, &call_count, &ready] {
|
||||
do {
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m);
|
||||
cv.wait(lk, [&ready] { return ready; });
|
||||
ready = false;
|
||||
}
|
||||
|
||||
auto const bc = static_cast<std::size_t>(rand()) % vals1.size();
|
||||
x.rehash(bc);
|
||||
call_count += 1;
|
||||
|
||||
std::this_thread::yield();
|
||||
} while (!done1 || !done2);
|
||||
|
||||
BOOST_TEST(done1);
|
||||
BOOST_TEST(done2);
|
||||
});
|
||||
|
||||
t1.join();
|
||||
t2.join();
|
||||
t3.join();
|
||||
|
||||
BOOST_TEST_GE(call_count, 1u);
|
||||
|
||||
test_fuzzy_matches_reference(x, ref_map, rg);
|
||||
}
|
||||
|
||||
check_raii_counts();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// clang-format off
|
||||
UNORDERED_TEST(
|
||||
insert_and_erase_with_rehash,
|
||||
((value_type_generator))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
// clang-format on
|
||||
|
||||
RUN_TESTS()
|
||||
@@ -0,0 +1,303 @@
|
||||
// Copyright (C) 2023 Christian Mazakas
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
#include <boost/unordered/concurrent_flat_map.hpp>
|
||||
|
||||
test::seed_t initialize_seed{996130204};
|
||||
|
||||
using test::default_generator;
|
||||
using test::limited_range;
|
||||
using test::sequential;
|
||||
|
||||
template <class T> struct pocs_allocator
|
||||
{
|
||||
using propagate_on_container_swap = std::true_type;
|
||||
|
||||
int x_ = -1;
|
||||
|
||||
using value_type = T;
|
||||
|
||||
pocs_allocator() = default;
|
||||
pocs_allocator(pocs_allocator const&) = default;
|
||||
pocs_allocator(pocs_allocator&&) = default;
|
||||
|
||||
pocs_allocator(int const x) : x_{x} {}
|
||||
|
||||
pocs_allocator& operator=(pocs_allocator const& rhs)
|
||||
{
|
||||
if (this != &rhs) {
|
||||
x_ = rhs.x_;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class U> pocs_allocator(pocs_allocator<U> const& rhs) : x_{rhs.x_}
|
||||
{
|
||||
}
|
||||
|
||||
T* allocate(std::size_t n)
|
||||
{
|
||||
return static_cast<T*>(::operator new(n * sizeof(T)));
|
||||
}
|
||||
|
||||
void deallocate(T* p, std::size_t) { ::operator delete(p); }
|
||||
|
||||
bool operator==(pocs_allocator const& rhs) const { return x_ == rhs.x_; }
|
||||
bool operator!=(pocs_allocator const& rhs) const { return x_ != rhs.x_; }
|
||||
|
||||
friend void swap(pocs_allocator& lhs, pocs_allocator& rhs) noexcept
|
||||
{
|
||||
std::swap(lhs.x_, rhs.x_);
|
||||
}
|
||||
};
|
||||
|
||||
using hasher = stateful_hash;
|
||||
using key_equal = stateful_key_equal;
|
||||
using allocator_type = stateful_allocator<std::pair<raii const, raii> >;
|
||||
|
||||
using map_type = boost::unordered::concurrent_flat_map<raii, raii, hasher,
|
||||
key_equal, allocator_type>;
|
||||
|
||||
using map_value_type = typename map_type::value_type;
|
||||
|
||||
using pocs_allocator_type = pocs_allocator<std::pair<const raii, raii> >;
|
||||
|
||||
using pocs_map_type = boost::unordered::concurrent_flat_map<raii, raii, hasher,
|
||||
key_equal, pocs_allocator_type>;
|
||||
|
||||
template <class T> struct is_nothrow_member_swappable
|
||||
{
|
||||
static bool const value =
|
||||
noexcept(std::declval<T&>().swap(std::declval<T&>()));
|
||||
};
|
||||
|
||||
BOOST_STATIC_ASSERT(is_nothrow_member_swappable<
|
||||
boost::unordered::concurrent_flat_map<int, int, std::hash<int>,
|
||||
std::equal_to<int>, std::allocator<std::pair<int const, int> > > >::value);
|
||||
|
||||
BOOST_STATIC_ASSERT(is_nothrow_member_swappable<pocs_map_type>::value);
|
||||
|
||||
BOOST_STATIC_ASSERT(!is_nothrow_member_swappable<map_type>::value);
|
||||
|
||||
namespace {
|
||||
struct
|
||||
{
|
||||
template <class T> void operator()(T& x1, T& x2) const { x1.swap(x2); }
|
||||
} member_fn_swap;
|
||||
|
||||
struct
|
||||
{
|
||||
template <class T> void operator()(T& x1, T& x2) const
|
||||
{
|
||||
using boost::unordered::swap;
|
||||
swap(x1, x2);
|
||||
}
|
||||
} free_fn_swap;
|
||||
|
||||
template <class X, class F, class G>
|
||||
void swap_tests(X*, F swapper, G gen, test::random_generator rg)
|
||||
{
|
||||
using allocator = typename X::allocator_type;
|
||||
|
||||
bool const pocs =
|
||||
boost::allocator_propagate_on_container_swap<allocator>::type::value;
|
||||
|
||||
auto vals1 = make_random_values(1024 * 8, [&] { return gen(rg); });
|
||||
auto vals2 = make_random_values(1024 * 4, [&] { return gen(rg); });
|
||||
|
||||
auto ref_map1 =
|
||||
boost::unordered_flat_map<raii, raii>(vals1.begin(), vals1.end());
|
||||
|
||||
auto ref_map2 =
|
||||
boost::unordered_flat_map<raii, raii>(vals2.begin(), vals2.end());
|
||||
|
||||
{
|
||||
raii::reset_counts();
|
||||
|
||||
X x1(vals1.begin(), vals1.end(), vals1.size(), hasher(1), key_equal(2),
|
||||
allocator(3));
|
||||
|
||||
X x2(vals2.begin(), vals2.end(), vals2.size(), hasher(2), key_equal(1),
|
||||
pocs ? allocator(4) : allocator(3));
|
||||
|
||||
if (pocs) {
|
||||
BOOST_TEST(x1.get_allocator() != x2.get_allocator());
|
||||
} else {
|
||||
BOOST_TEST(x1.get_allocator() == x2.get_allocator());
|
||||
}
|
||||
|
||||
auto const old_cc = +raii::copy_constructor;
|
||||
auto const old_mc = +raii::move_constructor;
|
||||
|
||||
thread_runner(vals1, [&x1, &x2, swapper](boost::span<map_value_type> s) {
|
||||
(void)s;
|
||||
|
||||
swapper(x1, x2);
|
||||
swapper(x2, x1);
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
|
||||
BOOST_TEST_EQ(raii::move_constructor, old_mc);
|
||||
|
||||
if (pocs) {
|
||||
if (x1.get_allocator() == allocator(3)) {
|
||||
BOOST_TEST(x2.get_allocator() == allocator(4));
|
||||
} else {
|
||||
BOOST_TEST(x1.get_allocator() == allocator(4));
|
||||
BOOST_TEST(x2.get_allocator() == allocator(3));
|
||||
}
|
||||
} else {
|
||||
BOOST_TEST(x1.get_allocator() == allocator(3));
|
||||
BOOST_TEST(x1.get_allocator() == x2.get_allocator());
|
||||
}
|
||||
|
||||
if (x1.size() == ref_map1.size()) {
|
||||
test_matches_reference(x1, ref_map1);
|
||||
test_matches_reference(x2, ref_map2);
|
||||
|
||||
BOOST_TEST_EQ(x1.hash_function(), hasher(1));
|
||||
BOOST_TEST_EQ(x1.key_eq(), key_equal(2));
|
||||
|
||||
BOOST_TEST_EQ(x2.hash_function(), hasher(2));
|
||||
BOOST_TEST_EQ(x2.key_eq(), key_equal(1));
|
||||
} else {
|
||||
test_matches_reference(x2, ref_map1);
|
||||
test_matches_reference(x1, ref_map2);
|
||||
|
||||
BOOST_TEST_EQ(x1.hash_function(), hasher(2));
|
||||
BOOST_TEST_EQ(x1.key_eq(), key_equal(1));
|
||||
|
||||
BOOST_TEST_EQ(x2.hash_function(), hasher(1));
|
||||
BOOST_TEST_EQ(x2.key_eq(), key_equal(2));
|
||||
}
|
||||
}
|
||||
check_raii_counts();
|
||||
}
|
||||
|
||||
template <class F, class G>
|
||||
void insert_and_swap(F swapper, G gen, test::random_generator rg)
|
||||
{
|
||||
auto vals1 = make_random_values(1024 * 8, [&] { return gen(rg); });
|
||||
auto vals2 = make_random_values(1024 * 4, [&] { return gen(rg); });
|
||||
|
||||
{
|
||||
raii::reset_counts();
|
||||
|
||||
map_type x1(vals1.size(), hasher(1), key_equal(2), allocator_type(3));
|
||||
map_type x2(vals2.size(), hasher(2), key_equal(1), allocator_type(3));
|
||||
|
||||
std::thread t1, t2, t3;
|
||||
boost::latch l(2);
|
||||
|
||||
std::mutex m;
|
||||
std::condition_variable cv;
|
||||
std::atomic_bool done1{false}, done2{false};
|
||||
std::atomic<unsigned> num_swaps{0};
|
||||
bool ready = false;
|
||||
|
||||
t1 = std::thread([&x1, &vals1, &l, &done1, &cv, &ready, &m] {
|
||||
l.arrive_and_wait();
|
||||
|
||||
for (std::size_t idx = 0; idx < vals1.size(); ++idx) {
|
||||
auto const& val = vals1[idx];
|
||||
x1.insert(val);
|
||||
if (idx % (vals1.size() / 128) == 0) {
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m);
|
||||
ready = true;
|
||||
}
|
||||
cv.notify_all();
|
||||
}
|
||||
std::this_thread::yield();
|
||||
}
|
||||
|
||||
done1 = true;
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m);
|
||||
ready = true;
|
||||
}
|
||||
cv.notify_all();
|
||||
});
|
||||
|
||||
t2 = std::thread([&x2, &vals2, &l, &done2, &ready, &cv, &m] {
|
||||
l.arrive_and_wait();
|
||||
|
||||
for (std::size_t idx = 0; idx < vals2.size(); ++idx) {
|
||||
auto const& val = vals2[idx];
|
||||
x2.insert(val);
|
||||
if (idx % 100 == 0) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
|
||||
done2 = true;
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m);
|
||||
ready = true;
|
||||
}
|
||||
cv.notify_all();
|
||||
});
|
||||
|
||||
t3 = std::thread(
|
||||
[&x1, &x2, &m, &cv, &done1, &done2, &num_swaps, swapper, &ready] {
|
||||
do {
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m);
|
||||
cv.wait(lk, [&ready] { return ready; });
|
||||
ready = false;
|
||||
}
|
||||
swapper(x1, x2);
|
||||
++num_swaps;
|
||||
std::this_thread::yield();
|
||||
} while (!done1 || !done2);
|
||||
|
||||
BOOST_TEST(done1);
|
||||
BOOST_TEST(done2);
|
||||
});
|
||||
|
||||
t1.join();
|
||||
t2.join();
|
||||
t3.join();
|
||||
|
||||
BOOST_TEST_GT(num_swaps, 0u);
|
||||
|
||||
if (x1.hash_function() == hasher(1)) {
|
||||
BOOST_TEST_EQ(x1.key_eq(), key_equal(2));
|
||||
|
||||
BOOST_TEST_EQ(x2.hash_function(), hasher(2));
|
||||
BOOST_TEST_EQ(x2.key_eq(), key_equal(1));
|
||||
} else {
|
||||
BOOST_TEST_EQ(x1.hash_function(), hasher(2));
|
||||
BOOST_TEST_EQ(x1.key_eq(), key_equal(1));
|
||||
|
||||
BOOST_TEST_EQ(x2.hash_function(), hasher(1));
|
||||
BOOST_TEST_EQ(x2.key_eq(), key_equal(2));
|
||||
}
|
||||
}
|
||||
|
||||
check_raii_counts();
|
||||
}
|
||||
|
||||
map_type* map;
|
||||
pocs_map_type* pocs_map;
|
||||
|
||||
} // namespace
|
||||
|
||||
// clang-format off
|
||||
UNORDERED_TEST(
|
||||
swap_tests,
|
||||
((map)(pocs_map))
|
||||
((member_fn_swap)(free_fn_swap))
|
||||
((value_type_generator))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
|
||||
UNORDERED_TEST(insert_and_swap,
|
||||
((member_fn_swap)(free_fn_swap))
|
||||
((value_type_generator))
|
||||
((default_generator)(sequential)(limited_range)))
|
||||
// clang-format on
|
||||
|
||||
RUN_TESTS()
|
||||
@@ -107,6 +107,45 @@ namespace {
|
||||
num_visits = 0;
|
||||
total_count = 0;
|
||||
}
|
||||
|
||||
{
|
||||
thread_runner(values, [&x, &total_count](boost::span<T> s) {
|
||||
for (auto const& val : s) {
|
||||
auto r = val.first.x_;
|
||||
BOOST_TEST(r >= 0);
|
||||
|
||||
auto count = x.count(val.first);
|
||||
BOOST_TEST_EQ(count, 1u);
|
||||
total_count += count;
|
||||
|
||||
count = x.count(val.second);
|
||||
BOOST_TEST_EQ(count, 0u);
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(total_count, values.size());
|
||||
|
||||
num_visits = 0;
|
||||
total_count = 0;
|
||||
}
|
||||
|
||||
{
|
||||
thread_runner(values, [&x](boost::span<T> s) {
|
||||
for (auto const& val : s) {
|
||||
auto r = val.first.x_;
|
||||
BOOST_TEST(r >= 0);
|
||||
|
||||
auto contains = x.contains(val.first);
|
||||
BOOST_TEST(contains);
|
||||
|
||||
contains = x.contains(val.second);
|
||||
BOOST_TEST(!contains);
|
||||
}
|
||||
});
|
||||
|
||||
num_visits = 0;
|
||||
total_count = 0;
|
||||
}
|
||||
}
|
||||
} lvalue_visitor;
|
||||
|
||||
@@ -204,6 +243,45 @@ namespace {
|
||||
num_visits = 0;
|
||||
total_count = 0;
|
||||
}
|
||||
|
||||
{
|
||||
thread_runner(values, [&x, &total_count](boost::span<T> s) {
|
||||
for (auto const& val : s) {
|
||||
auto r = val.first.x_;
|
||||
BOOST_TEST(r >= 0);
|
||||
|
||||
auto count = x.count(val.first.x_);
|
||||
BOOST_TEST_EQ(count, 1u);
|
||||
total_count += count;
|
||||
|
||||
count = x.count(val.second.x_);
|
||||
BOOST_TEST_EQ(count, 0u);
|
||||
}
|
||||
});
|
||||
|
||||
BOOST_TEST_EQ(total_count, values.size());
|
||||
|
||||
num_visits = 0;
|
||||
total_count = 0;
|
||||
}
|
||||
|
||||
{
|
||||
thread_runner(values, [&x](boost::span<T> s) {
|
||||
for (auto const& val : s) {
|
||||
auto r = val.first.x_;
|
||||
BOOST_TEST(r >= 0);
|
||||
|
||||
auto contains = x.contains(val.first.x_);
|
||||
BOOST_TEST(contains);
|
||||
|
||||
contains = x.contains(val.second.x_);
|
||||
BOOST_TEST(!contains);
|
||||
}
|
||||
});
|
||||
|
||||
num_visits = 0;
|
||||
total_count = 0;
|
||||
}
|
||||
}
|
||||
} transp_visitor;
|
||||
|
||||
@@ -299,7 +377,7 @@ namespace {
|
||||
thread_runner(values, [&x, &mut_visitor](boost::span<T>) {
|
||||
std::atomic<std::uint64_t> num_visits{0};
|
||||
|
||||
x.visit_all(std::execution::par_unseq, mut_visitor(num_visits));
|
||||
x.visit_all(std::execution::par, mut_visitor(num_visits));
|
||||
BOOST_TEST_EQ(x.size(), num_visits);
|
||||
});
|
||||
}
|
||||
@@ -309,7 +387,7 @@ namespace {
|
||||
std::atomic<std::uint64_t> num_visits{0};
|
||||
auto const& y = x;
|
||||
|
||||
y.visit_all(std::execution::par_unseq, const_visitor(num_visits));
|
||||
y.visit_all(std::execution::par, const_visitor(num_visits));
|
||||
BOOST_TEST_EQ(x.size(), num_visits);
|
||||
});
|
||||
}
|
||||
@@ -317,7 +395,7 @@ namespace {
|
||||
{
|
||||
thread_runner(values, [&x, &const_visitor](boost::span<T>) {
|
||||
std::atomic<std::uint64_t> num_visits{0};
|
||||
x.cvisit_all(std::execution::par_unseq, const_visitor(num_visits));
|
||||
x.cvisit_all(std::execution::par, const_visitor(num_visits));
|
||||
BOOST_TEST_EQ(x.size(), num_visits);
|
||||
});
|
||||
}
|
||||
@@ -342,6 +420,7 @@ namespace {
|
||||
|
||||
auto reference_map =
|
||||
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
|
||||
|
||||
raii::reset_counts();
|
||||
|
||||
{
|
||||
@@ -423,6 +502,58 @@ namespace {
|
||||
BOOST_TEST_EQ(raii::destructor, 0u);
|
||||
}
|
||||
|
||||
template <class X, class G>
|
||||
void insert_and_visit(X*, G gen, test::random_generator rg)
|
||||
{
|
||||
// here we attempt to ensure happens-before and synchronizes-with
|
||||
// the visitation thread essentially chases the insertion one
|
||||
// we double-check unreloated loads/stores to ensure that a store is visible
|
||||
// in the visitation thread
|
||||
|
||||
BOOST_TEST(rg == test::sequential);
|
||||
|
||||
auto const values = make_random_values(1024 * 16, [&] { return gen(rg); });
|
||||
|
||||
{
|
||||
raii::reset_counts();
|
||||
|
||||
X x;
|
||||
|
||||
std::thread t1, t2;
|
||||
boost::latch l(2);
|
||||
std::vector<std::string> strs(values.size());
|
||||
|
||||
t1 = std::thread([&l, &values, &x, &strs] {
|
||||
l.arrive_and_wait();
|
||||
for (std::size_t idx = 0; idx < values.size(); ++idx) {
|
||||
strs[idx] = "rawr";
|
||||
auto const& val = values[idx];
|
||||
x.insert(val);
|
||||
}
|
||||
});
|
||||
|
||||
t2 = std::thread([&l, &values, &x, &strs] {
|
||||
l.arrive_and_wait();
|
||||
|
||||
for (std::size_t idx = 0; idx < values.size(); ++idx) {
|
||||
std::atomic_bool b{false};
|
||||
while (!b) {
|
||||
x.cvisit(values[idx].first,
|
||||
[&b, &strs, idx, &values](typename X::value_type const& v) {
|
||||
BOOST_TEST_EQ(v.second, values[idx].second);
|
||||
BOOST_TEST_EQ(strs[idx], "rawr");
|
||||
b = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
t1.join();
|
||||
t2.join();
|
||||
}
|
||||
check_raii_counts();
|
||||
}
|
||||
|
||||
boost::unordered::concurrent_flat_map<raii, raii>* map;
|
||||
boost::unordered::concurrent_flat_map<raii, raii, transp_hash,
|
||||
transp_key_equal>* transp_map;
|
||||
@@ -456,6 +587,13 @@ UNORDERED_TEST(
|
||||
((default_generator)(sequential)(limited_range))
|
||||
)
|
||||
|
||||
UNORDERED_TEST(
|
||||
insert_and_visit,
|
||||
((map))
|
||||
((value_type_generator))
|
||||
((sequential))
|
||||
)
|
||||
|
||||
// clang-format on
|
||||
|
||||
RUN_TESTS()
|
||||
|
||||