Compare commits
408 Commits
boost-1.78
...
esp-idf-co
Author | SHA1 | Date | |
---|---|---|---|
016d255851 | |||
6b87a43162 | |||
a4c6bf90aa | |||
a31e894411 | |||
91e78fd746 | |||
3abe5de533 | |||
dfa3c7311f | |||
2c5b8577aa | |||
4e804a9d4d | |||
0ca8c5f56f | |||
912798e5cb | |||
5bcdd7fdf0 | |||
78ffc4c192 | |||
966b76182f | |||
b7101494f2 | |||
be467b3dc4 | |||
ee70d96c75 | |||
8fbd380879 | |||
7746518c0a | |||
c8a98e27e0 | |||
3df902af23 | |||
45542e26cb | |||
49f73b118c | |||
6e3dcfddb0 | |||
09088045ac | |||
e466232757 | |||
2ccd6654c1 | |||
7d7a6b881e | |||
9661227d00 | |||
5855c67d4d | |||
3edfe2b76f | |||
f36bfe24f6 | |||
9da4b3a45a | |||
95524a6af4 | |||
d4b61541b5 | |||
143c378ba6 | |||
dfac93aebb | |||
b6daca37d5 | |||
4937619ea0 | |||
6d34532301 | |||
fb733483c6 | |||
2670bb149d | |||
d49eda63f8 | |||
08e0fee141 | |||
d204b9b408 | |||
c53e0228c5 | |||
31cffd8412 | |||
0f71fe28a2 | |||
f00a29d3df | |||
e111389d6c | |||
7079341416 | |||
7fdbfc0c1a | |||
e1dff1c931 | |||
90b2536a99 | |||
97f54318e3 | |||
f1481f0deb | |||
b1a9cde690 | |||
1ed2a0a0f1 | |||
759645cab6 | |||
3203251539 | |||
b84b94b4db | |||
3dd77edd16 | |||
a24165083f | |||
8878482ca1 | |||
19c7bbf97d | |||
23f15947d3 | |||
152129bf70 | |||
20ac32c34c | |||
707b40e8c2 | |||
0d1d9f4634 | |||
5a64ca48ad | |||
f4ddf18124 | |||
657cf68e55 | |||
f0013a4d65 | |||
83fe0249b3 | |||
3701199cfa | |||
4b56bfac05 | |||
613a997694 | |||
e690e8910c | |||
34b9a8d21f | |||
0106ed3d91 | |||
f8342e4b04 | |||
8a74b192b0 | |||
d0ac539d09 | |||
0be4856144 | |||
7503b85f6a | |||
4a9abf20b3 | |||
2836bb5c41 | |||
e2925ba01b | |||
f2724b745b | |||
712d20079a | |||
99a5409b39 | |||
72fe06aa00 | |||
8081a3f9ff | |||
f218f9b5a2 | |||
4e38751187 | |||
e36e3bcf96 | |||
18503e5eb8 | |||
110c5dcf10 | |||
37f5a462e4 | |||
a1fb756831 | |||
5a456eb295 | |||
ad639ffb61 | |||
2ae686c366 | |||
641c9fba9c | |||
8473d8120f | |||
954db4e246 | |||
4f43bc5ec7 | |||
0bcc79baab | |||
e7d34a5ab1 | |||
33f81fd490 | |||
a1c156cec1 | |||
3d62482fe9 | |||
470c9ffed0 | |||
49c70046e4 | |||
ff1b01bd10 | |||
5bcb07dc7f | |||
35475a260f | |||
3d377ec0f3 | |||
bca33372c2 | |||
96696b33c4 | |||
5772941057 | |||
d676ad814b | |||
5f9fdb0b15 | |||
0f44fd0064 | |||
ceba60831c | |||
fd90df5d54 | |||
3fe2c29204 | |||
55d4aaeef5 | |||
71d3b77668 | |||
0d3ece98c1 | |||
3dc83e4075 | |||
b57ac04728 | |||
3d952d3c0f | |||
0897423e69 | |||
b994ddf894 | |||
c322cc5621 | |||
d943283f80 | |||
995707a43e | |||
107b5e6ab9 | |||
2e0fdf7eb4 | |||
14ecf54d8a | |||
c6bdeae570 | |||
2d539a9b8f | |||
8e1f05082e | |||
170d86be9a | |||
d810b2d073 | |||
e948bab4d9 | |||
7bed1417b9 | |||
f7eea71b0b | |||
a0eee06c16 | |||
b7c013c1e8 | |||
1ee2eaf5e9 | |||
fe55012007 | |||
42eb31e7e1 | |||
83423adc05 | |||
b019f17590 | |||
e58ba2e044 | |||
79ca8e968c | |||
5a095c3771 | |||
65094532eb | |||
13caa6691c | |||
bcd1770a46 | |||
aa96d87502 | |||
d20be2aaf8 | |||
d2ded394f6 | |||
2b8f458a38 | |||
e3a7ec6aed | |||
93f9fd7206 | |||
28915fdce0 | |||
497455d281 | |||
c758cbda5e | |||
4655133843 | |||
672a97cb34 | |||
0f8cc79c00 | |||
e2b6865938 | |||
bf0bc6468a | |||
526bf15c3c | |||
bdfb0e3e25 | |||
2d6ebf16d8 | |||
13c62043eb | |||
39d60cd91d | |||
aaf79c5202 | |||
884c790009 | |||
5e5dbf5984 | |||
0794cfec9e | |||
a878374d28 | |||
120861bf55 | |||
b7514e1e04 | |||
da390e3959 | |||
3062759ca8 | |||
cb4b417f76 | |||
ef951094b3 | |||
b14aefa1d3 | |||
aa7c11a873 | |||
b871699103 | |||
8b946ec36d | |||
da73e1eda9 | |||
cd8716400b | |||
854ab0b151 | |||
c1c98e16d3 | |||
1ee99268f1 | |||
403ed3abaf | |||
de2ae678a9 | |||
45c92568a1 | |||
2f455409e2 | |||
55bdde560a | |||
4e249125eb | |||
1f0ba0198b | |||
145ccc77d6 | |||
1cb0908961 | |||
1db7fbad66 | |||
7d79b35f93 | |||
2f331b7a8b | |||
d96d5335b4 | |||
4c2150fb3d | |||
2751b3515b | |||
76b36a81ca | |||
3eb244898f | |||
7aacf9836c | |||
24eeb67275 | |||
bf86730a62 | |||
98494420c5 | |||
7717ff01a1 | |||
5c3576c7c6 | |||
d6576ed2f1 | |||
9a61c8f8dd | |||
d192ec8fae | |||
fe913577f6 | |||
312d00cc33 | |||
c3ac504c10 | |||
5d94f0eea6 | |||
97734fd895 | |||
596e1ce135 | |||
e1c58b4584 | |||
f5d470c531 | |||
a87277c6e8 | |||
6700ecaf43 | |||
ad8a11bb49 | |||
ce2051ed39 | |||
d16989ce55 | |||
a26e1c0f41 | |||
510267f6e9 | |||
8e6a5e19c2 | |||
21244ab832 | |||
7a64f1634f | |||
2d8268d3d0 | |||
f6b96e4984 | |||
7fd972d669 | |||
42190df874 | |||
19673e3b1c | |||
b6b334dd16 | |||
a8443abe80 | |||
49b630c2d4 | |||
cd56cae032 | |||
6c74aa0289 | |||
8ce147dcbd | |||
7f51c8dba4 | |||
7c2ba681e9 | |||
1c459e6ee6 | |||
f6a077e102 | |||
b797862a91 | |||
abc7327116 | |||
7c58a8247c | |||
263150e599 | |||
7a177d6ac0 | |||
d5e5c08b87 | |||
c485bc975a | |||
2dfdaca3eb | |||
56f11f94d8 | |||
ccbe691cc8 | |||
f8b53c1cf7 | |||
c920354423 | |||
1ab8cc4c0f | |||
3aa62a821a | |||
ecf76830a5 | |||
ce6ca0cf9d | |||
202a438044 | |||
d7ffd48c67 | |||
7440e7f789 | |||
f813bbdf86 | |||
2656bfbcac | |||
dc95efea1a | |||
81e7e4dd81 | |||
57a2b65488 | |||
b23e47c478 | |||
ff4ca3098b | |||
b6f8363023 | |||
9c07cf60a6 | |||
36324af017 | |||
31392ce1aa | |||
6cf039eecc | |||
79ab9800c0 | |||
05373cbb6b | |||
1b009da4d0 | |||
b39b6b7635 | |||
c2d3713f40 | |||
c4345c809e | |||
c761934868 | |||
77c4a09a9b | |||
58326b8fff | |||
ee5d4b9e73 | |||
e667e6dbd9 | |||
ec288246d0 | |||
71c332803a | |||
5e30830cb9 | |||
57054f7451 | |||
1c6c085127 | |||
ff4d25d454 | |||
85cb09ae6d | |||
854a5aa3c3 | |||
bde33a1d6a | |||
8d98d8752b | |||
cebeb4ea5f | |||
a3a27a9a6c | |||
973c72bdf0 | |||
7bdd180c30 | |||
19d2fe8738 | |||
3d5a2d26d1 | |||
4e37a14bf8 | |||
91500344d4 | |||
d8fe1a17cc | |||
9945ce7583 | |||
54d36f89ea | |||
193cf30780 | |||
ab8c09fcb9 | |||
1db53ba155 | |||
b41bb5c595 | |||
c7676755ab | |||
4f88b3865f | |||
089d2db104 | |||
fefb6ad4c4 | |||
05b795bc14 | |||
ff3f5067c8 | |||
0c54f60e17 | |||
82b33708ba | |||
5b8289c05a | |||
ea5cabb27f | |||
a6b9fb285c | |||
4041d06e95 | |||
13cd5aa4ce | |||
00b504ebc5 | |||
afb83a6cb9 | |||
f5b03fb2e8 | |||
b8d3aa2a68 | |||
52f154ec02 | |||
e4d0693eb9 | |||
4a42c93897 | |||
8b438dea76 | |||
12977a50bc | |||
33f84624ec | |||
f252480bee | |||
c9df887c4c | |||
03edf7f4a8 | |||
a98a719546 | |||
a97483b928 | |||
9955886ef5 | |||
3646a7143e | |||
13f40e4333 | |||
6249660e1f | |||
3eb2d3c4b3 | |||
8f1fc75fdf | |||
d3c37344f0 | |||
5e8b6a9e55 | |||
bbd0eedb5f | |||
ad51b34438 | |||
0d4b753409 | |||
6f5727cbdb | |||
10e88d07af | |||
3f1e4a703a | |||
bc9eca70d0 | |||
69b882a14b | |||
6984e6a4f2 | |||
fd0cab2ab8 | |||
93216374ef | |||
8ba710637a | |||
937c3484cf | |||
59db6cf788 | |||
f41b3e8295 | |||
fe439890e8 | |||
e29f762116 | |||
c8abaf32ee | |||
c0a9f638ce | |||
4a90ae5b0f | |||
7ccd62ba98 | |||
2e1ef850e3 | |||
dbba786a35 | |||
d0d4be9e35 | |||
2d69c7a5ca | |||
24a38922bd | |||
1e553df5b6 | |||
0f37f774f1 | |||
34c07ea148 | |||
70fca4483e | |||
b2b017accb | |||
a97160cf57 | |||
1d42f5b7b1 | |||
70ac0509df | |||
f1678399af | |||
bae1f8ca82 | |||
790c33d6a7 | |||
a9f5da7799 | |||
6a59e6db39 | |||
76a44cff09 | |||
e36dce52ba | |||
c31ace5fc8 | |||
0f9f3eba72 | |||
0d033679d4 |
118
.appveyor.yml
@ -1,4 +1,6 @@
|
||||
# Copyright 2017 Daniel James
|
||||
# Copyright 2016, 2017 Peter Dimov
|
||||
# Copyright 2017 - 2019 James E. King III
|
||||
# Copyright 2019 - 2021 Alexander Grund
|
||||
# Distributed under the Boost Software License, Version 1.0.
|
||||
# (See accompanying file LICENSE_1_0.txt or copy at http://boost.org/LICENSE_1_0.txt)
|
||||
|
||||
@ -6,27 +8,107 @@ version: 1.0.{build}-{branch}
|
||||
|
||||
shallow_clone: true
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- develop
|
||||
- /bugfix\/.*/
|
||||
- /feature\/.*/
|
||||
- /fix\/.*/
|
||||
- /pr\/.*/
|
||||
|
||||
matrix:
|
||||
fast_finish: false
|
||||
# Adding MAYFAIL to any matrix job allows it to fail but the build stays green:
|
||||
allow_failures:
|
||||
- MAYFAIL: true
|
||||
|
||||
environment:
|
||||
global:
|
||||
B2_CI_VERSION: 1
|
||||
GIT_FETCH_JOBS: 4
|
||||
B2_ADDRESS_MODEL: 32,64
|
||||
B2_VARIANT: debug,release
|
||||
|
||||
matrix:
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
|
||||
TOOLSET: msvc-10.0,msvc-11.0,msvc-12.0,msvc-14.0
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
||||
TOOLSET: msvc-14.1
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||
TOOLSET: msvc-14.2
|
||||
- FLAVOR: Visual Studio 2008, 2010, 2012
|
||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
|
||||
B2_TOOLSET: msvc-9.0,msvc-10.0,msvc-11.0
|
||||
B2_ADDRESS_MODEL: 32 # No 64bit support
|
||||
|
||||
- FLAVOR: Visual Studio 2013, 2015
|
||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
|
||||
B2_TOOLSET: msvc-12.0,msvc-14.0
|
||||
|
||||
- FLAVOR: Visual Studio 2017
|
||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
||||
B2_CXXSTD: 14,17,latest
|
||||
B2_TOOLSET: msvc-14.1
|
||||
|
||||
- FLAVOR: cygwin (32-bit)
|
||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
||||
ADDPATH: C:\cygwin\bin;
|
||||
B2_ADDRESS_MODEL: 32
|
||||
B2_CXXSTD: 03,11,14,1z
|
||||
B2_TOOLSET: gcc
|
||||
|
||||
- FLAVOR: cygwin (64-bit)
|
||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
||||
ADDPATH: C:\cygwin64\bin;
|
||||
B2_ADDRESS_MODEL: 64
|
||||
B2_CXXSTD: 03,11,14,1z
|
||||
B2_TOOLSET: gcc
|
||||
|
||||
- FLAVOR: cygwin (64-bit, latest)
|
||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
|
||||
ADDPATH: C:\cygwin64\bin;
|
||||
B2_ADDRESS_MODEL: 64
|
||||
B2_CXXSTD: 03,11
|
||||
B2_TOOLSET: gcc
|
||||
B2_FLAGS: "include=libs/unordered/test/unordered include=libs/unordered/test/exception"
|
||||
|
||||
- FLAVOR: cygwin (64-bit, latest)
|
||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
|
||||
ADDPATH: C:\cygwin64\bin;
|
||||
B2_ADDRESS_MODEL: 64
|
||||
B2_CXXSTD: 14,1z
|
||||
B2_TOOLSET: gcc
|
||||
B2_FLAGS: "include=libs/unordered/test/unordered include=libs/unordered/test/exception"
|
||||
|
||||
- FLAVOR: mingw-w64, 32 bit
|
||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||
ADDPATH: C:\mingw-w64\i686-8.1.0-posix-dwarf-rt_v6-rev0\mingw32\bin;
|
||||
B2_CXXSTD: 03,11,14,17,2a
|
||||
B2_TOOLSET: gcc
|
||||
B2_ADDRESS_MODEL: 32
|
||||
|
||||
- FLAVOR: mingw-w64, 64 bit
|
||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||
ADDPATH: C:\mingw-w64\x86_64-8.1.0-posix-seh-rt_v6-rev0\mingw64\bin;
|
||||
B2_CXXSTD: 03,11,14,17,2a
|
||||
B2_TOOLSET: gcc
|
||||
B2_ADDRESS_MODEL: 64
|
||||
|
||||
#- FLAVOR: CodeCov (VS 2019)
|
||||
# APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||
# B2_CXXFLAGS: -permissive-
|
||||
# B2_CXXSTD: 14
|
||||
# B2_TOOLSET: msvc-14.2
|
||||
# COVERAGE: true
|
||||
|
||||
install:
|
||||
- set BOOST_ROOT=c:\projects\boost
|
||||
- cd c:\projects\
|
||||
- python %APPVEYOR_BUILD_FOLDER%\ci\download-boost-snapshot.py master
|
||||
- rd /s /q %BOOST_ROOT%\boost\unordered
|
||||
- cd %BOOST_ROOT%\tools\build
|
||||
- cmd /c bootstrap
|
||||
- cd %APPVEYOR_BUILD_FOLDER%
|
||||
- echo. 2>Jamroot.jam
|
||||
- git clone --depth 1 https://github.com/boostorg/boost-ci.git C:\boost-ci-cloned
|
||||
# Copy ci folder if not testing Boost.CI
|
||||
- if NOT "%APPVEYOR_PROJECT_NAME%" == "boost-ci" xcopy /s /e /q /i /y C:\boost-ci-cloned\ci .\ci
|
||||
- rmdir /s /q C:\boost-ci-cloned
|
||||
- ci\appveyor\install.bat
|
||||
|
||||
build: off
|
||||
|
||||
test_script:
|
||||
- cd %APPVEYOR_BUILD_FOLDER%\test
|
||||
- cmd /c %BOOST_ROOT%\tools\build\b2 -j 3 toolset=%TOOLSET% include=%APPVEYOR_BUILD_FOLDER%\include include=%BOOST_ROOT%
|
||||
test_script: ci\build.bat
|
||||
|
||||
for:
|
||||
# CodeCov coverage build
|
||||
- matrix:
|
||||
only: [COVERAGE: true]
|
||||
test_script: [ps: ci\codecov.ps1]
|
||||
|
23
.codecov.yml
Normal file
@ -0,0 +1,23 @@
|
||||
# Copyright 2019 - 2021 Alexander Grund
|
||||
# Distributed under the Boost Software License, Version 1.0.
|
||||
# (See accompanying file LICENSE_1_0.txt or copy at http://boost.org/LICENSE_1_0.txt)
|
||||
#
|
||||
# Sample codecov configuration file. Edit as required
|
||||
|
||||
codecov:
|
||||
max_report_age: off
|
||||
require_ci_to_pass: yes
|
||||
notify:
|
||||
# Increase this if you have multiple coverage collection jobs
|
||||
after_n_builds: 1
|
||||
wait_for_ci: yes
|
||||
|
||||
# Change how pull request comments look
|
||||
comment:
|
||||
layout: "reach,diff,flags,files,footer"
|
||||
|
||||
# Ignore specific files or folders. Glob patterns are supported.
|
||||
# See https://docs.codecov.com/docs/ignoring-paths
|
||||
ignore:
|
||||
- extra/**/*
|
||||
# - test/**/*
|
180
.drone.jsonnet
Normal file
@ -0,0 +1,180 @@
|
||||
# Copyright 2022 Peter Dimov
|
||||
# Distributed under the Boost Software License, Version 1.0.
|
||||
# https://www.boost.org/LICENSE_1_0.txt
|
||||
|
||||
local library = "unordered";
|
||||
|
||||
local triggers =
|
||||
{
|
||||
branch: [ "master", "develop", "feature/*", "bugfix/*" ]
|
||||
};
|
||||
|
||||
local ubsan = { UBSAN: '1', UBSAN_OPTIONS: 'print_stacktrace=1' };
|
||||
local asan = { ASAN: '1' };
|
||||
|
||||
local linux_pipeline(name, image, environment, packages = "", sources = [], arch = "amd64") =
|
||||
{
|
||||
name: name,
|
||||
kind: "pipeline",
|
||||
type: "docker",
|
||||
trigger: triggers,
|
||||
platform:
|
||||
{
|
||||
os: "linux",
|
||||
arch: arch
|
||||
},
|
||||
steps:
|
||||
[
|
||||
{
|
||||
name: "everything",
|
||||
image: image,
|
||||
environment: environment,
|
||||
commands:
|
||||
[
|
||||
'set -e',
|
||||
'wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add -',
|
||||
] +
|
||||
(if sources != [] then [ ('apt-add-repository "' + source + '"') for source in sources ] else []) +
|
||||
(if packages != "" then [ 'apt-get update', 'apt-get -y install ' + packages ] else []) +
|
||||
[
|
||||
'export LIBRARY=' + library,
|
||||
'./.drone/drone.sh',
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
local macos_pipeline(name, environment, xcode_version = "12.2", osx_version = "catalina", arch = "amd64") =
|
||||
{
|
||||
name: name,
|
||||
kind: "pipeline",
|
||||
type: "exec",
|
||||
trigger: triggers,
|
||||
platform: {
|
||||
"os": "darwin",
|
||||
"arch": arch
|
||||
},
|
||||
node: {
|
||||
"os": osx_version
|
||||
},
|
||||
steps: [
|
||||
{
|
||||
name: "everything",
|
||||
environment: environment + { "DEVELOPER_DIR": "/Applications/Xcode-" + xcode_version + ".app/Contents/Developer" },
|
||||
commands:
|
||||
[
|
||||
'export LIBRARY=' + library,
|
||||
'./.drone/drone.sh',
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
local windows_pipeline(name, image, environment, arch = "amd64") =
|
||||
{
|
||||
name: name,
|
||||
kind: "pipeline",
|
||||
type: "docker",
|
||||
trigger: triggers,
|
||||
platform:
|
||||
{
|
||||
os: "windows",
|
||||
arch: arch
|
||||
},
|
||||
"steps":
|
||||
[
|
||||
{
|
||||
name: "everything",
|
||||
image: image,
|
||||
environment: environment,
|
||||
commands:
|
||||
[
|
||||
'cmd /C .drone\\\\drone.bat ' + library,
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
[
|
||||
linux_pipeline(
|
||||
"Linux 14.04 GCC 4.4 32/64",
|
||||
"cppalliance/droneubuntu1404:1",
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++-4.4', CXXSTD: '98,0x', ADDRMD: '32,64' },
|
||||
"g++-4.4-multilib",
|
||||
[ "ppa:ubuntu-toolchain-r/test" ],
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 14.04 GCC 4.6 32/64",
|
||||
"cppalliance/droneubuntu1404:1",
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++-4.6', CXXSTD: '98,0x', ADDRMD: '32,64' },
|
||||
"g++-4.6-multilib",
|
||||
[ "ppa:ubuntu-toolchain-r/test" ],
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 14.04 GCC 4.7 32/64",
|
||||
"cppalliance/droneubuntu1404:1",
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++-4.7', CXXSTD: '98,0x', ADDRMD: '32,64' },
|
||||
"g++-4.7-multilib",
|
||||
[ "ppa:ubuntu-toolchain-r/test" ],
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 14.04 GCC 4.8* 32/64",
|
||||
"cppalliance/droneubuntu1404:1",
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++', CXXSTD: '03,11', ADDRMD: '32,64' },
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 14.04 GCC 4.9 32/64",
|
||||
"cppalliance/droneubuntu1404:1",
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++-4.9', CXXSTD: '03,11', ADDRMD: '32,64' },
|
||||
"g++-4.9-multilib",
|
||||
[ "ppa:ubuntu-toolchain-r/test" ],
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 20.04 GCC 9* ARM64 32",
|
||||
"cppalliance/droneubuntu2004:multiarch",
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++', CXXSTD: '03,11,14,17,2a', ADDRMD: '32' },
|
||||
arch="arm64",
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 20.04 GCC 9* ARM64 64",
|
||||
"cppalliance/droneubuntu2004:multiarch",
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++', CXXSTD: '03,11,14,17,2a', ADDRMD: '64' },
|
||||
arch="arm64",
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 20.04 GCC 9* S390x 32",
|
||||
"cppalliance/droneubuntu2004:multiarch",
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++', CXXSTD: '03,11,14,17,2a', ADDRMD: '32' },
|
||||
arch="s390x",
|
||||
),
|
||||
|
||||
linux_pipeline(
|
||||
"Linux 20.04 GCC 9* S390x 64",
|
||||
"cppalliance/droneubuntu2004:multiarch",
|
||||
{ TOOLSET: 'gcc', COMPILER: 'g++', CXXSTD: '03,11,14,17,2a', ADDRMD: '64' },
|
||||
arch="s390x",
|
||||
),
|
||||
|
||||
macos_pipeline(
|
||||
"MacOS 10.15 Xcode 12.2 UBSAN",
|
||||
{ TOOLSET: 'clang', COMPILER: 'clang++', CXXSTD: '03,11,14,1z' } + ubsan,
|
||||
),
|
||||
|
||||
macos_pipeline(
|
||||
"MacOS 10.15 Xcode 12.2 ASAN",
|
||||
{ TOOLSET: 'clang', COMPILER: 'clang++', CXXSTD: '03,11,14,1z' } + asan,
|
||||
),
|
||||
|
||||
windows_pipeline(
|
||||
"Windows VS2017 msvc-14.1",
|
||||
"cppalliance/dronevs2017",
|
||||
{ TOOLSET: 'msvc-14.1', CXXSTD: '14,17,latest' },
|
||||
),
|
||||
]
|
23
.drone/drone.bat
Normal file
@ -0,0 +1,23 @@
|
||||
@REM Copyright 2022 Peter Dimov
|
||||
@REM Distributed under the Boost Software License, Version 1.0.
|
||||
@REM https://www.boost.org/LICENSE_1_0.txt
|
||||
|
||||
@ECHO ON
|
||||
|
||||
set LIBRARY=%1
|
||||
set DRONE_BUILD_DIR=%CD%
|
||||
|
||||
set BOOST_BRANCH=develop
|
||||
if "%DRONE_BRANCH%" == "master" set BOOST_BRANCH=master
|
||||
cd ..
|
||||
git clone -b %BOOST_BRANCH% --depth 1 https://github.com/boostorg/boost.git boost-root
|
||||
cd boost-root
|
||||
git submodule update --init tools/boostdep
|
||||
xcopy /s /e /q %DRONE_BUILD_DIR% libs\%LIBRARY%\
|
||||
python tools/boostdep/depinst/depinst.py %LIBRARY%
|
||||
cmd /c bootstrap
|
||||
b2 -d0 headers
|
||||
|
||||
if not "%CXXSTD%" == "" set CXXSTD=cxxstd=%CXXSTD%
|
||||
if not "%ADDRMD%" == "" set ADDRMD=address-model=%ADDRMD%
|
||||
b2 -j3 libs/%LIBRARY%/test toolset=%TOOLSET% %CXXSTD% %ADDRMD% variant=debug,release embed-manifest-via=linker
|
24
.drone/drone.sh
Executable file
@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright 2022 Peter Dimov
|
||||
# Distributed under the Boost Software License, Version 1.0.
|
||||
# https://www.boost.org/LICENSE_1_0.txt
|
||||
|
||||
set -ex
|
||||
|
||||
DRONE_BUILD_DIR=$(pwd)
|
||||
|
||||
BOOST_BRANCH=develop
|
||||
if [ "$DRONE_BRANCH" = "master" ]; then BOOST_BRANCH=master; fi
|
||||
|
||||
cd ..
|
||||
git clone -b $BOOST_BRANCH --depth 1 https://github.com/boostorg/boost.git boost-root
|
||||
cd boost-root
|
||||
git submodule update --init tools/boostdep
|
||||
cp -r $DRONE_BUILD_DIR/* libs/$LIBRARY
|
||||
python tools/boostdep/depinst/depinst.py $LIBRARY
|
||||
./bootstrap.sh
|
||||
./b2 -d0 headers
|
||||
|
||||
echo "using $TOOLSET : : $COMPILER ;" > ~/user-config.jam
|
||||
./b2 -j3 libs/$LIBRARY/test toolset=$TOOLSET cxxstd=$CXXSTD variant=debug,release ${ADDRMD:+address-model=$ADDRMD} ${UBSAN:+undefined-sanitizer=norecover debug-symbols=on} ${ASAN:+address-sanitizer=norecover debug-symbols=on} ${LINKFLAGS:+linkflags=$LINKFLAGS}
|
397
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,397 @@
|
||||
# Copyright 2020-2021 Peter Dimov
|
||||
# Copyright 2021 Andrey Semashev
|
||||
# Copyright 2021 Alexander Grund
|
||||
# Copyright 2022 James E. King III
|
||||
#
|
||||
# Distributed under the Boost Software License, Version 1.0.
|
||||
# (See accompanying file LICENSE_1_0.txt or copy at http://boost.org/LICENSE_1_0.txt)
|
||||
---
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
- bugfix/**
|
||||
- feature/**
|
||||
- fix/**
|
||||
- pr/**
|
||||
|
||||
concurrency:
|
||||
group: ${{format('{0}:{1}', github.repository, github.ref)}}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
GIT_FETCH_JOBS: 8
|
||||
NET_RETRY_COUNT: 5
|
||||
B2_CI_VERSION: 1
|
||||
B2_VARIANT: debug,release
|
||||
LCOV_BRANCH_COVERAGE: 0
|
||||
CODECOV_NAME: Github Actions
|
||||
|
||||
jobs:
|
||||
posix:
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# Linux, gcc
|
||||
- { compiler: gcc-4.8, cxxstd: '03,11', os: ubuntu-18.04, install: 'g++-4.8-multilib', address-model: '32,64' }
|
||||
- { compiler: gcc-4.9, cxxstd: '03,11', os: ubuntu-20.04, container: 'ubuntu:16.04' }
|
||||
- { compiler: gcc-5, cxxstd: '03,11,14,1z', os: ubuntu-18.04, install: 'g++-5-multilib', address-model: '32,64' }
|
||||
- { compiler: gcc-6, cxxstd: '03,11,14,17', os: ubuntu-18.04, install: 'g++-6-multilib', address-model: '32,64' }
|
||||
- { compiler: gcc-7, cxxstd: '03,11,14,17', os: ubuntu-18.04, install: 'g++-7-multilib', address-model: '32,64' }
|
||||
- { compiler: gcc-8, cxxstd: '03,11,14,17,2a', os: ubuntu-18.04, install: 'g++-8-multilib', address-model: '32,64' }
|
||||
- { compiler: gcc-9, cxxstd: '03,11,14,17,2a', os: ubuntu-18.04, install: 'g++-9-multilib', address-model: '32,64' }
|
||||
- { compiler: gcc-10, cxxstd: '03,11,14,17,20', os: ubuntu-20.04, install: 'g++-10-multilib', address-model: '32,64' }
|
||||
- { compiler: gcc-11, cxxstd: '03,11,14,17,20', os: ubuntu-20.04, install: 'g++-11-multilib', address-model: '32,64' }
|
||||
- { compiler: gcc-12, cxxstd: '03,11,14,17,20', os: ubuntu-22.04, install: 'g++-12-multilib', address-model: '32,64' }
|
||||
- { name: GCC w/ sanitizers, sanitize: yes,
|
||||
compiler: gcc-12, cxxstd: '03,11,14,17,20', os: ubuntu-22.04 }
|
||||
- { name: Collect coverage, coverage: yes,
|
||||
compiler: gcc-8, cxxstd: '03,11', os: ubuntu-20.04, install: 'g++-8-multilib', address-model: '32,64' }
|
||||
|
||||
# Linux, clang
|
||||
- { compiler: clang-3.7, cxxstd: '03,11,14', os: ubuntu-20.04, container: 'ubuntu:16.04' }
|
||||
- { compiler: clang-3.8, cxxstd: '03,11,14', os: ubuntu-20.04, container: 'ubuntu:16.04' }
|
||||
- { compiler: clang-3.9, cxxstd: '03,11,14', os: ubuntu-18.04 }
|
||||
- { compiler: clang-4.0, cxxstd: '03,11,14', os: ubuntu-18.04 }
|
||||
- { compiler: clang-5.0, cxxstd: '03,11,14,1z', os: ubuntu-18.04 }
|
||||
- { compiler: clang-6.0, cxxstd: '03,11,14,17', os: ubuntu-18.04 }
|
||||
- { compiler: clang-7, cxxstd: '03,11,14,17', os: ubuntu-18.04 }
|
||||
- { compiler: clang-8, cxxstd: '03,11,14,17', os: ubuntu-18.04 }
|
||||
- { compiler: clang-9, cxxstd: '03,11,14,17,2a', os: ubuntu-20.04 }
|
||||
- { compiler: clang-10, cxxstd: '03,11,14,17,20', os: ubuntu-20.04 }
|
||||
- { compiler: clang-11, cxxstd: '03,11,14,17,20', os: ubuntu-20.04 }
|
||||
- { compiler: clang-12, cxxstd: '03,11,14,17,20', os: ubuntu-20.04 }
|
||||
- { compiler: clang-13, cxxstd: '03,11,14,17,20', os: ubuntu-22.04 }
|
||||
- { compiler: clang-14, cxxstd: '03,11,14,17,20', os: ubuntu-22.04 }
|
||||
|
||||
# libc++
|
||||
- { compiler: clang-6.0, cxxstd: '03,11,14', os: ubuntu-18.04, stdlib: libc++, install: 'clang-6.0 libc++-dev libc++abi-dev' }
|
||||
- { compiler: clang-12, cxxstd: '03,11,14,17,20', os: ubuntu-20.04, stdlib: libc++, install: 'clang-12 libc++-12-dev libc++abi-12-dev' }
|
||||
- { name: Clang w/ sanitizers, sanitize: yes,
|
||||
compiler: clang-12, cxxstd: '03,11,14,17,20', os: ubuntu-20.04, stdlib: libc++, install: 'clang-12 libc++-12-dev libc++abi-12-dev' }
|
||||
|
||||
# OSX, clang
|
||||
- { compiler: clang, cxxstd: '03,11,14,17,2a', os: macos-11, sanitize: yes }
|
||||
|
||||
timeout-minutes: 120
|
||||
runs-on: ${{matrix.os}}
|
||||
container: ${{matrix.container}}
|
||||
env: {B2_USE_CCACHE: 1}
|
||||
|
||||
steps:
|
||||
- name: Setup environment
|
||||
run: |
|
||||
if [ -f "/etc/debian_version" ]; then
|
||||
echo "DEBIAN_FRONTEND=noninteractive" >> $GITHUB_ENV
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
fi
|
||||
if [ -n "${{matrix.container}}" ] && [ -f "/etc/debian_version" ]; then
|
||||
apt-get -o Acquire::Retries=$NET_RETRY_COUNT update
|
||||
apt-get -o Acquire::Retries=$NET_RETRY_COUNT install -y sudo software-properties-common
|
||||
# Need (newer) git, and the older Ubuntu container may require requesting the key manually using port 80
|
||||
apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys E1DD270288B4E6030699E45FA1715D88E1DF1F24
|
||||
for i in {1..${NET_RETRY_COUNT:-3}}; do sudo -E add-apt-repository -y ppa:git-core/ppa && break || sleep 10; done
|
||||
apt-get -o Acquire::Retries=$NET_RETRY_COUNT update
|
||||
apt-get -o Acquire::Retries=$NET_RETRY_COUNT install -y g++ python libpython-dev git
|
||||
fi
|
||||
# For jobs not compatible with ccache, use "ccache: no" in the matrix
|
||||
if [[ "${{ matrix.ccache }}" == "no" ]]; then
|
||||
echo "B2_USE_CCACHE=0" >> $GITHUB_ENV
|
||||
fi
|
||||
git config --global pack.threads 0
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
# For coverage builds fetch the whole history, else only 1 commit using a 'fake ternary'
|
||||
fetch-depth: ${{ matrix.coverage && '0' || '1' }}
|
||||
|
||||
- name: Cache ccache
|
||||
uses: actions/cache@v3
|
||||
if: env.B2_USE_CCACHE
|
||||
with:
|
||||
path: ~/.ccache
|
||||
key: ${{matrix.os}}-${{matrix.container}}-${{matrix.compiler}}-${{github.sha}}
|
||||
restore-keys: ${{matrix.os}}-${{matrix.container}}-${{matrix.compiler}}-
|
||||
|
||||
- name: Fetch Boost.CI
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: boostorg/boost-ci
|
||||
ref: master
|
||||
path: boost-ci-cloned
|
||||
|
||||
- name: Get CI scripts folder
|
||||
run: |
|
||||
# Copy ci folder if not testing Boost.CI
|
||||
[[ "$GITHUB_REPOSITORY" =~ "boost-ci" ]] || cp -r boost-ci-cloned/ci .
|
||||
rm -rf boost-ci-cloned
|
||||
|
||||
- name: Install packages
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
run: |
|
||||
SOURCE_KEYS=(${{join(matrix.source_keys, ' ')}})
|
||||
SOURCES=(${{join(matrix.sources, ' ')}})
|
||||
# Add this by default
|
||||
SOURCES+=(ppa:ubuntu-toolchain-r/test)
|
||||
for key in "${SOURCE_KEYS[@]}"; do
|
||||
for i in {1..$NET_RETRY_COUNT}; do
|
||||
wget -O - "$key" | sudo apt-key add - && break || sleep 10
|
||||
done
|
||||
done
|
||||
for source in "${SOURCES[@]}"; do
|
||||
for i in {1..$NET_RETRY_COUNT}; do
|
||||
sudo add-apt-repository $source && break || sleep 10
|
||||
done
|
||||
done
|
||||
sudo apt-get -o Acquire::Retries=$NET_RETRY_COUNT update
|
||||
if [[ -z "${{matrix.install}}" ]]; then
|
||||
pkgs="${{matrix.compiler}}"
|
||||
pkgs="${pkgs/gcc-/g++-}"
|
||||
else
|
||||
pkgs="${{matrix.install}}"
|
||||
fi
|
||||
sudo apt-get -o Acquire::Retries=$NET_RETRY_COUNT install -y $pkgs
|
||||
|
||||
- name: Setup GCC Toolchain
|
||||
if: matrix.gcc_toolchain
|
||||
run: |
|
||||
GCC_TOOLCHAIN_ROOT="$HOME/gcc-toolchain"
|
||||
echo "GCC_TOOLCHAIN_ROOT=$GCC_TOOLCHAIN_ROOT" >> $GITHUB_ENV
|
||||
MULTIARCH_TRIPLET="$(dpkg-architecture -qDEB_HOST_MULTIARCH)"
|
||||
mkdir -p "$GCC_TOOLCHAIN_ROOT"
|
||||
ln -s /usr/include "$GCC_TOOLCHAIN_ROOT/include"
|
||||
ln -s /usr/bin "$GCC_TOOLCHAIN_ROOT/bin"
|
||||
mkdir -p "$GCC_TOOLCHAIN_ROOT/lib/gcc/$MULTIARCH_TRIPLET"
|
||||
ln -s "/usr/lib/gcc/$MULTIARCH_TRIPLET/${{matrix.gcc_toolchain}}" "$GCC_TOOLCHAIN_ROOT/lib/gcc/$MULTIARCH_TRIPLET/${{matrix.gcc_toolchain}}"
|
||||
|
||||
- name: Setup multiarch
|
||||
if: matrix.multiarch
|
||||
run: |
|
||||
sudo apt-get install --no-install-recommends -y binfmt-support qemu-user-static
|
||||
sudo docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
|
||||
git clone https://github.com/jeking3/bdde.git
|
||||
echo "$(pwd)/bdde/bin/linux" >> ${GITHUB_PATH}
|
||||
echo "BDDE_DISTRO=${{ matrix.distro }}" >> ${GITHUB_ENV}
|
||||
echo "BDDE_EDITION=${{ matrix.edition }}" >> ${GITHUB_ENV}
|
||||
echo "BDDE_ARCH=${{ matrix.arch }}" >> ${GITHUB_ENV}
|
||||
echo "B2_WRAPPER=bdde" >> ${GITHUB_ENV}
|
||||
|
||||
- name: Setup Boost
|
||||
env:
|
||||
B2_ADDRESS_MODEL: ${{matrix.address-model}}
|
||||
B2_COMPILER: ${{matrix.compiler}}
|
||||
B2_CXXSTD: ${{matrix.cxxstd}}
|
||||
B2_SANITIZE: ${{matrix.sanitize}}
|
||||
B2_STDLIB: ${{matrix.stdlib}}
|
||||
# More entries can be added in the same way, see the B2_ARGS assignment in ci/enforce.sh for the possible keys.
|
||||
# B2_DEFINES: ${{matrix.defines}}
|
||||
# Variables set here (to non-empty) will override the top-level environment variables, e.g.
|
||||
# B2_VARIANT: ${{matrix.variant}}
|
||||
run: source ci/github/install.sh
|
||||
|
||||
- name: Setup coverage collection
|
||||
if: matrix.coverage
|
||||
run: ci/github/codecov.sh "setup"
|
||||
|
||||
- name: Run tests
|
||||
if: '!matrix.coverity'
|
||||
run: ci/build.sh
|
||||
|
||||
- name: Upload coverage
|
||||
if: matrix.coverage
|
||||
run: ci/codecov.sh "upload"
|
||||
|
||||
- name: Run coverity
|
||||
if: matrix.coverity && github.event_name == 'push' && (github.ref_name == 'develop' || github.ref_name == 'master')
|
||||
run: ci/github/coverity.sh
|
||||
env:
|
||||
COVERITY_SCAN_NOTIFICATION_EMAIL: ${{ secrets.COVERITY_SCAN_NOTIFICATION_EMAIL }}
|
||||
COVERITY_SCAN_TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }}
|
||||
|
||||
windows:
|
||||
defaults:
|
||||
run:
|
||||
shell: cmd
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { toolset: msvc-14.0, cxxstd: '14,latest', addrmd: '32,64', os: windows-2019 }
|
||||
- { toolset: msvc-14.2, cxxstd: '14,17,20,latest', addrmd: '32,64', os: windows-2019 }
|
||||
- { toolset: msvc-14.3, cxxstd: '14,17,20,latest', addrmd: '32,64', os: windows-2022 }
|
||||
- { toolset: clang-win, cxxstd: '14,17,latest', addrmd: '32,64', os: windows-2022 }
|
||||
- { toolset: gcc, cxxstd: '03,11,14,17,2a', addrmd: '64', os: windows-2019 }
|
||||
|
||||
runs-on: ${{matrix.os}}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Fetch Boost.CI
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: boostorg/boost-ci
|
||||
ref: master
|
||||
path: boost-ci-cloned
|
||||
- name: Get CI scripts folder
|
||||
run: |
|
||||
REM Copy ci folder if not testing Boost.CI
|
||||
if "%GITHUB_REPOSITORY%" == "%GITHUB_REPOSITORY:boost-ci=%" xcopy /s /e /q /i /y boost-ci-cloned\ci .\ci
|
||||
rmdir /s /q boost-ci-cloned
|
||||
|
||||
- name: Setup Boost
|
||||
run: ci\github\install.bat
|
||||
|
||||
- name: Run tests
|
||||
if: '!matrix.coverage'
|
||||
run: ci\build.bat
|
||||
env:
|
||||
B2_TOOLSET: ${{matrix.toolset}}
|
||||
B2_CXXSTD: ${{matrix.cxxstd}}
|
||||
B2_ADDRESS_MODEL: ${{matrix.addrmd}}
|
||||
|
||||
- name: Collect coverage
|
||||
shell: powershell
|
||||
if: matrix.coverage
|
||||
run: ci\opencppcoverage.ps1
|
||||
env:
|
||||
B2_TOOLSET: ${{matrix.toolset}}
|
||||
B2_CXXSTD: ${{matrix.cxxstd}}
|
||||
B2_ADDRESS_MODEL: ${{matrix.addrmd}}
|
||||
|
||||
- name: Upload coverage
|
||||
if: matrix.coverage
|
||||
uses: codecov/codecov-action@v2
|
||||
with:
|
||||
files: __out/cobertura.xml
|
||||
|
||||
MSYS2:
|
||||
defaults:
|
||||
run:
|
||||
shell: msys2 {0}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { sys: MINGW32, compiler: gcc, cxxstd: '03,11,17,20' }
|
||||
- { sys: MINGW64, compiler: gcc, cxxstd: '03,11,17,20' }
|
||||
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup MSYS2 environment
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: ${{matrix.sys}}
|
||||
update: true
|
||||
install: git python
|
||||
pacboy: gcc:p cmake:p ninja:p
|
||||
|
||||
- name: Fetch Boost.CI
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: boostorg/boost-ci
|
||||
ref: master
|
||||
path: boost-ci-cloned
|
||||
- name: Get CI scripts folder
|
||||
run: |
|
||||
# Copy ci folder if not testing Boost.CI
|
||||
[[ "$GITHUB_REPOSITORY" =~ "boost-ci" ]] || cp -r boost-ci-cloned/ci .
|
||||
rm -rf boost-ci-cloned
|
||||
|
||||
- name: Setup Boost
|
||||
env:
|
||||
B2_COMPILER: ${{matrix.compiler}}
|
||||
B2_CXXSTD: ${{matrix.cxxstd}}
|
||||
B2_SANITIZE: ${{matrix.sanitize}}
|
||||
B2_STDLIB: ${{matrix.stdlib}}
|
||||
run: ci/github/install.sh
|
||||
|
||||
- name: Run tests
|
||||
run: ci/build.sh
|
||||
|
||||
# Run also the CMake tests to avoid having to setup another matrix for CMake on MSYS
|
||||
- name: Run CMake tests
|
||||
run: |
|
||||
cd "$BOOST_ROOT"
|
||||
mkdir __build_cmake_test__ && cd __build_cmake_test__
|
||||
cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug -DBOOST_INCLUDE_LIBRARIES=$SELF -DBUILD_SHARED_LIBS=ON -DBUILD_TESTING=ON -DBoost_VERBOSE=ON ..
|
||||
cmake --build . --target tests --config Debug -j$B2_JOBS
|
||||
ctest --output-on-failure --build-config Debug
|
||||
|
||||
CMake:
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { os: ubuntu-20.04, build_shared: ON, build_type: Debug, generator: 'Unix Makefiles' }
|
||||
- { os: windows-2019, build_shared: ON, build_type: Debug, generator: 'Visual Studio 16 2019' }
|
||||
|
||||
timeout-minutes: 120
|
||||
runs-on: ${{matrix.os}}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Fetch Boost.CI
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: boostorg/boost-ci
|
||||
ref: master
|
||||
path: boost-ci-cloned
|
||||
- name: Get CI scripts folder
|
||||
run: |
|
||||
# Copy ci folder if not testing Boost.CI
|
||||
[[ "$GITHUB_REPOSITORY" =~ "boost-ci" ]] || cp -r boost-ci-cloned/ci .
|
||||
rm -rf boost-ci-cloned
|
||||
- name: Setup Boost
|
||||
env: {B2_DONT_BOOTSTRAP: 1}
|
||||
run: source ci/github/install.sh
|
||||
|
||||
- name: Run CMake tests
|
||||
run: |
|
||||
cd "$BOOST_ROOT"
|
||||
mkdir __build_cmake_test__ && cd __build_cmake_test__
|
||||
cmake -G "${{matrix.generator}}" -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -DBOOST_INCLUDE_LIBRARIES=$SELF -DBUILD_SHARED_LIBS=${{matrix.build_shared}} -DBUILD_TESTING=ON -DBoost_VERBOSE=ON ..
|
||||
cmake --build . --target tests --config ${{matrix.build_type}} -j$B2_JOBS
|
||||
ctest --output-on-failure --build-config ${{matrix.build_type}}
|
||||
|
||||
- name: Run CMake subdir tests
|
||||
run: |
|
||||
cmake_test_folder="$BOOST_ROOT/libs/$SELF/test/cmake_test" # New unified folder
|
||||
[ -d "$cmake_test_folder" ] || cmake_test_folder="$BOOST_ROOT/libs/$SELF/test/cmake_subdir_test"
|
||||
cd "$cmake_test_folder"
|
||||
mkdir __build_cmake_subdir_test__ && cd __build_cmake_subdir_test__
|
||||
cmake -G "${{matrix.generator}}" -DBOOST_CI_INSTALL_TEST=OFF -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -DBUILD_SHARED_LIBS=${{matrix.build_shared}} ..
|
||||
cmake --build . --config ${{matrix.build_type}} -j$B2_JOBS
|
||||
ctest --output-on-failure --build-config ${{matrix.build_type}}
|
||||
|
||||
- name: Install Library
|
||||
run: |
|
||||
cd "$BOOST_ROOT"
|
||||
mkdir __build_cmake_install_test__ && cd __build_cmake_install_test__
|
||||
cmake -G "${{matrix.generator}}" -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -DBOOST_INCLUDE_LIBRARIES=$SELF -DBUILD_SHARED_LIBS=${{matrix.build_shared}} -DCMAKE_INSTALL_PREFIX=~/.local -DBoost_VERBOSE=ON -DBoost_DEBUG=ON ..
|
||||
cmake --build . --target install --config ${{matrix.build_type}} -j$B2_JOBS
|
||||
- name: Run CMake install tests
|
||||
run: |
|
||||
cmake_test_folder="$BOOST_ROOT/libs/$SELF/test/cmake_test" # New unified folder
|
||||
[ -d "$cmake_test_folder" ] || cmake_test_folder="$BOOST_ROOT/libs/$SELF/test/cmake_install_test"
|
||||
cd "$cmake_test_folder"
|
||||
mkdir __build_cmake_install_test__ && cd __build_cmake_install_test__
|
||||
cmake -G "${{matrix.generator}}" -DBOOST_CI_INSTALL_TEST=ON -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -DBUILD_SHARED_LIBS=${{matrix.build_shared}} -DCMAKE_PREFIX_PATH=~/.local ..
|
||||
cmake --build . --config ${{matrix.build_type}} -j$B2_JOBS
|
||||
ctest --output-on-failure --build-config ${{matrix.build_type}}
|
@ -1,9 +1,11 @@
|
||||
# Generated by `boostdep --cmake unordered`
|
||||
# Copyright 2020 Peter Dimov
|
||||
# Copyright 2020, 2021 Peter Dimov
|
||||
# Distributed under the Boost Software License, Version 1.0.
|
||||
# https://www.boost.org/LICENSE_1_0.txt
|
||||
|
||||
cmake_minimum_required(VERSION 3.5...3.16)
|
||||
if(NOT DEFINED IDF_TARGET)
|
||||
|
||||
cmake_minimum_required(VERSION 3.5...3.20)
|
||||
|
||||
project(boost_unordered VERSION "${BOOST_SUPERPROJECT_VERSION}" LANGUAGES CXX)
|
||||
|
||||
@ -16,14 +18,12 @@ target_link_libraries(boost_unordered
|
||||
INTERFACE
|
||||
Boost::assert
|
||||
Boost::config
|
||||
Boost::container
|
||||
Boost::container_hash
|
||||
Boost::core
|
||||
Boost::detail
|
||||
Boost::move
|
||||
Boost::mp11
|
||||
Boost::predef
|
||||
Boost::preprocessor
|
||||
Boost::smart_ptr
|
||||
Boost::throw_exception
|
||||
Boost::tuple
|
||||
Boost::type_traits
|
||||
@ -35,3 +35,27 @@ if(BUILD_TESTING AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test/CMakeLists.txt")
|
||||
|
||||
endif()
|
||||
|
||||
else()
|
||||
|
||||
FILE(GLOB_RECURSE headers include/*.h include/*.hpp)
|
||||
|
||||
idf_component_register(
|
||||
SRCS
|
||||
${headers}
|
||||
INCLUDE_DIRS
|
||||
include
|
||||
REQUIRES
|
||||
boost_assert
|
||||
boost_config
|
||||
boost_container_hash
|
||||
boost_core
|
||||
boost_move
|
||||
boost_mp11
|
||||
boost_predef
|
||||
boost_preprocessor
|
||||
boost_throw_exception
|
||||
boost_tuple
|
||||
boost_type_traits
|
||||
)
|
||||
|
||||
endif()
|
||||
|
23
LICENSE
Normal file
@ -0,0 +1,23 @@
|
||||
Boost Software License - Version 1.0 - August 17th, 2003
|
||||
|
||||
Permission is hereby granted, free of charge, to any person or organization
|
||||
obtaining a copy of the software and accompanying documentation covered by
|
||||
this license (the "Software") to use, reproduce, display, distribute,
|
||||
execute, and transmit the Software, and to prepare derivative works of the
|
||||
Software, and to permit third-parties to whom the Software is furnished to
|
||||
do so, all subject to the following:
|
||||
|
||||
The copyright notices in the Software and this entire statement, including
|
||||
the above license grant, this restriction and the following disclaimer,
|
||||
must be included in all copies of the Software, in whole or in part, and
|
||||
all derivative works of the Software, unless such copies or derivative
|
||||
works are solely in the form of machine-executable object code generated by
|
||||
a source language processor.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
49
README.md
Normal file
@ -0,0 +1,49 @@
|
||||
# Boost.Unordered
|
||||
|
||||
Part of collection of the [Boost C++ Libraries](http://github.com/boostorg).
|
||||
|
||||
For accessing data based on key lookup, the C++ 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 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.
|
||||
|
||||
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.
|
||||
|
||||
With this in mind, unordered associative containers were added to the C++ standard.
|
||||
This is an implementation of the containers described in C++11, with some deviations from the standard in order to work with non-C++11 compilers and libraries.
|
||||
|
||||
|
||||
### License
|
||||
|
||||
Distributed under the [Boost Software License, Version 1.0](http://www.boost.org/LICENSE_1_0.txt).
|
||||
|
||||
### Properties
|
||||
|
||||
* C++03
|
||||
* Header-Only
|
||||
|
||||
### Build Status
|
||||
|
||||
Branch | GH Actions | Appveyor | codecov.io | Deps | Docs | Tests |
|
||||
:-------------: | ---------- | -------- | ---------- | ---- | ---- | ----- |
|
||||
[`master`](https://github.com/boostorg/unordered/tree/master) | [](https://github.com/boostorg/unordered/actions/workflows/ci.yml) | [](https://ci.appveyor.com/project/danieljames/unordered-qtwe6/branch/master) | [](https://codecov.io/gh/boostorg/unordered/branch/master) | [](https://pdimov.github.io/boostdep-report/master/unordered.html) | [](https://www.boost.org/doc/libs/master/libs/unordered/doc/html/unordered.html) | [](http://www.boost.org/development/tests/master/developer/unordered.html)
|
||||
[`develop`](https://github.com/boostorg/unordered/tree/develop) | [](https://github.com/boostorg/unordered/actions/workflows/ci.yml) | [](https://ci.appveyor.com/project/danieljames/unordered-qtwe6/branch/develop) | [](https://codecov.io/gh/boostorg/unordered/branch/develop) | [](https://pdimov.github.io/boostdep-report/develop/unordered.html) | [](https://www.boost.org/doc/libs/develop/libs/unordered/doc/html/unordered.html) | [](http://www.boost.org/development/tests/develop/developer/unordered.html)
|
||||
|
||||
### Directories
|
||||
|
||||
| Name | Purpose |
|
||||
| ----------- | ------------------------------ |
|
||||
| `doc` | documentation |
|
||||
| `example` | examples |
|
||||
| `include` | headers |
|
||||
| `test` | unit tests |
|
||||
|
||||
### More information
|
||||
|
||||
* [Ask questions](http://stackoverflow.com/questions/ask?tags=c%2B%2B,boost,boost-unordered)
|
||||
* [Report bugs](https://github.com/boostorg/unordered/issues): Be sure to mention Boost version, platform and compiler you're using. A small compilable code sample to reproduce the problem is always good as well.
|
||||
* Submit your patches as pull requests against **develop** branch. Note that by submitting patches you agree to license your modifications under the [Boost Software License, Version 1.0](http://www.boost.org/LICENSE_1_0.txt).
|
||||
* Discussions about the library are held on the [Boost developers mailing list](http://www.boost.org/community/groups.html#main). Be sure to read the [discussion policy](http://www.boost.org/community/policy.html) before posting and add the `[unordered]` tag at the beginning of the subject line.
|
||||
|
488
benchmark/string.cpp
Normal file
@ -0,0 +1,488 @@
|
||||
// Copyright 2021 Peter Dimov.
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// https://www.boost.org/LICENSE_1_0.txt
|
||||
|
||||
#define _SILENCE_CXX17_OLD_ALLOCATOR_MEMBERS_DEPRECATION_WARNING
|
||||
|
||||
#include <boost/unordered_map.hpp>
|
||||
#include <boost/multi_index_container.hpp>
|
||||
#include <boost/multi_index/hashed_index.hpp>
|
||||
#include <boost/multi_index/member.hpp>
|
||||
#include <boost/core/detail/splitmix64.hpp>
|
||||
#include <boost/config.hpp>
|
||||
#ifdef HAVE_ABSEIL
|
||||
# include "absl/container/node_hash_map.h"
|
||||
# include "absl/container/flat_hash_map.h"
|
||||
#endif
|
||||
#ifdef HAVE_TSL_HOPSCOTCH
|
||||
# include "tsl/hopscotch_map.h"
|
||||
#endif
|
||||
#ifdef HAVE_TSL_ROBIN
|
||||
# include "tsl/robin_map.h"
|
||||
#endif
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <chrono>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
static void print_time( std::chrono::steady_clock::time_point & t1, char const* label, std::uint32_t s, std::size_t size )
|
||||
{
|
||||
auto t2 = std::chrono::steady_clock::now();
|
||||
|
||||
std::cout << label << ": " << ( t2 - t1 ) / 1ms << " ms (s=" << s << ", size=" << size << ")\n";
|
||||
|
||||
t1 = t2;
|
||||
}
|
||||
|
||||
constexpr unsigned N = 2'000'000;
|
||||
constexpr int K = 10;
|
||||
|
||||
static std::vector<std::string> indices1, indices2;
|
||||
|
||||
static std::string make_index( unsigned x )
|
||||
{
|
||||
char buffer[ 64 ];
|
||||
std::snprintf( buffer, sizeof(buffer), "pfx_%u_sfx", x );
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static std::string make_random_index( unsigned x )
|
||||
{
|
||||
char buffer[ 64 ];
|
||||
std::snprintf( buffer, sizeof(buffer), "pfx_%0*d_%u_sfx", x % 8 + 1, 0, x );
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static void init_indices()
|
||||
{
|
||||
indices1.reserve( N*2+1 );
|
||||
indices1.push_back( make_index( 0 ) );
|
||||
|
||||
for( unsigned i = 1; i <= N*2; ++i )
|
||||
{
|
||||
indices1.push_back( make_index( i ) );
|
||||
}
|
||||
|
||||
indices2.reserve( N*2+1 );
|
||||
indices2.push_back( make_index( 0 ) );
|
||||
|
||||
{
|
||||
boost::detail::splitmix64 rng;
|
||||
|
||||
for( unsigned i = 1; i <= N*2; ++i )
|
||||
{
|
||||
indices2.push_back( make_random_index( static_cast<std::uint32_t>( rng() ) ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class Map> BOOST_NOINLINE void test_insert( Map& map, std::chrono::steady_clock::time_point & t1 )
|
||||
{
|
||||
for( unsigned i = 1; i <= N; ++i )
|
||||
{
|
||||
map.insert( { indices1[ i ], i } );
|
||||
}
|
||||
|
||||
print_time( t1, "Consecutive insert", 0, map.size() );
|
||||
|
||||
for( unsigned i = 1; i <= N; ++i )
|
||||
{
|
||||
map.insert( { indices2[ i ], i } );
|
||||
}
|
||||
|
||||
print_time( t1, "Random insert", 0, map.size() );
|
||||
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
template<class Map> BOOST_NOINLINE void test_lookup( Map& map, std::chrono::steady_clock::time_point & t1 )
|
||||
{
|
||||
std::uint32_t s;
|
||||
|
||||
s = 0;
|
||||
|
||||
for( int j = 0; j < K; ++j )
|
||||
{
|
||||
for( unsigned i = 1; i <= N * 2; ++i )
|
||||
{
|
||||
auto it = map.find( indices1[ i ] );
|
||||
if( it != map.end() ) s += it->second;
|
||||
}
|
||||
}
|
||||
|
||||
print_time( t1, "Consecutive lookup", s, map.size() );
|
||||
|
||||
s = 0;
|
||||
|
||||
for( int j = 0; j < K; ++j )
|
||||
{
|
||||
for( unsigned i = 1; i <= N * 2; ++i )
|
||||
{
|
||||
auto it = map.find( indices2[ i ] );
|
||||
if( it != map.end() ) s += it->second;
|
||||
}
|
||||
}
|
||||
|
||||
print_time( t1, "Random lookup", s, map.size() );
|
||||
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
template<class Map> BOOST_NOINLINE void test_iteration( Map& map, std::chrono::steady_clock::time_point & t1 )
|
||||
{
|
||||
auto it = map.begin();
|
||||
|
||||
while( it != map.end() )
|
||||
{
|
||||
if( it->second & 1 )
|
||||
{
|
||||
if constexpr( std::is_void_v< decltype( map.erase( it ) ) > )
|
||||
{
|
||||
map.erase( it++ );
|
||||
}
|
||||
else
|
||||
{
|
||||
it = map.erase( it );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
print_time( t1, "Iterate and erase odd elements", 0, map.size() );
|
||||
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
template<class Map> BOOST_NOINLINE void test_erase( Map& map, std::chrono::steady_clock::time_point & t1 )
|
||||
{
|
||||
for( unsigned i = 1; i <= N; ++i )
|
||||
{
|
||||
map.erase( indices1[ i ] );
|
||||
}
|
||||
|
||||
print_time( t1, "Consecutive erase", 0, map.size() );
|
||||
|
||||
for( unsigned i = 1; i <= N; ++i )
|
||||
{
|
||||
map.erase( indices2[ i ] );
|
||||
}
|
||||
|
||||
print_time( t1, "Random erase", 0, map.size() );
|
||||
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
// counting allocator
|
||||
|
||||
static std::size_t s_alloc_bytes = 0;
|
||||
static std::size_t s_alloc_count = 0;
|
||||
|
||||
template<class T> struct allocator
|
||||
{
|
||||
using value_type = T;
|
||||
|
||||
allocator() = default;
|
||||
|
||||
template<class U> allocator( allocator<U> const & ) noexcept
|
||||
{
|
||||
}
|
||||
|
||||
template<class U> bool operator==( allocator<U> const & ) const noexcept
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
template<class U> bool operator!=( allocator<U> const& ) const noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
T* allocate( std::size_t n ) const
|
||||
{
|
||||
s_alloc_bytes += n * sizeof(T);
|
||||
s_alloc_count++;
|
||||
|
||||
return std::allocator<T>().allocate( n );
|
||||
}
|
||||
|
||||
void deallocate( T* p, std::size_t n ) const noexcept
|
||||
{
|
||||
s_alloc_bytes -= n * sizeof(T);
|
||||
s_alloc_count--;
|
||||
|
||||
std::allocator<T>().deallocate( p, n );
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
|
||||
struct record
|
||||
{
|
||||
std::string label_;
|
||||
long long time_;
|
||||
std::size_t bytes_;
|
||||
std::size_t count_;
|
||||
};
|
||||
|
||||
static std::vector<record> times;
|
||||
|
||||
template<template<class...> class Map> BOOST_NOINLINE void test( char const* label )
|
||||
{
|
||||
std::cout << label << ":\n\n";
|
||||
|
||||
s_alloc_bytes = 0;
|
||||
s_alloc_count = 0;
|
||||
|
||||
Map<std::string, std::uint32_t> map;
|
||||
|
||||
auto t0 = std::chrono::steady_clock::now();
|
||||
auto t1 = t0;
|
||||
|
||||
test_insert( map, t1 );
|
||||
|
||||
std::cout << "Memory: " << s_alloc_bytes << " bytes in " << s_alloc_count << " allocations\n\n";
|
||||
|
||||
record rec = { label, 0, s_alloc_bytes, s_alloc_count };
|
||||
|
||||
test_lookup( map, t1 );
|
||||
test_iteration( map, t1 );
|
||||
test_lookup( map, t1 );
|
||||
test_erase( map, t1 );
|
||||
|
||||
auto tN = std::chrono::steady_clock::now();
|
||||
std::cout << "Total: " << ( tN - t0 ) / 1ms << " ms\n\n";
|
||||
|
||||
rec.time_ = ( tN - t0 ) / 1ms;
|
||||
times.push_back( rec );
|
||||
}
|
||||
|
||||
// multi_index emulation of unordered_map
|
||||
|
||||
template<class K, class V> struct pair
|
||||
{
|
||||
K first;
|
||||
mutable V second;
|
||||
};
|
||||
|
||||
using namespace boost::multi_index;
|
||||
|
||||
template<class K, class V> using multi_index_map = multi_index_container<
|
||||
pair<K, V>,
|
||||
indexed_by<
|
||||
hashed_unique< member<pair<K, V>, K, &pair<K, V>::first> >
|
||||
>,
|
||||
::allocator< pair<K, V> >
|
||||
>;
|
||||
|
||||
// aliases using the counting allocator
|
||||
|
||||
template<class K, class V> using allocator_for = ::allocator< std::pair<K const, V> >;
|
||||
|
||||
template<class K, class V> using std_unordered_map =
|
||||
std::unordered_map<K, V, std::hash<K>, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
template<class K, class V> using boost_unordered_map =
|
||||
boost::unordered_map<K, V, boost::hash<K>, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
#ifdef HAVE_ABSEIL
|
||||
|
||||
template<class K, class V> using absl_node_hash_map =
|
||||
absl::node_hash_map<K, V, absl::container_internal::hash_default_hash<K>, absl::container_internal::hash_default_eq<K>, allocator_for<K, V>>;
|
||||
|
||||
template<class K, class V> using absl_flat_hash_map =
|
||||
absl::flat_hash_map<K, V, absl::container_internal::hash_default_hash<K>, absl::container_internal::hash_default_eq<K>, allocator_for<K, V>>;
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TSL_HOPSCOTCH
|
||||
|
||||
template<class K, class V> using tsl_hopscotch_map =
|
||||
tsl::hopscotch_map<K, V, std::hash<K>, std::equal_to<K>, ::allocator< std::pair<K, V> >>;
|
||||
|
||||
template<class K, class V> using tsl_hopscotch_pg_map =
|
||||
tsl::hopscotch_pg_map<K, V, std::hash<K>, std::equal_to<K>, ::allocator< std::pair<K, V> >>;
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TSL_ROBIN
|
||||
|
||||
template<class K, class V> using tsl_robin_map =
|
||||
tsl::robin_map<K, V, std::hash<K>, std::equal_to<K>, ::allocator< std::pair<K, V> >>;
|
||||
|
||||
template<class K, class V> using tsl_robin_pg_map =
|
||||
tsl::robin_pg_map<K, V, std::hash<K>, std::equal_to<K>, ::allocator< std::pair<K, V> >>;
|
||||
|
||||
#endif
|
||||
|
||||
// fnv1a_hash
|
||||
|
||||
template<int Bits> struct fnv1a_hash_impl;
|
||||
|
||||
template<> struct fnv1a_hash_impl<32>
|
||||
{
|
||||
std::size_t operator()( std::string const& s ) const
|
||||
{
|
||||
std::size_t h = 0x811C9DC5u;
|
||||
|
||||
char const * first = s.data();
|
||||
char const * last = first + s.size();
|
||||
|
||||
for( ; first != last; ++first )
|
||||
{
|
||||
h ^= static_cast<unsigned char>( *first );
|
||||
h *= 0x01000193ul;
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct fnv1a_hash_impl<64>
|
||||
{
|
||||
std::size_t operator()( std::string const& s ) const
|
||||
{
|
||||
std::size_t h = 0xCBF29CE484222325ull;
|
||||
|
||||
char const * first = s.data();
|
||||
char const * last = first + s.size();
|
||||
|
||||
for( ; first != last; ++first )
|
||||
{
|
||||
h ^= static_cast<unsigned char>( *first );
|
||||
h *= 0x00000100000001B3ull;
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
};
|
||||
|
||||
struct fnv1a_hash: fnv1a_hash_impl< std::numeric_limits<std::size_t>::digits > {};
|
||||
|
||||
template<class K, class V> using std_unordered_map_fnv1a =
|
||||
std::unordered_map<K, V, fnv1a_hash, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
template<class K, class V> using boost_unordered_map_fnv1a =
|
||||
boost::unordered_map<K, V, fnv1a_hash, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
template<class K, class V> using multi_index_map_fnv1a = multi_index_container<
|
||||
pair<K, V>,
|
||||
indexed_by<
|
||||
hashed_unique< member<pair<K, V>, K, &pair<K, V>::first>, fnv1a_hash >
|
||||
>,
|
||||
::allocator< pair<K, V> >
|
||||
>;
|
||||
|
||||
#ifdef HAVE_ABSEIL
|
||||
|
||||
template<class K, class V> using absl_node_hash_map_fnv1a =
|
||||
absl::node_hash_map<K, V, fnv1a_hash, absl::container_internal::hash_default_eq<K>, allocator_for<K, V>>;
|
||||
|
||||
template<class K, class V> using absl_flat_hash_map_fnv1a =
|
||||
absl::flat_hash_map<K, V, fnv1a_hash, absl::container_internal::hash_default_eq<K>, allocator_for<K, V>>;
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TSL_HOPSCOTCH
|
||||
|
||||
template<class K, class V> using tsl_hopscotch_map_fnv1a =
|
||||
tsl::hopscotch_map<K, V, fnv1a_hash, std::equal_to<K>, ::allocator< std::pair<K, V> >>;
|
||||
|
||||
template<class K, class V> using tsl_hopscotch_pg_map_fnv1a =
|
||||
tsl::hopscotch_pg_map<K, V, fnv1a_hash, std::equal_to<K>, ::allocator< std::pair<K, V> >>;
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TSL_ROBIN
|
||||
|
||||
template<class K, class V> using tsl_robin_map_fnv1a =
|
||||
tsl::robin_map<K, V, fnv1a_hash, std::equal_to<K>, ::allocator< std::pair<K, V> >>;
|
||||
|
||||
template<class K, class V> using tsl_robin_pg_map_fnv1a =
|
||||
tsl::robin_pg_map<K, V, fnv1a_hash, std::equal_to<K>, ::allocator< std::pair<K, V> >>;
|
||||
|
||||
#endif
|
||||
|
||||
//
|
||||
|
||||
int main()
|
||||
{
|
||||
init_indices();
|
||||
|
||||
#if 0
|
||||
|
||||
test<std_unordered_map>( "std::unordered_map" );
|
||||
test<boost_unordered_map>( "boost::unordered_map" );
|
||||
test<multi_index_map>( "multi_index_map" );
|
||||
|
||||
#ifdef HAVE_ABSEIL
|
||||
|
||||
test<absl_node_hash_map>( "absl::node_hash_map" );
|
||||
test<absl_flat_hash_map>( "absl::flat_hash_map" );
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TSL_HOPSCOTCH
|
||||
|
||||
test<tsl_hopscotch_map>( "tsl::hopscotch_map" );
|
||||
test<tsl_hopscotch_pg_map>( "tsl::hopscotch_pg_map" );
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TSL_ROBIN
|
||||
|
||||
test<tsl_robin_map>( "tsl::robin_map" );
|
||||
test<tsl_robin_pg_map>( "tsl::robin_pg_map" );
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
test<std_unordered_map_fnv1a>( "std::unordered_map, FNV-1a" );
|
||||
test<boost_unordered_map_fnv1a>( "boost::unordered_map, FNV-1a" );
|
||||
test<multi_index_map_fnv1a>( "multi_index_map, FNV-1a" );
|
||||
|
||||
#ifdef HAVE_ABSEIL
|
||||
|
||||
test<absl_node_hash_map_fnv1a>( "absl::node_hash_map, FNV-1a" );
|
||||
test<absl_flat_hash_map_fnv1a>( "absl::flat_hash_map, FNV-1a" );
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TSL_HOPSCOTCH
|
||||
|
||||
test<tsl_hopscotch_map_fnv1a>( "tsl::hopscotch_map, FNV-1a" );
|
||||
test<tsl_hopscotch_pg_map_fnv1a>( "tsl::hopscotch_pg_map, FNV-1a" );
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TSL_ROBIN
|
||||
|
||||
test<tsl_robin_map_fnv1a>( "tsl::robin_map, FNV-1a" );
|
||||
test<tsl_robin_pg_map_fnv1a>( "tsl::robin_pg_map, FNV-1a" );
|
||||
|
||||
#endif
|
||||
|
||||
std::cout << "---\n\n";
|
||||
|
||||
for( auto const& x: times )
|
||||
{
|
||||
std::cout << std::setw( 31 ) << ( x.label_ + ": " ) << std::setw( 5 ) << x.time_ << " ms, " << std::setw( 9 ) << x.bytes_ << " bytes in " << x.count_ << " allocations\n";
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_ABSEIL
|
||||
# include "absl/container/internal/raw_hash_set.cc"
|
||||
# include "absl/hash/internal/hash.cc"
|
||||
# include "absl/hash/internal/low_level_hash.cc"
|
||||
# include "absl/hash/internal/city.cc"
|
||||
#endif
|
489
benchmark/string_view.cpp
Normal file
@ -0,0 +1,489 @@
|
||||
// Copyright 2021 Peter Dimov.
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// https://www.boost.org/LICENSE_1_0.txt
|
||||
|
||||
#define _SILENCE_CXX17_OLD_ALLOCATOR_MEMBERS_DEPRECATION_WARNING
|
||||
|
||||
#include <boost/unordered_map.hpp>
|
||||
#include <boost/multi_index_container.hpp>
|
||||
#include <boost/multi_index/hashed_index.hpp>
|
||||
#include <boost/multi_index/member.hpp>
|
||||
#include <boost/core/detail/splitmix64.hpp>
|
||||
#include <boost/config.hpp>
|
||||
#ifdef HAVE_ABSEIL
|
||||
# include "absl/container/node_hash_map.h"
|
||||
# include "absl/container/flat_hash_map.h"
|
||||
#endif
|
||||
#ifdef HAVE_TSL_HOPSCOTCH
|
||||
# include "tsl/hopscotch_map.h"
|
||||
#endif
|
||||
#ifdef HAVE_TSL_ROBIN
|
||||
# include "tsl/robin_map.h"
|
||||
#endif
|
||||
#include <unordered_map>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <chrono>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
static void print_time( std::chrono::steady_clock::time_point & t1, char const* label, std::uint32_t s, std::size_t size )
|
||||
{
|
||||
auto t2 = std::chrono::steady_clock::now();
|
||||
|
||||
std::cout << label << ": " << ( t2 - t1 ) / 1ms << " ms (s=" << s << ", size=" << size << ")\n";
|
||||
|
||||
t1 = t2;
|
||||
}
|
||||
|
||||
constexpr unsigned N = 2'000'000;
|
||||
constexpr int K = 10;
|
||||
|
||||
static std::vector<std::string> indices1, indices2;
|
||||
|
||||
static std::string make_index( unsigned x )
|
||||
{
|
||||
char buffer[ 64 ];
|
||||
std::snprintf( buffer, sizeof(buffer), "pfx_%u_sfx", x );
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static std::string make_random_index( unsigned x )
|
||||
{
|
||||
char buffer[ 64 ];
|
||||
std::snprintf( buffer, sizeof(buffer), "pfx_%0*d_%u_sfx", x % 8 + 1, 0, x );
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static void init_indices()
|
||||
{
|
||||
indices1.reserve( N*2+1 );
|
||||
indices1.push_back( make_index( 0 ) );
|
||||
|
||||
for( unsigned i = 1; i <= N*2; ++i )
|
||||
{
|
||||
indices1.push_back( make_index( i ) );
|
||||
}
|
||||
|
||||
indices2.reserve( N*2+1 );
|
||||
indices2.push_back( make_index( 0 ) );
|
||||
|
||||
{
|
||||
boost::detail::splitmix64 rng;
|
||||
|
||||
for( unsigned i = 1; i <= N*2; ++i )
|
||||
{
|
||||
indices2.push_back( make_random_index( static_cast<std::uint32_t>( rng() ) ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class Map> BOOST_NOINLINE void test_insert( Map& map, std::chrono::steady_clock::time_point & t1 )
|
||||
{
|
||||
for( unsigned i = 1; i <= N; ++i )
|
||||
{
|
||||
map.insert( { indices1[ i ], i } );
|
||||
}
|
||||
|
||||
print_time( t1, "Consecutive insert", 0, map.size() );
|
||||
|
||||
for( unsigned i = 1; i <= N; ++i )
|
||||
{
|
||||
map.insert( { indices2[ i ], i } );
|
||||
}
|
||||
|
||||
print_time( t1, "Random insert", 0, map.size() );
|
||||
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
template<class Map> BOOST_NOINLINE void test_lookup( Map& map, std::chrono::steady_clock::time_point & t1 )
|
||||
{
|
||||
std::uint32_t s;
|
||||
|
||||
s = 0;
|
||||
|
||||
for( int j = 0; j < K; ++j )
|
||||
{
|
||||
for( unsigned i = 1; i <= N * 2; ++i )
|
||||
{
|
||||
auto it = map.find( indices1[ i ] );
|
||||
if( it != map.end() ) s += it->second;
|
||||
}
|
||||
}
|
||||
|
||||
print_time( t1, "Consecutive lookup", s, map.size() );
|
||||
|
||||
s = 0;
|
||||
|
||||
for( int j = 0; j < K; ++j )
|
||||
{
|
||||
for( unsigned i = 1; i <= N * 2; ++i )
|
||||
{
|
||||
auto it = map.find( indices2[ i ] );
|
||||
if( it != map.end() ) s += it->second;
|
||||
}
|
||||
}
|
||||
|
||||
print_time( t1, "Random lookup", s, map.size() );
|
||||
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
template<class Map> BOOST_NOINLINE void test_iteration( Map& map, std::chrono::steady_clock::time_point & t1 )
|
||||
{
|
||||
auto it = map.begin();
|
||||
|
||||
while( it != map.end() )
|
||||
{
|
||||
if( it->second & 1 )
|
||||
{
|
||||
if constexpr( std::is_void_v< decltype( map.erase( it ) ) > )
|
||||
{
|
||||
map.erase( it++ );
|
||||
}
|
||||
else
|
||||
{
|
||||
it = map.erase( it );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
print_time( t1, "Iterate and erase odd elements", 0, map.size() );
|
||||
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
template<class Map> BOOST_NOINLINE void test_erase( Map& map, std::chrono::steady_clock::time_point & t1 )
|
||||
{
|
||||
for( unsigned i = 1; i <= N; ++i )
|
||||
{
|
||||
map.erase( indices1[ i ] );
|
||||
}
|
||||
|
||||
print_time( t1, "Consecutive erase", 0, map.size() );
|
||||
|
||||
for( unsigned i = 1; i <= N; ++i )
|
||||
{
|
||||
map.erase( indices2[ i ] );
|
||||
}
|
||||
|
||||
print_time( t1, "Random erase", 0, map.size() );
|
||||
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
// counting allocator
|
||||
|
||||
static std::size_t s_alloc_bytes = 0;
|
||||
static std::size_t s_alloc_count = 0;
|
||||
|
||||
template<class T> struct allocator
|
||||
{
|
||||
using value_type = T;
|
||||
|
||||
allocator() = default;
|
||||
|
||||
template<class U> allocator( allocator<U> const & ) noexcept
|
||||
{
|
||||
}
|
||||
|
||||
template<class U> bool operator==( allocator<U> const & ) const noexcept
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
template<class U> bool operator!=( allocator<U> const& ) const noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
T* allocate( std::size_t n ) const
|
||||
{
|
||||
s_alloc_bytes += n * sizeof(T);
|
||||
s_alloc_count++;
|
||||
|
||||
return std::allocator<T>().allocate( n );
|
||||
}
|
||||
|
||||
void deallocate( T* p, std::size_t n ) const noexcept
|
||||
{
|
||||
s_alloc_bytes -= n * sizeof(T);
|
||||
s_alloc_count--;
|
||||
|
||||
std::allocator<T>().deallocate( p, n );
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
|
||||
struct record
|
||||
{
|
||||
std::string label_;
|
||||
long long time_;
|
||||
std::size_t bytes_;
|
||||
std::size_t count_;
|
||||
};
|
||||
|
||||
static std::vector<record> times;
|
||||
|
||||
template<template<class...> class Map> BOOST_NOINLINE void test( char const* label )
|
||||
{
|
||||
std::cout << label << ":\n\n";
|
||||
|
||||
s_alloc_bytes = 0;
|
||||
s_alloc_count = 0;
|
||||
|
||||
Map<std::string_view, std::uint32_t> map;
|
||||
|
||||
auto t0 = std::chrono::steady_clock::now();
|
||||
auto t1 = t0;
|
||||
|
||||
test_insert( map, t1 );
|
||||
|
||||
std::cout << "Memory: " << s_alloc_bytes << " bytes in " << s_alloc_count << " allocations\n\n";
|
||||
|
||||
record rec = { label, 0, s_alloc_bytes, s_alloc_count };
|
||||
|
||||
test_lookup( map, t1 );
|
||||
test_iteration( map, t1 );
|
||||
test_lookup( map, t1 );
|
||||
test_erase( map, t1 );
|
||||
|
||||
auto tN = std::chrono::steady_clock::now();
|
||||
std::cout << "Total: " << ( tN - t0 ) / 1ms << " ms\n\n";
|
||||
|
||||
rec.time_ = ( tN - t0 ) / 1ms;
|
||||
times.push_back( rec );
|
||||
}
|
||||
|
||||
// multi_index emulation of unordered_map
|
||||
|
||||
template<class K, class V> struct pair
|
||||
{
|
||||
K first;
|
||||
mutable V second;
|
||||
};
|
||||
|
||||
using namespace boost::multi_index;
|
||||
|
||||
template<class K, class V> using multi_index_map = multi_index_container<
|
||||
pair<K, V>,
|
||||
indexed_by<
|
||||
hashed_unique< member<pair<K, V>, K, &pair<K, V>::first> >
|
||||
>,
|
||||
::allocator< pair<K, V> >
|
||||
>;
|
||||
|
||||
// aliases using the counting allocator
|
||||
|
||||
template<class K, class V> using allocator_for = ::allocator< std::pair<K const, V> >;
|
||||
|
||||
template<class K, class V> using std_unordered_map =
|
||||
std::unordered_map<K, V, std::hash<K>, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
template<class K, class V> using boost_unordered_map =
|
||||
boost::unordered_map<K, V, boost::hash<K>, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
#ifdef HAVE_ABSEIL
|
||||
|
||||
template<class K, class V> using absl_node_hash_map =
|
||||
absl::node_hash_map<K, V, absl::container_internal::hash_default_hash<K>, absl::container_internal::hash_default_eq<K>, allocator_for<K, V>>;
|
||||
|
||||
template<class K, class V> using absl_flat_hash_map =
|
||||
absl::flat_hash_map<K, V, absl::container_internal::hash_default_hash<K>, absl::container_internal::hash_default_eq<K>, allocator_for<K, V>>;
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TSL_HOPSCOTCH
|
||||
|
||||
template<class K, class V> using tsl_hopscotch_map =
|
||||
tsl::hopscotch_map<K, V, std::hash<K>, std::equal_to<K>, ::allocator< std::pair<K, V> >>;
|
||||
|
||||
template<class K, class V> using tsl_hopscotch_pg_map =
|
||||
tsl::hopscotch_pg_map<K, V, std::hash<K>, std::equal_to<K>, ::allocator< std::pair<K, V> >>;
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TSL_ROBIN
|
||||
|
||||
template<class K, class V> using tsl_robin_map =
|
||||
tsl::robin_map<K, V, std::hash<K>, std::equal_to<K>, ::allocator< std::pair<K, V> >>;
|
||||
|
||||
template<class K, class V> using tsl_robin_pg_map =
|
||||
tsl::robin_pg_map<K, V, std::hash<K>, std::equal_to<K>, ::allocator< std::pair<K, V> >>;
|
||||
|
||||
#endif
|
||||
|
||||
// fnv1a_hash
|
||||
|
||||
template<int Bits> struct fnv1a_hash_impl;
|
||||
|
||||
template<> struct fnv1a_hash_impl<32>
|
||||
{
|
||||
std::size_t operator()( std::string_view const& s ) const
|
||||
{
|
||||
std::size_t h = 0x811C9DC5u;
|
||||
|
||||
char const * first = s.data();
|
||||
char const * last = first + s.size();
|
||||
|
||||
for( ; first != last; ++first )
|
||||
{
|
||||
h ^= static_cast<unsigned char>( *first );
|
||||
h *= 0x01000193ul;
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct fnv1a_hash_impl<64>
|
||||
{
|
||||
std::size_t operator()( std::string_view const& s ) const
|
||||
{
|
||||
std::size_t h = 0xCBF29CE484222325ull;
|
||||
|
||||
char const * first = s.data();
|
||||
char const * last = first + s.size();
|
||||
|
||||
for( ; first != last; ++first )
|
||||
{
|
||||
h ^= static_cast<unsigned char>( *first );
|
||||
h *= 0x00000100000001B3ull;
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
};
|
||||
|
||||
struct fnv1a_hash: fnv1a_hash_impl< std::numeric_limits<std::size_t>::digits > {};
|
||||
|
||||
template<class K, class V> using std_unordered_map_fnv1a =
|
||||
std::unordered_map<K, V, fnv1a_hash, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
template<class K, class V> using boost_unordered_map_fnv1a =
|
||||
boost::unordered_map<K, V, fnv1a_hash, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
template<class K, class V> using multi_index_map_fnv1a = multi_index_container<
|
||||
pair<K, V>,
|
||||
indexed_by<
|
||||
hashed_unique< member<pair<K, V>, K, &pair<K, V>::first>, fnv1a_hash >
|
||||
>,
|
||||
::allocator< pair<K, V> >
|
||||
>;
|
||||
|
||||
#ifdef HAVE_ABSEIL
|
||||
|
||||
template<class K, class V> using absl_node_hash_map_fnv1a =
|
||||
absl::node_hash_map<K, V, fnv1a_hash, absl::container_internal::hash_default_eq<K>, allocator_for<K, V>>;
|
||||
|
||||
template<class K, class V> using absl_flat_hash_map_fnv1a =
|
||||
absl::flat_hash_map<K, V, fnv1a_hash, absl::container_internal::hash_default_eq<K>, allocator_for<K, V>>;
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TSL_HOPSCOTCH
|
||||
|
||||
template<class K, class V> using tsl_hopscotch_map_fnv1a =
|
||||
tsl::hopscotch_map<K, V, fnv1a_hash, std::equal_to<K>, ::allocator< std::pair<K, V> >>;
|
||||
|
||||
template<class K, class V> using tsl_hopscotch_pg_map_fnv1a =
|
||||
tsl::hopscotch_pg_map<K, V, fnv1a_hash, std::equal_to<K>, ::allocator< std::pair<K, V> >>;
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TSL_ROBIN
|
||||
|
||||
template<class K, class V> using tsl_robin_map_fnv1a =
|
||||
tsl::robin_map<K, V, fnv1a_hash, std::equal_to<K>, ::allocator< std::pair<K, V> >>;
|
||||
|
||||
template<class K, class V> using tsl_robin_pg_map_fnv1a =
|
||||
tsl::robin_pg_map<K, V, fnv1a_hash, std::equal_to<K>, ::allocator< std::pair<K, V> >>;
|
||||
|
||||
#endif
|
||||
|
||||
//
|
||||
|
||||
int main()
|
||||
{
|
||||
init_indices();
|
||||
|
||||
#if 0
|
||||
|
||||
test<std_unordered_map>( "std::unordered_map" );
|
||||
test<boost_unordered_map>( "boost::unordered_map" );
|
||||
test<multi_index_map>( "multi_index_map" );
|
||||
|
||||
#ifdef HAVE_ABSEIL
|
||||
|
||||
test<absl_node_hash_map>( "absl::node_hash_map" );
|
||||
test<absl_flat_hash_map>( "absl::flat_hash_map" );
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TSL_HOPSCOTCH
|
||||
|
||||
test<tsl_hopscotch_map>( "tsl::hopscotch_map" );
|
||||
test<tsl_hopscotch_pg_map>( "tsl::hopscotch_pg_map" );
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TSL_ROBIN
|
||||
|
||||
test<tsl_robin_map>( "tsl::robin_map" );
|
||||
test<tsl_robin_pg_map>( "tsl::robin_pg_map" );
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
test<std_unordered_map_fnv1a>( "std::unordered_map, FNV-1a" );
|
||||
test<boost_unordered_map_fnv1a>( "boost::unordered_map, FNV-1a" );
|
||||
test<multi_index_map_fnv1a>( "multi_index_map, FNV-1a" );
|
||||
|
||||
#ifdef HAVE_ABSEIL
|
||||
|
||||
test<absl_node_hash_map_fnv1a>( "absl::node_hash_map, FNV-1a" );
|
||||
test<absl_flat_hash_map_fnv1a>( "absl::flat_hash_map, FNV-1a" );
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TSL_HOPSCOTCH
|
||||
|
||||
test<tsl_hopscotch_map_fnv1a>( "tsl::hopscotch_map, FNV-1a" );
|
||||
test<tsl_hopscotch_pg_map_fnv1a>( "tsl::hopscotch_pg_map, FNV-1a" );
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TSL_ROBIN
|
||||
|
||||
test<tsl_robin_map_fnv1a>( "tsl::robin_map, FNV-1a" );
|
||||
test<tsl_robin_pg_map_fnv1a>( "tsl::robin_pg_map, FNV-1a" );
|
||||
|
||||
#endif
|
||||
|
||||
std::cout << "---\n\n";
|
||||
|
||||
for( auto const& x: times )
|
||||
{
|
||||
std::cout << std::setw( 31 ) << ( x.label_ + ": " ) << std::setw( 5 ) << x.time_ << " ms, " << std::setw( 9 ) << x.bytes_ << " bytes in " << x.count_ << " allocations\n";
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_ABSEIL
|
||||
# include "absl/container/internal/raw_hash_set.cc"
|
||||
# include "absl/hash/internal/hash.cc"
|
||||
# include "absl/hash/internal/low_level_hash.cc"
|
||||
# include "absl/hash/internal/city.cc"
|
||||
#endif
|
386
benchmark/uint32.cpp
Normal file
@ -0,0 +1,386 @@
|
||||
// Copyright 2021 Peter Dimov.
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// https://www.boost.org/LICENSE_1_0.txt
|
||||
|
||||
#define _SILENCE_CXX17_OLD_ALLOCATOR_MEMBERS_DEPRECATION_WARNING
|
||||
|
||||
#include <boost/unordered_map.hpp>
|
||||
#include <boost/multi_index_container.hpp>
|
||||
#include <boost/multi_index/hashed_index.hpp>
|
||||
#include <boost/multi_index/member.hpp>
|
||||
#include <boost/endian/conversion.hpp>
|
||||
#include <boost/core/detail/splitmix64.hpp>
|
||||
#include <boost/config.hpp>
|
||||
#ifdef HAVE_ABSEIL
|
||||
# include "absl/container/node_hash_map.h"
|
||||
# include "absl/container/flat_hash_map.h"
|
||||
#endif
|
||||
#ifdef HAVE_TSL_HOPSCOTCH
|
||||
# include "tsl/hopscotch_map.h"
|
||||
#endif
|
||||
#ifdef HAVE_TSL_ROBIN
|
||||
# include "tsl/robin_map.h"
|
||||
#endif
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <chrono>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
static void print_time( std::chrono::steady_clock::time_point & t1, char const* label, std::uint32_t s, std::size_t size )
|
||||
{
|
||||
auto t2 = std::chrono::steady_clock::now();
|
||||
|
||||
std::cout << label << ": " << ( t2 - t1 ) / 1ms << " ms (s=" << s << ", size=" << size << ")\n";
|
||||
|
||||
t1 = t2;
|
||||
}
|
||||
|
||||
constexpr unsigned N = 2'000'000;
|
||||
constexpr int K = 10;
|
||||
|
||||
static std::vector< std::uint32_t > indices1, indices2, indices3;
|
||||
|
||||
static void init_indices()
|
||||
{
|
||||
indices1.push_back( 0 );
|
||||
|
||||
for( unsigned i = 1; i <= N*2; ++i )
|
||||
{
|
||||
indices1.push_back( i );
|
||||
}
|
||||
|
||||
indices2.push_back( 0 );
|
||||
|
||||
{
|
||||
boost::detail::splitmix64 rng;
|
||||
|
||||
for( unsigned i = 1; i <= N*2; ++i )
|
||||
{
|
||||
indices2.push_back( static_cast<std::uint32_t>( rng() ) );
|
||||
}
|
||||
}
|
||||
|
||||
indices3.push_back( 0 );
|
||||
|
||||
for( unsigned i = 1; i <= N*2; ++i )
|
||||
{
|
||||
indices3.push_back( boost::endian::endian_reverse( static_cast<std::uint32_t>( i ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
template<class Map> BOOST_NOINLINE void test_insert( Map& map, std::chrono::steady_clock::time_point & t1 )
|
||||
{
|
||||
for( unsigned i = 1; i <= N; ++i )
|
||||
{
|
||||
map.insert( { indices1[ i ], i } );
|
||||
}
|
||||
|
||||
print_time( t1, "Consecutive insert", 0, map.size() );
|
||||
|
||||
for( unsigned i = 1; i <= N; ++i )
|
||||
{
|
||||
map.insert( { indices2[ i ], i } );
|
||||
}
|
||||
|
||||
print_time( t1, "Random insert", 0, map.size() );
|
||||
|
||||
for( unsigned i = 1; i <= N; ++i )
|
||||
{
|
||||
map.insert( { indices3[ i ], i } );
|
||||
}
|
||||
|
||||
print_time( t1, "Consecutive reversed insert", 0, map.size() );
|
||||
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
template<class Map> BOOST_NOINLINE void test_lookup( Map& map, std::chrono::steady_clock::time_point & t1 )
|
||||
{
|
||||
std::uint32_t s;
|
||||
|
||||
s = 0;
|
||||
|
||||
for( int j = 0; j < K; ++j )
|
||||
{
|
||||
for( unsigned i = 1; i <= N * 2; ++i )
|
||||
{
|
||||
auto it = map.find( indices1[ i ] );
|
||||
if( it != map.end() ) s += it->second;
|
||||
}
|
||||
}
|
||||
|
||||
print_time( t1, "Consecutive lookup", s, map.size() );
|
||||
|
||||
s = 0;
|
||||
|
||||
for( int j = 0; j < K; ++j )
|
||||
{
|
||||
for( unsigned i = 1; i <= N * 2; ++i )
|
||||
{
|
||||
auto it = map.find( indices2[ i ] );
|
||||
if( it != map.end() ) s += it->second;
|
||||
}
|
||||
}
|
||||
|
||||
print_time( t1, "Random lookup", s, map.size() );
|
||||
|
||||
s = 0;
|
||||
|
||||
for( int j = 0; j < K; ++j )
|
||||
{
|
||||
for( unsigned i = 1; i <= N * 2; ++i )
|
||||
{
|
||||
auto it = map.find( indices3[ i ] );
|
||||
if( it != map.end() ) s += it->second;
|
||||
}
|
||||
}
|
||||
|
||||
print_time( t1, "Consecutive reversed lookup", s, map.size() );
|
||||
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
template<class Map> BOOST_NOINLINE void test_iteration( Map& map, std::chrono::steady_clock::time_point & t1 )
|
||||
{
|
||||
auto it = map.begin();
|
||||
|
||||
while( it != map.end() )
|
||||
{
|
||||
if( it->second & 1 )
|
||||
{
|
||||
if constexpr( std::is_void_v< decltype( map.erase( it ) ) > )
|
||||
{
|
||||
map.erase( it++ );
|
||||
}
|
||||
else
|
||||
{
|
||||
it = map.erase( it );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
print_time( t1, "Iterate and erase odd elements", 0, map.size() );
|
||||
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
template<class Map> BOOST_NOINLINE void test_erase( Map& map, std::chrono::steady_clock::time_point & t1 )
|
||||
{
|
||||
for( unsigned i = 1; i <= N; ++i )
|
||||
{
|
||||
map.erase( indices1[ i ] );
|
||||
}
|
||||
|
||||
print_time( t1, "Consecutive erase", 0, map.size() );
|
||||
|
||||
for( unsigned i = 1; i <= N; ++i )
|
||||
{
|
||||
map.erase( indices2[ i ] );
|
||||
}
|
||||
|
||||
print_time( t1, "Random erase", 0, map.size() );
|
||||
|
||||
for( unsigned i = 1; i <= N; ++i )
|
||||
{
|
||||
map.erase( indices3[ i ] );
|
||||
}
|
||||
|
||||
print_time( t1, "Consecutive reversed erase", 0, map.size() );
|
||||
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
// counting allocator
|
||||
|
||||
static std::size_t s_alloc_bytes = 0;
|
||||
static std::size_t s_alloc_count = 0;
|
||||
|
||||
template<class T> struct allocator
|
||||
{
|
||||
using value_type = T;
|
||||
|
||||
allocator() = default;
|
||||
|
||||
template<class U> allocator( allocator<U> const & ) noexcept
|
||||
{
|
||||
}
|
||||
|
||||
template<class U> bool operator==( allocator<U> const & ) const noexcept
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
template<class U> bool operator!=( allocator<U> const& ) const noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
T* allocate( std::size_t n ) const
|
||||
{
|
||||
s_alloc_bytes += n * sizeof(T);
|
||||
s_alloc_count++;
|
||||
|
||||
return std::allocator<T>().allocate( n );
|
||||
}
|
||||
|
||||
void deallocate( T* p, std::size_t n ) const noexcept
|
||||
{
|
||||
s_alloc_bytes -= n * sizeof(T);
|
||||
s_alloc_count--;
|
||||
|
||||
std::allocator<T>().deallocate( p, n );
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
|
||||
struct record
|
||||
{
|
||||
std::string label_;
|
||||
long long time_;
|
||||
std::size_t bytes_;
|
||||
std::size_t count_;
|
||||
};
|
||||
|
||||
static std::vector<record> times;
|
||||
|
||||
template<template<class...> class Map> BOOST_NOINLINE void test( char const* label )
|
||||
{
|
||||
std::cout << label << ":\n\n";
|
||||
|
||||
s_alloc_bytes = 0;
|
||||
s_alloc_count = 0;
|
||||
|
||||
Map<std::uint32_t, std::uint32_t> map;
|
||||
|
||||
auto t0 = std::chrono::steady_clock::now();
|
||||
auto t1 = t0;
|
||||
|
||||
test_insert( map, t1 );
|
||||
|
||||
std::cout << "Memory: " << s_alloc_bytes << " bytes in " << s_alloc_count << " allocations\n\n";
|
||||
|
||||
record rec = { label, 0, s_alloc_bytes, s_alloc_count };
|
||||
|
||||
test_lookup( map, t1 );
|
||||
test_iteration( map, t1 );
|
||||
test_lookup( map, t1 );
|
||||
test_erase( map, t1 );
|
||||
|
||||
auto tN = std::chrono::steady_clock::now();
|
||||
std::cout << "Total: " << ( tN - t0 ) / 1ms << " ms\n\n";
|
||||
|
||||
rec.time_ = ( tN - t0 ) / 1ms;
|
||||
times.push_back( rec );
|
||||
}
|
||||
|
||||
// multi_index emulation of unordered_map
|
||||
|
||||
template<class K, class V> struct pair
|
||||
{
|
||||
K first;
|
||||
mutable V second;
|
||||
};
|
||||
|
||||
using namespace boost::multi_index;
|
||||
|
||||
template<class K, class V> using multi_index_map = multi_index_container<
|
||||
pair<K, V>,
|
||||
indexed_by<
|
||||
hashed_unique< member<pair<K, V>, K, &pair<K, V>::first> >
|
||||
>,
|
||||
::allocator< pair<K, V> >
|
||||
>;
|
||||
|
||||
// aliases using the counting allocator
|
||||
|
||||
template<class K, class V> using allocator_for = ::allocator< std::pair<K const, V> >;
|
||||
|
||||
template<class K, class V> using std_unordered_map =
|
||||
std::unordered_map<K, V, std::hash<K>, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
template<class K, class V> using boost_unordered_map =
|
||||
boost::unordered_map<K, V, boost::hash<K>, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
#ifdef HAVE_ABSEIL
|
||||
|
||||
template<class K, class V> using absl_node_hash_map =
|
||||
absl::node_hash_map<K, V, absl::container_internal::hash_default_hash<K>, absl::container_internal::hash_default_eq<K>, allocator_for<K, V>>;
|
||||
|
||||
template<class K, class V> using absl_flat_hash_map =
|
||||
absl::flat_hash_map<K, V, absl::container_internal::hash_default_hash<K>, absl::container_internal::hash_default_eq<K>, allocator_for<K, V>>;
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TSL_HOPSCOTCH
|
||||
|
||||
template<class K, class V> using tsl_hopscotch_map =
|
||||
tsl::hopscotch_map<K, V, std::hash<K>, std::equal_to<K>, ::allocator< std::pair<K, V> >>;
|
||||
|
||||
template<class K, class V> using tsl_hopscotch_pg_map =
|
||||
tsl::hopscotch_pg_map<K, V, std::hash<K>, std::equal_to<K>, ::allocator< std::pair<K, V> >>;
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TSL_ROBIN
|
||||
|
||||
template<class K, class V> using tsl_robin_map =
|
||||
tsl::robin_map<K, V, std::hash<K>, std::equal_to<K>, ::allocator< std::pair<K, V> >>;
|
||||
|
||||
template<class K, class V> using tsl_robin_pg_map =
|
||||
tsl::robin_pg_map<K, V, std::hash<K>, std::equal_to<K>, ::allocator< std::pair<K, V> >>;
|
||||
|
||||
#endif
|
||||
|
||||
int main()
|
||||
{
|
||||
init_indices();
|
||||
|
||||
test<std_unordered_map>( "std::unordered_map" );
|
||||
test<boost_unordered_map>( "boost::unordered_map" );
|
||||
test<multi_index_map>( "multi_index_map" );
|
||||
|
||||
#ifdef HAVE_ABSEIL
|
||||
|
||||
test<absl_node_hash_map>( "absl::node_hash_map" );
|
||||
test<absl_flat_hash_map>( "absl::flat_hash_map" );
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TSL_HOPSCOTCH
|
||||
|
||||
test<tsl_hopscotch_map>( "tsl::hopscotch_map" );
|
||||
test<tsl_hopscotch_pg_map>( "tsl::hopscotch_pg_map" );
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TSL_ROBIN
|
||||
|
||||
test<tsl_robin_map>( "tsl::robin_map" );
|
||||
test<tsl_robin_pg_map>( "tsl::robin_pg_map" );
|
||||
|
||||
#endif
|
||||
|
||||
std::cout << "---\n\n";
|
||||
|
||||
for( auto const& x: times )
|
||||
{
|
||||
std::cout << std::setw( 25 ) << ( x.label_ + ": " ) << std::setw( 5 ) << x.time_ << " ms, " << std::setw( 9 ) << x.bytes_ << " bytes in " << x.count_ << " allocations\n";
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_ABSEIL
|
||||
# include "absl/container/internal/raw_hash_set.cc"
|
||||
# include "absl/hash/internal/hash.cc"
|
||||
# include "absl/hash/internal/low_level_hash.cc"
|
||||
# include "absl/hash/internal/city.cc"
|
||||
#endif
|
386
benchmark/uint64.cpp
Normal file
@ -0,0 +1,386 @@
|
||||
// Copyright 2021 Peter Dimov.
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// https://www.boost.org/LICENSE_1_0.txt
|
||||
|
||||
#define _SILENCE_CXX17_OLD_ALLOCATOR_MEMBERS_DEPRECATION_WARNING
|
||||
|
||||
#include <boost/unordered_map.hpp>
|
||||
#include <boost/multi_index_container.hpp>
|
||||
#include <boost/multi_index/hashed_index.hpp>
|
||||
#include <boost/multi_index/member.hpp>
|
||||
#include <boost/endian/conversion.hpp>
|
||||
#include <boost/core/detail/splitmix64.hpp>
|
||||
#include <boost/config.hpp>
|
||||
#ifdef HAVE_ABSEIL
|
||||
# include "absl/container/node_hash_map.h"
|
||||
# include "absl/container/flat_hash_map.h"
|
||||
#endif
|
||||
#ifdef HAVE_TSL_HOPSCOTCH
|
||||
# include "tsl/hopscotch_map.h"
|
||||
#endif
|
||||
#ifdef HAVE_TSL_ROBIN
|
||||
# include "tsl/robin_map.h"
|
||||
#endif
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <chrono>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
static void print_time( std::chrono::steady_clock::time_point & t1, char const* label, std::uint64_t s, std::size_t size )
|
||||
{
|
||||
auto t2 = std::chrono::steady_clock::now();
|
||||
|
||||
std::cout << label << ": " << ( t2 - t1 ) / 1ms << " ms (s=" << s << ", size=" << size << ")\n";
|
||||
|
||||
t1 = t2;
|
||||
}
|
||||
|
||||
constexpr unsigned N = 2'000'000;
|
||||
constexpr int K = 10;
|
||||
|
||||
static std::vector< std::uint64_t > indices1, indices2, indices3;
|
||||
|
||||
static void init_indices()
|
||||
{
|
||||
indices1.push_back( 0 );
|
||||
|
||||
for( unsigned i = 1; i <= N*2; ++i )
|
||||
{
|
||||
indices1.push_back( i );
|
||||
}
|
||||
|
||||
indices2.push_back( 0 );
|
||||
|
||||
{
|
||||
boost::detail::splitmix64 rng;
|
||||
|
||||
for( unsigned i = 1; i <= N*2; ++i )
|
||||
{
|
||||
indices2.push_back( rng() );
|
||||
}
|
||||
}
|
||||
|
||||
indices3.push_back( 0 );
|
||||
|
||||
for( unsigned i = 1; i <= N*2; ++i )
|
||||
{
|
||||
indices3.push_back( boost::endian::endian_reverse( static_cast<std::uint64_t>( i ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
template<class Map> BOOST_NOINLINE void test_insert( Map& map, std::chrono::steady_clock::time_point & t1 )
|
||||
{
|
||||
for( unsigned i = 1; i <= N; ++i )
|
||||
{
|
||||
map.insert( { indices1[ i ], i } );
|
||||
}
|
||||
|
||||
print_time( t1, "Consecutive insert", 0, map.size() );
|
||||
|
||||
for( unsigned i = 1; i <= N; ++i )
|
||||
{
|
||||
map.insert( { indices2[ i ], i } );
|
||||
}
|
||||
|
||||
print_time( t1, "Random insert", 0, map.size() );
|
||||
|
||||
for( unsigned i = 1; i <= N; ++i )
|
||||
{
|
||||
map.insert( { indices3[ i ], i } );
|
||||
}
|
||||
|
||||
print_time( t1, "Consecutive reversed insert", 0, map.size() );
|
||||
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
template<class Map> BOOST_NOINLINE void test_lookup( Map& map, std::chrono::steady_clock::time_point & t1 )
|
||||
{
|
||||
std::uint64_t s;
|
||||
|
||||
s = 0;
|
||||
|
||||
for( int j = 0; j < K; ++j )
|
||||
{
|
||||
for( unsigned i = 1; i <= N * 2; ++i )
|
||||
{
|
||||
auto it = map.find( indices1[ i ] );
|
||||
if( it != map.end() ) s += it->second;
|
||||
}
|
||||
}
|
||||
|
||||
print_time( t1, "Consecutive lookup", s, map.size() );
|
||||
|
||||
s = 0;
|
||||
|
||||
for( int j = 0; j < K; ++j )
|
||||
{
|
||||
for( unsigned i = 1; i <= N * 2; ++i )
|
||||
{
|
||||
auto it = map.find( indices2[ i ] );
|
||||
if( it != map.end() ) s += it->second;
|
||||
}
|
||||
}
|
||||
|
||||
print_time( t1, "Random lookup", s, map.size() );
|
||||
|
||||
s = 0;
|
||||
|
||||
for( int j = 0; j < K; ++j )
|
||||
{
|
||||
for( unsigned i = 1; i <= N * 2; ++i )
|
||||
{
|
||||
auto it = map.find( indices3[ i ] );
|
||||
if( it != map.end() ) s += it->second;
|
||||
}
|
||||
}
|
||||
|
||||
print_time( t1, "Consecutive reversed lookup", s, map.size() );
|
||||
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
template<class Map> BOOST_NOINLINE void test_iteration( Map& map, std::chrono::steady_clock::time_point & t1 )
|
||||
{
|
||||
auto it = map.begin();
|
||||
|
||||
while( it != map.end() )
|
||||
{
|
||||
if( it->second & 1 )
|
||||
{
|
||||
if constexpr( std::is_void_v< decltype( map.erase( it ) ) > )
|
||||
{
|
||||
map.erase( it++ );
|
||||
}
|
||||
else
|
||||
{
|
||||
it = map.erase( it );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
print_time( t1, "Iterate and erase odd elements", 0, map.size() );
|
||||
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
template<class Map> BOOST_NOINLINE void test_erase( Map& map, std::chrono::steady_clock::time_point & t1 )
|
||||
{
|
||||
for( unsigned i = 1; i <= N; ++i )
|
||||
{
|
||||
map.erase( indices1[ i ] );
|
||||
}
|
||||
|
||||
print_time( t1, "Consecutive erase", 0, map.size() );
|
||||
|
||||
for( unsigned i = 1; i <= N; ++i )
|
||||
{
|
||||
map.erase( indices2[ i ] );
|
||||
}
|
||||
|
||||
print_time( t1, "Random erase", 0, map.size() );
|
||||
|
||||
for( unsigned i = 1; i <= N; ++i )
|
||||
{
|
||||
map.erase( indices3[ i ] );
|
||||
}
|
||||
|
||||
print_time( t1, "Consecutive reversed erase", 0, map.size() );
|
||||
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
// counting allocator
|
||||
|
||||
static std::size_t s_alloc_bytes = 0;
|
||||
static std::size_t s_alloc_count = 0;
|
||||
|
||||
template<class T> struct allocator
|
||||
{
|
||||
using value_type = T;
|
||||
|
||||
allocator() = default;
|
||||
|
||||
template<class U> allocator( allocator<U> const & ) noexcept
|
||||
{
|
||||
}
|
||||
|
||||
template<class U> bool operator==( allocator<U> const & ) const noexcept
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
template<class U> bool operator!=( allocator<U> const& ) const noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
T* allocate( std::size_t n ) const
|
||||
{
|
||||
s_alloc_bytes += n * sizeof(T);
|
||||
s_alloc_count++;
|
||||
|
||||
return std::allocator<T>().allocate( n );
|
||||
}
|
||||
|
||||
void deallocate( T* p, std::size_t n ) const noexcept
|
||||
{
|
||||
s_alloc_bytes -= n * sizeof(T);
|
||||
s_alloc_count--;
|
||||
|
||||
std::allocator<T>().deallocate( p, n );
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
|
||||
struct record
|
||||
{
|
||||
std::string label_;
|
||||
long long time_;
|
||||
std::size_t bytes_;
|
||||
std::size_t count_;
|
||||
};
|
||||
|
||||
static std::vector<record> times;
|
||||
|
||||
template<template<class...> class Map> BOOST_NOINLINE void test( char const* label )
|
||||
{
|
||||
std::cout << label << ":\n\n";
|
||||
|
||||
s_alloc_bytes = 0;
|
||||
s_alloc_count = 0;
|
||||
|
||||
Map<std::uint64_t, std::uint64_t> map;
|
||||
|
||||
auto t0 = std::chrono::steady_clock::now();
|
||||
auto t1 = t0;
|
||||
|
||||
test_insert( map, t1 );
|
||||
|
||||
std::cout << "Memory: " << s_alloc_bytes << " bytes in " << s_alloc_count << " allocations\n\n";
|
||||
|
||||
record rec = { label, 0, s_alloc_bytes, s_alloc_count };
|
||||
|
||||
test_lookup( map, t1 );
|
||||
test_iteration( map, t1 );
|
||||
test_lookup( map, t1 );
|
||||
test_erase( map, t1 );
|
||||
|
||||
auto tN = std::chrono::steady_clock::now();
|
||||
std::cout << "Total: " << ( tN - t0 ) / 1ms << " ms\n\n";
|
||||
|
||||
rec.time_ = ( tN - t0 ) / 1ms;
|
||||
times.push_back( rec );
|
||||
}
|
||||
|
||||
// multi_index emulation of unordered_map
|
||||
|
||||
template<class K, class V> struct pair
|
||||
{
|
||||
K first;
|
||||
mutable V second;
|
||||
};
|
||||
|
||||
using namespace boost::multi_index;
|
||||
|
||||
template<class K, class V> using multi_index_map = multi_index_container<
|
||||
pair<K, V>,
|
||||
indexed_by<
|
||||
hashed_unique< member<pair<K, V>, K, &pair<K, V>::first> >
|
||||
>,
|
||||
::allocator< pair<K, V> >
|
||||
>;
|
||||
|
||||
// aliases using the counting allocator
|
||||
|
||||
template<class K, class V> using allocator_for = ::allocator< std::pair<K const, V> >;
|
||||
|
||||
template<class K, class V> using std_unordered_map =
|
||||
std::unordered_map<K, V, std::hash<K>, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
template<class K, class V> using boost_unordered_map =
|
||||
boost::unordered_map<K, V, boost::hash<K>, std::equal_to<K>, allocator_for<K, V>>;
|
||||
|
||||
#ifdef HAVE_ABSEIL
|
||||
|
||||
template<class K, class V> using absl_node_hash_map =
|
||||
absl::node_hash_map<K, V, absl::container_internal::hash_default_hash<K>, absl::container_internal::hash_default_eq<K>, allocator_for<K, V>>;
|
||||
|
||||
template<class K, class V> using absl_flat_hash_map =
|
||||
absl::flat_hash_map<K, V, absl::container_internal::hash_default_hash<K>, absl::container_internal::hash_default_eq<K>, allocator_for<K, V>>;
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TSL_HOPSCOTCH
|
||||
|
||||
template<class K, class V> using tsl_hopscotch_map =
|
||||
tsl::hopscotch_map<K, V, std::hash<K>, std::equal_to<K>, ::allocator< std::pair<K, V> >>;
|
||||
|
||||
template<class K, class V> using tsl_hopscotch_pg_map =
|
||||
tsl::hopscotch_pg_map<K, V, std::hash<K>, std::equal_to<K>, ::allocator< std::pair<K, V> >>;
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TSL_ROBIN
|
||||
|
||||
template<class K, class V> using tsl_robin_map =
|
||||
tsl::robin_map<K, V, std::hash<K>, std::equal_to<K>, ::allocator< std::pair<K, V> >>;
|
||||
|
||||
template<class K, class V> using tsl_robin_pg_map =
|
||||
tsl::robin_pg_map<K, V, std::hash<K>, std::equal_to<K>, ::allocator< std::pair<K, V> >>;
|
||||
|
||||
#endif
|
||||
|
||||
int main()
|
||||
{
|
||||
init_indices();
|
||||
|
||||
test<std_unordered_map>( "std::unordered_map" );
|
||||
test<boost_unordered_map>( "boost::unordered_map" );
|
||||
test<multi_index_map>( "multi_index_map" );
|
||||
|
||||
#ifdef HAVE_ABSEIL
|
||||
|
||||
test<absl_node_hash_map>( "absl::node_hash_map" );
|
||||
test<absl_flat_hash_map>( "absl::flat_hash_map" );
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TSL_HOPSCOTCH
|
||||
|
||||
test<tsl_hopscotch_map>( "tsl::hopscotch_map" );
|
||||
test<tsl_hopscotch_pg_map>( "tsl::hopscotch_pg_map" );
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TSL_ROBIN
|
||||
|
||||
test<tsl_robin_map>( "tsl::robin_map" );
|
||||
test<tsl_robin_pg_map>( "tsl::robin_pg_map" );
|
||||
|
||||
#endif
|
||||
|
||||
std::cout << "---\n\n";
|
||||
|
||||
for( auto const& x: times )
|
||||
{
|
||||
std::cout << std::setw( 25 ) << ( x.label_ + ": " ) << std::setw( 5 ) << x.time_ << " ms, " << std::setw( 9 ) << x.bytes_ << " bytes in " << x.count_ << " allocations\n";
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_ABSEIL
|
||||
# include "absl/container/internal/raw_hash_set.cc"
|
||||
# include "absl/hash/internal/hash.cc"
|
||||
# include "absl/hash/internal/low_level_hash.cc"
|
||||
# include "absl/hash/internal/city.cc"
|
||||
#endif
|
@ -1,66 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import urllib, os, os.path, sys, json, tarfile, zipfile, tempfile
|
||||
|
||||
def download(snapshot):
|
||||
if snapshot == 'stable':
|
||||
# TODO: Default version/filename if not available?
|
||||
downloads = [
|
||||
"https://sourceforge.net/projects/boost/files/boost/%s/%s.tar.bz2/download" %
|
||||
(os.environ['BOOST_VERSION'], os.environ['BOOST_FILENAME'])]
|
||||
else:
|
||||
json_response = urllib.urlopen('https://api.bintray.com/packages/boostorg/%s/snapshot/files' % (snapshot))
|
||||
x = json.load(json_response)
|
||||
|
||||
extension_priorities = { '.bz2': 2, '.gz': 1, '.zip': 0 }
|
||||
file_list = []
|
||||
version_dates = {}
|
||||
for file in x:
|
||||
file_extension = os.path.splitext(file['path'])[1]
|
||||
if (file_extension in extension_priorities):
|
||||
file['priority'] = extension_priorities[file_extension]
|
||||
file_list.append(file)
|
||||
if not file['version'] in version_dates or file['created'] < version_dates[file['version']]:
|
||||
version_dates[file['version']] = file['created']
|
||||
file_list.sort(key=lambda x: (version_dates[x['version']], x['priority']), reverse=True)
|
||||
downloads = ['http://dl.bintray.com/boostorg/%s/%s' % (snapshot, file['path']) for file in file_list]
|
||||
|
||||
filename = ''
|
||||
for download_url in downloads:
|
||||
try:
|
||||
print "Downloading: " + download_url
|
||||
(filename, headers) = urllib.urlretrieve(download_url)
|
||||
|
||||
print "Extracting: " + filename
|
||||
dir = tempfile.mkdtemp()
|
||||
extract(filename, dir)
|
||||
os.remove(filename)
|
||||
files = os.listdir(dir)
|
||||
assert(len(files) == 1)
|
||||
os.rename(os.path.join(dir, files[0]), 'boost')
|
||||
return
|
||||
except IOError:
|
||||
print "Error opening URL: " + download_url
|
||||
|
||||
def extract(filename, path = '.'):
|
||||
if (filename.endswith(".gz")):
|
||||
tar = tarfile.open(filename, "r:gz")
|
||||
tar.extractall(path)
|
||||
tar.close
|
||||
elif (filename.endswith(".bz2")):
|
||||
tar = tarfile.open(filename, "r:bz2")
|
||||
tar.extractall(path)
|
||||
tar.close
|
||||
elif (filename.endswith(".zip")):
|
||||
zip = zipfile.ZipFile(filename, "r")
|
||||
zip.extractall(path)
|
||||
zip.close
|
||||
else:
|
||||
assert False
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
download('stable')
|
||||
elif len(sys.argv) == 2:
|
||||
download(sys.argv[1])
|
||||
else:
|
||||
print "Usage: %s [stable|branch-name]" % (sys.argv[0])
|
@ -1,72 +1,21 @@
|
||||
|
||||
# Copyright 2005 Daniel James.
|
||||
# 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)
|
||||
|
||||
using boostbook ;
|
||||
using quickbook ;
|
||||
import asciidoctor ;
|
||||
|
||||
path-constant images_location : ../ ;
|
||||
path-constant admonishment_location : ../../../../doc/src/images ;
|
||||
html unordered.html : unordered.adoc ;
|
||||
|
||||
xml unordered : unordered.qbk :
|
||||
<xsl:param>generate.consistent.ids=1 ;
|
||||
install html_ : unordered.html : <location>html ;
|
||||
|
||||
boostbook standalone : unordered :
|
||||
<xsl:param>chunk.first.sections=1
|
||||
<xsl:param>chunk.section.depth=2
|
||||
<xsl:param>generate.section.toc.level=2
|
||||
<xsl:param>toc.section.depth=1
|
||||
<xsl:param>toc.max.depth=1
|
||||
pdf unordered.pdf : unordered.adoc ;
|
||||
explicit unordered.pdf ;
|
||||
|
||||
<xsl:param>boost.compact.typedef=0
|
||||
<xsl:param>boost.compact.function=0
|
||||
<xsl:param>boost.compact.enum=0
|
||||
|
||||
<xsl:param>generate.consistent.ids=1
|
||||
|
||||
# HTML Options:
|
||||
|
||||
<format>html:<xsl:param>boost.root=../../../..
|
||||
<format>html:<xsl:param>img.src.path=../../../../doc/html/
|
||||
<format>xhtml:<xsl:param>boost.root=../../../..
|
||||
<format>xhtml:<xsl:param>img.src.path=../../../../doc/html/
|
||||
|
||||
# PDF Options:
|
||||
|
||||
# TOC Generation: this is needed for FOP-0.9 and later:
|
||||
<xsl:param>fop1.extensions=0
|
||||
<format>pdf:<xsl:param>xep.extensions=1
|
||||
# TOC generation: this is needed for FOP 0.2, but must not be set to zero for FOP-0.9!
|
||||
<format>pdf:<xsl:param>fop.extensions=0
|
||||
# No indent on body text:
|
||||
<format>pdf:<xsl:param>body.start.indent=0pt
|
||||
# Margin size:
|
||||
<format>pdf:<xsl:param>page.margin.inner=0.5in
|
||||
# Margin size:
|
||||
<format>pdf:<xsl:param>page.margin.outer=0.5in
|
||||
# Paper type = A4
|
||||
<format>pdf:<xsl:param>paper.type=A4
|
||||
# Yes, we want graphics for admonishments:
|
||||
<xsl:param>admon.graphics=1
|
||||
# Set this one for PDF generation *only*:
|
||||
# default png graphics are awful in PDF form,
|
||||
# better use SVG's instead:
|
||||
<format>pdf:<xsl:param>admon.graphics.extension=".svg"
|
||||
<format>pdf:<xsl:param>use.role.for.mediaobject=1
|
||||
<format>pdf:<xsl:param>preferred.mediaobject.role=print
|
||||
<format>pdf:<xsl:param>img.src.path=$(images_location)/
|
||||
#<format>pdf:<xsl:param>admon.graphics.path=$(admonishment_location)
|
||||
<format>pdf:<xsl:param>draft.mode="no"
|
||||
<format>pdf:<xsl:param>boost.url.prefix=http://www.boost.org/doc/libs/release/libs/unordered/doc/html
|
||||
;
|
||||
install pdf_ : unordered.pdf : <location>pdf ;
|
||||
explicit pdf_ ;
|
||||
|
||||
###############################################################################
|
||||
alias boostdoc
|
||||
: unordered
|
||||
:
|
||||
:
|
||||
: ;
|
||||
alias boostdoc ;
|
||||
explicit boostdoc ;
|
||||
alias boostrelease ;
|
||||
alias boostrelease : html_ ;
|
||||
explicit boostrelease ;
|
||||
|
@ -1,26 +0,0 @@
|
||||
<!--
|
||||
Copyright Daniel James 2008-2009
|
||||
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)
|
||||
-->
|
||||
<section id="unordered.bibliography">
|
||||
<title>Bibliography</title>
|
||||
<bibliography>
|
||||
<biblioentry>
|
||||
<biblioset relation="journal">
|
||||
<title>C/C++ Users Journal</title>
|
||||
<date>February, 2006</date>
|
||||
</biblioset>
|
||||
<biblioset relation="article">
|
||||
<authorgroup>
|
||||
<author>
|
||||
<firstname>Pete</firstname>
|
||||
<surname>Becker</surname>
|
||||
</author>
|
||||
</authorgroup>
|
||||
<title><ulink url="http://www.ddj.com/cpp/184402066">STL and TR1: Part III - Unordered containers</ulink></title>
|
||||
</biblioset>
|
||||
<para>An introducation to the standard unordered containers.</para>
|
||||
</biblioentry>
|
||||
</bibliography>
|
||||
</section>
|
168
doc/buckets.qbk
@ -1,168 +0,0 @@
|
||||
[/ Copyright 2006-2008 Daniel James.
|
||||
/ 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) ]
|
||||
|
||||
[section:buckets The Data Structure]
|
||||
|
||||
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 an [classref
|
||||
boost::unordered_set 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).
|
||||
|
||||
[diagram buckets]
|
||||
|
||||
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
|
||||
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
|
||||
that value to choose a bucket to place the element in.
|
||||
|
||||
Retrieving the elements for a given key is simple. The same process is applied
|
||||
to the key to find the correct bucket. Then the key is compared with the
|
||||
elements in the bucket to find any elements that match (using the equality
|
||||
predicate `Pred`). If the hash function has worked well the elements will be
|
||||
evenly distributed amongst the buckets so only a small number of elements will
|
||||
need to be examined.
|
||||
|
||||
There is [link unordered.hash_equality more information on hash functions and
|
||||
equality predicates in the next section].
|
||||
|
||||
You can see in the diagram that `A` & `D` have been placed in the same bucket.
|
||||
When looking for elements in this bucket up to 2 comparisons are made, making
|
||||
the search slower. This is known as a collision. To keep things fast we try to
|
||||
keep collisions to a minimum.
|
||||
|
||||
'''
|
||||
<table frame="all"><title>Methods for Accessing Buckets</title>
|
||||
<tgroup cols="2">
|
||||
<thead><row>
|
||||
<entry><para>Method</para></entry>
|
||||
<entry><para>Description</para></entry>
|
||||
</row></thead>
|
||||
<tbody>
|
||||
<row>
|
||||
<entry>'''`size_type bucket_count() const`'''</entry>
|
||||
<entry>'''The number of buckets.'''</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>'''`size_type max_bucket_count() const`'''</entry>
|
||||
<entry>'''An upper bound on the number of buckets.'''</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>'''`size_type bucket_size(size_type n) const`'''</entry>
|
||||
<entry>'''The number of elements in bucket `n`.'''</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>'''`size_type bucket(key_type const& k) const`'''</entry>
|
||||
<entry>'''Returns the index of the bucket which would contain `k`.'''</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>'''`local_iterator begin(size_type n);`'''</entry>
|
||||
<entry morerows='5'>'''Return begin and end iterators for bucket `n`.'''</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>'''`local_iterator end(size_type n);`'''</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>'''`const_local_iterator begin(size_type n) const;`'''</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>'''`const_local_iterator end(size_type n) const;`'''</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>'''`const_local_iterator cbegin(size_type n) const;`'''</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>'''`const_local_iterator cend(size_type n) const;`'''</entry>
|
||||
</row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</table>
|
||||
'''
|
||||
|
||||
[h2 Controlling the number of buckets]
|
||||
|
||||
As more elements are added to an unordered associative container, the number
|
||||
of elements in the buckets will increase causing performance to degrade.
|
||||
To combat this the containers increase the bucket count as elements are inserted.
|
||||
You can also tell the container to change the bucket count (if required) by
|
||||
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 average number of elements per bucket.
|
||||
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
|
||||
influence it:
|
||||
|
||||
* Specify the minimum number of buckets when constructing a container or
|
||||
when calling `rehash`.
|
||||
* Suggest a maximum load factor by calling `max_load_factor`.
|
||||
|
||||
`max_load_factor` doesn't let you set the maximum load factor yourself, it just
|
||||
lets you give a /hint/. And even then, the draft standard doesn't actually
|
||||
require the container to pay much attention to this value. The only time the
|
||||
load factor is /required/ to be less than the maximum is following a call to
|
||||
`rehash`. But most implementations will try to keep the number of elements
|
||||
below the max load factor, and set the maximum load factor to be the same as
|
||||
or close to the hint - unless your hint is unreasonably small or large.
|
||||
|
||||
[table:bucket_size Methods for Controlling Bucket Size
|
||||
[[Method] [Description]]
|
||||
|
||||
[
|
||||
[`X(size_type n)`]
|
||||
[Construct an empty container with at least `n` buckets (`X` is the container type).]
|
||||
]
|
||||
[
|
||||
[`X(InputIterator i, InputIterator j, size_type n)`]
|
||||
[Construct an empty container with at least `n` buckets and insert elements
|
||||
from the range \[`i`, `j`) (`X` is the container type).]
|
||||
]
|
||||
[
|
||||
[`float load_factor() const`]
|
||||
[The average number of elements per bucket.]
|
||||
]
|
||||
[
|
||||
[`float max_load_factor() const`]
|
||||
[Returns the current maximum load factor.]
|
||||
]
|
||||
[
|
||||
[`float max_load_factor(float z)`]
|
||||
[Changes the container's maximum load factor, using `z` as a hint.]
|
||||
]
|
||||
[
|
||||
[`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.]
|
||||
]
|
||||
|
||||
]
|
||||
|
||||
[h2 Iterator Invalidation]
|
||||
|
||||
It is not specified how member functions other than `rehash` affect
|
||||
the bucket count, although `insert` is only allowed to invalidate iterators
|
||||
when the insertion causes the load factor to be greater than or equal to the
|
||||
maximum load factor. For most implementations this means that `insert` will only
|
||||
change the number of buckets when this happens. While iterators can be
|
||||
invalidated by calls to `insert` and `rehash`, pointers and references to the
|
||||
container's elements are never invalidated.
|
||||
|
||||
In a similar manner to using `reserve` for `vector`s, it can be a good idea
|
||||
to call `rehash` 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.rehash((x.size() + n) / x.max_load_factor());
|
||||
|
||||
[blurb Note: `rehash`'s argument is the minimum number of buckets, not the
|
||||
number of elements, which is why the new size is divided by the maximum load factor.]
|
||||
|
||||
[endsect]
|
@ -1,161 +0,0 @@
|
||||
[/ Copyright 2006-2011 Daniel James.
|
||||
/ 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) ]
|
||||
|
||||
[section:comparison Comparison with Associative Containers]
|
||||
|
||||
[table:interface_differences Interface differences.
|
||||
[[Associative Containers] [Unordered Associative Containers]]
|
||||
|
||||
[
|
||||
[Parameterized by an ordering relation `Compare`]
|
||||
[Parameterized by a function object `Hash` and an equivalence relation
|
||||
`Pred`]
|
||||
]
|
||||
[
|
||||
[Keys can be compared using `key_compare` which is accessed by member function `key_comp()`,
|
||||
values can be compared using `value_compare` which is accessed by member function `value_comp()`.]
|
||||
[Keys can be hashed using `hasher` which is accessed by member function `hash_function()`,
|
||||
and checked for equality using `key_equal` which is accessed by member function `key_eq()`.
|
||||
There is no function object for compared or hashing values.]
|
||||
]
|
||||
[
|
||||
[Constructors have optional extra parameters for the comparison object.]
|
||||
[Constructors have optional extra parameters for the initial minimum
|
||||
number of buckets, a hash function and an equality object.]
|
||||
]
|
||||
|
||||
[
|
||||
[Keys `k1`, `k2` are considered equivalent if
|
||||
`!Compare(k1, k2) && !Compare(k2, k1)`]
|
||||
[Keys `k1`, `k2` are considered equivalent if `Pred(k1, k2)`]
|
||||
]
|
||||
[
|
||||
[Member function `lower_bound(k)` and `upper_bound(k)`]
|
||||
[No equivalent. Since the elements aren't ordered `lower_bound` and
|
||||
`upper_bound` would be meaningless.]
|
||||
]
|
||||
[
|
||||
[`equal_range(k)` returns an empty range at the position that k
|
||||
would be inserted if k isn't present in the container.]
|
||||
[`equal_range(k)` returns a range at the end of the container if
|
||||
k isn't present in the container. It can't return a positioned
|
||||
range as k could be inserted into multiple place. To find out the
|
||||
bucket that k would be inserted into use `bucket(k)`. But remember
|
||||
that an insert can cause the container to rehash - meaning that the
|
||||
element can be inserted into a different bucket.]
|
||||
]
|
||||
[
|
||||
[`iterator`, `const_iterator` are of the bidirectional category.]
|
||||
[`iterator`, `const_iterator` are of at least the forward category.]
|
||||
]
|
||||
[
|
||||
[Iterators, pointers and references to the container's elements are
|
||||
never invalidated.]
|
||||
[[link unordered.buckets.iterator_invalidation Iterators can
|
||||
be invalidated by calls to insert or rehash]. Pointers and
|
||||
references to the container's elements are never invalidated.]
|
||||
]
|
||||
[
|
||||
[Iterators iterate through the container in the order defined by
|
||||
the comparison object.]
|
||||
[Iterators iterate through the container in an arbitrary order, that
|
||||
can change as elements are inserted, although equivalent elements
|
||||
are always adjacent.]
|
||||
]
|
||||
[
|
||||
[No equivalent]
|
||||
[Local iterators can be used to iterate through individual buckets.
|
||||
(The order of local iterators and iterators aren't
|
||||
required to have any correspondence.)]
|
||||
]
|
||||
[
|
||||
[Can be compared using the `==`, `!=`, `<`, `<=`, `>`, `>=` operators.]
|
||||
[Can be compared using the `==` and `!=` operators.]
|
||||
]
|
||||
[
|
||||
[]
|
||||
[When inserting with a hint, implementations are permitted to ignore
|
||||
the hint.]
|
||||
]
|
||||
[
|
||||
[`erase` never throws an exception]
|
||||
[The containers' hash or predicate function can throw exceptions
|
||||
from `erase`]
|
||||
]
|
||||
]
|
||||
|
||||
[table:complexity_guarantees Complexity Guarantees
|
||||
[[Operation] [Associative Containers] [Unordered Associative Containers]]
|
||||
[
|
||||
[Construction of empty container]
|
||||
[constant]
|
||||
[O(/n/) where /n/ is the minimum number of buckets.]
|
||||
]
|
||||
[
|
||||
[Construction of container from a range of /N/ elements]
|
||||
[O(/N/ log /N/), O(/N/) if the range is sorted with `value_comp()`]
|
||||
[Average case O(/N/), worst case
|
||||
O(/N/'''<superscript>2</superscript>''')]
|
||||
]
|
||||
[
|
||||
[Insert a single element]
|
||||
[logarithmic]
|
||||
[Average case constant, worst case linear]
|
||||
]
|
||||
[
|
||||
[Insert a single element with a hint]
|
||||
[Amortized constant if t elements inserted right after hint,
|
||||
logarithmic otherwise]
|
||||
[Average case constant, worst case linear (ie. the same as
|
||||
a normal insert).]
|
||||
]
|
||||
[
|
||||
[Inserting a range of /N/ elements]
|
||||
[ /N/ log(`size()`+/N/) ]
|
||||
[Average case O(/N/), worst case O(/N/ * `size()`)]
|
||||
]
|
||||
[
|
||||
[Erase by key, `k`]
|
||||
[O(log(`size()`) + `count(k)`)]
|
||||
[Average case: O(`count(k)`), Worst case: O(`size()`)]
|
||||
]
|
||||
[
|
||||
[Erase a single element by iterator]
|
||||
[Amortized constant]
|
||||
[Average case: O(1), Worst case: O(`size()`)]
|
||||
]
|
||||
[
|
||||
[Erase a range of /N/ elements]
|
||||
[O(log(`size()`) + /N/)]
|
||||
[Average case: O(/N/), Worst case: O(`size()`)]
|
||||
]
|
||||
[
|
||||
[Clearing the container]
|
||||
[O(`size()`)]
|
||||
[O(`size()`)]
|
||||
]
|
||||
[
|
||||
[Find]
|
||||
[logarithmic]
|
||||
[Average case: O(1), Worst case: O(`size()`)]
|
||||
]
|
||||
[/ TODO: Average case is probably wrong. ]
|
||||
[
|
||||
[Count]
|
||||
[O(log(`size()`) + `count(k)`)]
|
||||
[Average case: O(1), Worst case: O(`size()`)]
|
||||
]
|
||||
[
|
||||
[`equal_range(k)`]
|
||||
[logarithmic]
|
||||
[Average case: O(`count(k)`), Worst case: O(`size()`)]
|
||||
]
|
||||
[
|
||||
[`lower_bound`,`upper_bound`]
|
||||
[logarithmic]
|
||||
[n/a]
|
||||
]
|
||||
]
|
||||
|
||||
[endsect]
|
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 31 KiB |
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 31 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 35 KiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 32 KiB |
BIN
doc/diagrams/benchmarks/gcc/running insertion.xlsx.practice.png
Normal file
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 33 KiB |
BIN
doc/diagrams/benchmarks/gcc/scattered erasure.xlsx.practice.png
Normal file
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 35 KiB |
After Width: | Height: | Size: 35 KiB |
After Width: | Height: | Size: 38 KiB |
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 35 KiB |
After Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 38 KiB |
BIN
doc/diagrams/benchmarks/vs/running insertion.xlsx.practice.png
Normal file
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 35 KiB |
After Width: | Height: | Size: 34 KiB |
BIN
doc/diagrams/benchmarks/vs/scattered erasure.xlsx.practice.png
Normal file
After Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 49 KiB |
After Width: | Height: | Size: 43 KiB |
After Width: | Height: | Size: 42 KiB |
BIN
doc/diagrams/bucket-groups.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
@ -1,313 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.0"
|
||||
width="507.85925"
|
||||
height="400.45422"
|
||||
viewBox="1.33 0.95 6.01 4.09"
|
||||
id="svg2">
|
||||
<defs
|
||||
id="defs95" />
|
||||
<rect
|
||||
width="6.0023117"
|
||||
height="4.0815721"
|
||||
x="1.3310578"
|
||||
y="0.95080346"
|
||||
id="rect4"
|
||||
style="fill:#e5e5e5;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
width="6.0023117"
|
||||
height="4.0815721"
|
||||
x="1.3310578"
|
||||
y="0.95080346"
|
||||
id="rect6"
|
||||
style="opacity:1;fill:none;stroke:#000000;stroke-width:0.02400924" />
|
||||
<rect
|
||||
width="1.2004625"
|
||||
height="1.6806473"
|
||||
x="1.5711501"
|
||||
y="1.1908962"
|
||||
id="rect8"
|
||||
style="fill:#ffffff;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
width="1.2004625"
|
||||
height="1.6806473"
|
||||
x="1.5711501"
|
||||
y="1.1908962"
|
||||
id="rect10"
|
||||
style="fill:none;stroke:#000000;stroke-width:0.02400924" />
|
||||
<text
|
||||
x="1.7289008"
|
||||
y="1.4950322"
|
||||
id="text12"
|
||||
style="font-size:0.19207397px;font-style:normal;font-weight:400;text-anchor:start;fill:#000000;font-family:sans">Bucket 1</text>
|
||||
<line
|
||||
x1="1.5711501"
|
||||
y1="1.6710808"
|
||||
x2="2.7716124"
|
||||
y2="1.6710808"
|
||||
id="line14"
|
||||
style="stroke:#000000;stroke-width:0.02400924" />
|
||||
<rect
|
||||
width="1.2004625"
|
||||
height="1.6806473"
|
||||
x="3.0117054"
|
||||
y="1.1908962"
|
||||
id="rect16"
|
||||
style="fill:#ffffff;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
width="1.2004625"
|
||||
height="1.6806473"
|
||||
x="3.0117054"
|
||||
y="1.1908962"
|
||||
id="rect18"
|
||||
style="fill:none;stroke:#000000;stroke-width:0.02400924" />
|
||||
<text
|
||||
x="3.1603069"
|
||||
y="1.4950322"
|
||||
id="text20"
|
||||
style="font-size:0.19207397px;font-style:normal;font-weight:400;text-anchor:start;fill:#000000;font-family:sans">Bucket 2</text>
|
||||
<line
|
||||
x1="3.0117054"
|
||||
y1="1.6710808"
|
||||
x2="4.2121677"
|
||||
y2="1.6710808"
|
||||
id="line22"
|
||||
style="stroke:#000000;stroke-width:0.02400924" />
|
||||
<rect
|
||||
width="1.2004625"
|
||||
height="1.6806473"
|
||||
x="4.4522605"
|
||||
y="1.1908962"
|
||||
id="rect24"
|
||||
style="fill:#ffffff;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
width="1.2004625"
|
||||
height="1.6806473"
|
||||
x="4.4522605"
|
||||
y="1.1908962"
|
||||
id="rect26"
|
||||
style="fill:none;stroke:#000000;stroke-width:0.02400924" />
|
||||
<text
|
||||
x="4.5917125"
|
||||
y="1.4950322"
|
||||
id="text28"
|
||||
style="font-size:0.19207397px;font-style:normal;font-weight:400;text-anchor:start;fill:#000000;font-family:sans">Bucket 3</text>
|
||||
<line
|
||||
x1="4.4522605"
|
||||
y1="1.6710808"
|
||||
x2="5.6527228"
|
||||
y2="1.6710808"
|
||||
id="line30"
|
||||
style="stroke:#000000;stroke-width:0.02400924" />
|
||||
<rect
|
||||
width="1.2004625"
|
||||
height="1.6806473"
|
||||
x="5.8928151"
|
||||
y="1.1908962"
|
||||
id="rect32"
|
||||
style="fill:#ffffff;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
width="1.2004625"
|
||||
height="1.6806473"
|
||||
x="5.8928151"
|
||||
y="1.1908962"
|
||||
id="rect34"
|
||||
style="fill:none;stroke:#000000;stroke-width:0.02400924" />
|
||||
<text
|
||||
x="6.0688629"
|
||||
y="1.4858831"
|
||||
id="text36"
|
||||
style="font-size:0.19207397px;font-style:normal;font-weight:400;text-anchor:start;fill:#000000;font-family:sans">Bucket 4</text>
|
||||
<line
|
||||
x1="5.8928151"
|
||||
y1="1.6710808"
|
||||
x2="7.093277"
|
||||
y2="1.6710808"
|
||||
id="line38"
|
||||
style="stroke:#000000;stroke-width:0.02400924" />
|
||||
<rect
|
||||
width="1.2004625"
|
||||
height="1.6806473"
|
||||
x="2.2941716"
|
||||
y="3.1054616"
|
||||
id="rect40"
|
||||
style="fill:#ffffff;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
width="1.2004625"
|
||||
height="1.6806473"
|
||||
x="2.2941716"
|
||||
y="3.1054616"
|
||||
id="rect42"
|
||||
style="fill:none;stroke:#000000;stroke-width:0.02400924" />
|
||||
<text
|
||||
x="2.4427731"
|
||||
y="3.4187472"
|
||||
id="text44"
|
||||
style="font-size:0.19207397px;font-style:normal;font-weight:400;text-anchor:start;fill:#000000;font-family:sans">Bucket 5</text>
|
||||
<line
|
||||
x1="2.2941716"
|
||||
y1="3.5856469"
|
||||
x2="3.4946339"
|
||||
y2="3.5856469"
|
||||
id="line46"
|
||||
style="stroke:#000000;stroke-width:0.02400924" />
|
||||
<rect
|
||||
width="1.2004625"
|
||||
height="1.6806473"
|
||||
x="3.7347264"
|
||||
y="3.1054616"
|
||||
id="rect48"
|
||||
style="fill:#ffffff;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
width="1.2004625"
|
||||
height="1.6806473"
|
||||
x="3.7347264"
|
||||
y="3.1054616"
|
||||
id="rect50"
|
||||
style="fill:none;stroke:#000000;stroke-width:0.02400924" />
|
||||
<text
|
||||
x="3.8833277"
|
||||
y="3.4187472"
|
||||
id="text52"
|
||||
style="font-size:0.19207397px;font-style:normal;font-weight:400;text-anchor:start;fill:#000000;font-family:sans">Bucket 6</text>
|
||||
<line
|
||||
x1="3.7347264"
|
||||
y1="3.5856469"
|
||||
x2="4.9351892"
|
||||
y2="3.5856469"
|
||||
id="line54"
|
||||
style="stroke:#000000;stroke-width:0.02400924" />
|
||||
<rect
|
||||
width="1.2004625"
|
||||
height="1.6806473"
|
||||
x="5.175281"
|
||||
y="3.1054616"
|
||||
id="rect56"
|
||||
style="fill:#ffffff;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
width="1.2004625"
|
||||
height="1.6806473"
|
||||
x="5.175281"
|
||||
y="3.1054616"
|
||||
id="rect58"
|
||||
style="fill:none;stroke:#000000;stroke-width:0.02400924" />
|
||||
<text
|
||||
x="5.3330317"
|
||||
y="3.4187472"
|
||||
id="text60"
|
||||
style="font-size:0.19207397px;font-style:normal;font-weight:400;text-anchor:start;fill:#000000;font-family:sans">Bucket 7</text>
|
||||
<line
|
||||
x1="5.175281"
|
||||
y1="3.5856469"
|
||||
x2="6.3757439"
|
||||
y2="3.5856469"
|
||||
id="line62"
|
||||
style="stroke:#000000;stroke-width:0.02400924" />
|
||||
<ellipse
|
||||
cx="7.1999998"
|
||||
cy="4.0110002"
|
||||
rx="0.308"
|
||||
ry="0.308"
|
||||
transform="matrix(0.6859785,0,0,0.6859785,1.3310577,-0.7298436)"
|
||||
id="ellipse64"
|
||||
style="fill:#ffffff;stroke:none" />
|
||||
<ellipse
|
||||
cx="7.1999998"
|
||||
cy="4.0110002"
|
||||
rx="0.308"
|
||||
ry="0.308"
|
||||
transform="matrix(0.6859785,0,0,0.6859785,1.3310577,-0.7298436)"
|
||||
id="ellipse66"
|
||||
style="fill:none;stroke:#000000;stroke-width:0.035" />
|
||||
<text
|
||||
x="6.1443377"
|
||||
y="2.1364057"
|
||||
id="text68"
|
||||
style="font-size:0.34038281px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;font-family:Sans;-inkscape-font-specification:Sans">A</text>
|
||||
<ellipse
|
||||
cx="3.007"
|
||||
cy="4.0300002"
|
||||
rx="0.308"
|
||||
ry="0.308"
|
||||
transform="matrix(0.6859785,0,0,0.6859785,1.3310577,-0.7298436)"
|
||||
id="ellipse70"
|
||||
style="fill:#ffffff;stroke:none" />
|
||||
<ellipse
|
||||
cx="3.007"
|
||||
cy="4.0300002"
|
||||
rx="0.308"
|
||||
ry="0.308"
|
||||
transform="matrix(0.6859785,0,0,0.6859785,1.3310577,-0.7298436)"
|
||||
id="ellipse72"
|
||||
style="fill:none;stroke:#000000;stroke-width:0.035" />
|
||||
<text
|
||||
x="3.2742035"
|
||||
y="2.1540098"
|
||||
id="text74"
|
||||
style="font-size:0.34038281px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;font-family:Sans;-inkscape-font-specification:Sans">B</text>
|
||||
<ellipse
|
||||
cx="4.0599999"
|
||||
cy="6.7820001"
|
||||
rx="0.308"
|
||||
ry="0.308"
|
||||
transform="matrix(0.6859785,0,0,0.6859785,1.3310577,-0.7298436)"
|
||||
id="ellipse76"
|
||||
style="fill:#ffffff;stroke:none" />
|
||||
<ellipse
|
||||
cx="4.0599999"
|
||||
cy="6.7820001"
|
||||
rx="0.308"
|
||||
ry="0.308"
|
||||
transform="matrix(0.6859785,0,0,0.6859785,1.3310577,-0.7298436)"
|
||||
id="ellipse78"
|
||||
style="fill:none;stroke:#000000;stroke-width:0.035" />
|
||||
<text
|
||||
x="3.976877"
|
||||
y="4.0473108"
|
||||
id="text80"
|
||||
style="font-size:0.34038281px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;font-family:Sans;-inkscape-font-specification:Sans">C</text>
|
||||
<ellipse
|
||||
cx="7.8449998"
|
||||
cy="4.6550002"
|
||||
rx="0.308"
|
||||
ry="0.308"
|
||||
transform="matrix(0.6859785,0,0,0.6859785,1.3310577,-0.7298436)"
|
||||
id="ellipse82"
|
||||
style="fill:#ffffff;stroke:none" />
|
||||
<ellipse
|
||||
cx="7.8449998"
|
||||
cy="4.6550002"
|
||||
rx="0.308"
|
||||
ry="0.308"
|
||||
transform="matrix(0.6859785,0,0,0.6859785,1.3310577,-0.7298436)"
|
||||
id="ellipse84"
|
||||
style="fill:none;stroke:#000000;stroke-width:0.035" />
|
||||
<text
|
||||
x="6.5808516"
|
||||
y="2.5937216"
|
||||
id="text86"
|
||||
style="font-size:0.34038281px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;font-family:Sans;-inkscape-font-specification:Sans">D</text>
|
||||
<ellipse
|
||||
cx="0.87"
|
||||
cy="4.0079999"
|
||||
rx="0.308"
|
||||
ry="0.308"
|
||||
transform="matrix(0.6859785,0,0,0.6859785,1.3310577,-0.7298436)"
|
||||
id="ellipse88"
|
||||
style="fill:#ffffff;stroke:none" />
|
||||
<ellipse
|
||||
cx="0.87"
|
||||
cy="4.0079999"
|
||||
rx="0.308"
|
||||
ry="0.308"
|
||||
transform="matrix(0.6859785,0,0,0.6859785,1.3310577,-0.7298436)"
|
||||
id="ellipse90"
|
||||
style="fill:none;stroke:#000000;stroke-width:0.035" />
|
||||
<text
|
||||
x="1.7991183"
|
||||
y="2.1403852"
|
||||
id="text92"
|
||||
style="font-size:0.34038281px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;font-family:Sans;-inkscape-font-specification:Sans">E</text>
|
||||
</svg>
|
Before Width: | Height: | Size: 9.1 KiB |
BIN
doc/diagrams/fca.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
doc/diagrams/singly-linked.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
@ -1,86 +0,0 @@
|
||||
[/ Copyright 2006-2008 Daniel James.
|
||||
/ 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) ]
|
||||
|
||||
[section:hash_equality Equality Predicates and Hash Functions]
|
||||
|
||||
While the associative containers use an ordering relation to specify how the
|
||||
elements are stored, the unordered associative containers use an equality
|
||||
predicate and a hash function. For example, [classref boost::unordered_map]
|
||||
is declared as:
|
||||
|
||||
template <
|
||||
class Key, class Mapped,
|
||||
class Hash = ``[classref boost::hash]``<Key>,
|
||||
class Pred = std::equal_to<Key>,
|
||||
class Alloc = std::allocator<std::pair<Key const, Mapped> > >
|
||||
class ``[classref boost::unordered_map 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:
|
||||
|
||||
[import src_code/dictionary.cpp]
|
||||
[case_sensitive_dictionary_fnv]
|
||||
|
||||
There is an [@boost:/libs/unordered/examples/fnv1.hpp implementation
|
||||
of FNV-1] 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:
|
||||
|
||||
[case_insensitive_functions]
|
||||
|
||||
Which you can then use in a case insensitive dictionary:
|
||||
|
||||
[case_insensitive_dictionary]
|
||||
|
||||
This is a simplified version of the example at
|
||||
[@boost:/libs/unordered/examples/case_insensitive.hpp /libs/unordered/examples/case_insensitive.hpp]
|
||||
which supports other locales and string types.
|
||||
|
||||
[caution
|
||||
Be careful when using the equality (`==`) operator with custom equality
|
||||
predicates, especially if you're using a function pointer. If you compare two
|
||||
containers with different equality predicates then the result is undefined.
|
||||
For most stateless function objects this is impossible - since you can only
|
||||
compare objects with the same equality predicate you know the equality
|
||||
predicates must be equal. But if you're using function pointers or a stateful
|
||||
equality predicate (e.g. boost::function) then you can get into trouble.
|
||||
]
|
||||
|
||||
[h2 Custom Types]
|
||||
|
||||
Similarly, a custom hash function can be used for custom types:
|
||||
|
||||
[import src_code/point1.cpp]
|
||||
[point_example1]
|
||||
|
||||
Since the default hash function is [link hash Boost.Hash],
|
||||
we can [link hash.custom extend it to support the type]
|
||||
so that the hash function doesn't need to be explicitly given:
|
||||
|
||||
[import src_code/point2.cpp]
|
||||
[point_example2]
|
||||
|
||||
See the [link hash.custom Boost.Hash documentation] for more detail on how to
|
||||
do this. Remember that it relies on extensions to the draft standard - so it
|
||||
won't work for other implementations of the unordered associative containers,
|
||||
you'll need to explicitly use Boost.Hash.
|
||||
|
||||
[table:access_methods Methods for accessing the hash and equality functions.
|
||||
[[Method] [Description]]
|
||||
|
||||
[
|
||||
[`hasher hash_function() const`]
|
||||
[Returns the container's hash function.]
|
||||
]
|
||||
[
|
||||
[`key_equal key_eq() const`]
|
||||
[Returns the container's key equality function.]
|
||||
]
|
||||
]
|
||||
|
||||
[endsect]
|
101
doc/intro.qbk
@ -1,101 +0,0 @@
|
||||
[/ Copyright 2006-2008 Daniel James.
|
||||
/ 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) ]
|
||||
|
||||
[def __hash-table__ [@http://en.wikipedia.org/wiki/Hash_table
|
||||
hash table]]
|
||||
[def __hash-function__ [@http://en.wikipedia.org/wiki/Hash_function
|
||||
hash function]]
|
||||
|
||||
[section:intro Introduction]
|
||||
|
||||
For accessing data based on key lookup, the C++ 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
|
||||
__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.
|
||||
|
||||
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.
|
||||
|
||||
With this in mind, unordered associative containers were added to the C++
|
||||
standard. This is an implementation of the containers described in C++11,
|
||||
with some [link unordered.compliance deviations from the standard] in
|
||||
order to work with non-C++11 compilers and libraries.
|
||||
|
||||
`unordered_set` and `unordered_multiset` are defined in the header
|
||||
<[headerref boost/unordered_set.hpp]>
|
||||
|
||||
namespace boost {
|
||||
template <
|
||||
class Key,
|
||||
class Hash = ``[classref boost::hash]``<Key>,
|
||||
class Pred = std::equal_to<Key>,
|
||||
class Alloc = std::allocator<Key> >
|
||||
class ``[classref boost::unordered_set unordered_set]``;
|
||||
|
||||
template<
|
||||
class Key,
|
||||
class Hash = ``[classref boost::hash]``<Key>,
|
||||
class Pred = std::equal_to<Key>,
|
||||
class Alloc = std::allocator<Key> >
|
||||
class ``[classref boost::unordered_multiset unordered_multiset]``;
|
||||
}
|
||||
|
||||
`unordered_map` and `unordered_multimap` are defined in the header
|
||||
<[headerref boost/unordered_map.hpp]>
|
||||
|
||||
namespace boost {
|
||||
template <
|
||||
class Key, class Mapped,
|
||||
class Hash = ``[classref boost::hash]``<Key>,
|
||||
class Pred = std::equal_to<Key>,
|
||||
class Alloc = std::allocator<std::pair<Key const, Mapped> > >
|
||||
class ``[classref boost::unordered_map unordered_map]``;
|
||||
|
||||
template<
|
||||
class Key, class Mapped,
|
||||
class Hash = ``[classref boost::hash]``<Key>,
|
||||
class Pred = std::equal_to<Key>,
|
||||
class Alloc = std::allocator<std::pair<Key const, Mapped> > >
|
||||
class ``[classref boost::unordered_multimap unordered_multimap]``;
|
||||
}
|
||||
|
||||
When using Boost.TR1, these classes are included from `<unordered_set>` and
|
||||
`<unordered_map>`, with the classes added to the `std::tr1` namespace.
|
||||
|
||||
The containers are used in a similar manner to the normal associative
|
||||
containers:
|
||||
|
||||
[import src_code/intro.cpp]
|
||||
[intro_example1_2]
|
||||
|
||||
But since the elements aren't ordered, the output of:
|
||||
|
||||
[intro_example1_3]
|
||||
|
||||
can be in any order. For example, it might be:
|
||||
|
||||
two,2
|
||||
one,1
|
||||
three,3
|
||||
|
||||
To store 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
|
||||
Boost.Unordered uses [classref boost::hash] it also supports some other types,
|
||||
including standard containers. To use any types not supported by these methods
|
||||
you have to [link hash.custom extend Boost.Hash to support the type] or use
|
||||
your own custom equality predicates and hash functions. See the
|
||||
[link unordered.hash_equality Equality Predicates and Hash Functions] section
|
||||
for more details.
|
||||
|
||||
There are other differences, which are listed in the
|
||||
[link unordered.comparison Comparison with Associative Containers] section.
|
||||
|
||||
[endsect]
|
47
doc/preview.md
Normal file
@ -0,0 +1,47 @@
|
||||
# Building Boost from the Tip of Develop
|
||||
|
||||
To build Boost from the tip of the develop branch without cloning the entire
|
||||
history, use the command below:
|
||||
|
||||
Linux:
|
||||
```bash
|
||||
cwd=$(pwd) \
|
||||
&& cd ~ \
|
||||
&& git clone -b develop --depth 1 --recurse-submodules --shallow-submodules --jobs 8 https://github.com/boostorg/boost.git boost-develop-beta \
|
||||
&& cd boost-develop-beta \
|
||||
&& ./bootstrap.sh \
|
||||
&& ./b2 install --prefix=boost-install cxxstd=17 \
|
||||
&& echo "Boost successfully installed into $(realpath boost-install)!
|
||||
Add this to your CMake toolchain file.
|
||||
list(APPEND CMAKE_PREFIX_PATH $(realpath boost-install))
|
||||
" \
|
||||
&& cd $cwd
|
||||
```
|
||||
|
||||
Windows (using plain Command Prompt):
|
||||
```bat
|
||||
cmd /v
|
||||
set cwd=%cd% ^
|
||||
&& cd %homepath% ^
|
||||
&& git clone -b develop --depth 1 --recurse-submodules --shallow-submodules --jobs 8 https://github.com/boostorg/boost.git boost-develop-beta ^
|
||||
&& cd boost-develop-beta ^
|
||||
&& bootstrap.bat ^
|
||||
&& b2 install --prefix=boost-install cxxstd=17 address-model=64 ^
|
||||
&& echo Boost successfully installed into !cd!\boost-install! ^
|
||||
&& echo Add this to your CMake toolchain file. ^
|
||||
&& echo list(APPEND CMAKE_PREFIX_PATH !cd:\=/!/boost-install) ^
|
||||
&& cd !cwd! ^
|
||||
&& exit
|
||||
```
|
||||
|
||||
Note: you can build Boost with a specific compiler by setting the toolset in
|
||||
the `./b2` command above. To build with clang, specify `toolset=clang`; to build
|
||||
with a specific version of gcc, clang or msvc, specify e.g. `toolset=gcc-12` for GCC
|
||||
12, `clang-14` for Clang 14, `msvc-14.3` for MSVC 14.3. The value of `cxxstd`
|
||||
can also be set to other values such as 11 for C++11, 14 for C++14, etc.
|
||||
|
||||
For more info on what's possible, check out this link on b2:
|
||||
https://www.boost.org/doc/libs/develop/tools/build/doc/html/index.html#bbv2.overview.builtins.features
|
||||
|
||||
This should hopefully cover the two most common cases of building a dependency,
|
||||
setting the compiler and C++ standard used.
|
@ -1,111 +0,0 @@
|
||||
[/ Copyright 2006-2008 Daniel James.
|
||||
/ 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) ]
|
||||
|
||||
[def __wang__
|
||||
[@http://web.archive.org/web/20121102023700/http://www.concentric.net/~Ttwang/tech/inthash.htm
|
||||
Thomas Wang's article on integer hash functions]]
|
||||
|
||||
[section:rationale Implementation Rationale]
|
||||
|
||||
The intent of this library is to implement the unordered
|
||||
containers in the draft standard, so the interface was fixed. But there are
|
||||
still some implementation decisions to make. The priorities are
|
||||
conformance to the standard and portability.
|
||||
|
||||
The [@http://en.wikipedia.org/wiki/Hash_table Wikipedia article on hash tables]
|
||||
has a good summary of the implementation issues for hash tables in general.
|
||||
|
||||
[h2 Data Structure]
|
||||
|
||||
By specifying an interface for accessing the buckets of the container the
|
||||
standard pretty much requires that the hash table uses chained addressing.
|
||||
|
||||
It would be conceivable to write a hash table that uses another method. For
|
||||
example, it could use open addressing, and use the lookup chain to act as a
|
||||
bucket but there are some serious problems with this:
|
||||
|
||||
* The draft standard requires that pointers to elements aren't invalidated, so
|
||||
the elements can't be stored in one array, but will need a layer of
|
||||
indirection instead - losing the efficiency and most of the memory gain,
|
||||
the main advantages of open addressing.
|
||||
|
||||
* Local iterators would be very inefficient and may not be able to
|
||||
meet the complexity requirements.
|
||||
|
||||
* There are also the restrictions on when iterators can be invalidated. Since
|
||||
open addressing degrades badly when there are a high number of collisions the
|
||||
restrictions could prevent a rehash when it's really needed. The maximum load
|
||||
factor could be set to a fairly low value to work around this - but the
|
||||
standard requires that it is initially set to 1.0.
|
||||
|
||||
* And since the standard is written with a eye towards chained
|
||||
addressing, users will be surprised if the performance doesn't reflect that.
|
||||
|
||||
So chained addressing is used.
|
||||
|
||||
[/ (Removing for now as this is out of date)
|
||||
|
||||
For containers with unique keys I store the buckets in a single-linked list.
|
||||
There are other possible data structures (such as a double-linked list)
|
||||
that allow for some operations to be faster (such as erasing and iteration)
|
||||
but the possible gain seems small compared to the extra memory needed.
|
||||
The most commonly used operations (insertion and lookup) would not be improved
|
||||
at all.
|
||||
|
||||
But for containers with equivalent keys a single-linked list can degrade badly
|
||||
when a large number of elements with equivalent keys are inserted. I think it's
|
||||
reasonable to assume that users who choose to use `unordered_multiset` or
|
||||
`unordered_multimap` do so because they are likely to insert elements with
|
||||
equivalent keys. So I have used an alternative data structure that doesn't
|
||||
degrade, at the expense of an extra pointer per node.
|
||||
|
||||
This works by adding storing a circular linked list for each group of equivalent
|
||||
nodes in reverse order. This allows quick navigation to the end of a group (since
|
||||
the first element points to the last) and can be quickly updated when elements
|
||||
are inserted or erased. The main disadvantage of this approach is some hairy code
|
||||
for erasing elements.
|
||||
]
|
||||
|
||||
[/ (Starting to write up new structure, might not be ready in time)
|
||||
The node used to be stored in a linked list for each bucket but that
|
||||
didn't meet the complexity requirements for C++11, so now the nodes
|
||||
are stored in one long single linked list. But there needs a way to get
|
||||
the bucket from the node, to do that a copy of the key's hash value is
|
||||
stored in the node. Another possibility would be to store a pointer to
|
||||
the bucket, or the bucket's index, but storing the hash value allows
|
||||
some operations to be faster.
|
||||
]
|
||||
|
||||
[h2 Number of Buckets]
|
||||
|
||||
There are two popular methods for choosing the number of buckets in a hash
|
||||
table. One is to have a prime number of buckets, another is to use a power
|
||||
of 2.
|
||||
|
||||
Using a prime number of buckets, and choosing a bucket by using the modulus
|
||||
of the hash function's result will usually give a good result. The downside
|
||||
is that the required modulus operation is fairly expensive. This is what the
|
||||
containers do in most cases.
|
||||
|
||||
Using a power of 2 allows for much quicker selection of the bucket
|
||||
to use, but at the expense of losing the upper bits of the hash value.
|
||||
For some specially designed hash functions it is possible to do this and
|
||||
still get a good result but as the containers can take arbitrary hash
|
||||
functions this can't be relied on.
|
||||
|
||||
To avoid this a transformation could be applied to the hash function, for an
|
||||
example see __wang__. Unfortunately, a transformation like Wang's requires
|
||||
knowledge of the number of bits in the hash value, so it isn't portable enough
|
||||
to use as a default. It can applicable in certain cases so the containers
|
||||
have a policy based implementation that can use this alternative technique.
|
||||
|
||||
Currently this is only done on 64 bit architectures, where prime number
|
||||
modulus can be expensive. Although this varies depending on the architecture,
|
||||
so I probably should revisit it.
|
||||
|
||||
I'm also thinking of introducing a mechanism whereby a hash function can
|
||||
indicate that it's safe to be used directly with power of 2 buckets, in
|
||||
which case a faster plain power of 2 implementation can be used.
|
||||
|
||||
[endsect]
|
1788
doc/ref.php
5889
doc/ref.xml
188
doc/roadmap.md
Normal file
@ -0,0 +1,188 @@
|
||||
# Refactoring Roadmap
|
||||
|
||||
[Proof of concept](https://github.com/joaquintides/fca_unordered) implementation for a fast closed-addressing implementation.
|
||||
|
||||
## Plan of Refactoring
|
||||
|
||||
* remove `ptr_node` and `ptr_bucket`
|
||||
* see if the code can survive a lack of the `extra_node` or maybe we hard-code it in
|
||||
* implement bucket groups as they are in `fca` but don't use them directly yet, add alongside the `buckets_` data member in `struct table`
|
||||
* try to remove `bucket_info_` from the node structure (breaks all call-sites that use `get_bucket()` and dependents)
|
||||
* make sure `fca` can successfully handle multi-variants at this stage + supports mutable iterators for `map`/`multimap`
|
||||
* do a hard-break:
|
||||
* update code to no longer use one single linked list across all buckets (each bucket contains its own unique list)
|
||||
* integrate the `bucket_group<Node>` structure into the `table` (update iterator call-sites to include `bucket_iterator`s)
|
||||
|
||||
Blockers:
|
||||
* how to handle `multi` variants with new `fca` prototype
|
||||
|
||||
## Implementation Differences
|
||||
|
||||
### Unordered
|
||||
|
||||
### Node Type
|
||||
|
||||
Bullet Points:
|
||||
* reify node type into a single one
|
||||
* come up with implementation for multi- variants
|
||||
* code that touches `get_bucket()` and `*_in_group()` member functions may need updating
|
||||
|
||||
There are two node types in Unordered, `struct node` and `struct ptr_node`, and the node type is selected conditionally based on the Allocator's pointer type:
|
||||
```c++
|
||||
template <typename A, typename T, typename NodePtr, typename BucketPtr>
|
||||
struct pick_node2
|
||||
{
|
||||
typedef boost::unordered::detail::node<A, T> node;
|
||||
// ...
|
||||
};
|
||||
|
||||
template <typename A, typename T>
|
||||
struct pick_node2<A, T, boost::unordered::detail::ptr_node<T>*,
|
||||
boost::unordered::detail::ptr_bucket*>
|
||||
{
|
||||
typedef boost::unordered::detail::ptr_node<T> node;
|
||||
// ...
|
||||
};
|
||||
|
||||
template <typename A, typename T> struct pick_node
|
||||
{
|
||||
typedef typename boost::remove_const<T>::type nonconst;
|
||||
|
||||
typedef boost::unordered::detail::allocator_traits<
|
||||
typename boost::unordered::detail::rebind_wrap<A,
|
||||
boost::unordered::detail::ptr_node<nonconst> >::type>
|
||||
tentative_node_traits;
|
||||
|
||||
typedef boost::unordered::detail::allocator_traits<
|
||||
typename boost::unordered::detail::rebind_wrap<A,
|
||||
boost::unordered::detail::ptr_bucket>::type>
|
||||
tentative_bucket_traits;
|
||||
|
||||
typedef pick_node2<A, nonconst, typename tentative_node_traits::pointer,
|
||||
typename tentative_bucket_traits::pointer>
|
||||
pick;
|
||||
|
||||
typedef typename pick::node node;
|
||||
typedef typename pick::bucket bucket;
|
||||
typedef typename pick::link_pointer link_pointer;
|
||||
};
|
||||
```
|
||||
|
||||
The node types are identical in terms of interface and the only difference is that `node` is chosen when the Allocator uses fancy pointers and `ptr_node` is chosen when the Allocator's pointer type is `T*`.
|
||||
|
||||
Nodes in Unorderd store `bucket_info_`:
|
||||
```cpp
|
||||
template <typename A, typename T>
|
||||
struct node : boost::unordered::detail::value_base<T>
|
||||
{
|
||||
link_pointer next_;
|
||||
std::size_t bucket_info_;
|
||||
node() : next_(), bucket_info_(0) {}
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
`bucket_info_` maps each node back to its corresponding bucket via the member function:
|
||||
```cpp
|
||||
std::size_t get_bucket() const
|
||||
{
|
||||
return bucket_info_ & ((std::size_t)-1 >> 1);
|
||||
}
|
||||
```
|
||||
|
||||
`bucket_info_` is also used to demarcate the start of equivalent nodes in the containers via:
|
||||
```cpp
|
||||
// Note that nodes start out as the first in their group, as `bucket_info_` defaults to 0.
|
||||
std::size_t is_first_in_group() const
|
||||
{ return !(bucket_info_ & ~((std::size_t)-1 >> 1)); }
|
||||
|
||||
void set_first_in_group()
|
||||
{ bucket_info_ = bucket_info_ & ((std::size_t)-1 >> 1); }
|
||||
|
||||
void reset_first_in_group()
|
||||
{ bucket_info_ = bucket_info_ | ~((std::size_t)-1 >> 1); }
|
||||
```
|
||||
|
||||
A goal of refactoring is to simply have one node type:
|
||||
```cpp
|
||||
template<class T>
|
||||
struct node {
|
||||
node *next;
|
||||
T value;
|
||||
};
|
||||
```
|
||||
that is used unconditionally. This also requires updating the code that touches the `bucket_info_` along with the code that that touches the `*_in_group()` member functions.
|
||||
|
||||
### Bucket Type
|
||||
|
||||
Bullet points:
|
||||
* reify bucket structure into a single one
|
||||
* figure out how to add `bucket_group`s to the table struct
|
||||
|
||||
Buckets are similar to nodes in that there are two variations: `template<class NodePointer> struct bucket` and `struct ptr_bucket`.
|
||||
|
||||
The buckets exist to contain a pointer to a node, however they contain an `enum { extra_node = true };` or `enum { extra_node = false }` to determine whether or not the code should explicitly allocate a default constructed node whose address assigned as the dummy node at the end of the bucket array.
|
||||
|
||||
`extra_node` is used in the creation and deletion of the bucket array but it is not inherently clear what its intended purpose is.
|
||||
|
||||
### Iterators
|
||||
|
||||
Iterators are currently templated on the type of Node they store. Because `fca` constructs iterators with two arguments, all the call-sites that instantiate iterators will need to be updated but this a straight-forward mechanical change.
|
||||
|
||||
Iterators are selected, as of now, via the `detail::map` and `detail::set` class templates.
|
||||
|
||||
For example, for `unordered_map`, `iterator` is defined as:
|
||||
```cpp
|
||||
typedef boost::unordered::detail::map<A, K, T, H, P> types;
|
||||
typedef typename types::table table;
|
||||
typedef typename table::iterator iterator;
|
||||
```
|
||||
|
||||
The iterator is a member typedef of the `table` which is `types::table`. Examining `types` (aka `detail::map<...>`), we see:
|
||||
```cpp
|
||||
template <typename A, typename K, typename M, typename H, typename P>
|
||||
struct map {
|
||||
// ...
|
||||
typedef boost::unordered::detail::table<types> table;
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
Examining the `detail::table<types>` struct, we see:
|
||||
```cpp
|
||||
template <typename Types>
|
||||
struct table {
|
||||
// ...
|
||||
typedef typename Types::iterator iterator;
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Collapsing all of this, we see that our iterator types are defined here:
|
||||
```cpp
|
||||
template <typename A, typename K, typename M, typename H, typename P>
|
||||
struct map
|
||||
{
|
||||
// ...
|
||||
typedef boost::unordered::detail::pick_node<A, value_type> pick;
|
||||
typedef typename pick::node node;
|
||||
|
||||
typedef boost::unordered::iterator_detail::iterator<node> iterator;
|
||||
typedef boost::unordered::iterator_detail::c_iterator<node> c_iterator;
|
||||
typedef boost::unordered::iterator_detail::l_iterator<node> l_iterator;
|
||||
typedef boost::unordered::iterator_detail::cl_iterator<node>
|
||||
cl_iterator;
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
This is similarly designed for `detail::set`:
|
||||
```cpp
|
||||
typedef boost::unordered::iterator_detail::c_iterator<node> iterator;
|
||||
typedef boost::unordered::iterator_detail::c_iterator<node> c_iterator;
|
||||
typedef boost::unordered::iterator_detail::cl_iterator<node> l_iterator;
|
||||
typedef boost::unordered::iterator_detail::cl_iterator<node>
|
||||
cl_iterator;
|
||||
```
|
||||
|
||||
The only difference here is that `set::iterator` is always a `c_iterator`, a `const_iterator` type.
|
@ -1,101 +0,0 @@
|
||||
|
||||
// Copyright 2006-2007 Daniel James.
|
||||
// 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 <boost/unordered_map.hpp>
|
||||
#include <boost/core/lightweight_test.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include "../../examples/fnv1.hpp"
|
||||
|
||||
//[case_insensitive_functions
|
||||
struct iequal_to
|
||||
{
|
||||
bool operator()(std::string const& x,
|
||||
std::string const& y) const
|
||||
{
|
||||
return boost::algorithm::iequals(x, y, std::locale());
|
||||
}
|
||||
};
|
||||
|
||||
struct ihash
|
||||
{
|
||||
std::size_t operator()(std::string const& x) const
|
||||
{
|
||||
std::size_t seed = 0;
|
||||
std::locale locale;
|
||||
|
||||
for(std::string::const_iterator it = x.begin();
|
||||
it != x.end(); ++it)
|
||||
{
|
||||
boost::hash_combine(seed, std::toupper(*it, locale));
|
||||
}
|
||||
|
||||
return seed;
|
||||
}
|
||||
};
|
||||
//]
|
||||
|
||||
int main() {
|
||||
//[case_sensitive_dictionary_fnv
|
||||
boost::unordered_map<std::string, int, hash::fnv_1>
|
||||
dictionary;
|
||||
//]
|
||||
|
||||
BOOST_TEST(dictionary.empty());
|
||||
|
||||
dictionary["one"] = 1;
|
||||
BOOST_TEST(dictionary.size() == 1);
|
||||
BOOST_TEST(dictionary.find("ONE") == dictionary.end());
|
||||
|
||||
dictionary.insert(std::make_pair("ONE", 2));
|
||||
BOOST_TEST(dictionary.size() == 2);
|
||||
BOOST_TEST(dictionary.find("ONE") != dictionary.end() &&
|
||||
dictionary.find("ONE")->first == "ONE" &&
|
||||
dictionary.find("ONE")->second == 2);
|
||||
|
||||
dictionary["One"] = 3;
|
||||
BOOST_TEST(dictionary.size() == 3);
|
||||
BOOST_TEST(dictionary.find("One") != dictionary.end() &&
|
||||
dictionary.find("One")->first == "One" &&
|
||||
dictionary.find("One")->second == 3);
|
||||
|
||||
dictionary["two"] = 4;
|
||||
BOOST_TEST(dictionary.size() == 4);
|
||||
BOOST_TEST(dictionary.find("Two") == dictionary.end() &&
|
||||
dictionary.find("two") != dictionary.end() &&
|
||||
dictionary.find("two")->second == 4);
|
||||
|
||||
|
||||
//[case_insensitive_dictionary
|
||||
boost::unordered_map<std::string, int, ihash, iequal_to>
|
||||
idictionary;
|
||||
//]
|
||||
|
||||
BOOST_TEST(idictionary.empty());
|
||||
|
||||
idictionary["one"] = 1;
|
||||
BOOST_TEST(idictionary.size() == 1);
|
||||
BOOST_TEST(idictionary.find("ONE") != idictionary.end() &&
|
||||
idictionary.find("ONE") == idictionary.find("one"));
|
||||
|
||||
idictionary.insert(std::make_pair("ONE", 2));
|
||||
BOOST_TEST(idictionary.size() == 1);
|
||||
BOOST_TEST(idictionary.find("ONE") != idictionary.end() &&
|
||||
idictionary.find("ONE")->first == "one" &&
|
||||
idictionary.find("ONE")->second == 1);
|
||||
|
||||
idictionary["One"] = 3;
|
||||
BOOST_TEST(idictionary.size() == 1);
|
||||
BOOST_TEST(idictionary.find("ONE") != idictionary.end() &&
|
||||
idictionary.find("ONE")->first == "one" &&
|
||||
idictionary.find("ONE")->second == 3);
|
||||
|
||||
idictionary["two"] = 4;
|
||||
BOOST_TEST(idictionary.size() == 2);
|
||||
BOOST_TEST(idictionary.find("two") != idictionary.end() &&
|
||||
idictionary.find("TWO")->first == "two" &&
|
||||
idictionary.find("Two")->second == 4);
|
||||
|
||||
return boost::report_errors();
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
|
||||
// Copyright 2006-2009 Daniel James.
|
||||
// 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)
|
||||
|
||||
//[intro_example1_1
|
||||
#include <boost/unordered_map.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
//]
|
||||
|
||||
int main() {
|
||||
//[intro_example1_2
|
||||
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());
|
||||
//]
|
||||
|
||||
//[intro_example1_3
|
||||
BOOST_FOREACH(map::value_type i, x) {
|
||||
std::cout<<i.first<<","<<i.second<<"\n";
|
||||
}
|
||||
//]
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
|
||||
// Copyright 2006-2009 Daniel James.
|
||||
// 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 <boost/unordered_set.hpp>
|
||||
#include <boost/core/lightweight_test.hpp>
|
||||
|
||||
//[point_example1
|
||||
struct point {
|
||||
int x;
|
||||
int y;
|
||||
};
|
||||
|
||||
bool operator==(point const& p1, point const& p2)
|
||||
{
|
||||
return p1.x == p2.x && p1.y == p2.y;
|
||||
}
|
||||
|
||||
struct point_hash
|
||||
{
|
||||
std::size_t operator()(point const& p) const
|
||||
{
|
||||
std::size_t seed = 0;
|
||||
boost::hash_combine(seed, p.x);
|
||||
boost::hash_combine(seed, p.y);
|
||||
return seed;
|
||||
}
|
||||
};
|
||||
|
||||
boost::unordered_multiset<point, point_hash> points;
|
||||
//]
|
||||
|
||||
int main() {
|
||||
point x[] = {{1,2}, {3,4}, {1,5}, {1,2}};
|
||||
for(int i = 0; i < sizeof(x) / sizeof(point); ++i)
|
||||
points.insert(x[i]);
|
||||
BOOST_TEST(points.count(x[0]) == 2);
|
||||
BOOST_TEST(points.count(x[1]) == 1);
|
||||
point y = {10, 2};
|
||||
BOOST_TEST(points.count(y) == 0);
|
||||
|
||||
return boost::report_errors();
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
|
||||
// Copyright 2006-2009 Daniel James.
|
||||
// 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 <boost/unordered_set.hpp>
|
||||
#include <boost/functional/hash.hpp>
|
||||
#include <boost/core/lightweight_test.hpp>
|
||||
|
||||
//[point_example2
|
||||
struct point {
|
||||
int x;
|
||||
int y;
|
||||
};
|
||||
|
||||
bool operator==(point const& p1, point const& p2)
|
||||
{
|
||||
return p1.x == p2.x && p1.y == p2.y;
|
||||
}
|
||||
|
||||
std::size_t hash_value(point const& p) {
|
||||
std::size_t seed = 0;
|
||||
boost::hash_combine(seed, p.x);
|
||||
boost::hash_combine(seed, p.y);
|
||||
return seed;
|
||||
}
|
||||
|
||||
// Now the default function objects work.
|
||||
boost::unordered_multiset<point> points;
|
||||
//]
|
||||
|
||||
int main() {
|
||||
point x[] = {{1,2}, {3,4}, {1,5}, {1,2}};
|
||||
for(int i = 0; i < sizeof(x) / sizeof(point); ++i)
|
||||
points.insert(x[i]);
|
||||
BOOST_TEST(points.count(x[0]) == 2);
|
||||
BOOST_TEST(points.count(x[1]) == 1);
|
||||
point y = {10, 2};
|
||||
BOOST_TEST(points.count(y) == 0);
|
||||
|
||||
return boost::report_errors();
|
||||
}
|
||||
|
23
doc/unordered.adoc
Normal file
@ -0,0 +1,23 @@
|
||||
= Boost.Unordered
|
||||
:toc: left
|
||||
:toclevels: 3
|
||||
:idprefix:
|
||||
:docinfo: private-footer
|
||||
:source-highlighter: rouge
|
||||
:source-language: c++
|
||||
:nofooter:
|
||||
:sectlinks:
|
||||
|
||||
:leveloffset: +1
|
||||
|
||||
include::unordered/intro.adoc[]
|
||||
include::unordered/buckets.adoc[]
|
||||
include::unordered/benchmarks.adoc[]
|
||||
include::unordered/hash_equality.adoc[]
|
||||
include::unordered/comparison.adoc[]
|
||||
include::unordered/compliance.adoc[]
|
||||
include::unordered/rationale.adoc[]
|
||||
include::unordered/ref.adoc[]
|
||||
include::unordered/changes.adoc[]
|
||||
include::unordered/bibliography.adoc[]
|
||||
include::unordered/copyright.adoc[]
|
@ -1,39 +0,0 @@
|
||||
[/ Copyright 2006-2008 Daniel James.
|
||||
/ 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) ]
|
||||
|
||||
[library Boost.Unordered
|
||||
[quickbook 1.7]
|
||||
[compatibility-mode 1.5]
|
||||
[authors [James, Daniel]]
|
||||
[copyright 2003 2004 Jeremy B. Maitin-Shepard]
|
||||
[copyright 2005 2006 2007 2008 Daniel James]
|
||||
[purpose std::tr1 compliant hash containers]
|
||||
[id unordered]
|
||||
[dirname unordered]
|
||||
[license
|
||||
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])
|
||||
]
|
||||
]
|
||||
|
||||
[template diagram[name] '''<inlinemediaobject>
|
||||
<imageobject role="html">
|
||||
<imagedata align = "center" fileref="../../libs/unordered/doc/diagrams/'''[name]'''.png"></imagedata>
|
||||
</imageobject>
|
||||
<imageobject role="print">
|
||||
<imagedata align = "center" fileref="../../libs/unordered/doc/diagrams/'''[name]'''.svg"></imagedata>
|
||||
</imageobject>
|
||||
</inlinemediaobject>''']
|
||||
|
||||
|
||||
[include:unordered intro.qbk]
|
||||
[include:unordered buckets.qbk]
|
||||
[include:unordered hash_equality.qbk]
|
||||
[include:unordered comparison.qbk]
|
||||
[include:unordered compliance.qbk]
|
||||
[include:unordered rationale.qbk]
|
||||
[include:unordered changes.qbk]
|
||||
[xinclude ref.xml]
|
||||
[xinclude bibliography.xml]
|
277
doc/unordered/benchmarks.adoc
Normal file
@ -0,0 +1,277 @@
|
||||
[#benchmarks]
|
||||
:idprefix: benchmarks_
|
||||
:imagesdir: ../diagrams
|
||||
|
||||
= Benchmarks
|
||||
|
||||
All benchmarks were created using `unordered_set<unsigned int>` (non-duplicate) and `unordered_multiset<unsigned int>` (duplicate). The source code can be https://github.com/joaquintides/boost_unordered_benchmark[found here].
|
||||
|
||||
The insertion benchmarks insert `n` random values, where `n` is between 10,000 and 3 million. For the duplicated benchmarks, the same random values are repeated an average of 5 times.
|
||||
|
||||
The erasure benchmarks erase all `n` elements randomly until the container is empty.
|
||||
|
||||
The successful lookup benchmarks are done by looking up all `n` values, in the their original insertion order.
|
||||
|
||||
The unsuccessful lookup benchmarks use `n` randomly generated integers but using a different seed value.
|
||||
|
||||
== GCC 11 + libstdc++-v3
|
||||
|
||||
=== Insertion
|
||||
|
||||
[caption=]
|
||||
[cols="3*^.^a", frame=all, grid=all]
|
||||
|===
|
||||
|
||||
|image::benchmarks/gcc/running insertion.xlsx.practice.png[width=250,link=../diagrams/benchmarks/gcc/running insertion.xlsx.practice.png,window=_blank]
|
||||
|image::benchmarks/gcc/running%20insertion.xlsx.practice non-unique.png[width=250,link=../diagrams/benchmarks/gcc/running%20insertion.xlsx.practice non-unique.png,window=_blank]
|
||||
|image::benchmarks/gcc/running%20insertion.xlsx.practice non-unique 5.png[width=250,link=../diagrams/benchmarks/gcc/running%20insertion.xlsx.practice non-unique 5.png,window=_blank]
|
||||
|
||||
h|non-duplicate elements
|
||||
h|duplicate elements
|
||||
h|duplicate elements +
|
||||
max load factor 5
|
||||
|===
|
||||
|
||||
[caption=]
|
||||
[cols="3*^.^a", frame=all, grid=all]
|
||||
|===
|
||||
|
||||
|image::benchmarks/gcc/running%20insertion.xlsx.practice norehash.png[width=250,link=../diagrams/benchmarks/gcc/running%20insertion.xlsx.practice norehash.png,window=_blank]
|
||||
|image::benchmarks/gcc/running%20insertion.xlsx.practice norehash non-unique.png[width=250,link=../diagrams/benchmarks/gcc/running%20insertion.xlsx.practice norehash non-unique.png,window=_blank]
|
||||
|image::benchmarks/gcc/running%20insertion.xlsx.practice norehash non-unique 5.png[width=250,link=../diagrams/benchmarks/gcc/running%20insertion.xlsx.practice norehash non-unique 5.png,window=_blank]
|
||||
|
||||
h|non-duplicate elements, +
|
||||
prior `reserve`
|
||||
h|duplicate elements, +
|
||||
prior `reserve`
|
||||
h|duplicate elements, +
|
||||
max load factor 5, +
|
||||
prior `reserve`
|
||||
|
||||
|===
|
||||
|
||||
=== Erasure
|
||||
|
||||
[caption=]
|
||||
[cols="3*^.^a", frame=all, grid=all]
|
||||
|===
|
||||
|
||||
|image::benchmarks/gcc/scattered%20erasure.xlsx.practice.png[width=250,link=../diagrams/benchmarks/gcc/scattered%20erasure.xlsx.practice.png,window=_blank]
|
||||
|image::benchmarks/gcc/scattered%20erasure.xlsx.practice non-unique.png[width=250,link=../diagrams/benchmarks/gcc/scattered%20erasure.xlsx.practice non-unique.png,window=_blank]
|
||||
|image::benchmarks/gcc/scattered%20erasure.xlsx.practice non-unique 5.png[width=250,link=../diagrams/benchmarks/gcc/scattered%20erasure.xlsx.practice non-unique 5.png,window=_blank]
|
||||
|
||||
h|non-duplicate elements
|
||||
h|duplicate elements
|
||||
h|duplicate elements +
|
||||
max load factor 5
|
||||
|===
|
||||
|
||||
=== Successful Lookup
|
||||
|
||||
[caption=]
|
||||
[cols="3*^.^a", frame=all, grid=all]
|
||||
|===
|
||||
|
||||
|image::benchmarks/gcc/scattered%20successful%20looukp.xlsx.practice.png[width=250,window=_blank,link=../diagrams/benchmarks/gcc/scattered%20successful%20looukp.xlsx.practice.png]
|
||||
|image::benchmarks/gcc/scattered%20successful%20looukp.xlsx.practice non-unique.png[width=250,window=_blank,link=../diagrams/benchmarks/gcc/scattered%20successful%20looukp.xlsx.practice non-unique.png]
|
||||
|image::benchmarks/gcc/scattered%20successful%20looukp.xlsx.practice non-unique 5.png[width=250,window=_blank,link=../diagrams/benchmarks/gcc/scattered%20successful%20looukp.xlsx.practice non-unique 5.png]
|
||||
|
||||
h|non-duplicate elements
|
||||
h|duplicate elements
|
||||
h|duplicate elements, +
|
||||
max load factor 5
|
||||
|
||||
|===
|
||||
|
||||
=== Unsuccessful lookup
|
||||
|
||||
[caption=]
|
||||
[cols="3*^.^a", frame=all, grid=all]
|
||||
|===
|
||||
|
||||
|image::benchmarks/gcc/scattered%20unsuccessful%20looukp.xlsx.practice.png[width=250,window=_blank,link=../diagrams/benchmarks/gcc/scattered%20unsuccessful%20looukp.xlsx.practice.png]
|
||||
|image::benchmarks/gcc/scattered%20unsuccessful%20looukp.xlsx.practice non-unique.png[width=250,window=_blank,link=../diagrams/benchmarks/gcc/scattered%20unsuccessful%20looukp.xlsx.practice non-unique.png]
|
||||
|image::benchmarks/gcc/scattered%20unsuccessful%20looukp.xlsx.practice non-unique 5.png[width=250,window=_blank,link=../diagrams/benchmarks/gcc/scattered%20unsuccessful%20looukp.xlsx.practice non-unique 5.png]
|
||||
|
||||
h|non-duplicate elements
|
||||
h|duplicate elements
|
||||
h|duplicate elements, +
|
||||
max load factor 5
|
||||
|
||||
|===
|
||||
|
||||
== Clang 12 + libc++
|
||||
|
||||
=== Insertion
|
||||
|
||||
[caption=]
|
||||
[cols="3*^.^a", frame=all, grid=all]
|
||||
|===
|
||||
|
||||
|image::benchmarks/clang_libcpp/running%20insertion.xlsx.practice.png[width=250, window=_blank,link=../diagrams/benchmarks/clang_libcpp/running%20insertion.xlsx.practice.png]
|
||||
|image::benchmarks/clang_libcpp/running%20insertion.xlsx.practice non-unique.png[width=250, window=_blank,link=../diagrams/benchmarks/clang_libcpp/running%20insertion.xlsx.practice non-unique.png]
|
||||
|image::benchmarks/clang_libcpp/running%20insertion.xlsx.practice non-unique 5.png[width=250, window=_blank,link=../diagrams/benchmarks/clang_libcpp/running%20insertion.xlsx.practice non-unique 5.png]
|
||||
|
||||
h|non-duplicate elements
|
||||
h|duplicate elements
|
||||
h|duplicate elements, +
|
||||
max load factor 5
|
||||
|
||||
|===
|
||||
|
||||
[caption=]
|
||||
[cols="3*^.^a", frame=all, grid=all]
|
||||
|===
|
||||
|
||||
|image::benchmarks/clang_libcpp/running%20insertion.xlsx.practice norehash.png[width=250,window=_blank,link=../diagrams/benchmarks/clang_libcpp/running%20insertion.xlsx.practice norehash.png]
|
||||
|image::benchmarks/clang_libcpp/running%20insertion.xlsx.practice norehash non-unique.png[width=250,window=_blank,link=../diagrams/benchmarks/clang_libcpp/running%20insertion.xlsx.practice norehash non-unique.png]
|
||||
|image::benchmarks/clang_libcpp/running%20insertion.xlsx.practice norehash non-unique 5.png[width=250,window=_blank,link=../diagrams/benchmarks/clang_libcpp/running%20insertion.xlsx.practice norehash non-unique 5.png]
|
||||
|
||||
h|non-duplicate elements, +
|
||||
prior `reserve`
|
||||
h|duplicate elements, +
|
||||
prior `reserve`
|
||||
h|duplicate elements, +
|
||||
max load factor 5, +
|
||||
prior `reserve`
|
||||
|
||||
|===
|
||||
|
||||
=== Erasure
|
||||
|
||||
[caption=]
|
||||
[cols="3*^.^a", frame=all, grid=all]
|
||||
|===
|
||||
|
||||
|image::benchmarks/clang_libcpp/scattered%20erasure.xlsx.practice.png[width=250,window=_blank,link=../diagrams/benchmarks/clang_libcpp/scattered%20erasure.xlsx.practice.png]
|
||||
|image::benchmarks/clang_libcpp/scattered%20erasure.xlsx.practice non-unique.png[width=250,window=_blank,link=../diagrams/benchmarks/clang_libcpp/scattered%20erasure.xlsx.practice non-unique.png]
|
||||
|image::benchmarks/clang_libcpp/scattered%20erasure.xlsx.practice non-unique 5.png[width=250,window=_blank,link=../diagrams/benchmarks/clang_libcpp/scattered%20erasure.xlsx.practice non-unique 5.png]
|
||||
|
||||
h|non-duplicate elements
|
||||
h|duplicate elements
|
||||
h|duplicate elements, +
|
||||
max load factor 5
|
||||
|
||||
|===
|
||||
|
||||
=== Successful lookup
|
||||
|
||||
[caption=]
|
||||
[cols="3*^.^a", frame=all, grid=all]
|
||||
|===
|
||||
|
||||
|image::benchmarks/clang_libcpp/scattered%20successful%20looukp.xlsx.practice.png[width=250,window=_blank,link=../diagrams/benchmarks/clang_libcpp/scattered%20successful%20looukp.xlsx.practice.png]
|
||||
|image::benchmarks/clang_libcpp/scattered%20successful%20looukp.xlsx.practice non-unique.png[width=250,window=_blank,link=../diagrams/benchmarks/clang_libcpp/scattered%20successful%20looukp.xlsx.practice non-unique.png]
|
||||
|image::benchmarks/clang_libcpp/scattered%20successful%20looukp.xlsx.practice non-unique 5.png[width=250,window=_blank,link=../diagrams/benchmarks/clang_libcpp/scattered%20successful%20looukp.xlsx.practice non-unique 5.png]
|
||||
|
||||
h|non-duplicate elements
|
||||
h|duplicate elements
|
||||
h|duplicate elements, +
|
||||
max load factor 5
|
||||
|
||||
|===
|
||||
|
||||
=== Unsuccessful lookup
|
||||
|
||||
[caption=]
|
||||
[cols="3*^.^a", frame=all, grid=all]
|
||||
|===
|
||||
|
||||
|image::benchmarks/clang_libcpp/scattered%20unsuccessful%20looukp.xlsx.practice.png[width=250,window=_blank,link=../diagrams/benchmarks/clang_libcpp/scattered%20unsuccessful%20looukp.xlsx.practice.png]
|
||||
|image::benchmarks/clang_libcpp/scattered%20unsuccessful%20looukp.xlsx.practice non-unique.png[width=250,window=_blank,link=../diagrams/benchmarks/clang_libcpp/scattered%20unsuccessful%20looukp.xlsx.practice non-unique.png]
|
||||
|image::benchmarks/clang_libcpp/scattered%20unsuccessful%20looukp.xlsx.practice non-unique 5.png[width=250,window=_blank,link=../diagrams/benchmarks/clang_libcpp/scattered%20unsuccessful%20looukp.xlsx.practice non-unique 5.png]
|
||||
|
||||
h|non-duplicate elements
|
||||
h|duplicate elements
|
||||
h|duplicate elements, +
|
||||
max load factor 5
|
||||
|
||||
|===
|
||||
|
||||
== Visual Studio 2019 + Dinkumware
|
||||
|
||||
=== Insertion
|
||||
|
||||
[caption=]
|
||||
[cols="3*^.^a", frame=all, grid=all]
|
||||
|===
|
||||
|
||||
|image::benchmarks/vs/running%20insertion.xlsx.practice.png[width=250,window=_blank,link=../diagrams/benchmarks/vs/running%20insertion.xlsx.practice.png]
|
||||
|image::benchmarks/vs/running%20insertion.xlsx.practice non-unique.png[width=250,window=_blank,link=../diagrams/benchmarks/vs/running%20insertion.xlsx.practice non-unique.png]
|
||||
|image::benchmarks/vs/running%20insertion.xlsx.practice non-unique 5.png[width=250,window=_blank,link=../diagrams/benchmarks/vs/running%20insertion.xlsx.practice non-unique 5.png]
|
||||
|
||||
h|non-duplicate elements
|
||||
h|duplicate elements
|
||||
h|duplicate elements, +
|
||||
max load factor 5
|
||||
|
||||
|===
|
||||
|
||||
[caption=]
|
||||
[cols="3*^.^a", frame=all, grid=all]
|
||||
|===
|
||||
|
||||
|image::benchmarks/vs/running%20insertion.xlsx.practice norehash.png[width=250,window=_blank,link=../diagrams/benchmarks/vs/running%20insertion.xlsx.practice norehash.png]
|
||||
|image::benchmarks/vs/running%20insertion.xlsx.practice norehash non-unique.png[width=250,window=_blank,link=../diagrams/benchmarks/vs/running%20insertion.xlsx.practice norehash non-unique.png]
|
||||
|image::benchmarks/vs/running%20insertion.xlsx.practice norehash non-unique 5.png[width=250,window=_blank,link=../diagrams/benchmarks/vs/running%20insertion.xlsx.practice norehash non-unique 5.png]
|
||||
|
||||
h|non-duplicate elements, +
|
||||
prior `reserve`
|
||||
h|duplicate elements, +
|
||||
prior `reserve`
|
||||
h|duplicate elements, +
|
||||
max load factor 5, +
|
||||
prior `reserve`
|
||||
|
||||
|===
|
||||
|
||||
=== Erasure
|
||||
|
||||
[caption=]
|
||||
[cols="3*^.^a", frame=all, grid=all]
|
||||
|===
|
||||
|
||||
|image::benchmarks/vs/scattered%20erasure.xlsx.practice.png[width=250,window=_blank,link=../diagrams/benchmarks/vs/scattered%20erasure.xlsx.practice.png]
|
||||
|image::benchmarks/vs/scattered%20erasure.xlsx.practice non-unique.png[width=250,window=_blank,link=../diagrams/benchmarks/vs/scattered%20erasure.xlsx.practice non-unique.png]
|
||||
|image::benchmarks/vs/scattered%20erasure.xlsx.practice non-unique 5.png[width=250,window=_blank,link=../diagrams/benchmarks/vs/scattered%20erasure.xlsx.practice non-unique 5.png]
|
||||
|
||||
h|non-duplicate elements
|
||||
h|duplicate elements
|
||||
h|duplicate elements, +
|
||||
max load factor 5
|
||||
|
||||
|===
|
||||
|
||||
=== Successful lookup
|
||||
|
||||
[caption=]
|
||||
[cols="3*^.^a", frame=all, grid=all]
|
||||
|===
|
||||
|
||||
|image::benchmarks/vs/scattered%20successful%20looukp.xlsx.practice.png[width=250,window=_blank,link=../diagrams/benchmarks/vs/scattered%20successful%20looukp.xlsx.practice.png]
|
||||
|image::benchmarks/vs/scattered%20successful%20looukp.xlsx.practice non-unique.png[width=250,window=_blank,link=../diagrams/benchmarks/vs/scattered%20successful%20looukp.xlsx.practice non-unique.png]
|
||||
|image::benchmarks/vs/scattered%20successful%20looukp.xlsx.practice non-unique 5.png[width=250,window=_blank,link=../diagrams/benchmarks/vs/scattered%20successful%20looukp.xlsx.practice non-unique 5.png]
|
||||
|
||||
h|non-duplicate elements
|
||||
h|duplicate elements
|
||||
h|duplicate elements, +
|
||||
max load factor 5
|
||||
|
||||
|===
|
||||
|
||||
=== Unsuccessful lookup
|
||||
|
||||
[caption=]
|
||||
[cols="3*^.^a", frame=all, grid=all]
|
||||
|===
|
||||
|
||||
|image::benchmarks/vs/scattered%20unsuccessful%20looukp.xlsx.practice.png[width=250,window=_blank,link=../diagrams/benchmarks/vs/scattered%20unsuccessful%20looukp.xlsx.practice.png]
|
||||
|image::benchmarks/vs/scattered%20unsuccessful%20looukp.xlsx.practice non-unique.png[width=250,window=_blank,link=../diagrams/benchmarks/vs/scattered%20unsuccessful%20looukp.xlsx.practice non-unique.png]
|
||||
|image::benchmarks/vs/scattered%20unsuccessful%20looukp.xlsx.practice non-unique 5.png[width=250,window=_blank,link=../diagrams/benchmarks/vs/scattered%20unsuccessful%20looukp.xlsx.practice non-unique 5.png]
|
||||
|
||||
h|non-duplicate elements
|
||||
h|duplicate elements
|
||||
h|duplicate elements, +
|
||||
max load factor 5
|
||||
|
||||
|===
|
10
doc/unordered/bibliography.adoc
Normal file
@ -0,0 +1,10 @@
|
||||
[#bibliography]
|
||||
|
||||
:idprefix: bibliography_
|
||||
|
||||
= Bibliography
|
||||
|
||||
* _C/C++ Users Journal_. February, 2006. Pete Becker. http://www.ddj.com/cpp/184402066[STL and TR1: Part III - Unordered containers^]. +
|
||||
An introducation to the standard unordered containers.
|
||||
|
||||
|
210
doc/unordered/buckets.adoc
Normal file
@ -0,0 +1,210 @@
|
||||
[#buckets]
|
||||
:idprefix: buckets_
|
||||
:imagesdir: ../diagrams
|
||||
|
||||
= The Data Structure
|
||||
|
||||
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 an <<unordered_set,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).
|
||||
|
||||
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
|
||||
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
|
||||
that value to choose a bucket to place the element in.
|
||||
|
||||
Retrieving the elements for a given key is simple. The same process is applied
|
||||
to the key to find the correct bucket. Then the key is compared with the
|
||||
elements in the bucket to find any elements that match (using the equality
|
||||
predicate `Pred`). If the hash function has worked well the elements will be
|
||||
evenly distributed amongst the buckets so only a small number of elements will
|
||||
need to be examined.
|
||||
|
||||
There is <<hash_equality, more information on hash functions and
|
||||
equality predicates in the next section>>.
|
||||
|
||||
You can see in the diagram that `A` & `D` have been placed in the same bucket.
|
||||
When looking for elements in this bucket up to 2 comparisons are made, making
|
||||
the search slower. This is known as a collision. To keep things fast we try to
|
||||
keep collisions to a minimum.
|
||||
|
||||
[caption=, title='Table {counter:table-counter}. Methods for Accessing Buckets']
|
||||
[cols="1,.^1", frame=all, grid=rows]
|
||||
|===
|
||||
|Method |Description
|
||||
|
||||
|`size_type bucket_count() const`
|
||||
|The number of buckets.
|
||||
|
||||
|`size_type max_bucket_count() const`
|
||||
|An upper bound on the number of buckets.
|
||||
|
||||
|`size_type bucket_size(size_type n) const`
|
||||
|The number of elements in bucket `n`.
|
||||
|
||||
|`size_type bucket(key_type const& k) const`
|
||||
|Returns the index of the bucket which would contain `k`.
|
||||
|
||||
|`local_iterator begin(size_type n)`
|
||||
1.6+|Return begin and end iterators for bucket `n`.
|
||||
|
||||
|`local_iterator end(size_type n)`
|
||||
|
||||
|`const_local_iterator begin(size_type n) const`
|
||||
|
||||
|`const_local_iterator end(size_type n) const`
|
||||
|
||||
|`const_local_iterator cbegin(size_type n) const`
|
||||
|
||||
|`const_local_iterator cend(size_type n) const`
|
||||
|
||||
|===
|
||||
|
||||
== Controlling the number of buckets
|
||||
|
||||
As more elements are added to an unordered associative container, the number
|
||||
of elements in the buckets will increase causing performance to degrade.
|
||||
To combat this the containers increase the bucket count as elements are inserted.
|
||||
You can also tell the container to change the bucket count (if required) by
|
||||
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 average number of elements per bucket.
|
||||
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
|
||||
influence it:
|
||||
|
||||
* Specify the minimum number of buckets when constructing a container or when calling `rehash`.
|
||||
* Suggest a maximum load factor by calling `max_load_factor`.
|
||||
|
||||
`max_load_factor` doesn't let you set the maximum load factor yourself, it just
|
||||
lets you give a _hint_. And even then, the standard doesn't actually
|
||||
require the container to pay much attention to this value. The only time the
|
||||
load factor is _required_ to be less than the maximum is following a call to
|
||||
`rehash`. But most implementations will try to keep the number of elements
|
||||
below the max load factor, and set the maximum load factor to be the same as
|
||||
or close to the hint - unless your hint is unreasonably small or large.
|
||||
|
||||
[caption=, title='Table {counter:table-counter}. Methods for Controlling Bucket Size']
|
||||
[cols="1,.^1", frame=all, grid=rows]
|
||||
|===
|
||||
|Method |Description
|
||||
|
||||
|`X(size_type n)`
|
||||
|Construct an empty container with at least `n` buckets (`X` is the container type).
|
||||
|
||||
|`X(InputIterator i, InputIterator j, size_type n)`
|
||||
|Construct an empty container with at least `n` buckets and insert elements from the range `[i, j)` (`X` is the container type).
|
||||
|
||||
|`float load_factor() const`
|
||||
|The average number of elements per bucket.
|
||||
|
||||
|`float max_load_factor() const`
|
||||
|Returns the current maximum load factor.
|
||||
|
||||
|`float max_load_factor(float z)`
|
||||
|Changes the container's maximum load factor, using `z` as a hint.
|
||||
|
||||
|`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.
|
||||
|
||||
|===
|
||||
|
||||
== Iterator Invalidation
|
||||
|
||||
It is not specified how member functions other than `rehash` and `reserve` affect
|
||||
the bucket count, although `insert` is only allowed to invalidate iterators
|
||||
when the insertion causes the load factor to be greater than or equal to the
|
||||
maximum load factor. For most implementations this means that `insert` will only
|
||||
change the number of buckets when this happens. While iterators can be
|
||||
invalidated by calls to `insert`, `rehash` and `reserve`, pointers and references to the
|
||||
container's elements are never invalidated.
|
||||
|
||||
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.
|
||||
|
||||
For more information on implementation rationale, read the <<Implementation Rationale, corresponding section>>.
|
@ -1,158 +1,232 @@
|
||||
[#changes]
|
||||
= Change Log
|
||||
|
||||
[/ Copyright 2008 Daniel James.
|
||||
/ 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) ]
|
||||
:idprefix: changes_
|
||||
:svn-ticket-url: https://svn.boost.org/trac/boost/ticket
|
||||
:github-pr-url: https://github.com/boostorg/unordered/pull
|
||||
:cpp: C++
|
||||
|
||||
[template ticket[number]'''<ulink
|
||||
url="https://svn.boost.org/trac/boost/ticket/'''[number]'''">'''#[number]'''</ulink>''']
|
||||
== Release 1.80.0 - Major update
|
||||
|
||||
[template pull_request[number][@https://github.com/boostorg/unordered/pull/[number] GitHub #[number]]]
|
||||
* Refactor internal implementation to be dramatically faster
|
||||
* Allow `final` Hasher and KeyEqual objects
|
||||
* Update documentation, adding benchmark graphs and notes on the new internal
|
||||
data structures
|
||||
|
||||
[section:changes Change Log]
|
||||
== Release 1.79.0
|
||||
|
||||
[h2 Review Version]
|
||||
* Improved {cpp}20 support:
|
||||
** All containers have been updated to support
|
||||
heterogeneous `count`, `equal_range` and `find`.
|
||||
** All containers now implement the member function `contains`.
|
||||
** `erase_if` has been implemented for all containers.
|
||||
* Improved {cpp}23 support:
|
||||
** All containers have been updated to support
|
||||
heterogeneous `erase` and `extract`.
|
||||
* Changed behavior of `reserve` to eagerly
|
||||
allocate ({github-pr-url}/59[PR#59^]).
|
||||
* Various warning fixes in the test suite.
|
||||
* Update code to internally use `boost::allocator_traits`.
|
||||
* Switch to Fibonacci hashing.
|
||||
* Update documentation to be written in AsciiDoc instead of QuickBook.
|
||||
|
||||
Initial review version, for the review conducted from 7th December 2007 to
|
||||
16th December 2007.
|
||||
== Release 1.67.0
|
||||
|
||||
[h2 1.35.0 Add-on - 31st March 2008]
|
||||
* Improved {cpp}17 support:
|
||||
** Add template deduction guides from the standard.
|
||||
** Use a simple implementation of `optional` in node handles, so
|
||||
that they're closer to the standard.
|
||||
** Add missing `noexcept` specifications to `swap`, `operator=`
|
||||
and node handles, and change the implementation to match.
|
||||
Using `std::allocator_traits::is_always_equal`, or our own
|
||||
implementation when not available, and
|
||||
`boost::is_nothrow_swappable` in the implementation.
|
||||
* Improved {cpp}20 support:
|
||||
** Use `boost::to_address`, which has the proposed {cpp}20 semantics,
|
||||
rather than the old custom implementation.
|
||||
* Add `element_type` to iterators, so that `std::pointer_traits`
|
||||
will work.
|
||||
* Use `std::piecewise_construct` on recent versions of Visual {cpp},
|
||||
and other uses of the Dinkumware standard library,
|
||||
now using Boost.Predef to check compiler and library versions.
|
||||
* Use `std::iterator_traits` rather than the boost iterator traits
|
||||
in order to remove dependency on Boost.Iterator.
|
||||
* Remove iterators' inheritance from `std::iterator`, which is
|
||||
deprecated in {cpp}17, thanks to Daniela Engert
|
||||
({github-pr-url}/7[PR#7^]).
|
||||
* Stop using `BOOST_DEDUCED_TYPENAME`.
|
||||
* Update some Boost include paths.
|
||||
* Rename some internal methods, and variables.
|
||||
* Various testing improvements.
|
||||
* Miscellaneous internal changes.
|
||||
|
||||
Unofficial release uploaded to vault, to be used with Boost 1.35.0. Incorporated
|
||||
many of the suggestions from the review.
|
||||
== Release 1.66.0
|
||||
|
||||
* Improved portability thanks to Boost regression testing.
|
||||
* Fix lots of typos, and clearer text in the documentation.
|
||||
* Fix floating point to `std::size_t` conversion when calculating sizes from
|
||||
the max load factor, and use `double` in the calculation for greater accuracy.
|
||||
* Fix some errors in the examples.
|
||||
* Simpler move construction implementation.
|
||||
* Documentation fixes ({github-pr-url}/6[GitHub #6^]).
|
||||
|
||||
[h2 Boost 1.36.0]
|
||||
== Release 1.65.0
|
||||
|
||||
First official release.
|
||||
* Add deprecated attributes to `quick_erase` and `erase_return_void`.
|
||||
I really will remove them in a future version this time.
|
||||
* Small standards compliance fixes:
|
||||
** `noexpect` specs for `swap` free functions.
|
||||
** Add missing `insert(P&&)` methods.
|
||||
|
||||
* Rearrange the internals.
|
||||
* Move semantics - full support when rvalue references are available, emulated
|
||||
using a cut down version of the Adobe move library when they are not.
|
||||
* Emplace support when rvalue references and variadic template are available.
|
||||
* More efficient node allocation when rvalue references and variadic template
|
||||
are available.
|
||||
* Added equality operators.
|
||||
== Release 1.64.0
|
||||
|
||||
[h2 Boost 1.37.0]
|
||||
* Initial support for new {cpp}17 member functions:
|
||||
`insert_or_assign` and `try_emplace` in `unordered_map`,
|
||||
* Initial support for `merge` and `extract`.
|
||||
Does not include transferring nodes between
|
||||
`unordered_map` and `unordered_multimap` or between `unordered_set` and
|
||||
`unordered_multiset` yet. That will hopefully be in the next version of
|
||||
Boost.
|
||||
|
||||
* Rename overload of `emplace` with hint, to `emplace_hint` as specified in
|
||||
[@http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2008/n2691.pdf n2691].
|
||||
* Provide forwarding headers at `<boost/unordered/unordered_map_fwd.hpp>` and
|
||||
`<boost/unordered/unordered_set_fwd.hpp>`.
|
||||
* Move all the implementation inside `boost/unordered`, to assist
|
||||
modularization and hopefully make it easier to track changes in subversion.
|
||||
== Release 1.63.0
|
||||
|
||||
[h2 Boost 1.38.0]
|
||||
* Check hint iterator in `insert`/`emplace_hint`.
|
||||
* Fix some warnings, mostly in the tests.
|
||||
* Manually write out `emplace_args` for small numbers of arguments -
|
||||
should make template error messages a little more bearable.
|
||||
* Remove superfluous use of `boost::forward` in emplace arguments,
|
||||
which fixes emplacing string literals in old versions of Visual {cpp}.
|
||||
* Fix an exception safety issue in assignment. If bucket allocation
|
||||
throws an exception, it can overwrite the hash and equality functions while
|
||||
leaving the existing elements in place. This would mean that the function
|
||||
objects wouldn't match the container elements, so elements might be in the
|
||||
wrong bucket and equivalent elements would be incorrectly handled.
|
||||
* Various reference documentation improvements.
|
||||
* Better allocator support ({svn-ticket-url}/12459[#12459^]).
|
||||
* Make the no argument constructors implicit.
|
||||
* Implement missing allocator aware constructors.
|
||||
* Fix assigning the hash/key equality functions for empty containers.
|
||||
* Remove unary/binary_function from the examples in the documentation.
|
||||
They are removed in {cpp}17.
|
||||
* Support 10 constructor arguments in emplace. It was meant to support up to 10
|
||||
arguments, but an off by one error in the preprocessor code meant it only
|
||||
supported up to 9.
|
||||
|
||||
* Use [@boost:/libs/core/swap.html `boost::swap`].
|
||||
* [@https://svn.boost.org/trac/boost/ticket/2237 Ticket 2237]:
|
||||
Document that the equality and inequality operators are undefined for two
|
||||
objects if their equality predicates aren't equivalent. Thanks to Daniel
|
||||
Krügler.
|
||||
* [@https://svn.boost.org/trac/boost/ticket/1710 Ticket 1710]:
|
||||
Use a larger prime number list. Thanks to Thorsten Ottosen and Hervé
|
||||
Brönnimann.
|
||||
* Use
|
||||
[@boost:/libs/type_traits/doc/html/boost_typetraits/category/alignment.html
|
||||
aligned storage] to store the types. This changes the way the allocator is
|
||||
used to construct nodes. It used to construct the node with two calls to
|
||||
the allocator's `construct` method - once for the pointers and once for the
|
||||
value. It now constructs the node with a single call to construct and
|
||||
then constructs the value using in place construction.
|
||||
* Add support for C++0x initializer lists where they're available (currently
|
||||
only g++ 4.4 in C++0x mode).
|
||||
== Release 1.62.0
|
||||
|
||||
[h2 Boost 1.39.0]
|
||||
* Remove use of deprecated `boost::iterator`.
|
||||
* Remove `BOOST_NO_STD_DISTANCE` workaround.
|
||||
* Remove `BOOST_UNORDERED_DEPRECATED_EQUALITY` warning.
|
||||
* Simpler implementation of assignment, fixes an exception safety issue
|
||||
for `unordered_multiset` and `unordered_multimap`. Might be a little slower.
|
||||
* Stop using return value SFINAE which some older compilers have issues
|
||||
with.
|
||||
|
||||
* [@https://svn.boost.org/trac/boost/ticket/2756 Ticket 2756]: Avoid a warning
|
||||
on Visual C++ 2009.
|
||||
* Some other minor internal changes to the implementation, tests and
|
||||
documentation.
|
||||
* Avoid an unnecessary copy in `operator[]`.
|
||||
* [@https://svn.boost.org/trac/boost/ticket/2975 Ticket 2975]: Fix length of
|
||||
prime number list.
|
||||
== Release 1.58.0
|
||||
|
||||
[h2 Boost 1.40.0]
|
||||
* Remove unnecessary template parameter from const iterators.
|
||||
* Rename private `iterator` typedef in some iterator classes, as it
|
||||
confuses some traits classes.
|
||||
* Fix move assignment with stateful, propagate_on_container_move_assign
|
||||
allocators ({svn-ticket-url}/10777[#10777^]).
|
||||
* Fix rare exception safety issue in move assignment.
|
||||
* Fix potential overflow when calculating number of buckets to allocate
|
||||
({github-pr-url}/4[GitHub #4^]).
|
||||
|
||||
* [@https://svn.boost.org/trac/boost/ticket/2975 Ticket 2975]:
|
||||
Store the prime list as a preprocessor sequence - so that it will always get
|
||||
the length right if it changes again in the future.
|
||||
* [@https://svn.boost.org/trac/boost/ticket/1978 Ticket 1978]:
|
||||
Implement `emplace` for all compilers.
|
||||
* [@https://svn.boost.org/trac/boost/ticket/2908 Ticket 2908],
|
||||
[@https://svn.boost.org/trac/boost/ticket/3096 Ticket 3096]:
|
||||
Some workarounds for old versions of borland, including adding explicit
|
||||
destructors to all containers.
|
||||
* [@https://svn.boost.org/trac/boost/ticket/3082 Ticket 3082]:
|
||||
Disable incorrect Visual C++ warnings.
|
||||
* Better configuration for C++0x features when the headers aren't available.
|
||||
* Create less buckets by default.
|
||||
== Release 1.57.0
|
||||
|
||||
[h2 Boost 1.41.0 - Major update]
|
||||
* Fix the `pointer` typedef in iterators ({svn-ticket-url}/10672[#10672^]).
|
||||
* Fix Coverity warning
|
||||
({github-pr-url}/2[GitHub #2^]).
|
||||
|
||||
* The original version made heavy use of macros to sidestep some of the older
|
||||
compilers' poor template support. But since I no longer support those
|
||||
compilers and the macro use was starting to become a maintenance burden it
|
||||
has been rewritten to use templates instead of macros for the implementation
|
||||
classes.
|
||||
== Release 1.56.0
|
||||
|
||||
* The container object is now smaller thanks to using `boost::compressed_pair`
|
||||
for EBO and a slightly different function buffer - now using a bool instead
|
||||
of a member pointer.
|
||||
* Fix some shadowed variable warnings ({svn-ticket-url}/9377[#9377^]).
|
||||
* Fix allocator use in documentation ({svn-ticket-url}/9719[#9719^]).
|
||||
* Always use prime number of buckets for integers. Fixes performance
|
||||
regression when inserting consecutive integers, although makes other
|
||||
uses slower ({svn-ticket-url}/9282[#9282^]).
|
||||
* Only construct elements using allocators, as specified in {cpp}11 standard.
|
||||
|
||||
* Buckets are allocated lazily which means that constructing an empty container
|
||||
will not allocate any memory.
|
||||
== Release 1.55.0
|
||||
|
||||
[h2 Boost 1.42.0]
|
||||
* Avoid some warnings ({svn-ticket-url}/8851[#8851^], {svn-ticket-url}/8874[#8874^]).
|
||||
* Avoid exposing some detail functions via. ADL on the iterators.
|
||||
* Follow the standard by only using the allocators' construct and destroy
|
||||
methods to construct and destroy stored elements. Don't use them for internal
|
||||
data like pointers.
|
||||
|
||||
* Support instantiating the containers with incomplete value types.
|
||||
* Reduced the number of warnings (mostly in tests).
|
||||
* Improved codegear compatibility.
|
||||
* [@http://svn.boost.org/trac/boost/ticket/3693 Ticket 3693]:
|
||||
Add `erase_return_void` as a temporary workaround for the current
|
||||
`erase` which can be inefficient because it has to find the next
|
||||
element to return an iterator.
|
||||
* Add templated find overload for compatible keys.
|
||||
* [@http://svn.boost.org/trac/boost/ticket/3773 Ticket 3773]:
|
||||
Add missing `std` qualifier to `ptrdiff_t`.
|
||||
* Some code formatting changes to fit almost all lines into 80 characters.
|
||||
== Release 1.54.0
|
||||
|
||||
[h2 Boost 1.43.0]
|
||||
* Mark methods specified in standard as `noexpect`. More to come in the next
|
||||
release.
|
||||
* If the hash function and equality predicate are known to both have nothrow
|
||||
move assignment or construction then use them.
|
||||
|
||||
* [@http://svn.boost.org/trac/boost/ticket/3966 Ticket 3966]:
|
||||
`erase_return_void` is now `quick_erase`, which is the
|
||||
[@http://home.roadrunner.com/~hinnant/issue_review/lwg-active.html#579
|
||||
current forerunner for resolving the slow erase by iterator], although
|
||||
there's a strong possibility that this may change in the future. The old
|
||||
method name remains for backwards compatibility but is considered deprecated
|
||||
and will be removed in a future release.
|
||||
* Use Boost.Exception.
|
||||
* Stop using deprecated `BOOST_HAS_*` macros.
|
||||
== Release 1.53.0
|
||||
|
||||
[h2 Boost 1.45.0]
|
||||
* Remove support for the old pre-standard variadic pair constructors, and
|
||||
equality implementation. Both have been deprecated since Boost 1.48.
|
||||
* Remove use of deprecated config macros.
|
||||
* More internal implementation changes, including a much simpler
|
||||
implementation of `erase`.
|
||||
|
||||
* Fix a bug when inserting into an `unordered_map` or `unordered_set` using
|
||||
iterators which returns `value_type` by copy.
|
||||
== Release 1.52.0
|
||||
|
||||
[h2 Boost 1.48.0 - Major update]
|
||||
* Faster assign, which assigns to existing nodes where possible, rather than
|
||||
creating entirely new nodes and copy constructing.
|
||||
* Fixed bug in `erase_range` ({svn-ticket-url}/7471[#7471^]).
|
||||
* Reverted some of the internal changes to how nodes are created, especially
|
||||
for {cpp}11 compilers. 'construct' and 'destroy' should work a little better
|
||||
for {cpp}11 allocators.
|
||||
* Simplified the implementation a bit. Hopefully more robust.
|
||||
|
||||
== Release 1.51.0
|
||||
|
||||
* Fix construction/destruction issue when using a {cpp}11 compiler with a
|
||||
{cpp}03 allocator ({svn-ticket-url}/7100[#7100^]).
|
||||
* Remove a `try..catch` to support compiling without exceptions.
|
||||
* Adjust SFINAE use to try to support g++ 3.4 ({svn-ticket-url}/7175[#7175^]).
|
||||
* Updated to use the new config macros.
|
||||
|
||||
== Release 1.50.0
|
||||
|
||||
* Fix equality for `unordered_multiset` and `unordered_multimap`.
|
||||
* {svn-ticket-url}/6857[Ticket 6857^]:
|
||||
Implement `reserve`.
|
||||
* {svn-ticket-url}/6771[Ticket 6771^]:
|
||||
Avoid gcc's `-Wfloat-equal` warning.
|
||||
* {svn-ticket-url}/6784[Ticket 6784^]:
|
||||
Fix some Sun specific code.
|
||||
* {svn-ticket-url}/6190[Ticket 6190^]:
|
||||
Avoid gcc's `-Wshadow` warning.
|
||||
* {svn-ticket-url}/6905[Ticket 6905^]:
|
||||
Make namespaces in macros compatible with `bcp` custom namespaces.
|
||||
Fixed by Luke Elliott.
|
||||
* Remove some of the smaller prime number of buckets, as they may make
|
||||
collisions quite probable (e.g. multiples of 5 are very common because
|
||||
we used base 10).
|
||||
* On old versions of Visual {cpp}, use the container library's implementation
|
||||
of `allocator_traits`, as it's more likely to work.
|
||||
* On machines with 64 bit std::size_t, use power of 2 buckets, with Thomas
|
||||
Wang's hash function to pick which one to use. As modulus is very slow
|
||||
for 64 bit values.
|
||||
* Some internal changes.
|
||||
|
||||
== Release 1.49.0
|
||||
|
||||
* Fix warning due to accidental odd assignment.
|
||||
* Slightly better error messages.
|
||||
|
||||
== Release 1.48.0 - Major update
|
||||
|
||||
This is major change which has been converted to use Boost.Move's move
|
||||
emulation, and be more compliant with the C++11 standard. See the
|
||||
[link unordered.compliance compliance section] for details.
|
||||
emulation, and be more compliant with the {cpp}11 standard. See the
|
||||
xref:unordered.adoc#compliance[compliance section] for details.
|
||||
|
||||
The container now meets C++11's complexity requirements, but to do so
|
||||
The container now meets {cpp}11's complexity requirements, but to do so
|
||||
uses a little more memory. This means that `quick_erase` and
|
||||
`erase_return_void` are no longer required, they'll be removed in a
|
||||
future version.
|
||||
|
||||
C++11 support has resulted in some breaking changes:
|
||||
{cpp}11 support has resulted in some breaking changes:
|
||||
|
||||
* Equality comparison has been changed to the C++11 specification.
|
||||
* Equality comparison has been changed to the {cpp}11 specification.
|
||||
In a container with equivalent keys, elements in a group with equal
|
||||
keys used to have to be in the same order to be considered equal,
|
||||
now they can be a permutation of each other. To use the old
|
||||
@ -168,193 +242,140 @@ C++11 support has resulted in some breaking changes:
|
||||
pointers, rather than the allocator's `pointer` type.
|
||||
|
||||
* `emplace` used to emulate the variadic pair constructors that
|
||||
appeared in early C++0x drafts. Since they were removed it no
|
||||
appeared in early {cpp}0x drafts. Since they were removed it no
|
||||
longer does so. It does emulate the new `piecewise_construct`
|
||||
pair constructors - only you need to use
|
||||
`boost::piecewise_construct`. To use the old emulation of
|
||||
the variadic constructors define
|
||||
`BOOST_UNORDERED_DEPRECATED_PAIR_CONSTRUCT`.
|
||||
|
||||
[h2 Boost 1.49.0]
|
||||
== Release 1.45.0
|
||||
|
||||
* Fix warning due to accidental odd assignment.
|
||||
* Slightly better error messages.
|
||||
* Fix a bug when inserting into an `unordered_map` or `unordered_set` using
|
||||
iterators which returns `value_type` by copy.
|
||||
|
||||
[h2 Boost 1.50.0]
|
||||
== Release 1.43.0
|
||||
|
||||
* Fix equality for `unordered_multiset` and `unordered_multimap`.
|
||||
* [@https://svn.boost.org/trac/boost/ticket/6857 Ticket 6857]:
|
||||
Implement `reserve`.
|
||||
* [@https://svn.boost.org/trac/boost/ticket/6771 Ticket 6771]:
|
||||
Avoid gcc's `-Wfloat-equal` warning.
|
||||
* [@https://svn.boost.org/trac/boost/ticket/6784 Ticket 6784]:
|
||||
Fix some Sun specific code.
|
||||
* [@https://svn.boost.org/trac/boost/ticket/6190 Ticket 6190]:
|
||||
Avoid gcc's `-Wshadow` warning.
|
||||
* [@https://svn.boost.org/trac/boost/ticket/6905 Ticket 6905]:
|
||||
Make namespaces in macros compatible with `bcp` custom namespaces.
|
||||
Fixed by Luke Elliott.
|
||||
* Remove some of the smaller prime number of buckets, as they may make
|
||||
collisions quite probable (e.g. multiples of 5 are very common because
|
||||
we used base 10).
|
||||
* On old versions of Visual C++, use the container library's implementation
|
||||
of `allocator_traits`, as it's more likely to work.
|
||||
* On machines with 64 bit std::size_t, use power of 2 buckets, with Thomas
|
||||
Wang's hash function to pick which one to use. As modulus is very slow
|
||||
for 64 bit values.
|
||||
* Some internal changes.
|
||||
* {svn-ticket-url}/3966[Ticket 3966^]:
|
||||
`erase_return_void` is now `quick_erase`, which is the
|
||||
http://home.roadrunner.com/~hinnant/issue_review/lwg-active.html#579[
|
||||
current forerunner for resolving the slow erase by iterator^], although
|
||||
there's a strong possibility that this may change in the future. The old
|
||||
method name remains for backwards compatibility but is considered deprecated
|
||||
and will be removed in a future release.
|
||||
* Use Boost.Exception.
|
||||
* Stop using deprecated `BOOST_HAS_*` macros.
|
||||
|
||||
[h2 Boost 1.51.0]
|
||||
== Release 1.42.0
|
||||
|
||||
* Fix construction/destruction issue when using a C++11 compiler with a
|
||||
C++03 allocator ([ticket 7100]).
|
||||
* Remove a `try..catch` to support compiling without exceptions.
|
||||
* Adjust SFINAE use to try to support g++ 3.4 ([ticket 7175]).
|
||||
* Updated to use the new config macros.
|
||||
* Support instantiating the containers with incomplete value types.
|
||||
* Reduced the number of warnings (mostly in tests).
|
||||
* Improved codegear compatibility.
|
||||
* {svn-ticket-url}/3693[Ticket 3693^]:
|
||||
Add `erase_return_void` as a temporary workaround for the current
|
||||
`erase` which can be inefficient because it has to find the next
|
||||
element to return an iterator.
|
||||
* Add templated find overload for compatible keys.
|
||||
* {svn-ticket-url}/3773[Ticket 3773^]:
|
||||
Add missing `std` qualifier to `ptrdiff_t`.
|
||||
* Some code formatting changes to fit almost all lines into 80 characters.
|
||||
|
||||
[h2 Boost 1.52.0]
|
||||
== Release 1.41.0 - Major update
|
||||
|
||||
* Faster assign, which assigns to existing nodes where possible, rather than
|
||||
creating entirely new nodes and copy constructing.
|
||||
* Fixed bug in `erase_range` ([ticket 7471]).
|
||||
* Reverted some of the internal changes to how nodes are created, especially
|
||||
for C++11 compilers. 'construct' and 'destroy' should work a little better
|
||||
for C++11 allocators.
|
||||
* Simplified the implementation a bit. Hopefully more robust.
|
||||
* The original version made heavy use of macros to sidestep some of the older
|
||||
compilers' poor template support. But since I no longer support those
|
||||
compilers and the macro use was starting to become a maintenance burden it
|
||||
has been rewritten to use templates instead of macros for the implementation
|
||||
classes.
|
||||
|
||||
[h2 Boost 1.53.0]
|
||||
* The container object is now smaller thanks to using `boost::compressed_pair`
|
||||
for EBO and a slightly different function buffer - now using a bool instead
|
||||
of a member pointer.
|
||||
|
||||
* Remove support for the old pre-standard variadic pair constructors, and
|
||||
equality implementation. Both have been deprecated since Boost 1.48.
|
||||
* Remove use of deprecated config macros.
|
||||
* More internal implementation changes, including a much simpler
|
||||
implementation of `erase`.
|
||||
* Buckets are allocated lazily which means that constructing an empty container
|
||||
will not allocate any memory.
|
||||
|
||||
[h2 Boost 1.54.0]
|
||||
== Release 1.40.0
|
||||
|
||||
* Mark methods specified in standard as `noexpect`. More to come in the next
|
||||
release.
|
||||
* If the hash function and equality predicate are known to both have nothrow
|
||||
move assignment or construction then use them.
|
||||
* {svn-ticket-url}/2975[Ticket 2975^]:
|
||||
Store the prime list as a preprocessor sequence - so that it will always get
|
||||
the length right if it changes again in the future.
|
||||
* {svn-ticket-url}/1978[Ticket 1978^]:
|
||||
Implement `emplace` for all compilers.
|
||||
* {svn-ticket-url}/2908[Ticket 2908^],
|
||||
{svn-ticket-url}/3096[Ticket 3096^]:
|
||||
Some workarounds for old versions of borland, including adding explicit
|
||||
destructors to all containers.
|
||||
* {svn-ticket-url}/3082[Ticket 3082^]:
|
||||
Disable incorrect Visual {cpp} warnings.
|
||||
* Better configuration for {cpp}0x features when the headers aren't available.
|
||||
* Create less buckets by default.
|
||||
|
||||
[h2 Boost 1.55.0]
|
||||
== Release 1.39.0
|
||||
|
||||
* Avoid some warnings ([ticket 8851], [ticket 8874]).
|
||||
* Avoid exposing some detail functions via. ADL on the iterators.
|
||||
* Follow the standard by only using the allocators' construct and destroy
|
||||
methods to construct and destroy stored elements. Don't use them for internal
|
||||
data like pointers.
|
||||
* {svn-ticket-url}/2756[Ticket 2756^]: Avoid a warning
|
||||
on Visual {cpp} 2009.
|
||||
* Some other minor internal changes to the implementation, tests and
|
||||
documentation.
|
||||
* Avoid an unnecessary copy in `operator[]`.
|
||||
* {svn-ticket-url}/2975[Ticket 2975^]: Fix length of
|
||||
prime number list.
|
||||
|
||||
[h2 Boost 1.56.0]
|
||||
== Release 1.38.0
|
||||
|
||||
* Fix some shadowed variable warnings ([ticket 9377]).
|
||||
* Fix allocator use in documentation ([ticket 9719]).
|
||||
* Always use prime number of buckets for integers. Fixes performance
|
||||
regression when inserting consecutive integers, although makes other
|
||||
uses slower ([ticket 9282]).
|
||||
* Only construct elements using allocators, as specified in C++11 standard.
|
||||
* Use link:../../../core/swap.html[`boost::swap`^].
|
||||
* {svn-ticket-url}/2237[Ticket 2237^]:
|
||||
Document that the equality and inequality operators are undefined for two
|
||||
objects if their equality predicates aren't equivalent. Thanks to Daniel
|
||||
Krügler.
|
||||
* {svn-ticket-url}/1710[Ticket 1710^]:
|
||||
Use a larger prime number list. Thanks to Thorsten Ottosen and Hervé
|
||||
Brönnimann.
|
||||
* Use
|
||||
link:../../../type_traits/index.html[aligned storage^] to store the types.
|
||||
This changes the way the allocator is used to construct nodes. It used to
|
||||
construct the node with two calls to the allocator's `construct`
|
||||
method - once for the pointers and once for the value. It now constructs
|
||||
the node with a single call to construct and then constructs the value using
|
||||
in place construction.
|
||||
* Add support for {cpp}0x initializer lists where they're available (currently
|
||||
only g++ 4.4 in {cpp}0x mode).
|
||||
|
||||
[h2 Boost 1.57.0]
|
||||
== Release 1.37.0
|
||||
|
||||
* Fix the `pointer` typedef in iterators ([ticket 10672]).
|
||||
* Fix Coverity warning
|
||||
([@https://github.com/boostorg/unordered/pull/2 GitHub #2]).
|
||||
* Rename overload of `emplace` with hint, to `emplace_hint` as specified in
|
||||
http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2008/n2691.pdf[n2691^].
|
||||
* Provide forwarding headers at `<boost/unordered/unordered_map_fwd.hpp>` and
|
||||
`<boost/unordered/unordered_set_fwd.hpp>`.
|
||||
* Move all the implementation inside `boost/unordered`, to assist
|
||||
modularization and hopefully make it easier to track Release subversion.
|
||||
|
||||
[h2 Boost 1.58.0]
|
||||
== Release 1.36.0
|
||||
|
||||
* Remove unnecessary template parameter from const iterators.
|
||||
* Rename private `iterator` typedef in some iterator classes, as it
|
||||
confuses some traits classes.
|
||||
* Fix move assignment with stateful, propagate_on_container_move_assign
|
||||
allocators ([ticket 10777]).
|
||||
* Fix rare exception safety issue in move assignment.
|
||||
* Fix potential overflow when calculating number of buckets to allocate
|
||||
([@https://github.com/boostorg/unordered/pull/4 GitHub #4]).
|
||||
First official release.
|
||||
|
||||
[h2 Boost 1.62.0]
|
||||
* Rearrange the internals.
|
||||
* Move semantics - full support when rvalue references are available, emulated
|
||||
using a cut down version of the Adobe move library when they are not.
|
||||
* Emplace support when rvalue references and variadic template are available.
|
||||
* More efficient node allocation when rvalue references and variadic template
|
||||
are available.
|
||||
* Added equality operators.
|
||||
|
||||
* Remove use of deprecated `boost::iterator`.
|
||||
* Remove `BOOST_NO_STD_DISTANCE` workaround.
|
||||
* Remove `BOOST_UNORDERED_DEPRECATED_EQUALITY` warning.
|
||||
* Simpler implementation of assignment, fixes an exception safety issue
|
||||
for `unordered_multiset` and `unordered_multimap`. Might be a little slower.
|
||||
* Stop using return value SFINAE which some older compilers have issues
|
||||
with.
|
||||
== Boost 1.35.0 Add-on - 31st March 2008
|
||||
|
||||
[h2 Boost 1.63.0]
|
||||
Unofficial release uploaded to vault, to be used with Boost 1.35.0. Incorporated
|
||||
many of the suggestions from the review.
|
||||
|
||||
* Check hint iterator in `insert`/`emplace_hint`.
|
||||
* Fix some warnings, mostly in the tests.
|
||||
* Manually write out `emplace_args` for small numbers of arguments -
|
||||
should make template error messages a little more bearable.
|
||||
* Remove superfluous use of `boost::forward` in emplace arguments,
|
||||
which fixes emplacing string literals in old versions of Visual C++.
|
||||
* Fix an exception safety issue in assignment. If bucket allocation
|
||||
throws an exception, it can overwrite the hash and equality functions while
|
||||
leaving the existing elements in place. This would mean that the function
|
||||
objects wouldn't match the container elements, so elements might be in the
|
||||
wrong bucket and equivalent elements would be incorrectly handled.
|
||||
* Various reference documentation improvements.
|
||||
* Better allocator support ([ticket 12459]).
|
||||
* Make the no argument constructors implicit.
|
||||
* Implement missing allocator aware constructors.
|
||||
* Fix assigning the hash/key equality functions for empty containers.
|
||||
* Remove unary/binary_function from the examples in the documentation.
|
||||
They are removed in C++17.
|
||||
* Support 10 constructor arguments in emplace. It was meant to support up to 10
|
||||
arguments, but an off by one error in the preprocessor code meant it only
|
||||
supported up to 9.
|
||||
* Improved portability thanks to Boost regression testing.
|
||||
* Fix lots of typos, and clearer text in the documentation.
|
||||
* Fix floating point to `std::size_t` conversion when calculating sizes from
|
||||
the max load factor, and use `double` in the calculation for greater accuracy.
|
||||
* Fix some errors in the examples.
|
||||
|
||||
[h2 Boost 1.64.0]
|
||||
* Initial support for new C++17 member functions:
|
||||
`insert_or_assign` and `try_emplace` in `unordered_map`,
|
||||
* Initial support for `merge` and `extract`.
|
||||
Does not include transferring nodes between
|
||||
`unordered_map` and `unordered_multimap` or between `unordered_set` and
|
||||
`unordered_multiset` yet. That will hopefully be in the next version of
|
||||
Boost.
|
||||
== Review Version
|
||||
|
||||
[h2 Boost 1.65.0]
|
||||
Initial review version, for the review conducted from 7th December 2007 to
|
||||
16th December 2007.
|
||||
|
||||
* Add deprecated attributes to `quick_erase` and `erase_return_void`.
|
||||
I really will remove them in a future version this time.
|
||||
* Small standards compliance fixes:
|
||||
* `noexpect` specs for `swap` free functions.
|
||||
* Add missing `insert(P&&)` methods.
|
||||
|
||||
[h2 Boost 1.66.0]
|
||||
|
||||
* Simpler move construction implementation.
|
||||
* Documentation fixes ([pull_request 6]).
|
||||
|
||||
[h2 Boost 1.67.0]
|
||||
|
||||
* Improved C++17 support:
|
||||
* Add template deduction guides from the standard.
|
||||
* Use a simple implementation of `optional` in node handles, so
|
||||
that they're closer to the standard.
|
||||
* Add missing `noexcept` specifications to `swap`, `operator=`
|
||||
and node handles, and change the implementation to match.
|
||||
Using `std::allocator_traits::is_always_equal`, or our own
|
||||
implementation when not available, and
|
||||
`boost::is_nothrow_swappable` in the implementation.
|
||||
* Improved C++20 support:
|
||||
* Use `boost::to_address`, which has the proposed C++20 semantics,
|
||||
rather than the old custom implementation.
|
||||
* Add `element_type` to iterators, so that `std::pointer_traits`
|
||||
will work.
|
||||
* Use `std::piecewise_construct` on recent versions of Visual C++,
|
||||
and other uses of the Dinkumware standard library,
|
||||
now using Boost.Predef to check compiler and library versions.
|
||||
* Use `std::iterator_traits` rather than the boost iterator traits
|
||||
in order to remove dependency on Boost.Iterator.
|
||||
* Remove iterators' inheritance from `std::iterator`, which is
|
||||
deprecated in C++17, thanks to Daniela Engert
|
||||
([@https://github.com/boostorg/unordered/pull/7 PR#7]).
|
||||
* Stop using `BOOST_DEDUCED_TYPENAME`.
|
||||
* Update some Boost include paths.
|
||||
* Rename some internal methods, and variables.
|
||||
* Various testing improvements.
|
||||
* Miscellaneous internal changes.
|
||||
|
||||
[endsect]
|
112
doc/unordered/comparison.adoc
Normal file
@ -0,0 +1,112 @@
|
||||
[#comparison]
|
||||
|
||||
:idprefix: comparison_
|
||||
|
||||
= Comparison with Associative Containers
|
||||
|
||||
[caption=, title='Table {counter:table-counter} Interface differences']
|
||||
[cols="1,1", frame=all, grid=rows]
|
||||
|===
|
||||
|Associative Containers |Unordered Associative Containers
|
||||
|
||||
|Parameterized by an ordering relation `Compare`
|
||||
|Parameterized by a function object `Hash` and an equivalence relation `Pred`
|
||||
|
||||
|Keys can be compared using `key_compare` which is accessed by member function `key_comp()`, values can be compared using `value_compare` which is accessed by member function `value_comp()`.
|
||||
|Keys can be hashed using `hasher` which is accessed by member function `hash_function()`, and checked for equality using `key_equal` which is accessed by member function `key_eq()`. There is no function object for compared or hashing values.
|
||||
|
||||
|Constructors have optional extra parameters for the comparison object.
|
||||
|Constructors have optional extra parameters for the initial minimum number of buckets, a hash function and an equality object.
|
||||
|
||||
|Keys `k1`, `k2` are considered equivalent if `!Compare(k1, k2) && !Compare(k2, k1)`.
|
||||
|Keys `k1`, `k2` are considered equivalent if `Pred(k1, k2)`
|
||||
|
||||
|Member function `lower_bound(k)` and `upper_bound(k)`
|
||||
|No equivalent. Since the elements aren't ordered `lower_bound` and `upper_bound` would be meaningless.
|
||||
|
||||
|`equal_range(k)` returns an empty range at the position that `k` would be inserted if `k` isn't present in the container.
|
||||
|`equal_range(k)` returns a range at the end of the container if `k` isn't present in the container. It can't return a positioned range as `k` could be inserted into multiple place. To find out the bucket that `k` would be inserted into use `bucket(k)`. But remember that an insert can cause the container to rehash - meaning that the element can be inserted into a different bucket.
|
||||
|
||||
|`iterator`, `const_iterator` are of the bidirectional category.
|
||||
|`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>>. Pointers and references to the container's elements are never invalidated.
|
||||
|
||||
|Iterators iterate through the container in the order defined by the comparison object.
|
||||
|Iterators iterate through the container in an arbitrary order, that can change as elements are inserted, although equivalent elements are always adjacent.
|
||||
|
||||
|No equivalent
|
||||
|Local iterators can be used to iterate through individual buckets. (The order of local iterators and iterators aren't required to have any correspondence.)
|
||||
|
||||
|Can be compared using the `==`, `!=`, `<`, `\<=`, `>`, `>=` operators.
|
||||
|Can be compared using the `==` and `!=` operators.
|
||||
|
||||
|
|
||||
|When inserting with a hint, implementations are permitted to ignore the hint.
|
||||
|
||||
|`erase` never throws an exception
|
||||
|The containers' hash or predicate function can throw exceptions from `erase`.
|
||||
|
||||
|===
|
||||
|
||||
---
|
||||
|
||||
[caption=, title='Table {counter:table-counter} Complexity Guarantees']
|
||||
[cols="1,1,1", frame=all, grid=rows]
|
||||
|===
|
||||
|Operation |Associative Containers |Unordered Associative Containers
|
||||
|
||||
|Construction of empty container
|
||||
|constant
|
||||
|O(_n_) where _n_ is the minimum number of buckets.
|
||||
|
||||
|Construction of container from a range of _N_ elements
|
||||
|O(_N log N_), O(_N_) if the range is sorted with `value_comp()`
|
||||
|Average case O(_N_), worst case O(_N^2^_)
|
||||
|
||||
|Insert a single element
|
||||
|logarithmic
|
||||
|Average case constant, worst case linear
|
||||
|
||||
|Insert a single element with a hint
|
||||
|Amortized constant if `t` elements inserted right after hint, logarithmic otherwise
|
||||
|Average case constant, worst case linear (ie. the same as a normal insert).
|
||||
|
||||
|Inserting a range of _N_ elements
|
||||
|_N_ log(`size()` + _N_)
|
||||
|Average case O(_N_), worst case O(_N_ * `size()`)
|
||||
|
||||
|Erase by key, `k`
|
||||
|O(log(`size()`) + `count(k)`)
|
||||
|Average case: O(`count(k)`), Worst case: O(`size()`)
|
||||
|
||||
|Erase a single element by iterator
|
||||
|Amortized constant
|
||||
|Average case: O(1), Worst case: O(`size()`)
|
||||
|
||||
|Erase a range of _N_ elements
|
||||
|O(log(`size()`) + _N_)
|
||||
|Average case: O(_N_), Worst case: O(`size()`)
|
||||
|
||||
|Clearing the container
|
||||
|O(`size()`)
|
||||
|O(`size()`)
|
||||
|
||||
|Find
|
||||
|logarithmic
|
||||
|Average case: O(1), Worst case: O(`size()`)
|
||||
|
||||
|Count
|
||||
|O(log(`size()`) + `count(k)`)
|
||||
|Average case: O(1), Worst case: O(`size()`)
|
||||
|
||||
|`equal_range(k)`
|
||||
|logarithmic
|
||||
|Average case: O(`count(k)`), Worst case: O(`size()`)
|
||||
|
||||
|`lower_bound`,`upper_bound`
|
||||
|logarithmic
|
||||
|n/a
|
||||
|
||||
|===
|
@ -1,16 +1,17 @@
|
||||
[/ Copyright 2011 Daniel James.
|
||||
/ 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) ]
|
||||
[#compliance]
|
||||
= Standard Compliance
|
||||
|
||||
[section:compliance Standard Compliance]
|
||||
:idprefix: compliance_
|
||||
|
||||
:cpp: C++
|
||||
|
||||
The intent of Boost.Unordered is to implement a close (but imperfect)
|
||||
implementation of the C++17 standard, that will work with C++98 upwards.
|
||||
implementation of the {cpp}17 standard, that will work with {cpp}98 upwards.
|
||||
The wide compatibility does mean some comprimises have to be made.
|
||||
With a compiler and library that fully support C++11, the differences should
|
||||
With a compiler and library that fully support {cpp}11, the differences should
|
||||
be minor.
|
||||
|
||||
[section:move 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,
|
||||
@ -22,11 +23,9 @@ but imperfect emulation. On such compilers:
|
||||
* The containers themselves are not movable.
|
||||
* Argument forwarding is not perfect.
|
||||
|
||||
[endsect]
|
||||
== Use of allocators
|
||||
|
||||
[section:allocator_compliance Use of allocators]
|
||||
|
||||
C++11 introduced a new allocator system. It's backwards compatible due to
|
||||
{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
|
||||
some changes for allocators which worked with the old versions of the
|
||||
unordered containers.
|
||||
@ -57,11 +56,9 @@ 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.
|
||||
|
||||
[endsect]
|
||||
== Construction/Destruction using allocators
|
||||
|
||||
[section:construction Construction/Destruction using allocators]
|
||||
|
||||
The following support is required for full use of C++11 style
|
||||
The following support is required for full use of {cpp}11 style
|
||||
construction/destruction:
|
||||
|
||||
* Variadic templates.
|
||||
@ -74,15 +71,12 @@ otherwise.
|
||||
|
||||
When this is the case `allocator_traits::construct` and
|
||||
`allocator_traits::destroy` will always be used, apart from when piecewise
|
||||
constructing a `std::pair` using `boost::tuple` (see [link
|
||||
unordered.compliance.pairs below]), but that should be easily avoided.
|
||||
constructing a `std::pair` using `boost::tuple` (see <<compliance_pairs,below>>), but that should be easily avoided.
|
||||
|
||||
When support is not available `allocator_traits::construct` and
|
||||
`allocator_traits::destroy` are never called.
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:pointer_traits Pointer Traits]
|
||||
== Pointer Traits
|
||||
|
||||
`pointer_traits` aren't used. Instead, pointer types are obtained from
|
||||
rebound allocators, this can cause problems if the allocator can't be
|
||||
@ -90,30 +84,28 @@ used with incomplete types. If `const_pointer` is not defined in the
|
||||
allocator, `boost::pointer_to_other<pointer, const value_type>::type`
|
||||
is used to obtain a const pointer.
|
||||
|
||||
[endsect]
|
||||
|
||||
[#unordered.compliance.pairs]
|
||||
[section:pairs Pairs]
|
||||
== Pairs
|
||||
|
||||
Since the containers use `std::pair` they're limited to the version
|
||||
from the current standard library. But since C++11 `std::pair`'s
|
||||
from the current standard library. But since {cpp}11 ``std::pair``'s
|
||||
`piecewise_construct` based constructor is very useful, `emplace`
|
||||
emulates it with a `piecewise_construct` in the `boost::unordered`
|
||||
namespace. So for example, the following will work:
|
||||
|
||||
boost::unordered_multimap<std::string, std::complex> x;
|
||||
[source,c++]
|
||||
----
|
||||
boost::unordered_multimap<std::string, std::complex> x;
|
||||
|
||||
x.emplace(
|
||||
boost::unordered::piecewise_construct,
|
||||
boost::make_tuple("key"), boost::make_tuple(1, 2));
|
||||
x.emplace(
|
||||
boost::unordered::piecewise_construct,
|
||||
boost::make_tuple("key"), boost::make_tuple(1, 2));
|
||||
----
|
||||
|
||||
Older drafts of the standard also supported variadic constructors
|
||||
for `std::pair`, where the first argument would be used for the
|
||||
first part of the pair, and the remaining for the second part.
|
||||
|
||||
[endsect]
|
||||
|
||||
[section:misc Miscellaneous]
|
||||
== Miscellaneous
|
||||
|
||||
When swapping, `Pred` and `Hash` are not currently swapped by calling
|
||||
`swap`, their copy constructors are used. As a consequence when swapping
|
||||
@ -122,7 +114,3 @@ an exception may be thrown from their copy constructor.
|
||||
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.
|
||||
|
||||
[endsect]
|
||||
|
||||
[endsect]
|
14
doc/unordered/copyright.adoc
Normal file
@ -0,0 +1,14 @@
|
||||
[#copyright]
|
||||
= Copyright and License
|
||||
|
||||
:idprefix: copyright_
|
||||
|
||||
*Daniel James*
|
||||
|
||||
Copyright (C) 2003, 2004 Jeremy B. Maitin-Shepard
|
||||
|
||||
Copyright (C) 2005-2008 Daniel James
|
||||
|
||||
Copyright (C) 2022 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)
|
149
doc/unordered/hash_equality.adoc
Normal file
@ -0,0 +1,149 @@
|
||||
[#hash_equality]
|
||||
|
||||
:idprefix: hash_equality_
|
||||
|
||||
= Equality Predicates and Hash Functions
|
||||
|
||||
While the associative containers use an ordering relation to specify how the
|
||||
elements are stored, the unordered associative containers use an equality
|
||||
predicate and a hash function. For example, <<unordered_map,boost::unordered_map>>
|
||||
is declared as:
|
||||
|
||||
```
|
||||
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;
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```
|
||||
boost::unordered_map<std::string, int, hash::fnv_1>
|
||||
dictionary;
|
||||
```
|
||||
|
||||
There is an link:../../examples/fnv1.hpp[implementation of FNV-1^] 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:
|
||||
|
||||
```
|
||||
struct iequal_to
|
||||
{
|
||||
bool operator()(std::string const& x,
|
||||
std::string const& y) const
|
||||
{
|
||||
return boost::algorithm::iequals(x, y, std::locale());
|
||||
}
|
||||
};
|
||||
|
||||
struct ihash
|
||||
{
|
||||
std::size_t operator()(std::string const& x) const
|
||||
{
|
||||
std::size_t seed = 0;
|
||||
std::locale locale;
|
||||
|
||||
for(std::string::const_iterator it = x.begin();
|
||||
it != x.end(); ++it)
|
||||
{
|
||||
boost::hash_combine(seed, std::toupper(*it, locale));
|
||||
}
|
||||
|
||||
return seed;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Which you can then use in a case insensitive dictionary:
|
||||
```
|
||||
boost::unordered_map<std::string, int, ihash, iequal_to>
|
||||
idictionary;
|
||||
```
|
||||
|
||||
This is a simplified version of the example at
|
||||
link:../../examples/case_insensitive.hpp[/libs/unordered/examples/case_insensitive.hpp^] which supports other locales and string types.
|
||||
|
||||
CAUTION: Be careful when using the equality (`==`) operator with custom equality
|
||||
predicates, especially if you're using a function pointer. If you compare two
|
||||
containers with different equality predicates then the result is undefined.
|
||||
For most stateless function objects this is impossible - since you can only
|
||||
compare objects with the same equality predicate you know the equality
|
||||
predicates must be equal. But if you're using function pointers or a stateful
|
||||
equality predicate (e.g. `boost::function`) then you can get into trouble.
|
||||
|
||||
== Custom Types
|
||||
|
||||
Similarly, a custom hash function can be used for custom types:
|
||||
|
||||
```
|
||||
struct point {
|
||||
int x;
|
||||
int y;
|
||||
};
|
||||
|
||||
bool operator==(point const& p1, point const& p2)
|
||||
{
|
||||
return p1.x == p2.x && p1.y == p2.y;
|
||||
}
|
||||
|
||||
struct point_hash
|
||||
{
|
||||
std::size_t operator()(point const& p) const
|
||||
{
|
||||
std::size_t seed = 0;
|
||||
boost::hash_combine(seed, p.x);
|
||||
boost::hash_combine(seed, p.y);
|
||||
return seed;
|
||||
}
|
||||
};
|
||||
|
||||
boost::unordered_multiset<point, point_hash> points;
|
||||
```
|
||||
|
||||
Since the default hash function is link:../../../container_hash/index.html[Boost.Hash^],
|
||||
we can extend it to support the type so that the hash function doesn't need to be explicitly given:
|
||||
|
||||
```
|
||||
struct point {
|
||||
int x;
|
||||
int y;
|
||||
};
|
||||
|
||||
bool operator==(point const& p1, point const& p2)
|
||||
{
|
||||
return p1.x == p2.x && p1.y == p2.y;
|
||||
}
|
||||
|
||||
std::size_t hash_value(point const& p) {
|
||||
std::size_t seed = 0;
|
||||
boost::hash_combine(seed, p.x);
|
||||
boost::hash_combine(seed, p.y);
|
||||
return seed;
|
||||
}
|
||||
|
||||
// Now the default function objects work.
|
||||
boost::unordered_multiset<point> points;
|
||||
```
|
||||
|
||||
See the link:../../../container_hash/index.html[Boost.Hash documentation^] for more detail on how to
|
||||
do this. Remember that it relies on extensions to the standard - so it
|
||||
won't work for other implementations of the unordered associative containers,
|
||||
you'll need to explicitly use Boost.Hash.
|
||||
|
||||
[caption=, title='Table {counter:table-counter} Methods for accessing the hash and equality functions']
|
||||
[cols="1,.^1", frame=all, grid=rows]
|
||||
|===
|
||||
|Method |Description
|
||||
|
||||
|`hasher hash_function() const`
|
||||
|Returns the container's hash function.
|
||||
|
||||
|`key_equal key_eq() const`
|
||||
|Returns the container's key equality function..
|
||||
|
||||
|===
|
116
doc/unordered/intro.adoc
Normal file
@ -0,0 +1,116 @@
|
||||
[#intro]
|
||||
= Introduction
|
||||
|
||||
: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.
|
||||
|
||||
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.
|
||||
|
||||
With this in mind, unordered associative containers were added to the {cpp}
|
||||
standard. This is 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.
|
||||
|
||||
`unordered_set` and `unordered_multiset` are defined in the header
|
||||
`<boost/unordered_set.hpp>`
|
||||
[source,c++]
|
||||
----
|
||||
namespace boost {
|
||||
template <
|
||||
class Key,
|
||||
class Hash = boost::hash<Key>,
|
||||
class Pred = std::equal_to<Key>,
|
||||
class Alloc = std::allocator<Key> >
|
||||
class unordered_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_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;
|
||||
}
|
||||
----
|
||||
|
||||
When using Boost.TR1, these classes are included from `<unordered_set>` and
|
||||
`<unordered_map>`, with the classes added to the `std::tr1` namespace.
|
||||
|
||||
The 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++]
|
||||
----
|
||||
BOOST_FOREACH(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
|
||||
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
|
||||
Boost.Unordered uses link:../../../container_hash/index.html[boost::hash^] it also supports some other types,
|
||||
including standard containers. To use any types not supported by these methods
|
||||
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.
|
72
doc/unordered/rationale.adoc
Normal file
@ -0,0 +1,72 @@
|
||||
[#rationale]
|
||||
|
||||
:idprefix: rationale_
|
||||
|
||||
= Implementation Rationale
|
||||
|
||||
The intent of this library is to implement the unordered
|
||||
containers in the standard, so the interface was fixed. But there are
|
||||
still some implementation decisions to make. The priorities are
|
||||
conformance to the standard and portability.
|
||||
|
||||
The http://en.wikipedia.org/wiki/Hash_table[Wikipedia article on hash tables^]
|
||||
has a good summary of the implementation issues for hash tables in general.
|
||||
|
||||
== Data Structure
|
||||
|
||||
By specifying an interface for accessing the buckets of the container the
|
||||
standard pretty much requires that the hash table uses chained addressing.
|
||||
|
||||
It would be conceivable to write a hash table that uses another method. For
|
||||
example, it could use open addressing, and use the lookup chain to act as a
|
||||
bucket but there are some serious problems with this:
|
||||
|
||||
* The standard requires that pointers to elements aren't invalidated, so
|
||||
the elements can't be stored in one array, but will need a layer of
|
||||
indirection instead - losing the efficiency and most of the memory gain,
|
||||
the main advantages of open addressing.
|
||||
* Local iterators would be very inefficient and may not be able to
|
||||
meet the complexity requirements.
|
||||
* There are also the restrictions on when iterators can be invalidated. Since
|
||||
open addressing degrades badly when there are a high number of collisions the
|
||||
restrictions could prevent a rehash when it's really needed. The maximum load
|
||||
factor could be set to a fairly low value to work around this - but the
|
||||
standard requires that it is initially set to 1.0.
|
||||
* And since the standard is written with a eye towards chained
|
||||
addressing, users will be surprised if the performance doesn't reflect that.
|
||||
|
||||
So chained addressing is used.
|
||||
|
||||
== Number of Buckets
|
||||
|
||||
There are two popular methods for choosing the number of buckets in a hash
|
||||
table. One is to have a prime number of buckets, another is to use a power
|
||||
of 2.
|
||||
|
||||
Using a prime number of buckets, and choosing a bucket by using the modulus
|
||||
of the hash function's result will usually give a good result. The downside
|
||||
is that the required modulus operation is fairly expensive. This is what the
|
||||
containers used to do in most cases.
|
||||
|
||||
Using a power of 2 allows for much quicker selection of the bucket to use,
|
||||
but at the expense of losing the upper bits of the hash value. For some
|
||||
specially designed hash functions it is possible to do this and still get a
|
||||
good result but as the containers can take arbitrary hash functions this can't
|
||||
be relied on.
|
||||
|
||||
To avoid this a transformation could be applied to the hash function, for an
|
||||
example see
|
||||
http://web.archive.org/web/20121102023700/http://www.concentric.net/~Ttwang/tech/inthash.htm[Thomas Wang's article on integer hash functions^].
|
||||
Unfortunately, a transformation like Wang's requires knowledge of the number
|
||||
of bits in the hash value, so it was only used when `size_t` was 64 bit.
|
||||
|
||||
Since release 1.79.0, https://en.wikipedia.org/wiki/Hash_function#Fibonacci_hashing[Fibonacci hashing]
|
||||
is used instead. With this implementation, the bucket number is determined
|
||||
by using `(h * m) >> (w - k)`, where `h` is the hash value, `m` is the golden
|
||||
ratio multiplied by `2^w`, `w` is the word size (32 or 64), and `2^k` is the
|
||||
number of buckets. This provides a good compromise between speed and
|
||||
distribution.
|
||||
|
||||
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.
|
7
doc/unordered/ref.adoc
Normal file
@ -0,0 +1,7 @@
|
||||
[#reference]
|
||||
= Reference
|
||||
|
||||
include::unordered_map.adoc[]
|
||||
include::unordered_multimap.adoc[]
|
||||
include::unordered_set.adoc[]
|
||||
include::unordered_multiset.adoc[]
|
1664
doc/unordered/unordered_map.adoc
Normal file
1447
doc/unordered/unordered_multimap.adoc
Normal file
1408
doc/unordered/unordered_multiset.adoc
Normal file
1429
doc/unordered/unordered_set.adoc
Normal file
819
include/boost/unordered/detail/fca.hpp
Normal file
@ -0,0 +1,819 @@
|
||||
// Copyright (C) 2022 Joaquin M Lopez Munoz.
|
||||
// Copyright (C) 2022 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_DETAIL_FCA_HPP
|
||||
#define BOOST_UNORDERED_DETAIL_FCA_HPP
|
||||
|
||||
/*
|
||||
|
||||
The general structure of the fast closed addressing implementation is that we
|
||||
use straight-forward separate chaining (i.e. each bucket contains its own linked
|
||||
list) and then improve iteration time by adding an array of "bucket groups".
|
||||
|
||||
A bucket group is a constant-width view into a subsection of the buckets array,
|
||||
containing a bitmask that indicates which one of the buckets in the subsection
|
||||
contains a list of nodes. This allows the code to test N buckets for occupancy
|
||||
in a single operation. Additional speed can be found by inter-linking occupied
|
||||
bucket groups with one another in a doubly-linked list. To this end, large
|
||||
swathes of the bucket groups array no longer need to be iterated and have their
|
||||
bitmasks examined for occupancy.
|
||||
|
||||
A bucket group iterator contains a pointer to a bucket group along with a
|
||||
pointer into the buckets array. The iterator's bucket pointer is guaranteed to
|
||||
point to a bucket within the bucket group's view of the array. To advance the
|
||||
iterator, we need to determine if we need to skip to the next bucket group or
|
||||
simply move to the next occupied bucket as denoted by the bitmask.
|
||||
|
||||
To accomplish this, we perform something roughly equivalent to this:
|
||||
```
|
||||
bucket_iterator itb = ...
|
||||
bucket_pointer p = itb.p
|
||||
bucket_group_pointer pbg = itb.pbg
|
||||
|
||||
offset = p - pbg->buckets
|
||||
// because we wish to see if the _next_ bit in the mask is occupied, we'll
|
||||
// generate a testing mask from the current offset + 1
|
||||
//
|
||||
testing_mask = reset_first_bits(offset + 1)
|
||||
n = ctz(pbg->bitmask & testing_mask)
|
||||
|
||||
if (n < N) {
|
||||
p = pbg->buckets + n
|
||||
} else {
|
||||
pbg = pbg->next
|
||||
p = pbg->buckets + ctz(pbg->bitmask)
|
||||
}
|
||||
```
|
||||
|
||||
`reset_first_bits` yields an unsigned integral with the first n bits set to 0
|
||||
and then by counting the number of trailing zeroes when AND'd against the bucket
|
||||
group's bitmask, we can derive the offset into the buckets array. When the
|
||||
calculated offset is equal to N, we know we've reached the end of a bucket group
|
||||
and we can advance to the next one.
|
||||
|
||||
This is a rough explanation for how iterator incrementation should work for a
|
||||
fixed width size of N as 3 for the bucket groups
|
||||
```
|
||||
N = 3
|
||||
p = buckets
|
||||
pbg->bitmask = 0b101
|
||||
pbg->buckets = buckets
|
||||
|
||||
offset = p - pbg->buckets // => 0
|
||||
testing_mask = reset_first_bits(offset + 1) // reset_first_bits(1) => 0b110
|
||||
|
||||
x = bitmask & testing_mask // => 0b101 & 0b110 => 0b100
|
||||
ctz(x) // ctz(0b100) => 2
|
||||
// 2 < 3
|
||||
=> p = pbg->buckets + 2
|
||||
|
||||
// increment again...
|
||||
offset = p - pbg->buckets // => 2
|
||||
testing_mask = reset_first_bits(offset + 1) // reset_first_bits(3) => 0b000
|
||||
|
||||
bitmask & testing_mask // 0b101 & 0b000 => 0b000
|
||||
ctz(0b000) => 3
|
||||
// 3 < 3 is false now
|
||||
pbg = pbg->next
|
||||
initial_offset = ctz(pbg->bitmask)
|
||||
p = pbg->buckets + initial_offset
|
||||
```
|
||||
|
||||
For `size_` number of buckets, there are `1 + (size_ / N)` bucket groups where
|
||||
`N` is the width of a bucket group, determined at compile-time.
|
||||
|
||||
We allocate space for `size_ + 1` buckets, using the last one as a dummy bucket
|
||||
which is kept permanently empty so it can act as a sentinel value in the
|
||||
implementation of `iterator end();`. We set the last bucket group to act as a
|
||||
sentinel.
|
||||
|
||||
```
|
||||
num_groups = size_ / N + 1
|
||||
groups = allocate(num_groups)
|
||||
pbg = groups + (num_groups - 1)
|
||||
|
||||
// not guaranteed to point to exactly N buckets
|
||||
pbg->buckets = buckets + N * (size_ / N)
|
||||
|
||||
// this marks the true end of the bucket array
|
||||
buckets pbg->bitmask = set_bit(size_ % N)
|
||||
|
||||
// links in on itself
|
||||
pbg->next = pbg->prev = pbg
|
||||
```
|
||||
|
||||
To this end, we can devise a safe iteration scheme while also creating a useful
|
||||
sentinel to use as the end iterator.
|
||||
|
||||
Otherwise, usage of the data structure is relatively straight-forward compared
|
||||
to normal separate chaining implementations.
|
||||
|
||||
*/
|
||||
|
||||
#include <boost/unordered/detail/prime_fmod.hpp>
|
||||
|
||||
#include <boost/core/addressof.hpp>
|
||||
#include <boost/core/allocator_access.hpp>
|
||||
#include <boost/core/bit.hpp>
|
||||
#include <boost/core/empty_value.hpp>
|
||||
#include <boost/core/no_exceptions_support.hpp>
|
||||
#include <boost/cstdint.hpp>
|
||||
#include <boost/move/core.hpp>
|
||||
#include <boost/move/utility_core.hpp>
|
||||
#include <boost/swap.hpp>
|
||||
#include <boost/type_traits/aligned_storage.hpp>
|
||||
#include <boost/type_traits/alignment_of.hpp>
|
||||
|
||||
#include <boost/config.hpp>
|
||||
|
||||
#include <iterator>
|
||||
|
||||
namespace boost {
|
||||
namespace unordered {
|
||||
namespace detail {
|
||||
|
||||
template <class ValueType, class VoidPtr> struct node
|
||||
{
|
||||
typedef ValueType value_type;
|
||||
typedef typename boost::pointer_traits<VoidPtr>::template rebind_to<
|
||||
node>::type node_pointer;
|
||||
|
||||
node_pointer next;
|
||||
typename boost::aligned_storage<sizeof(value_type),
|
||||
boost::alignment_of<value_type>::value>::type buf;
|
||||
|
||||
node() BOOST_NOEXCEPT : next(), buf() {}
|
||||
|
||||
value_type* value_ptr() BOOST_NOEXCEPT
|
||||
{
|
||||
return reinterpret_cast<value_type*>(buf.address());
|
||||
}
|
||||
|
||||
value_type& value() BOOST_NOEXCEPT
|
||||
{
|
||||
return *reinterpret_cast<value_type*>(buf.address());
|
||||
}
|
||||
};
|
||||
|
||||
template <class Node, class VoidPtr> struct bucket
|
||||
{
|
||||
typedef typename boost::pointer_traits<VoidPtr>::template rebind_to<
|
||||
Node>::type node_pointer;
|
||||
|
||||
typedef typename boost::pointer_traits<VoidPtr>::template rebind_to<
|
||||
bucket>::type bucket_pointer;
|
||||
|
||||
node_pointer next;
|
||||
|
||||
bucket() BOOST_NOEXCEPT : next() {}
|
||||
};
|
||||
|
||||
template <class Bucket> struct bucket_group
|
||||
{
|
||||
typedef typename Bucket::bucket_pointer bucket_pointer;
|
||||
typedef
|
||||
typename boost::pointer_traits<bucket_pointer>::template rebind_to<
|
||||
bucket_group>::type bucket_group_pointer;
|
||||
|
||||
BOOST_STATIC_CONSTANT(std::size_t, N = sizeof(std::size_t) * CHAR_BIT);
|
||||
|
||||
bucket_pointer buckets;
|
||||
std::size_t bitmask;
|
||||
bucket_group_pointer next, prev;
|
||||
|
||||
bucket_group() BOOST_NOEXCEPT : buckets(), bitmask(0), next(), prev() {}
|
||||
~bucket_group() {}
|
||||
};
|
||||
|
||||
inline std::size_t set_bit(std::size_t n) { return std::size_t(1) << n; }
|
||||
|
||||
inline std::size_t reset_bit(std::size_t n)
|
||||
{
|
||||
return ~(std::size_t(1) << n);
|
||||
}
|
||||
|
||||
inline std::size_t reset_first_bits(std::size_t n) // n>0
|
||||
{
|
||||
return ~(~(std::size_t(0)) >> (sizeof(std::size_t) * 8 - n));
|
||||
}
|
||||
|
||||
template <class Bucket> struct grouped_bucket_iterator
|
||||
{
|
||||
public:
|
||||
typedef typename Bucket::bucket_pointer bucket_pointer;
|
||||
typedef
|
||||
typename boost::pointer_traits<bucket_pointer>::template rebind_to<
|
||||
bucket_group<Bucket> >::type bucket_group_pointer;
|
||||
|
||||
typedef Bucket value_type;
|
||||
typedef typename boost::pointer_traits<bucket_pointer>::difference_type
|
||||
difference_type;
|
||||
typedef Bucket& reference;
|
||||
typedef Bucket* pointer;
|
||||
typedef std::forward_iterator_tag iterator_category;
|
||||
|
||||
private:
|
||||
bucket_pointer p;
|
||||
bucket_group_pointer pbg;
|
||||
|
||||
public:
|
||||
grouped_bucket_iterator() : p(), pbg() {}
|
||||
|
||||
reference operator*() const BOOST_NOEXCEPT { return dereference(); }
|
||||
pointer operator->() const BOOST_NOEXCEPT
|
||||
{
|
||||
return boost::to_address(p);
|
||||
}
|
||||
|
||||
grouped_bucket_iterator& operator++() BOOST_NOEXCEPT
|
||||
{
|
||||
increment();
|
||||
return *this;
|
||||
}
|
||||
|
||||
grouped_bucket_iterator operator++(int) BOOST_NOEXCEPT
|
||||
{
|
||||
grouped_bucket_iterator old = *this;
|
||||
increment();
|
||||
return old;
|
||||
}
|
||||
|
||||
bool operator==(
|
||||
grouped_bucket_iterator const& other) const BOOST_NOEXCEPT
|
||||
{
|
||||
return equal(other);
|
||||
}
|
||||
|
||||
bool operator!=(
|
||||
grouped_bucket_iterator const& other) const BOOST_NOEXCEPT
|
||||
{
|
||||
return !equal(other);
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename, typename, typename>
|
||||
friend class grouped_bucket_array;
|
||||
|
||||
BOOST_STATIC_CONSTANT(std::size_t, N = bucket_group<Bucket>::N);
|
||||
|
||||
grouped_bucket_iterator(bucket_pointer p_, bucket_group_pointer pbg_)
|
||||
: p(p_), pbg(pbg_)
|
||||
{
|
||||
}
|
||||
|
||||
Bucket& dereference() const BOOST_NOEXCEPT { return *p; }
|
||||
|
||||
bool equal(const grouped_bucket_iterator& x) const BOOST_NOEXCEPT
|
||||
{
|
||||
return p == x.p;
|
||||
}
|
||||
|
||||
void increment() BOOST_NOEXCEPT
|
||||
{
|
||||
std::size_t const offset = static_cast<std::size_t>(p - pbg->buckets);
|
||||
|
||||
std::size_t n = std::size_t(boost::core::countr_zero(
|
||||
pbg->bitmask & reset_first_bits(offset + 1)));
|
||||
|
||||
if (n < N) {
|
||||
p = pbg->buckets + static_cast<difference_type>(n);
|
||||
} else {
|
||||
pbg = pbg->next;
|
||||
|
||||
std::ptrdiff_t x = boost::core::countr_zero(pbg->bitmask);
|
||||
p = pbg->buckets + x;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Node> struct const_grouped_local_bucket_iterator;
|
||||
|
||||
template <class Node> struct grouped_local_bucket_iterator
|
||||
{
|
||||
typedef typename Node::node_pointer node_pointer;
|
||||
|
||||
public:
|
||||
typedef typename Node::value_type value_type;
|
||||
typedef value_type element_type;
|
||||
typedef value_type* pointer;
|
||||
typedef value_type& reference;
|
||||
typedef std::ptrdiff_t difference_type;
|
||||
typedef std::forward_iterator_tag iterator_category;
|
||||
|
||||
grouped_local_bucket_iterator() : p() {}
|
||||
|
||||
reference operator*() const BOOST_NOEXCEPT { return dereference(); }
|
||||
|
||||
pointer operator->() const BOOST_NOEXCEPT
|
||||
{
|
||||
return boost::to_address(p);
|
||||
}
|
||||
|
||||
grouped_local_bucket_iterator& operator++() BOOST_NOEXCEPT
|
||||
{
|
||||
increment();
|
||||
return *this;
|
||||
}
|
||||
|
||||
grouped_local_bucket_iterator operator++(int) BOOST_NOEXCEPT
|
||||
{
|
||||
grouped_local_bucket_iterator old = *this;
|
||||
increment();
|
||||
return old;
|
||||
}
|
||||
|
||||
bool operator==(
|
||||
grouped_local_bucket_iterator const& other) const BOOST_NOEXCEPT
|
||||
{
|
||||
return equal(other);
|
||||
}
|
||||
|
||||
bool operator!=(
|
||||
grouped_local_bucket_iterator const& other) const BOOST_NOEXCEPT
|
||||
{
|
||||
return !equal(other);
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename, typename, typename>
|
||||
friend class grouped_bucket_array;
|
||||
|
||||
template <class> friend struct const_grouped_local_bucket_iterator;
|
||||
|
||||
grouped_local_bucket_iterator(node_pointer p_) : p(p_) {}
|
||||
|
||||
value_type& dereference() const BOOST_NOEXCEPT { return p->value(); }
|
||||
|
||||
bool equal(const grouped_local_bucket_iterator& x) const BOOST_NOEXCEPT
|
||||
{
|
||||
return p == x.p;
|
||||
}
|
||||
|
||||
void increment() BOOST_NOEXCEPT { p = p->next; }
|
||||
|
||||
node_pointer p;
|
||||
};
|
||||
|
||||
template <class Node> struct const_grouped_local_bucket_iterator
|
||||
{
|
||||
typedef typename Node::node_pointer node_pointer;
|
||||
|
||||
public:
|
||||
typedef typename Node::value_type const value_type;
|
||||
typedef value_type const element_type;
|
||||
typedef value_type const* pointer;
|
||||
typedef value_type const& reference;
|
||||
typedef std::ptrdiff_t difference_type;
|
||||
typedef std::forward_iterator_tag iterator_category;
|
||||
|
||||
const_grouped_local_bucket_iterator() : p() {}
|
||||
const_grouped_local_bucket_iterator(
|
||||
grouped_local_bucket_iterator<Node> it)
|
||||
: p(it.p)
|
||||
{
|
||||
}
|
||||
|
||||
reference operator*() const BOOST_NOEXCEPT { return dereference(); }
|
||||
|
||||
pointer operator->() const BOOST_NOEXCEPT
|
||||
{
|
||||
return boost::to_address(p);
|
||||
}
|
||||
|
||||
const_grouped_local_bucket_iterator& operator++() BOOST_NOEXCEPT
|
||||
{
|
||||
increment();
|
||||
return *this;
|
||||
}
|
||||
|
||||
const_grouped_local_bucket_iterator operator++(int) BOOST_NOEXCEPT
|
||||
{
|
||||
const_grouped_local_bucket_iterator old = *this;
|
||||
increment();
|
||||
return old;
|
||||
}
|
||||
|
||||
bool operator==(
|
||||
const_grouped_local_bucket_iterator const& other) const BOOST_NOEXCEPT
|
||||
{
|
||||
return equal(other);
|
||||
}
|
||||
|
||||
bool operator!=(
|
||||
const_grouped_local_bucket_iterator const& other) const BOOST_NOEXCEPT
|
||||
{
|
||||
return !equal(other);
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename, typename, typename>
|
||||
friend class grouped_bucket_array;
|
||||
|
||||
const_grouped_local_bucket_iterator(node_pointer p_) : p(p_) {}
|
||||
|
||||
value_type& dereference() const BOOST_NOEXCEPT { return p->value(); }
|
||||
|
||||
bool equal(
|
||||
const const_grouped_local_bucket_iterator& x) const BOOST_NOEXCEPT
|
||||
{
|
||||
return p == x.p;
|
||||
}
|
||||
|
||||
void increment() BOOST_NOEXCEPT { p = p->next; }
|
||||
|
||||
node_pointer p;
|
||||
};
|
||||
|
||||
template <class T> struct span
|
||||
{
|
||||
T* begin() const BOOST_NOEXCEPT { return data; }
|
||||
T* end() const BOOST_NOEXCEPT { return data + size; }
|
||||
|
||||
T* data;
|
||||
std::size_t size;
|
||||
|
||||
span(T* data_, std::size_t size_) : data(data_), size(size_) {}
|
||||
};
|
||||
|
||||
template <class Bucket, class Allocator, class SizePolicy>
|
||||
class grouped_bucket_array
|
||||
: boost::empty_value<typename boost::allocator_rebind<Allocator,
|
||||
node<typename boost::allocator_value_type<Allocator>::type,
|
||||
typename boost::allocator_void_pointer<Allocator>::type> >::
|
||||
type>
|
||||
{
|
||||
BOOST_MOVABLE_BUT_NOT_COPYABLE(grouped_bucket_array)
|
||||
|
||||
typedef typename boost::allocator_value_type<Allocator>::type
|
||||
allocator_value_type;
|
||||
typedef
|
||||
typename boost::allocator_void_pointer<Allocator>::type void_pointer;
|
||||
typedef typename boost::allocator_difference_type<Allocator>::type
|
||||
difference_type;
|
||||
|
||||
public:
|
||||
typedef typename boost::allocator_rebind<Allocator,
|
||||
node<allocator_value_type, void_pointer> >::type node_allocator_type;
|
||||
|
||||
typedef node<allocator_value_type, void_pointer> node_type;
|
||||
typedef typename boost::allocator_pointer<node_allocator_type>::type
|
||||
node_pointer;
|
||||
typedef SizePolicy size_policy;
|
||||
|
||||
private:
|
||||
typedef typename boost::allocator_rebind<Allocator, Bucket>::type
|
||||
bucket_allocator_type;
|
||||
typedef typename boost::allocator_pointer<bucket_allocator_type>::type
|
||||
bucket_pointer;
|
||||
typedef boost::pointer_traits<bucket_pointer> bucket_pointer_traits;
|
||||
|
||||
typedef bucket_group<Bucket> group;
|
||||
typedef typename boost::allocator_rebind<Allocator, group>::type
|
||||
group_allocator_type;
|
||||
typedef typename boost::allocator_pointer<group_allocator_type>::type
|
||||
group_pointer;
|
||||
typedef typename boost::pointer_traits<group_pointer>
|
||||
group_pointer_traits;
|
||||
|
||||
public:
|
||||
typedef Bucket value_type;
|
||||
typedef Bucket bucket_type;
|
||||
typedef std::size_t size_type;
|
||||
typedef Allocator allocator_type;
|
||||
typedef grouped_bucket_iterator<Bucket> iterator;
|
||||
typedef grouped_local_bucket_iterator<node_type> local_iterator;
|
||||
typedef const_grouped_local_bucket_iterator<node_type>
|
||||
const_local_iterator;
|
||||
|
||||
private:
|
||||
std::size_t size_index_, size_;
|
||||
bucket_pointer buckets;
|
||||
group_pointer groups;
|
||||
|
||||
public:
|
||||
grouped_bucket_array(size_type n, const Allocator& al)
|
||||
: empty_value<node_allocator_type>(empty_init_t(), al),
|
||||
size_index_(size_policy::size_index(n)),
|
||||
size_(size_policy::size(size_index_)), buckets(), groups()
|
||||
{
|
||||
bucket_allocator_type bucket_alloc = this->get_bucket_allocator();
|
||||
group_allocator_type group_alloc = this->get_group_allocator();
|
||||
|
||||
size_type const num_buckets = buckets_len();
|
||||
size_type const num_groups = groups_len();
|
||||
|
||||
buckets = boost::allocator_allocate(bucket_alloc, num_buckets);
|
||||
BOOST_TRY
|
||||
{
|
||||
groups = boost::allocator_allocate(group_alloc, num_groups);
|
||||
|
||||
bucket_type* pb = boost::to_address(buckets);
|
||||
for (size_type i = 0; i < num_buckets; ++i) {
|
||||
new (pb + i) bucket_type();
|
||||
}
|
||||
|
||||
group* pg = boost::to_address(groups);
|
||||
for (size_type i = 0; i < num_groups; ++i) {
|
||||
new (pg + i) group();
|
||||
}
|
||||
}
|
||||
BOOST_CATCH(...)
|
||||
{
|
||||
boost::allocator_deallocate(bucket_alloc, buckets, num_buckets);
|
||||
BOOST_RETHROW
|
||||
}
|
||||
BOOST_CATCH_END
|
||||
|
||||
size_type const N = group::N;
|
||||
group_pointer pbg =
|
||||
groups + static_cast<difference_type>(num_groups - 1);
|
||||
|
||||
pbg->buckets =
|
||||
buckets + static_cast<difference_type>(N * (size_ / N));
|
||||
pbg->bitmask = set_bit(size_ % N);
|
||||
pbg->next = pbg->prev = pbg;
|
||||
}
|
||||
|
||||
~grouped_bucket_array() { this->deallocate(); }
|
||||
|
||||
grouped_bucket_array(
|
||||
BOOST_RV_REF(grouped_bucket_array) other) BOOST_NOEXCEPT
|
||||
: empty_value<node_allocator_type>(
|
||||
empty_init_t(), other.get_node_allocator()),
|
||||
size_index_(other.size_index_),
|
||||
size_(other.size_),
|
||||
buckets(other.buckets),
|
||||
groups(other.groups)
|
||||
{
|
||||
other.size_ = 0;
|
||||
other.size_index_ = 0;
|
||||
other.buckets = bucket_pointer();
|
||||
other.groups = group_pointer();
|
||||
}
|
||||
|
||||
grouped_bucket_array& operator=(
|
||||
BOOST_RV_REF(grouped_bucket_array) other) BOOST_NOEXCEPT
|
||||
{
|
||||
BOOST_ASSERT(
|
||||
this->get_node_allocator() == other.get_node_allocator());
|
||||
|
||||
if (this == boost::addressof(other)) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
this->deallocate();
|
||||
size_index_ = other.size_index_;
|
||||
size_ = other.size_;
|
||||
|
||||
buckets = other.buckets;
|
||||
groups = other.groups;
|
||||
|
||||
other.size_index_ = 0;
|
||||
other.size_ = 0;
|
||||
other.buckets = bucket_pointer();
|
||||
other.groups = group_pointer();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void deallocate() BOOST_NOEXCEPT
|
||||
{
|
||||
if (buckets) {
|
||||
bucket_allocator_type bucket_alloc = this->get_bucket_allocator();
|
||||
boost::allocator_deallocate(
|
||||
bucket_alloc, buckets, this->buckets_len());
|
||||
|
||||
buckets = bucket_pointer();
|
||||
}
|
||||
|
||||
if (groups) {
|
||||
group_allocator_type group_alloc = this->get_group_allocator();
|
||||
boost::allocator_deallocate(
|
||||
group_alloc, groups, this->groups_len());
|
||||
|
||||
groups = group_pointer();
|
||||
}
|
||||
}
|
||||
|
||||
void swap(grouped_bucket_array& other)
|
||||
{
|
||||
std::swap(size_index_, other.size_index_);
|
||||
std::swap(size_, other.size_);
|
||||
std::swap(buckets, other.buckets);
|
||||
std::swap(groups, other.groups);
|
||||
|
||||
bool b = boost::allocator_propagate_on_container_swap<
|
||||
allocator_type>::type::value;
|
||||
if (b) {
|
||||
boost::swap(get_node_allocator(), other.get_node_allocator());
|
||||
}
|
||||
}
|
||||
|
||||
node_allocator_type const& get_node_allocator() const
|
||||
{
|
||||
return empty_value<node_allocator_type>::get();
|
||||
}
|
||||
|
||||
node_allocator_type& get_node_allocator()
|
||||
{
|
||||
return empty_value<node_allocator_type>::get();
|
||||
}
|
||||
|
||||
bucket_allocator_type get_bucket_allocator() const
|
||||
{
|
||||
return this->get_node_allocator();
|
||||
}
|
||||
|
||||
group_allocator_type get_group_allocator() const
|
||||
{
|
||||
return this->get_node_allocator();
|
||||
}
|
||||
|
||||
size_type buckets_len() const BOOST_NOEXCEPT { return size_ + 1; }
|
||||
|
||||
size_type groups_len() const BOOST_NOEXCEPT
|
||||
{
|
||||
return size_ / group::N + 1;
|
||||
}
|
||||
|
||||
void reset_allocator(Allocator const& allocator_)
|
||||
{
|
||||
this->get_node_allocator() = node_allocator_type(allocator_);
|
||||
}
|
||||
|
||||
size_type bucket_count() const { return size_; }
|
||||
|
||||
iterator begin() const { return ++at(size_); }
|
||||
|
||||
iterator end() const
|
||||
{
|
||||
// micro optimization: no need to return the bucket group
|
||||
// as end() is not incrementable
|
||||
iterator pbg;
|
||||
pbg.p =
|
||||
buckets + static_cast<difference_type>(this->buckets_len() - 1);
|
||||
return pbg;
|
||||
}
|
||||
|
||||
local_iterator begin(size_type n) const
|
||||
{
|
||||
return local_iterator(
|
||||
(buckets + static_cast<difference_type>(n))->next);
|
||||
}
|
||||
|
||||
local_iterator end(size_type) const { return local_iterator(); }
|
||||
|
||||
size_type capacity() const BOOST_NOEXCEPT { return size_; }
|
||||
|
||||
iterator at(size_type n) const
|
||||
{
|
||||
std::size_t const N = group::N;
|
||||
|
||||
iterator pbg(buckets + static_cast<difference_type>(n),
|
||||
groups + static_cast<difference_type>(n / N));
|
||||
|
||||
return pbg;
|
||||
}
|
||||
|
||||
span<Bucket> raw()
|
||||
{
|
||||
BOOST_ASSERT(size_ == 0 || size_ < this->buckets_len());
|
||||
return span<Bucket>(boost::to_address(buckets), size_);
|
||||
}
|
||||
|
||||
size_type position(std::size_t hash) const
|
||||
{
|
||||
return size_policy::position(hash, size_index_);
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
this->deallocate();
|
||||
size_index_ = 0;
|
||||
size_ = 0;
|
||||
}
|
||||
|
||||
void append_bucket_group(iterator itb) BOOST_NOEXCEPT
|
||||
{
|
||||
std::size_t const N = group::N;
|
||||
|
||||
bool const is_empty_bucket = (!itb->next);
|
||||
if (is_empty_bucket) {
|
||||
bucket_pointer pb = itb.p;
|
||||
group_pointer pbg = itb.pbg;
|
||||
|
||||
std::size_t n =
|
||||
static_cast<std::size_t>(boost::to_address(pb) - &buckets[0]);
|
||||
|
||||
bool const is_empty_group = (!pbg->bitmask);
|
||||
if (is_empty_group) {
|
||||
size_type const num_groups = this->groups_len();
|
||||
group_pointer last_group =
|
||||
groups + static_cast<difference_type>(num_groups - 1);
|
||||
|
||||
pbg->buckets =
|
||||
buckets + static_cast<difference_type>(N * (n / N));
|
||||
pbg->next = last_group->next;
|
||||
pbg->next->prev = pbg;
|
||||
pbg->prev = last_group;
|
||||
pbg->prev->next = pbg;
|
||||
}
|
||||
|
||||
pbg->bitmask |= set_bit(n % N);
|
||||
}
|
||||
}
|
||||
|
||||
void insert_node(iterator itb, node_pointer p) BOOST_NOEXCEPT
|
||||
{
|
||||
this->append_bucket_group(itb);
|
||||
|
||||
p->next = itb->next;
|
||||
itb->next = p;
|
||||
}
|
||||
|
||||
void insert_node_hint(
|
||||
iterator itb, node_pointer p, node_pointer hint) BOOST_NOEXCEPT
|
||||
{
|
||||
this->append_bucket_group(itb);
|
||||
|
||||
if (hint) {
|
||||
p->next = hint->next;
|
||||
hint->next = p;
|
||||
} else {
|
||||
p->next = itb->next;
|
||||
itb->next = p;
|
||||
}
|
||||
}
|
||||
|
||||
void extract_node(iterator itb, node_pointer p) BOOST_NOEXCEPT
|
||||
{
|
||||
node_pointer* pp = boost::addressof(itb->next);
|
||||
while ((*pp) != p)
|
||||
pp = boost::addressof((*pp)->next);
|
||||
*pp = p->next;
|
||||
if (!itb->next)
|
||||
unlink_bucket(itb);
|
||||
}
|
||||
|
||||
void extract_node_after(iterator itb, node_pointer* pp) BOOST_NOEXCEPT
|
||||
{
|
||||
*pp = (*pp)->next;
|
||||
if (!itb->next)
|
||||
unlink_bucket(itb);
|
||||
}
|
||||
|
||||
void unlink_empty_buckets() BOOST_NOEXCEPT
|
||||
{
|
||||
std::size_t const N = group::N;
|
||||
|
||||
group_pointer pbg = groups,
|
||||
last = groups + static_cast<difference_type>(
|
||||
this->groups_len() - 1);
|
||||
|
||||
for (; pbg != last; ++pbg) {
|
||||
if (!pbg->buckets) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (std::size_t n = 0; n < N; ++n) {
|
||||
bucket_pointer bs = pbg->buckets;
|
||||
bucket_type& b = bs[static_cast<std::ptrdiff_t>(n)];
|
||||
if (!b.next)
|
||||
pbg->bitmask &= reset_bit(n);
|
||||
}
|
||||
if (!pbg->bitmask && pbg->next)
|
||||
unlink_group(pbg);
|
||||
}
|
||||
|
||||
// do not check end bucket
|
||||
for (std::size_t n = 0; n < size_ % N; ++n) {
|
||||
if (!pbg->buckets[static_cast<std::ptrdiff_t>(n)].next)
|
||||
pbg->bitmask &= reset_bit(n);
|
||||
}
|
||||
}
|
||||
|
||||
void unlink_bucket(iterator itb)
|
||||
{
|
||||
typename iterator::bucket_pointer p = itb.p;
|
||||
typename iterator::bucket_group_pointer pbg = itb.pbg;
|
||||
if (!(pbg->bitmask &=
|
||||
reset_bit(static_cast<std::size_t>(p - pbg->buckets))))
|
||||
unlink_group(pbg);
|
||||
}
|
||||
|
||||
private:
|
||||
void unlink_group(group_pointer pbg)
|
||||
{
|
||||
pbg->next->prev = pbg->prev;
|
||||
pbg->prev->next = pbg->next;
|
||||
pbg->prev = pbg->next = group_pointer();
|
||||
}
|
||||
};
|
||||
} // namespace detail
|
||||
} // namespace unordered
|
||||
} // namespace boost
|
||||
|
||||
#endif // BOOST_UNORDERED_DETAIL_FCA_HPP
|
@ -1,5 +1,6 @@
|
||||
|
||||
// Copyright (C) 2008-2016 Daniel James.
|
||||
// Copyright (C) 2022 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)
|
||||
|
||||
@ -31,7 +32,7 @@
|
||||
// 2012 = VC+11 = BOOST_MSVC 1700 Hopefully!
|
||||
// I have no idea when Dinkumware added it, probably a lot
|
||||
// earlier than this check.
|
||||
#if BOOST_LIB_STD_DINKUMWARE >= BOOST_VERSION_NUMBER(6, 50, 0) || \
|
||||
#if BOOST_LIB_STD_DINKUMWARE >= BOOST_VERSION_NUMBER(6, 10, 0) || \
|
||||
BOOST_COMP_MSVC >= BOOST_VERSION_NUMBER(17, 0, 0)
|
||||
#define BOOST_UNORDERED_HAVE_PIECEWISE_CONSTRUCT 1
|
||||
#endif
|
||||
|
@ -1,5 +1,6 @@
|
||||
|
||||
// Copyright (C) 2005-2016 Daniel James
|
||||
// Copyright (C) 2022 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)
|
||||
|
||||
@ -25,25 +26,18 @@ namespace boost {
|
||||
typedef boost::unordered::detail::allocator_traits<value_allocator>
|
||||
value_allocator_traits;
|
||||
|
||||
typedef boost::unordered::detail::pick_node<A, value_type> pick;
|
||||
typedef typename pick::node node;
|
||||
typedef typename pick::bucket bucket;
|
||||
typedef typename pick::link_pointer link_pointer;
|
||||
|
||||
typedef boost::unordered::detail::table<types> table;
|
||||
typedef boost::unordered::detail::map_extractor<value_type> extractor;
|
||||
|
||||
typedef typename boost::unordered::detail::pick_policy<K>::type policy;
|
||||
typedef typename boost::allocator_void_pointer<value_allocator>::type
|
||||
void_pointer;
|
||||
|
||||
typedef boost::unordered::iterator_detail::iterator<node> iterator;
|
||||
typedef boost::unordered::iterator_detail::c_iterator<node> c_iterator;
|
||||
typedef boost::unordered::iterator_detail::l_iterator<node> l_iterator;
|
||||
typedef boost::unordered::iterator_detail::cl_iterator<node>
|
||||
cl_iterator;
|
||||
typedef boost::unordered::node_handle_map<
|
||||
node<value_type, void_pointer>, K, M, A>
|
||||
node_type;
|
||||
|
||||
typedef boost::unordered::node_handle_map<node, K, M, A> node_type;
|
||||
typedef boost::unordered::insert_return_type_map<node, K, M, A>
|
||||
insert_return_type;
|
||||
typedef typename table::iterator iterator;
|
||||
typedef boost::unordered::insert_return_type_map<iterator, node_type> insert_return_type;
|
||||
};
|
||||
|
||||
template <typename K, typename M, typename H, typename P, typename A>
|
||||
|
262
include/boost/unordered/detail/prime_fmod.hpp
Normal file
@ -0,0 +1,262 @@
|
||||
// Copyright (C) 2022 Joaquin M Lopez Munoz.
|
||||
// Copyright (C) 2022 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_DETAIL_PRIME_FMOD_HPP
|
||||
#define BOOST_UNORDERED_DETAIL_PRIME_FMOD_HPP
|
||||
|
||||
#include <boost/cstdint.hpp>
|
||||
#include <boost/preprocessor/seq/enum.hpp>
|
||||
#include <boost/preprocessor/seq/for_each.hpp>
|
||||
#include <boost/preprocessor/seq/size.hpp>
|
||||
|
||||
#include <boost/config.hpp>
|
||||
|
||||
#include <climits>
|
||||
#include <cstddef>
|
||||
|
||||
#if defined(SIZE_MAX)
|
||||
#if ((((SIZE_MAX >> 16) >> 16) >> 16) >> 15) != 0
|
||||
#define BOOST_UNORDERED_FCA_HAS_64B_SIZE_T
|
||||
#endif
|
||||
#elif defined(UINTPTR_MAX) /* used as proxy for std::size_t */
|
||||
#if ((((UINTPTR_MAX >> 16) >> 16) >> 16) >> 15) != 0
|
||||
#define BOOST_UNORDERED_FCA_HAS_64B_SIZE_T
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(BOOST_UNORDERED_FCA_HAS_64B_SIZE_T) && defined(_MSC_VER)
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
|
||||
namespace boost {
|
||||
namespace unordered {
|
||||
namespace detail {
|
||||
template <class = void> struct prime_fmod_size
|
||||
{
|
||||
// Because we compile for C++03, we don't have access to any inline
|
||||
// initialization for array data members so the definitions must exist
|
||||
// out-of-line. To keep the library header-only, we introduce a dummy
|
||||
// template parameter which permits the definition to be included in
|
||||
// multiple TUs without conflict.
|
||||
//
|
||||
static std::size_t sizes[];
|
||||
static std::size_t const sizes_len;
|
||||
static std::size_t (*positions[])(std::size_t);
|
||||
|
||||
#if defined(BOOST_UNORDERED_FCA_HAS_64B_SIZE_T)
|
||||
static boost::uint64_t inv_sizes32[];
|
||||
static std::size_t const inv_sizes32_len;
|
||||
#endif /* defined(BOOST_UNORDERED_FCA_HAS_64B_SIZE_T) */
|
||||
|
||||
static inline std::size_t size_index(std::size_t n)
|
||||
{
|
||||
std::size_t i = 0;
|
||||
for (; i < (sizes_len - 1); ++i) {
|
||||
if (sizes[i] >= n) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
static inline std::size_t size(std::size_t size_index)
|
||||
{
|
||||
return sizes[size_index];
|
||||
}
|
||||
|
||||
template <std::size_t Size> static std::size_t modulo(std::size_t hash)
|
||||
{
|
||||
return hash % Size;
|
||||
}
|
||||
|
||||
#if defined(BOOST_UNORDERED_FCA_HAS_64B_SIZE_T)
|
||||
// We emulate the techniques taken from:
|
||||
// Faster Remainder by Direct Computation: Applications to Compilers and
|
||||
// Software Libraries
|
||||
// https://arxiv.org/abs/1902.01961
|
||||
//
|
||||
// In essence, use fancy math to directly calculate the remainder (aka
|
||||
// modulo) exploiting how compilers transform division
|
||||
//
|
||||
|
||||
static inline boost::uint64_t get_remainder(
|
||||
boost::uint64_t fractional, boost::uint32_t d)
|
||||
{
|
||||
#if defined(_MSC_VER)
|
||||
// use MSVC intrinsics when available to avoid promotion to 128 bits
|
||||
|
||||
return __umulh(fractional, d);
|
||||
#elif defined(BOOST_HAS_INT128)
|
||||
return static_cast<boost::uint64_t>(
|
||||
((boost::uint128_type)fractional * d) >> 64);
|
||||
#else
|
||||
// portable implementation in the absence of boost::uint128_type on 64
|
||||
// bits, which happens at least in GCC 4.5 and prior
|
||||
|
||||
boost::uint64_t r1 = (fractional & UINT32_MAX) * d;
|
||||
boost::uint64_t r2 = (fractional >> 32) * d;
|
||||
r2 += r1 >> 32;
|
||||
return r2 >> 32;
|
||||
#endif /* defined(_MSC_VER) */
|
||||
}
|
||||
|
||||
static inline boost::uint32_t fast_modulo(
|
||||
boost::uint32_t a, boost::uint64_t M, boost::uint32_t d)
|
||||
{
|
||||
boost::uint64_t fractional = M * a;
|
||||
return (boost::uint32_t)(get_remainder(fractional, d));
|
||||
}
|
||||
#endif /* defined(BOOST_UNORDERED_FCA_HAS_64B_SIZE_T) */
|
||||
|
||||
static inline std::size_t position(
|
||||
std::size_t hash, std::size_t size_index)
|
||||
{
|
||||
#if defined(BOOST_UNORDERED_FCA_HAS_64B_SIZE_T)
|
||||
std::size_t sizes_under_32bit = inv_sizes32_len;
|
||||
if (BOOST_LIKELY(size_index < sizes_under_32bit)) {
|
||||
return fast_modulo(
|
||||
boost::uint32_t(hash) + boost::uint32_t(hash >> 32),
|
||||
inv_sizes32[size_index], boost::uint32_t(sizes[size_index]));
|
||||
} else {
|
||||
return positions[size_index - sizes_under_32bit](hash);
|
||||
}
|
||||
#else
|
||||
return positions[size_index](hash);
|
||||
#endif /* defined(BOOST_UNORDERED_FCA_HAS_64B_SIZE_T) */
|
||||
}
|
||||
}; // prime_fmod_size
|
||||
|
||||
#define BOOST_UNORDERED_PRIME_FMOD_SIZES_32BIT_INCOMPLETE \
|
||||
(13ul)(29ul)(53ul)(97ul)(193ul)(389ul)(769ul)(1543ul)(3079ul)(6151ul)( \
|
||||
12289ul)(24593ul)(49157ul)(98317ul)(196613ul)(393241ul)(786433ul)( \
|
||||
1572869ul)(3145739ul)(6291469ul)(12582917ul)(25165843ul)(50331653ul)( \
|
||||
100663319ul)(201326611ul)(402653189ul)(805306457ul)(1610612741ul)( \
|
||||
3221225473ul)
|
||||
|
||||
#if !defined(BOOST_UNORDERED_FCA_HAS_64B_SIZE_T)
|
||||
|
||||
#define BOOST_UNORDERED_PRIME_FMOD_SIZES_32BIT \
|
||||
BOOST_UNORDERED_PRIME_FMOD_SIZES_32BIT_INCOMPLETE(4294967291ul)
|
||||
|
||||
#define BOOST_UNORDERED_PRIME_FMOD_SIZES_64BIT
|
||||
|
||||
#else
|
||||
|
||||
#define BOOST_UNORDERED_PRIME_FMOD_SIZES_32BIT \
|
||||
BOOST_UNORDERED_PRIME_FMOD_SIZES_32BIT_INCOMPLETE
|
||||
|
||||
// The original sequence here is this:
|
||||
// (6442450939ul)
|
||||
// (12884901893ul)
|
||||
// (25769803751ul)
|
||||
// (51539607551ul)
|
||||
// (103079215111ul)
|
||||
// (206158430209ul)
|
||||
// (412316860441ul)
|
||||
// (824633720831ul)
|
||||
// (1649267441651ul)
|
||||
//
|
||||
// but this causes problems on versions of mingw where the `long` type is 32
|
||||
// bits, even for 64-bit targets. We work around this by replacing the literals
|
||||
// with compile-time arithmetic, using bitshifts to reconstruct the number.
|
||||
//
|
||||
|
||||
// clang-format off
|
||||
#define BOOST_UNORDERED_PRIME_FMOD_SIZES_64BIT \
|
||||
((boost::ulong_long_type(1ul) << 32) + boost::ulong_long_type(2147483643ul)) \
|
||||
((boost::ulong_long_type(3ul) << 32) + boost::ulong_long_type(5ul)) \
|
||||
((boost::ulong_long_type(5ul) << 32) + boost::ulong_long_type(4294967271ul)) \
|
||||
((boost::ulong_long_type(11ul) << 32) + boost::ulong_long_type(4294967295ul)) \
|
||||
((boost::ulong_long_type(24ul) << 32) + boost::ulong_long_type(7ul)) \
|
||||
((boost::ulong_long_type(48ul) << 32) + boost::ulong_long_type(1ul)) \
|
||||
((boost::ulong_long_type(96ul) << 32) + boost::ulong_long_type(25ul)) \
|
||||
((boost::ulong_long_type(191ul) << 32) + boost::ulong_long_type(4294967295ul)) \
|
||||
((boost::ulong_long_type(383ul) << 32) + boost::ulong_long_type(4294967283ul))
|
||||
// clang-format on
|
||||
|
||||
#endif /* BOOST_UNORDERED_FCA_HAS_64B_SIZE_T */
|
||||
|
||||
#define BOOST_UNORDERED_PRIME_FMOD_SIZES \
|
||||
BOOST_UNORDERED_PRIME_FMOD_SIZES_32BIT BOOST_UNORDERED_PRIME_FMOD_SIZES_64BIT
|
||||
|
||||
template <class T>
|
||||
std::size_t prime_fmod_size<T>::sizes[] = {
|
||||
BOOST_PP_SEQ_ENUM(BOOST_UNORDERED_PRIME_FMOD_SIZES)};
|
||||
|
||||
template <class T>
|
||||
std::size_t const prime_fmod_size<T>::sizes_len = BOOST_PP_SEQ_SIZE(
|
||||
BOOST_UNORDERED_PRIME_FMOD_SIZES);
|
||||
|
||||
// Similarly here, we have to re-express the integer initialization using
|
||||
// arithmetic such that each literal can fit in a 32-bit value.
|
||||
//
|
||||
#if defined(BOOST_UNORDERED_FCA_HAS_64B_SIZE_T)
|
||||
// clang-format off
|
||||
template <class T>
|
||||
boost::uint64_t prime_fmod_size<T>::inv_sizes32[] = {
|
||||
(boost::ulong_long_type(330382099ul) << 32) + boost::ulong_long_type(2973438898ul) /* = 1418980313362273202 */,
|
||||
(boost::ulong_long_type(148102320ul) << 32) + boost::ulong_long_type(2369637129ul) /* = 636094623231363849 */,
|
||||
(boost::ulong_long_type(81037118ul) << 32) + boost::ulong_long_type(3403558990ul) /* = 348051774975651918 */,
|
||||
(boost::ulong_long_type(44278013ul) << 32) + boost::ulong_long_type(1549730468ul) /* = 190172619316593316 */,
|
||||
(boost::ulong_long_type(22253716ul) << 32) + boost::ulong_long_type(2403401389ul) /* = 95578984837873325 */,
|
||||
(boost::ulong_long_type(11041047ul) << 32) + boost::ulong_long_type(143533612ul) /* = 47420935922132524 */,
|
||||
(boost::ulong_long_type(5585133ul) << 32) + boost::ulong_long_type(106117528ul) /* = 23987963684927896 */,
|
||||
(boost::ulong_long_type(2783517ul) << 32) + boost::ulong_long_type(1572687312ul) /* = 11955116055547344 */,
|
||||
(boost::ulong_long_type(1394922ul) << 32) + boost::ulong_long_type(3428720239ul) /* = 5991147799191151 */,
|
||||
(boost::ulong_long_type(698255ul) << 32) + boost::ulong_long_type(552319807ul) /* = 2998982941588287 */,
|
||||
(boost::ulong_long_type(349496ul) << 32) + boost::ulong_long_type(3827689953ul) /* = 1501077717772769 */,
|
||||
(boost::ulong_long_type(174641ul) << 32) + boost::ulong_long_type(3699438549ul) /* = 750081082979285 */,
|
||||
(boost::ulong_long_type(87372ul) << 32) + boost::ulong_long_type(1912757574ul) /* = 375261795343686 */,
|
||||
(boost::ulong_long_type(43684ul) << 32) + boost::ulong_long_type(3821029929ul) /* = 187625172388393 */,
|
||||
(boost::ulong_long_type(21844ul) << 32) + boost::ulong_long_type(3340590800ul) /* = 93822606204624 */,
|
||||
(boost::ulong_long_type(10921ul) << 32) + boost::ulong_long_type(4175852267ul) /* = 46909513691883 */,
|
||||
(boost::ulong_long_type(5461ul) << 32) + boost::ulong_long_type(1401829642ul) /* = 23456218233098 */,
|
||||
(boost::ulong_long_type(2730ul) << 32) + boost::ulong_long_type(2826028947ul) /* = 11728086747027 */,
|
||||
(boost::ulong_long_type(1365ul) << 32) + boost::ulong_long_type(1411150351ul) /* = 5864041509391 */,
|
||||
(boost::ulong_long_type(682ul) << 32) + boost::ulong_long_type(2857253105ul) /* = 2932024948977 */,
|
||||
(boost::ulong_long_type(341ul) << 32) + boost::ulong_long_type(1431073224ul) /* = 1466014921160 */,
|
||||
(boost::ulong_long_type(170ul) << 32) + boost::ulong_long_type(2862758116ul) /* = 733007198436 */,
|
||||
(boost::ulong_long_type(85ul) << 32) + boost::ulong_long_type(1431619357ul) /* = 366503839517 */,
|
||||
(boost::ulong_long_type(42ul) << 32) + boost::ulong_long_type(2863269661ul) /* = 183251896093 */,
|
||||
(boost::ulong_long_type(21ul) << 32) + boost::ulong_long_type(1431647119ul) /* = 91625960335 */,
|
||||
(boost::ulong_long_type(10ul) << 32) + boost::ulong_long_type(2863310962ul) /* = 45812983922 */,
|
||||
(boost::ulong_long_type(5ul) << 32) + boost::ulong_long_type(1431653234ul) /* = 22906489714 */,
|
||||
(boost::ulong_long_type(2ul) << 32) + boost::ulong_long_type(2863311496ul) /* = 11453246088 */,
|
||||
(boost::ulong_long_type(1ul) << 32) + boost::ulong_long_type(1431655764ul) /* = 5726623060 */,
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
template <class T>
|
||||
std::size_t const
|
||||
prime_fmod_size<T>::inv_sizes32_len = sizeof(inv_sizes32) /
|
||||
sizeof(inv_sizes32[0]);
|
||||
|
||||
#endif /* defined(BOOST_UNORDERED_FCA_HAS_64B_SIZE_T) */
|
||||
|
||||
#define BOOST_UNORDERED_PRIME_FMOD_POSITIONS_ELEMENT(z, _, n) \
|
||||
prime_fmod_size<T>::template modulo<n>,
|
||||
|
||||
template <class T>
|
||||
std::size_t (*prime_fmod_size<T>::positions[])(std::size_t) = {
|
||||
#if !defined(BOOST_UNORDERED_FCA_HAS_64B_SIZE_T)
|
||||
BOOST_PP_SEQ_FOR_EACH(BOOST_UNORDERED_PRIME_FMOD_POSITIONS_ELEMENT, ~,
|
||||
BOOST_UNORDERED_PRIME_FMOD_SIZES_32BIT)
|
||||
#else
|
||||
BOOST_PP_SEQ_FOR_EACH(BOOST_UNORDERED_PRIME_FMOD_POSITIONS_ELEMENT, ~,
|
||||
BOOST_UNORDERED_PRIME_FMOD_SIZES_64BIT)
|
||||
#endif
|
||||
};
|
||||
|
||||
#undef BOOST_UNORDERED_PRIME_FMOD_POSITIONS_ELEMENT
|
||||
#undef BOOST_UNORDERED_PRIME_FMOD_SIZES
|
||||
#undef BOOST_UNORDERED_PRIME_FMOD_SIZES_64BIT
|
||||
#undef BOOST_UNORDERED_PRIME_FMOD_SIZES_32BIT
|
||||
#undef BOOST_UNORDERED_PRIME_FMOD_SIZES_32BIT_INCOMPLETE
|
||||
} // namespace detail
|
||||
} // namespace unordered
|
||||
} // namespace boost
|
||||
|
||||
#endif // BOOST_UNORDERED_DETAIL_PRIME_FMOD_HPP
|