Compare commits
35 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
8b878a841e | ||
|
8aade382bb | ||
|
a522016953 | ||
|
6f444be1de | ||
|
72527d63a0 | ||
|
3ff2819922 | ||
|
559768c351 | ||
|
316b8dbec2 | ||
|
1724c3a7fa | ||
|
112e089166 | ||
|
250c406098 | ||
|
11c3369cf4 | ||
|
f4ce1dc8f6 | ||
|
49f07cbb8e | ||
|
68d5dbc919 | ||
|
068a1da098 | ||
|
ff02ae92f4 | ||
|
a65c9cbcb1 | ||
|
13d8eebbca | ||
|
93820be50e | ||
|
47d1630e7f | ||
|
79da3793c1 | ||
|
c5cf8c31ac | ||
|
67558b796f | ||
|
2a3f539bb2 | ||
|
ada954cd8d | ||
|
9398bbc382 | ||
|
2e3f3e90ef | ||
|
2e54842ce8 | ||
|
cf4141dffd | ||
|
c3683201e6 | ||
|
0f5ffc2a84 | ||
|
fafe1d4f81 | ||
|
f1087e81ec | ||
|
178bcb974e |
2
.github/workflows/osrm-backend.yml
vendored
2
.github/workflows/osrm-backend.yml
vendored
@ -688,7 +688,7 @@ jobs:
|
||||
gunzip -c ./pr/test/data/poland_gps_traces.csv.gz > ~/gps_traces.csv
|
||||
else
|
||||
if [ ! -f "~/data.osm.pbf" ]; then
|
||||
wget http://download.geofabrik.de/europe/germany/berlin-latest.osm.pbf -O ~/data.osm.pbf
|
||||
wget http://download.geofabrik.de/europe/germany/berlin-latest.osm.pbf -O ~/data.osm.pbf --quiet
|
||||
else
|
||||
echo "Using cached data.osm.pbf"
|
||||
fi
|
||||
|
@ -299,6 +299,10 @@ include_directories(SYSTEM ${PROTOZERO_INCLUDE_DIR})
|
||||
set(VTZERO_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/third_party/vtzero/include")
|
||||
include_directories(SYSTEM ${VTZERO_INCLUDE_DIR})
|
||||
|
||||
set(ANKERL_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/third_party/unordered_dense/include")
|
||||
include_directories(SYSTEM ${ANKERL_INCLUDE_DIR})
|
||||
|
||||
|
||||
set(FLATBUFFERS_BUILD_TESTS OFF CACHE BOOL "Disable the build of Flatbuffers tests and samples.")
|
||||
set(FLATBUFFERS_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/third_party/flatbuffers")
|
||||
set(FLATBUFFERS_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/third_party/flatbuffers/include")
|
||||
|
@ -5,58 +5,19 @@
|
||||
#include <boost/heap/d_ary_heap.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <ankerl/unordered_dense.h>
|
||||
#include <boost/unordered_map.hpp>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace osrm::util
|
||||
{
|
||||
|
||||
template <typename NodeID, typename Key> class GenerationArrayStorage
|
||||
{
|
||||
using GenerationCounter = std::uint16_t;
|
||||
|
||||
public:
|
||||
explicit GenerationArrayStorage(std::size_t size)
|
||||
: positions(size, 0), generation(1), generations(size, 0)
|
||||
{
|
||||
}
|
||||
|
||||
Key &operator[](NodeID node)
|
||||
{
|
||||
generation[node] = generation;
|
||||
return positions[node];
|
||||
}
|
||||
|
||||
Key peek_index(const NodeID node) const
|
||||
{
|
||||
if (generations[node] < generation)
|
||||
{
|
||||
return std::numeric_limits<Key>::max();
|
||||
}
|
||||
return positions[node];
|
||||
}
|
||||
|
||||
void Clear()
|
||||
{
|
||||
generation++;
|
||||
// if generation overflows we end up at 0 again and need to clear the vector
|
||||
if (generation == 0)
|
||||
{
|
||||
generation = 1;
|
||||
std::fill(generations.begin(), generations.end(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
GenerationCounter generation;
|
||||
std::vector<GenerationCounter> generations;
|
||||
std::vector<Key> positions;
|
||||
};
|
||||
|
||||
template <typename NodeID, typename Key> class ArrayStorage
|
||||
{
|
||||
public:
|
||||
@ -72,33 +33,10 @@ template <typename NodeID, typename Key> class ArrayStorage
|
||||
std::vector<Key> positions;
|
||||
};
|
||||
|
||||
template <typename NodeID, typename Key> class MapStorage
|
||||
{
|
||||
public:
|
||||
explicit MapStorage(std::size_t) {}
|
||||
|
||||
Key &operator[](NodeID node) { return nodes[node]; }
|
||||
|
||||
void Clear() { nodes.clear(); }
|
||||
|
||||
Key peek_index(const NodeID node) const
|
||||
{
|
||||
const auto iter = nodes.find(node);
|
||||
if (nodes.end() != iter)
|
||||
{
|
||||
return iter->second;
|
||||
}
|
||||
return std::numeric_limits<Key>::max();
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<NodeID, Key> nodes;
|
||||
};
|
||||
|
||||
template <typename NodeID, typename Key> class UnorderedMapStorage
|
||||
{
|
||||
public:
|
||||
explicit UnorderedMapStorage(std::size_t) { nodes.rehash(1000); }
|
||||
explicit UnorderedMapStorage(std::size_t) {}
|
||||
|
||||
Key &operator[](const NodeID node) { return nodes[node]; }
|
||||
|
||||
@ -121,7 +59,7 @@ template <typename NodeID, typename Key> class UnorderedMapStorage
|
||||
void Clear() { nodes.clear(); }
|
||||
|
||||
private:
|
||||
std::unordered_map<NodeID, Key> nodes;
|
||||
ankerl::unordered_dense::segmented_map<NodeID, Key> nodes;
|
||||
};
|
||||
|
||||
template <typename NodeID,
|
||||
|
@ -102,4 +102,4 @@ def main():
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
@ -30,6 +30,9 @@ VTZERO_TAG=v1.1.0
|
||||
FMT_PATH="fmtlib/fmt"
|
||||
FMT_TAG=v10.2.1
|
||||
|
||||
ANKERL_PATH="martinus/unordered_dense"
|
||||
ANKERL_TAG=v4.4.0
|
||||
|
||||
function update_subtree () {
|
||||
name=$(echo "$1" | tr '[:lower:]' '[:upper:]')
|
||||
path=$(tmpvar=${name}_PATH && echo ${!tmpvar})
|
||||
@ -53,6 +56,6 @@ function update_subtree () {
|
||||
}
|
||||
|
||||
## Update dependencies
|
||||
for dep in osmium sol rapidjson microtar protozero vtzero fmt; do
|
||||
for dep in ankerl osmium sol rapidjson microtar protozero vtzero fmt; do
|
||||
update_subtree $dep
|
||||
done
|
||||
|
20
third_party/unordered_dense/.clang-format
vendored
Normal file
20
third_party/unordered_dense/.clang-format
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
# see https://clang.llvm.org/docs/ClangFormatStyleOptions.html
|
||||
---
|
||||
BasedOnStyle: LLVM
|
||||
Language: Cpp
|
||||
Standard: c++17
|
||||
ColumnLimit: 127
|
||||
|
||||
AccessModifierOffset: -4
|
||||
AlignEscapedNewlines: Left
|
||||
AllowShortFunctionsOnASingleLine: Empty
|
||||
AllowShortLambdasOnASingleLine: false
|
||||
AlwaysBreakTemplateDeclarations: true
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BreakConstructorInitializers: BeforeComma
|
||||
BreakStringLiterals: false
|
||||
IndentPPDirectives: AfterHash
|
||||
IndentWidth: 4
|
||||
PointerAlignment: Left
|
||||
UseTab: Never
|
55
third_party/unordered_dense/.clang-tidy
vendored
Normal file
55
third_party/unordered_dense/.clang-tidy
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
---
|
||||
Checks: '*
|
||||
-abseil-string-find-str-contains
|
||||
-altera*
|
||||
-bugprone-easily-swappable-parameters
|
||||
-cert-err58-cpp
|
||||
-cppcoreguidelines-avoid-magic-numbers
|
||||
-cppcoreguidelines-pro-bounds-constant-array-index
|
||||
-cppcoreguidelines-pro-bounds-pointer-arithmetic
|
||||
-fuchsia*
|
||||
-llvm-header-guard
|
||||
-llvmlibc*
|
||||
-readability-function-cognitive-complexity
|
||||
-readability-identifier-length
|
||||
-readability-magic-numbers
|
||||
'
|
||||
HeaderFilterRegex: ''
|
||||
CheckOptions:
|
||||
cppcoreguidelines-avoid-do-while.IgnoreMacros: 'true'
|
||||
readability-identifier-naming.MacroDefinitionCase: 'UPPER_CASE'
|
||||
readability-identifier-naming.TemplateParameterCase: 'CamelCase'
|
||||
readability-identifier-naming.TypeTemplateParameterCase: 'CamelCase'
|
||||
readability-identifier-naming.ValueTemplateParameterCase: 'CamelCase'
|
||||
readability-identifier-naming.ParameterPackCase: 'lower_case'
|
||||
readability-identifier-naming.AbstractClassCase: 'lower_case'
|
||||
readability-identifier-naming.ClassCase: 'lower_case'
|
||||
readability-identifier-naming.ClassMemberCase: 'lower_case'
|
||||
readability-identifier-naming.ConstantCase: 'lower_case'
|
||||
readability-identifier-naming.ConstexprVariableCase: 'lower_case'
|
||||
readability-identifier-naming.EnumCase: 'lower_case'
|
||||
readability-identifier-naming.EnumConstantCase: 'lower_case'
|
||||
readability-identifier-naming.FunctionCase: 'lower_case'
|
||||
readability-identifier-naming.GlobalConstantCase: 'lower_case'
|
||||
readability-identifier-naming.LocalVariableCase: 'lower_case'
|
||||
readability-identifier-naming.MemberCase: 'lower_case'
|
||||
readability-identifier-naming.NamespaceCase: 'lower_case'
|
||||
readability-identifier-naming.ParameterCase: 'lower_case'
|
||||
readability-identifier-naming.StructCase: 'lower_case'
|
||||
readability-identifier-naming.TypeAliasCase: 'lower_case'
|
||||
readability-identifier-naming.TypedefCase: 'lower_case'
|
||||
readability-identifier-naming.StaticConstantCase: 'lower_case'
|
||||
readability-identifier-naming.StaticVariableCase: 'lower_case'
|
||||
readability-identifier-naming.UnionCase: 'lower_case'
|
||||
readability-identifier-naming.VariableCase: 'lower_case'
|
||||
readability-identifier-naming.GlobalConstantPrefix: 'global_'
|
||||
readability-identifier-naming.GlobalVariablePrefix: 'global_'
|
||||
readability-identifier-naming.MemberPrefix: 'm_'
|
||||
readability-identifier-naming.PrivateMemberPrefix: 'm_'
|
||||
readability-identifier-naming.PrivateMemberPrefix: 'm_'
|
||||
readability-identifier-naming.ProtectedMemberPrefix: 'm_'
|
||||
readability-identifier-naming.PublicMemberPrefix: ''
|
||||
readability-identifier-naming.StaticConstantPrefix: 'static_'
|
||||
readability-identifier-naming.StaticVariablePrefix: 'static_'
|
||||
readability-identifier-naming.ClassMemberPrefix: 'static_'
|
||||
...
|
1
third_party/unordered_dense/.fuzz-corpus-base-dir
vendored
Normal file
1
third_party/unordered_dense/.fuzz-corpus-base-dir
vendored
Normal file
@ -0,0 +1 @@
|
||||
data/fuzz
|
13
third_party/unordered_dense/.github/FUNDING.yml
vendored
Normal file
13
third_party/unordered_dense/.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [martinus] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
28
third_party/unordered_dense/.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
28
third_party/unordered_dense/.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[BUG] "
|
||||
labels: bug
|
||||
assignees: martinus
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**System (please complete the following information):**
|
||||
- OS: [e.g. Linux]
|
||||
- Compiler: [e.g. clang++, g++]
|
||||
- Version [e.g. 13.0.1]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
20
third_party/unordered_dense/.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
third_party/unordered_dense/.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: martinus
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
74
third_party/unordered_dense/.github/workflows/main.yml
vendored
Normal file
74
third_party/unordered_dense/.github/workflows/main.yml
vendored
Normal file
@ -0,0 +1,74 @@
|
||||
name: Build, Test, Lint
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
# see https://github.com/mesonbuild/meson/blob/master/docs/markdown/Continuous-Integration.md
|
||||
jobs:
|
||||
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- run: ./scripts/lint/lint-version.py
|
||||
|
||||
linux:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- run: sudo apt-get install -yq libboost-dev
|
||||
- uses: hendrikmuhs/ccache-action@v1.2
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- run: pip install meson ninja
|
||||
- run: meson setup builddir/
|
||||
env:
|
||||
CXX: ccache c++
|
||||
- run: meson test -C builddir/ -v
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: failure()
|
||||
with:
|
||||
name: Linux_Meson_Testlog
|
||||
path: builddir/meson-logs/testlog.txt
|
||||
|
||||
macos:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- run: brew install gcc ccache meson ninja
|
||||
- run: meson setup builddir/
|
||||
env:
|
||||
CXX: ccache c++
|
||||
- run: meson test -C builddir/ -v
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: failure()
|
||||
with:
|
||||
name: MacOS_Meson_Testlog
|
||||
path: builddir/meson-logs/testlog.txt
|
||||
|
||||
windows:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- uses: BSFishy/pip-action@v1
|
||||
with:
|
||||
packages: ninja meson
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
- run: meson setup builddir
|
||||
- run: meson test -C builddir -v
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: failure()
|
||||
with:
|
||||
name: Windows_Meson_Testlog
|
||||
path: |
|
||||
builddir/meson-logs/testlog.txt
|
||||
builddir/test/udm-test.exe
|
14
third_party/unordered_dense/.gitignore
vendored
Normal file
14
third_party/unordered_dense/.gitignore
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
build
|
||||
builddir
|
||||
.cache
|
||||
.vscode
|
||||
compile_commands.json
|
||||
|
||||
# ignore all in subprojects except the .wrap files
|
||||
/subprojects/*
|
||||
!/subprojects/*.wrap
|
||||
|
||||
# c++ modules
|
||||
*.pcm
|
||||
a.out
|
||||
*.o
|
61
third_party/unordered_dense/CMakeLists.txt
vendored
Normal file
61
third_party/unordered_dense/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
cmake_minimum_required(VERSION 3.12)
|
||||
project("unordered_dense"
|
||||
VERSION 4.4.0
|
||||
DESCRIPTION "A fast & densely stored hashmap and hashset based on robin-hood backward shift deletion"
|
||||
HOMEPAGE_URL "https://github.com/martinus/unordered_dense")
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
# determine whether this is a standalone project or included by other projects
|
||||
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
|
||||
set(_unordered_dense_is_toplevel_project TRUE)
|
||||
else()
|
||||
set(_unordered_dense_is_toplevel_project FALSE)
|
||||
endif()
|
||||
|
||||
add_library(unordered_dense INTERFACE)
|
||||
add_library(unordered_dense::unordered_dense ALIAS unordered_dense)
|
||||
|
||||
target_include_directories(
|
||||
unordered_dense
|
||||
INTERFACE
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
|
||||
|
||||
target_compile_features(unordered_dense INTERFACE cxx_std_17)
|
||||
|
||||
if(_unordered_dense_is_toplevel_project)
|
||||
# locations are provided by GNUInstallDirs
|
||||
install(
|
||||
TARGETS unordered_dense
|
||||
EXPORT unordered_dense_Targets
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
|
||||
include(CMakePackageConfigHelpers)
|
||||
write_basic_package_version_file(
|
||||
"unordered_denseConfigVersion.cmake"
|
||||
VERSION ${PROJECT_VERSION}
|
||||
COMPATIBILITY SameMajorVersion)
|
||||
|
||||
configure_package_config_file(
|
||||
"${PROJECT_SOURCE_DIR}/cmake/unordered_denseConfig.cmake.in"
|
||||
"${PROJECT_BINARY_DIR}/unordered_denseConfig.cmake"
|
||||
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
|
||||
|
||||
install(
|
||||
EXPORT unordered_dense_Targets
|
||||
FILE unordered_denseTargets.cmake
|
||||
NAMESPACE unordered_dense::
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
|
||||
|
||||
install(
|
||||
FILES "${PROJECT_BINARY_DIR}/unordered_denseConfig.cmake"
|
||||
"${PROJECT_BINARY_DIR}/unordered_denseConfigVersion.cmake"
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
|
||||
|
||||
install(
|
||||
DIRECTORY ${PROJECT_SOURCE_DIR}/include/ankerl
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||
endif()
|
76
third_party/unordered_dense/CODE_OF_CONDUCT.md
vendored
Normal file
76
third_party/unordered_dense/CODE_OF_CONDUCT.md
vendored
Normal file
@ -0,0 +1,76 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at martin.ankerl@gmail.com. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
3
third_party/unordered_dense/CONTRIBUTING.md
vendored
Normal file
3
third_party/unordered_dense/CONTRIBUTING.md
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
* Coding style should be consistent with the code around you.
|
||||
* Use automatic formatting with clang-format.
|
||||
* One feature per pull request
|
21
third_party/unordered_dense/LICENSE
vendored
Normal file
21
third_party/unordered_dense/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Martin Leitner-Ankerl
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
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 AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
374
third_party/unordered_dense/README.md
vendored
Normal file
374
third_party/unordered_dense/README.md
vendored
Normal file
@ -0,0 +1,374 @@
|
||||
<a id="top"></a>
|
||||
|
||||
[](https://github.com/martinus/unordered_dense/releases)
|
||||
[](https://raw.githubusercontent.com/martinus/unordered_dense/main/LICENSE)
|
||||
[](https://github.com/martinus/unordered_dense/actions)
|
||||
[](https://bestpractices.coreinfrastructure.org/projects/6220)
|
||||
[](https://github.com/sponsors/martinus)
|
||||
|
||||
# 🚀 ankerl::unordered_dense::{map, set} <!-- omit in toc -->
|
||||
|
||||
A fast & densely stored hashmap and hashset based on robin-hood backward shift deletion for C++17 and later.
|
||||
|
||||
The classes `ankerl::unordered_dense::map` and `ankerl::unordered_dense::set` are (almost) drop-in replacements of `std::unordered_map` and `std::unordered_set`. While they don't have as strong iterator / reference stability guaranties, they are typically *much* faster.
|
||||
|
||||
Additionally, there are `ankerl::unordered_dense::segmented_map` and `ankerl::unordered_dense::segmented_set` with lower peak memory usage. and stable iterator/references on insert.
|
||||
|
||||
- [1. Overview](#1-overview)
|
||||
- [2. Installation](#2-installation)
|
||||
- [2.1. Installing using cmake](#21-installing-using-cmake)
|
||||
- [3. Usage](#3-usage)
|
||||
- [3.1. Modules](#31-modules)
|
||||
- [3.2. Hash](#32-hash)
|
||||
- [3.2.1. Simple Hash](#321-simple-hash)
|
||||
- [3.2.2. High Quality Hash](#322-high-quality-hash)
|
||||
- [3.2.3. Specialize `ankerl::unordered_dense::hash`](#323-specialize-ankerlunordered_densehash)
|
||||
- [3.2.4. Heterogeneous Overloads using `is_transparent`](#324-heterogeneous-overloads-using-is_transparent)
|
||||
- [3.2.5. Automatic Fallback to `std::hash`](#325-automatic-fallback-to-stdhash)
|
||||
- [3.2.6. Hash the Whole Memory](#326-hash-the-whole-memory)
|
||||
- [3.3. Container API](#33-container-api)
|
||||
- [3.3.1. `auto extract() && -> value_container_type`](#331-auto-extract----value_container_type)
|
||||
- [3.3.2. `extract()` single Elements](#332-extract-single-elements)
|
||||
- [3.3.3. `[[nodiscard]] auto values() const noexcept -> value_container_type const&`](#333-nodiscard-auto-values-const-noexcept---value_container_type-const)
|
||||
- [3.3.4. `auto replace(value_container_type&& container)`](#334-auto-replacevalue_container_type-container)
|
||||
- [3.4. Custom Container Types](#34-custom-container-types)
|
||||
- [3.5. Custom Bucket Types](#35-custom-bucket-types)
|
||||
- [3.5.1. `ankerl::unordered_dense::bucket_type::standard`](#351-ankerlunordered_densebucket_typestandard)
|
||||
- [3.5.2. `ankerl::unordered_dense::bucket_type::big`](#352-ankerlunordered_densebucket_typebig)
|
||||
- [4. `segmented_map` and `segmented_set`](#4-segmented_map-and-segmented_set)
|
||||
- [5. Design](#5-design)
|
||||
- [5.1. Inserts](#51-inserts)
|
||||
- [5.2. Lookups](#52-lookups)
|
||||
- [5.3. Removals](#53-removals)
|
||||
- [6. Real World Usage](#6-real-world-usage)
|
||||
|
||||
## 1. Overview
|
||||
|
||||
The chosen design has a few advantages over `std::unordered_map`:
|
||||
|
||||
* Perfect iteration speed - Data is stored in a `std::vector`, all data is contiguous!
|
||||
* Very fast insertion & lookup speed, in the same ballpark as [`absl::flat_hash_map`](https://abseil.io/docs/cpp/guides/container`)
|
||||
* Low memory usage
|
||||
* Full support for `std::allocators`, and [polymorphic allocators](https://en.cppreference.com/w/cpp/memory/polymorphic_allocator). There are `ankerl::unordered_dense::pmr` typedefs available
|
||||
* Customizeable storage type: with a template parameter you can e.g. switch from `std::vector` to `boost::interprocess::vector` or any other compatible random-access container.
|
||||
* Better debugging: the underlying data can be easily seen in any debugger that can show an `std::vector`.
|
||||
|
||||
There's no free lunch, so there are a few disadvantages:
|
||||
|
||||
* Deletion speed is relatively slow. This needs two lookups: one for the element to delete, and one for the element that is moved onto the newly empty spot.
|
||||
* no `const Key` in `std::pair<Key, Value>`
|
||||
* Iterators and references are not stable on insert or erase.
|
||||
|
||||
## 2. Installation
|
||||
|
||||
<!-- See https://github.com/bernedom/SI/blob/main/doc/installation-guide.md -->
|
||||
The default installation location is `/usr/local`.
|
||||
|
||||
### 2.1. Installing using cmake
|
||||
|
||||
Clone the repository and run these commands in the cloned folder:
|
||||
|
||||
```sh
|
||||
mkdir build && cd build
|
||||
cmake ..
|
||||
cmake --build . --target install
|
||||
```
|
||||
|
||||
Consider setting an install prefix if you do not want to install `unordered_dense` system wide, like so:
|
||||
|
||||
```sh
|
||||
mkdir build && cd build
|
||||
cmake -DCMAKE_INSTALL_PREFIX:PATH=${HOME}/unordered_dense_install ..
|
||||
cmake --build . --target install
|
||||
```
|
||||
|
||||
To make use of the installed library, add this to your project:
|
||||
|
||||
```cmake
|
||||
find_package(unordered_dense CONFIG REQUIRED)
|
||||
target_link_libraries(your_project_name unordered_dense::unordered_dense)
|
||||
```
|
||||
|
||||
## 3. Usage
|
||||
|
||||
### 3.1. Modules
|
||||
|
||||
`ankerl::unordered_dense` supports c++20 modules. Simply compile `src/ankerl.unordered_dense.cpp` and use the resulting module, e.g. like so:
|
||||
|
||||
```sh
|
||||
clang++ -std=c++20 -I include --precompile -x c++-module src/ankerl.unordered_dense.cpp
|
||||
clang++ -std=c++20 -c ankerl.unordered_dense.pcm
|
||||
```
|
||||
|
||||
To use the module with e.g. in `module_test.cpp`, use
|
||||
|
||||
```cpp
|
||||
import ankerl.unordered_dense;
|
||||
```
|
||||
|
||||
and compile with e.g.
|
||||
|
||||
```sh
|
||||
clang++ -std=c++20 -fprebuilt-module-path=. ankerl.unordered_dense.o module_test.cpp -o main
|
||||
```
|
||||
|
||||
A simple demo script can be found in `test/modules`.
|
||||
|
||||
### 3.2. Hash
|
||||
|
||||
`ankerl::unordered_dense::hash` is a fast and high quality hash, based on [wyhash](https://github.com/wangyi-fudan/wyhash). The `ankerl::unordered_dense` map/set differentiates between hashes of high quality (good [avalanching effect](https://en.wikipedia.org/wiki/Avalanche_effect)) and bad quality. Hashes with good quality contain a special marker:
|
||||
|
||||
```cpp
|
||||
using is_avalanching = void;
|
||||
```
|
||||
|
||||
This is the cases for the specializations `bool`, `char`, `signed char`, `unsigned char`, `char8_t`, `char16_t`, `char32_t`, `wchar_t`, `short`, `unsigned short`, `int`, `unsigned int`, `long`, `long long`, `unsigned long`, `unsigned long long`, `T*`, `std::unique_ptr<T>`, `std::shared_ptr<T>`, `enum`, `std::basic_string<C>`, and `std::basic_string_view<C>`.
|
||||
|
||||
Hashes that do not contain such a marker are assumed to be of bad quality and receive an additional mixing step inside the map/set implementation.
|
||||
|
||||
#### 3.2.1. Simple Hash
|
||||
|
||||
Consider a simple custom key type:
|
||||
|
||||
```cpp
|
||||
struct id {
|
||||
uint64_t value{};
|
||||
|
||||
auto operator==(id const& other) const -> bool {
|
||||
return value == other.value;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
The simplest implementation of a hash is this:
|
||||
|
||||
```cpp
|
||||
struct custom_hash_simple {
|
||||
auto operator()(id const& x) const noexcept -> uint64_t {
|
||||
return x.value;
|
||||
}
|
||||
};
|
||||
```
|
||||
This can be used e.g. with
|
||||
|
||||
```cpp
|
||||
auto ids = ankerl::unordered_dense::set<id, custom_hash_simple>();
|
||||
```
|
||||
|
||||
Since `custom_hash_simple` doesn't have a `using is_avalanching = void;` marker it is considered to be of bad quality and additional mixing of `x.value` is automatically provided inside the set.
|
||||
|
||||
#### 3.2.2. High Quality Hash
|
||||
|
||||
Back to the `id` example, we can easily implement a higher quality hash:
|
||||
|
||||
```cpp
|
||||
struct custom_hash_avalanching {
|
||||
using is_avalanching = void;
|
||||
|
||||
auto operator()(id const& x) const noexcept -> uint64_t {
|
||||
return ankerl::unordered_dense::detail::wyhash::hash(x.value);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
We know `wyhash::hash` is of high quality, so we can add `using is_avalanching = void;` which makes the map/set directly use the returned value.
|
||||
|
||||
|
||||
#### 3.2.3. Specialize `ankerl::unordered_dense::hash`
|
||||
|
||||
Instead of creating a new class you can also specialize `ankerl::unordered_dense::hash`:
|
||||
|
||||
```cpp
|
||||
template <>
|
||||
struct ankerl::unordered_dense::hash<id> {
|
||||
using is_avalanching = void;
|
||||
|
||||
[[nodiscard]] auto operator()(id const& x) const noexcept -> uint64_t {
|
||||
return detail::wyhash::hash(x.value);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
#### 3.2.4. Heterogeneous Overloads using `is_transparent`
|
||||
|
||||
This map/set supports heterogeneous overloads as described in [P2363 Extending associative containers with the remaining heterogeneous overloads](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2363r3.html) which is [targeted for C++26](https://wg21.link/p2077r2). This has overloads for `find`, `count`, `contains`, `equal_range` (see [P0919R3](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0919r3.html)), `erase` (see [P2077R2](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p2077r2.html)), and `try_emplace`, `insert_or_assign`, `operator[]`, `at`, and `insert` & `emplace` for sets (see [P2363R3](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2363r3.html)).
|
||||
|
||||
For heterogeneous overloads to take affect, both `hasher` and `key_equal` need to have the attribute `is_transparent` set.
|
||||
|
||||
Here is an example implementation that's usable with any string types that is convertible to `std::string_view` (e.g. `char const*` and `std::string`):
|
||||
|
||||
```cpp
|
||||
struct string_hash {
|
||||
using is_transparent = void; // enable heterogeneous overloads
|
||||
using is_avalanching = void; // mark class as high quality avalanching hash
|
||||
|
||||
[[nodiscard]] auto operator()(std::string_view str) const noexcept -> uint64_t {
|
||||
return ankerl::unordered_dense::hash<std::string_view>{}(str);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
To make use of this hash you'll need to specify it as a type, and also a `key_equal` with `is_transparent` like [std::equal_to<>](https://en.cppreference.com/w/cpp/utility/functional/equal_to_void):
|
||||
|
||||
```cpp
|
||||
auto map = ankerl::unordered_dense::map<std::string, size_t, string_hash, std::equal_to<>>();
|
||||
```
|
||||
|
||||
For more information see the examples in `test/unit/transparent.cpp`.
|
||||
|
||||
|
||||
#### 3.2.5. Automatic Fallback to `std::hash`
|
||||
|
||||
When an implementation for `std::hash` of a custom type is available, this is automatically used and assumed to be of bad quality (thus `std::hash` is used, but an additional mixing step is performed).
|
||||
|
||||
|
||||
#### 3.2.6. Hash the Whole Memory
|
||||
|
||||
When the type [has a unique object representation](https://en.cppreference.com/w/cpp/types/has_unique_object_representations) (no padding, trivially copyable), one can just hash the object's memory. Consider a simple class
|
||||
|
||||
```cpp
|
||||
struct point {
|
||||
int x{};
|
||||
int y{};
|
||||
|
||||
auto operator==(point const& other) const -> bool {
|
||||
return x == other.x && y == other.y;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
A fast and high quality hash can be easily provided like so:
|
||||
|
||||
```cpp
|
||||
struct custom_hash_unique_object_representation {
|
||||
using is_avalanching = void;
|
||||
|
||||
[[nodiscard]] auto operator()(point const& f) const noexcept -> uint64_t {
|
||||
static_assert(std::has_unique_object_representations_v<point>);
|
||||
return ankerl::unordered_dense::detail::wyhash::hash(&f, sizeof(f));
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 3.3. Container API
|
||||
|
||||
In addition to the standard `std::unordered_map` API (see https://en.cppreference.com/w/cpp/container/unordered_map) we have additional API that is somewhat similar to the node API, but leverages the fact that we're using a random access container internally:
|
||||
|
||||
#### 3.3.1. `auto extract() && -> value_container_type`
|
||||
|
||||
Extracts the internally used container. `*this` is emptied.
|
||||
|
||||
#### 3.3.2. `extract()` single Elements
|
||||
|
||||
Similar to `erase()` I have an API call `extract()`. It behaves exactly the same as `erase`, except that the return value is the moved element that is removed from the container:
|
||||
|
||||
* `auto extract(const_iterator it) -> value_type`
|
||||
* `auto extract(Key const& key) -> std::optional<value_type>`
|
||||
* `template <class K> auto extract(K&& key) -> std::optional<value_type>`
|
||||
|
||||
Note that the `extract(key)` API returns an `std::optional<value_type>` that is empty when the key is not found.
|
||||
|
||||
#### 3.3.3. `[[nodiscard]] auto values() const noexcept -> value_container_type const&`
|
||||
|
||||
Exposes the underlying values container.
|
||||
|
||||
#### 3.3.4. `auto replace(value_container_type&& container)`
|
||||
|
||||
Discards the internally held container and replaces it with the one passed. Non-unique elements are
|
||||
removed, and the container will be partly reordered when non-unique elements are found.
|
||||
|
||||
### 3.4. Custom Container Types
|
||||
|
||||
`unordered_dense` accepts a custom allocator, but you can also specify a custom container for that template argument. That way it is possible to replace the internally used `std::vector` with e.g. `std::deque` or any other container like `boost::interprocess::vector`. This supports fancy pointers (e.g. [offset_ptr](https://www.boost.org/doc/libs/1_80_0/doc/html/interprocess/offset_ptr.html)), so the container can be used with e.g. shared memory provided by `boost::interprocess`.
|
||||
|
||||
### 3.5. Custom Bucket Types
|
||||
|
||||
The map/set supports two different bucket types. The default should be good for pretty much everyone.
|
||||
|
||||
#### 3.5.1. `ankerl::unordered_dense::bucket_type::standard`
|
||||
|
||||
* Up to 2^32 = 4.29 billion elements.
|
||||
* 8 bytes overhead per bucket.
|
||||
|
||||
#### 3.5.2. `ankerl::unordered_dense::bucket_type::big`
|
||||
|
||||
* up to 2^63 = 9223372036854775808 elements.
|
||||
* 12 bytes overhead per bucket.
|
||||
|
||||
## 4. `segmented_map` and `segmented_set`
|
||||
|
||||
`ankerl::unordered_dense` provides a custom container implementation that has lower memory requirements than the default `std::vector`. Memory is not contiguous, but it can allocate segments without having to reallocate and move all the elements. In summary, this leads to
|
||||
|
||||
* Much smoother memory usage, memory usage increases continuously.
|
||||
* No high peak memory usage.
|
||||
* Faster insertion because elements never need to be moved to new allocated blocks
|
||||
* Slightly slower indexing compared to `std::vector` because an additional indirection is needed.
|
||||
|
||||
Here is a comparison against `absl::flat_hash_map` and the `ankerl::unordered_dense::map` when inserting 10 million entries
|
||||

|
||||
|
||||
Abseil is fastest for this simple inserting test, taking a bit over 0.8 seconds. It's peak memory usage is about 430 MB. Note how the memory usage goes down after the last peak; when it goes down to ~290MB it has finished rehashing and could free the previously used memory block.
|
||||
|
||||
`ankerl::unordered_dense::segmented_map` doesn't have these peaks, and instead has a smooth increase of memory usage. Note there are still sudden drops & increases in memory because the indexing data structure needs still needs to increase by a fixed factor. But due to holding the data in a separate container we are able to first free the old data structure, and then allocate a new, bigger indexing structure; thus we do not have peaks.
|
||||
|
||||
## 5. Design
|
||||
|
||||
The map/set has two data structures:
|
||||
* `std::vector<value_type>` which holds all data. map/set iterators are just `std::vector<value_type>::iterator`!
|
||||
* An indexing structure (bucket array), which is a flat array with 8-byte buckets.
|
||||
|
||||
### 5.1. Inserts
|
||||
|
||||
Whenever an element is added it is `emplace_back` to the vector. The key is hashed, and an entry (bucket) is added at the
|
||||
corresponding location in the bucket array. The bucket has this structure:
|
||||
|
||||
```cpp
|
||||
struct Bucket {
|
||||
uint32_t dist_and_fingerprint;
|
||||
uint32_t value_idx;
|
||||
};
|
||||
```
|
||||
|
||||
Each bucket stores 3 things:
|
||||
* The distance of that value from the original hashed location (3 most significant bytes in `dist_and_fingerprint`)
|
||||
* A fingerprint; 1 byte of the hash (lowest significant byte in `dist_and_fingerprint`)
|
||||
* An index where in the vector the actual data is stored.
|
||||
|
||||
This structure is especially designed for the collision resolution strategy robin-hood hashing with backward shift
|
||||
deletion.
|
||||
|
||||
### 5.2. Lookups
|
||||
|
||||
The key is hashed and the bucket array is searched if it has an entry at that location with that fingerprint. When found,
|
||||
the key in the data vector is compared, and when equal the value is returned.
|
||||
|
||||
### 5.3. Removals
|
||||
|
||||
Since all data is stored in a vector, removals are a bit more complicated:
|
||||
|
||||
1. First, lookup the element to delete in the index array.
|
||||
2. When found, replace that element in the vector with the last element in the vector.
|
||||
3. Update *two* locations in the bucket array: First remove the bucket for the removed element
|
||||
4. Then, update the `value_idx` of the moved element. This requires another lookup.
|
||||
|
||||
|
||||
## 6. Real World Usage
|
||||
|
||||
On 2023-09-10 I did a quick search on github to see if this map is used in any popular open source projects. Here are some of the projects
|
||||
I found. Please send me a note if you want on that list!
|
||||
|
||||
* [PruaSlicer](https://github.com/prusa3d/PrusaSlicer) - G-code generator for 3D printers (RepRap, Makerbot, Ultimaker etc.)
|
||||
* [Kismet](https://github.com/kismetwireless/kismet): Wi-Fi, Bluetooth, RF, and more. Kismet is a sniffer, WIDS, and wardriving tool for Wi-Fi, Bluetooth, Zigbee, RF, and more, which runs on Linux and macOS
|
||||
* [Rspamd](https://github.com/rspamd/rspamd) - Fast, free and open-source spam filtering system.
|
||||
* [kallisto](https://github.com/pachterlab/kallisto) - Near-optimal RNA-Seq quantification
|
||||
* [Slang](https://github.com/shader-slang/slang) - Slang is a shading language that makes it easier to build and maintain large shader codebases in a modular and extensible fashion.
|
||||
* [CyberFSR2](https://github.com/PotatoOfDoom/CyberFSR2) - Drop-in DLSS replacement with FSR 2.0 for various games such as Cyberpunk 2077.
|
||||
* [ossia score](https://github.com/ossia/score) - A free, open-source, cross-platform intermedia sequencer for precise and flexible scripting of interactive scenarios.
|
||||
* [HiveWE](https://github.com/stijnherfst/HiveWE) - A Warcraft III World Editor (WE) that focusses on speed and ease of use.
|
||||
* [opentxs](https://github.com/Open-Transactions/opentxs) - The Open-Transactions project is a collaborative effort to develop a robust, commercial-grade, fully-featured, free-software toolkit implementing the OTX protocol as well as a full-strength financial cryptography library, API, GUI, command-line interface, and prototype notary server.
|
||||
* [LuisaCompute](https://github.com/LuisaGroup/LuisaCompute) - High-Performance Rendering Framework on Stream Architectures
|
||||
* [Lethe](https://github.com/lethe-cfd/lethe) - Lethe (pronounced /ˈliːθiː/) is open-source computational fluid dynamics (CFD) software which uses high-order continuous Galerkin formulations to solve the incompressible Navier–Stokes equations (among others).
|
||||
* [PECOS](https://github.com/amzn/pecos) - PECOS is a versatile and modular machine learning (ML) framework for fast learning and inference on problems with large output spaces, such as extreme multi-label ranking (XMR) and large-scale retrieval.
|
||||
* [Operon](https://github.com/heal-research/operon) - A modern C++ framework for symbolic regression that uses genetic programming to explore a hypothesis space of possible mathematical expressions in order to find the best-fitting model for a given regression target.
|
||||
* [MashMap](https://github.com/marbl/MashMap) - A fast approximate aligner for long DNA sequences
|
||||
* [minigpt4.cpp](https://github.com/Maknee/minigpt4.cpp) - Port of MiniGPT4 in C++ (4bit, 5bit, 6bit, 8bit, 16bit CPU inference with GGML)
|
4
third_party/unordered_dense/cmake/unordered_denseConfig.cmake.in
vendored
Normal file
4
third_party/unordered_dense/cmake/unordered_denseConfig.cmake.in
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
@PACKAGE_INIT@
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")
|
||||
check_required_components("@PROJECT_NAME@")
|
44
third_party/unordered_dense/doc/allocated_memory.gnuplot
vendored
Executable file
44
third_party/unordered_dense/doc/allocated_memory.gnuplot
vendored
Executable file
@ -0,0 +1,44 @@
|
||||
#!/usr/bin/gnuplot
|
||||
|
||||
#set terminal pngcairo
|
||||
#set terminal pngcairo size 730,510 enhanced font 'Verdana,10'
|
||||
set terminal pngcairo size 800,600 enhanced font 'Verdana,10'
|
||||
|
||||
# define axis
|
||||
# remove border on top and right and set color to gray
|
||||
set style line 11 lc rgb '#808080' lt 1
|
||||
set border 3 back ls 11
|
||||
set tics nomirror
|
||||
# define grid
|
||||
set style line 12 lc rgb '#808080' lt 0 lw 1
|
||||
set grid back ls 12
|
||||
|
||||
# line styles
|
||||
set style line 1 lt 1 lc rgb '#1B9E77' # dark teal
|
||||
set style line 2 lt 1 lc rgb '#D95F02' # dark orange
|
||||
set style line 3 lt 1 lc rgb '#7570B3' # dark lilac
|
||||
set style line 4 lt 1 lc rgb '#E7298A' # dark magenta
|
||||
set style line 5 lt 1 lc rgb '#66A61E' # dark lime green
|
||||
set style line 6 lt 1 lc rgb '#E6AB02' # dark banana
|
||||
set style line 7 lt 1 lc rgb '#A6761D' # dark tan
|
||||
set style line 8 lt 1 lc rgb '#666666' # dark gray
|
||||
|
||||
|
||||
set style line 101 lc rgb '#808080' lt 1 lw 1
|
||||
set border 3 front ls 101
|
||||
set tics nomirror out scale 0.75
|
||||
|
||||
set key left top
|
||||
|
||||
set output 'allocated_memory.png'
|
||||
|
||||
set xlabel "Runtime [s]"
|
||||
set ylabel "Allocated memory [MB]"
|
||||
|
||||
set title "Inserting 10 Million uint64\\\_t -> uint64\\\_t pairs"
|
||||
|
||||
# allocated_memory_segmented_vector.txt allocated_memory_std_unordered_map.txt allocated_memory_std_vector.txt
|
||||
plot \
|
||||
'allocated_memory_segmented_vector.txt' using ($1):($2/1e6) w steps ls 1 lw 2 title "ankerl::unordered\\\_dense::segmented\\\_map" , \
|
||||
'allocated_memory_std_vector.txt' using ($1):($2/1e6) w steps ls 2 lw 2 title "ankerl::unordered\\\_dense::map" , \
|
||||
'allocated_memory_absl_flat_hash_map.txt' using ($1):($2/1e6) w steps ls 3 lw 2 title "absl::flat\\\_hash\\\_map"
|
BIN
third_party/unordered_dense/doc/allocated_memory.png
vendored
Normal file
BIN
third_party/unordered_dense/doc/allocated_memory.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
6
third_party/unordered_dense/example/CMakeLists.txt
vendored
Normal file
6
third_party/unordered_dense/example/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
cmake_minimum_required(VERSION 3.12)
|
||||
project("UnorderedDenseExample")
|
||||
|
||||
add_executable(UnorderedDenseExample main.cpp)
|
||||
find_package(unordered_dense CONFIG REQUIRED)
|
||||
target_link_libraries(UnorderedDenseExample unordered_dense::unordered_dense)
|
16
third_party/unordered_dense/example/README.md
vendored
Normal file
16
third_party/unordered_dense/example/README.md
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
A simple example that demonstrats how to make use of `unordered_dense` with cmake.
|
||||
|
||||
Use globally installed `unordered_dense`:
|
||||
```sh
|
||||
mkdir build && cd build
|
||||
cmake -DCMAKE_INSTALL_PREFIX:PATH=${HOME}/unordered_dense_install ..
|
||||
make
|
||||
```
|
||||
|
||||
Use locall installed `unordered_dense`, as in the main README.md:
|
||||
|
||||
```sh
|
||||
mkdir build && cd build
|
||||
cmake -DCMAKE_INSTALL_PREFIX:PATH=${HOME}/unordered_dense_install ..
|
||||
make
|
||||
```
|
13
third_party/unordered_dense/example/main.cpp
vendored
Normal file
13
third_party/unordered_dense/example/main.cpp
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
auto main() -> int {
|
||||
auto map = ankerl::unordered_dense::map<int, std::string>();
|
||||
map[123] = "hello";
|
||||
map[987] = "world!";
|
||||
|
||||
for (auto const& [key, val] : map) {
|
||||
std::cout << key << " => " << val << std::endl;
|
||||
}
|
||||
}
|
2032
third_party/unordered_dense/include/ankerl/unordered_dense.h
vendored
Normal file
2032
third_party/unordered_dense/include/ankerl/unordered_dense.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
32
third_party/unordered_dense/meson.build
vendored
Normal file
32
third_party/unordered_dense/meson.build
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
# meson commands cheat sheet:
|
||||
#
|
||||
# # Setup ###################
|
||||
#
|
||||
# release & debug setup
|
||||
# CXX="ccache clang++" meson setup --buildtype release builddir/clang_release
|
||||
# CXX="ccache clang++" meson setup builddir/clang_debug
|
||||
# c++20 build
|
||||
# CXX="ccache clang++" meson setup -Dcpp_std=c++20 builddir/clang_cpp20
|
||||
# lcov coverage:
|
||||
# CXX="ccache clang++" meson setup -Db_coverage=true builddir/coverage
|
||||
# ninja clean && ninja test && ninja coverage
|
||||
#
|
||||
# # Testing ################
|
||||
#
|
||||
# Run with valgrind:
|
||||
# meson test --wrap='valgrind --leak-check=full --error-exitcode=1'
|
||||
#
|
||||
|
||||
project('unordered_dense', 'cpp',
|
||||
version: '4.4.0',
|
||||
license: 'MIT',
|
||||
default_options : [
|
||||
'cpp_std=c++17',
|
||||
'warning_level=3',
|
||||
'werror=true',
|
||||
'b_ndebug=true', # otherwise absl is really slow!
|
||||
])
|
||||
|
||||
incdir = include_directories('include')
|
||||
subdir('test')
|
||||
|
86
third_party/unordered_dense/scripts/build.py
vendored
Executable file
86
third_party/unordered_dense/scripts/build.py
vendored
Executable file
@ -0,0 +1,86 @@
|
||||
#!/bin/env python
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
|
||||
|
||||
cmd_and_dir = [
|
||||
# needs honggfuzz installed
|
||||
['env', 'CXX_LD=mold', 'CXX=ccache clang++', 'meson', 'setup', '--buildtype', 'debug', '-Dcpp_std=c++17', 'builddir/clang_cpp17_debug'],
|
||||
['env', 'CXX_LD=mold', 'CXX=ccache clang++', 'meson', 'setup', '--buildtype', 'release', '-Dcpp_std=c++17', 'builddir/clang_cpp17_release'],
|
||||
['env', 'CXX_LD=mold', 'CXX=ccache g++', 'meson', 'setup', '--buildtype', 'release', '-Dcpp_std=c++17', 'builddir/gcc_cpp17_release'],
|
||||
['env', 'CXX_LD=mold', 'CXX=ccache hfuzz-clang++ -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION', 'meson', 'setup', '--buildtype', 'release', '-Dcpp_std=c++17', 'builddir/hfuzz-clang_cpp17_release'],
|
||||
['env', 'CXX_LD=mold', 'CXX=ccache g++', 'meson', 'setup', '--buildtype', 'debug', '-Dcpp_std=c++17', 'builddir/gcc_cpp17_debug'],
|
||||
|
||||
# 32bit. Install lib32-clang
|
||||
['env', 'CXX_LD=mold', 'CXX=ccache g++', 'meson', 'setup', '--buildtype', 'debug', '-Dcpp_std=c++17', '-Dcpp_args=-m32', '-Dcpp_link_args=-m32', '-Dc_args=-m32', '-Dc_link_args=-m32', 'builddir/gcc_cpp17_debug_32'],
|
||||
['env', 'CXX_LD=mold', 'CXX=ccache clang++', 'meson', 'setup', '--buildtype', 'debug', '-Dcpp_std=c++17', '-Dcpp_args=-m32', '-Dcpp_link_args=-m32', '-Dc_args=-m32', '-Dc_link_args=-m32', 'builddir/clang_cpp17_debug_32'],
|
||||
|
||||
# c++20
|
||||
['env', 'CXX_LD=mold', 'CXX=ccache clang++', 'meson', 'setup', '--buildtype', 'debug', '-Dcpp_std=c++20', 'builddir/clang_cpp20_debug'],
|
||||
['env', 'CXX_LD=mold', 'CXX=ccache g++', 'meson', 'setup', '--buildtype', 'debug', '-Dcpp_std=c++20', 'builddir/gcc_cpp20_debug'],
|
||||
|
||||
# coverage; use "ninja clean && ninja test && ninja coverage"
|
||||
#['env', 'CXX_LD=mold', 'CXX=ccache clang++', 'meson', 'setup', '-Db_coverage=true', 'builddir/coverage'],
|
||||
|
||||
# sanitizers
|
||||
# It is not possible to combine more than one of the -fsanitize=address, -fsanitize=thread, and -fsanitize=memory checkers in the same program.
|
||||
# see https://clang.llvm.org/docs/UsersManual.html#controlling-code-generation
|
||||
#
|
||||
# can't use ccache, it doesn't work with the ignorelist.txt
|
||||
['env', 'CXX_LD=mold', 'CXX=ccache g++', 'meson', 'setup', '-Db_sanitize=address', 'builddir/gcc_sanitize_address'],
|
||||
['env', 'CXX_LD=mold', 'CXX=ccache clang++', 'meson', 'setup', '-Db_sanitize=address', 'builddir/clang_sanitize_address'],
|
||||
|
||||
['env', 'CXX_LD=mold', 'CXX=ccache g++', 'meson', 'setup', '-Db_sanitize=thread', 'builddir/gcc_sanitize_thread'],
|
||||
['env', 'CXX_LD=mold', 'CXX=ccache clang++', 'meson', 'setup', '-Db_sanitize=thread', 'builddir/clang_sanitize_thread'],
|
||||
|
||||
# ['env', 'CXX_LD=mold', 'CXX=ccache g++', 'meson', 'setup', '-Db_sanitize=memory', 'builddir/gcc_sanitize_memory'], # doesn't work due to STL, and ignore doesn't work either :-(
|
||||
# ['env', 'CXX_LD=mold', 'CXX=ccache clang++', 'meson', 'setup', '-Db_sanitize=memory', 'builddir/clang_sanitize_memory'], # doesn't work due to STL, and ignore doesn't work either :-(
|
||||
|
||||
['env', 'CXX_LD=mold', 'CXX=ccache g++', 'meson', 'setup', '-Db_sanitize=undefined', 'builddir/gcc_sanitize_undefined'],
|
||||
['env', 'CXX_LD=mold', 'CXX=ccache clang++', 'meson', 'setup', '-Db_sanitize=undefined', 'builddir/clang_sanitize_undefined'],
|
||||
]
|
||||
|
||||
root_path = Path(__file__).parent.parent
|
||||
os.chdir(root_path)
|
||||
|
||||
|
||||
def run(cmd):
|
||||
result = subprocess.run(cmd)
|
||||
if result.returncode != 0:
|
||||
exit(result.returncode)
|
||||
|
||||
run('scripts/lint/lint-all.py')
|
||||
|
||||
for cmd_dir in cmd_and_dir:
|
||||
workdir = cmd_dir[-1]
|
||||
|
||||
# setup
|
||||
if not os.path.isdir(workdir):
|
||||
out = run(cmd_dir)
|
||||
|
||||
# clean
|
||||
run(['meson', 'compile', '--clean', '-C', workdir])
|
||||
|
||||
# compile everything
|
||||
run(['meson', 'compile', '-C', workdir])
|
||||
|
||||
# test
|
||||
#if workdir.find("clang_cpp17_debug") != -1:
|
||||
# run(['meson', 'test', '--wrap=\'valgrind --leak-check=full --error-exitcode=1\'', '-q', '--print-errorlogs', '-C', workdir])
|
||||
#else:
|
||||
|
||||
if workdir.find("hfuzz") == -1:
|
||||
# no testing for hfuzz
|
||||
run(['meson', 'test', '-q', '--print-errorlogs', '-C', workdir])
|
||||
|
||||
# coverage
|
||||
if workdir.find("coverage") != -1:
|
||||
print(workdir)
|
||||
run(['meson', 'compile', '--ninja-args', 'coverage', '-C', workdir])
|
||||
|
||||
|
||||
for cmd_dir in cmd_and_dir:
|
||||
workdir = cmd_dir[-1]
|
||||
|
11
third_party/unordered_dense/scripts/fuzz_merge.sh
vendored
Executable file
11
third_party/unordered_dense/scripts/fuzz_merge.sh
vendored
Executable file
@ -0,0 +1,11 @@
|
||||
#!/bin/env bash
|
||||
set -e
|
||||
|
||||
# Start from a build directory, usually clang_cpp17_release
|
||||
# usage: fuzz_merge.sh <testname>
|
||||
FUZZ_TARGET=$1
|
||||
SCRIPT_DIR=`dirname "$0"`
|
||||
CORPUS_SMALL=${SCRIPT_DIR}/../data/fuzz/${FUZZ_TARGET}
|
||||
CORPUS_BIG=CORPUS_BIG/${FUZZ_TARGET}
|
||||
ninja
|
||||
./test/fuzz_${FUZZ_TARGET} -merge=1 ${CORPUS_SMALL} ${CORPUS_BIG}
|
18
third_party/unordered_dense/scripts/fuzz_run.sh
vendored
Executable file
18
third_party/unordered_dense/scripts/fuzz_run.sh
vendored
Executable file
@ -0,0 +1,18 @@
|
||||
#!/bin/env bash
|
||||
set -ev
|
||||
|
||||
# Start from a build directory, usually clang_cpp17_release
|
||||
# ../../scripts/fuzz_run.sh <testname>
|
||||
#
|
||||
# Found a crash? Minimize it like so:
|
||||
# ./test/fuzz_replace -minimize_crash=1 ./crash-123abcdef
|
||||
|
||||
FUZZ_TARGET=$1
|
||||
SCRIPT_DIR=`dirname "$0"`
|
||||
CORPUS_SMALL=${SCRIPT_DIR}/../data/fuzz/${FUZZ_TARGET}
|
||||
CORPUS_BIG=CORPUS_BIG/${FUZZ_TARGET}
|
||||
NUM_JOBS=$(nproc)
|
||||
|
||||
mkdir -p ${CORPUS_BIG}
|
||||
ninja
|
||||
chrt -i 0 ./test/fuzz_${FUZZ_TARGET} -jobs=${NUM_JOBS} -workers=${NUM_JOBS} ${CORPUS_BIG} ${CORPUS_SMALL}
|
30
third_party/unordered_dense/scripts/lint/lint-all.py
vendored
Executable file
30
third_party/unordered_dense/scripts/lint/lint-all.py
vendored
Executable file
@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from glob import glob
|
||||
from pathlib import Path
|
||||
from subprocess import run
|
||||
from os import path
|
||||
from time import time
|
||||
|
||||
|
||||
time_start = time()
|
||||
|
||||
exit_code = 0
|
||||
num_linters = 0
|
||||
mod_path = Path(__file__).parent
|
||||
for lint in glob(f"{mod_path}/lint-*"):
|
||||
lint = path.abspath(lint)
|
||||
if lint == path.abspath(__file__):
|
||||
continue
|
||||
|
||||
num_linters += 1
|
||||
result = run([lint])
|
||||
if result.returncode == 0:
|
||||
continue
|
||||
|
||||
print(f"^---- failure from {lint.split('/')[-1]}")
|
||||
exit_code |= result.returncode
|
||||
|
||||
time_end = time()
|
||||
print(f"{num_linters} linters in {time_end - time_start:0.2}s")
|
||||
exit(exit_code)
|
50
third_party/unordered_dense/scripts/lint/lint-clang-format.py
vendored
Executable file
50
third_party/unordered_dense/scripts/lint/lint-clang-format.py
vendored
Executable file
@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from glob import glob
|
||||
from pathlib import Path
|
||||
from subprocess import run
|
||||
from os import path
|
||||
import subprocess
|
||||
import sys
|
||||
from time import time
|
||||
import re
|
||||
|
||||
root_path = path.abspath(Path(__file__).parent.parent.parent)
|
||||
|
||||
globs = [
|
||||
f"{root_path}/include/**/*.h",
|
||||
f"{root_path}/test/**/*.h",
|
||||
f"{root_path}/test/**/*.cpp",
|
||||
]
|
||||
exclusions = [
|
||||
"nanobench\\.h",
|
||||
"FuzzedDataProvider\\.h",
|
||||
'/third-party/']
|
||||
|
||||
files = []
|
||||
for g in globs:
|
||||
r = glob(g, recursive=True)
|
||||
files.extend(r)
|
||||
|
||||
# filter out exclusions
|
||||
for exclusion in exclusions:
|
||||
l = filter(lambda file: re.search(exclusion, file) == None, files)
|
||||
files = list(l)
|
||||
|
||||
if len(files) == 0:
|
||||
print("could not find any files!")
|
||||
sys.exit(1)
|
||||
|
||||
command = ['clang-format', '--dry-run', '-Werror'] + files
|
||||
p = subprocess.Popen(command,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=None,
|
||||
stdin=subprocess.PIPE,
|
||||
universal_newlines=True)
|
||||
|
||||
stdout, stderr = p.communicate()
|
||||
|
||||
print(f"clang-format checked {len(files)} files")
|
||||
|
||||
if p.returncode != 0:
|
||||
sys.exit(p.returncode)
|
68
third_party/unordered_dense/scripts/lint/lint-version.py
vendored
Executable file
68
third_party/unordered_dense/scripts/lint/lint-version.py
vendored
Executable file
@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
|
||||
|
||||
root = os.path.abspath(pathlib.Path(__file__).parent.parent.parent)
|
||||
|
||||
# filename, pattern, number of occurrences
|
||||
file_pattern_count = [
|
||||
(
|
||||
f"{root}/meson.build",
|
||||
r"version: '(\d+)\.(\d+)\.(\d+)'",
|
||||
1),
|
||||
(
|
||||
f"{root}/include/ankerl/unordered_dense.h",
|
||||
r"Version (\d+)\.(\d+)\.(\d+)\n",
|
||||
1),
|
||||
(
|
||||
f"{root}/CMakeLists.txt",
|
||||
r"^\s+VERSION (\d+)\.(\d+)\.(\d+)\n",
|
||||
1),
|
||||
(
|
||||
f"{root}/test/unit/namespace.cpp",
|
||||
r"unordered_dense::v(\d+)_(\d+)_(\d+)",
|
||||
1
|
||||
)
|
||||
]
|
||||
|
||||
# let's parse the reference from svector.h
|
||||
major = "??"
|
||||
minor = "??"
|
||||
patch = "??"
|
||||
with open(f"{root}/include/ankerl/unordered_dense.h", "r") as f:
|
||||
for line in f:
|
||||
r = re.search(r"#define ANKERL_UNORDERED_DENSE_VERSION_([A-Z]+) (\d+)", line)
|
||||
if not r:
|
||||
continue
|
||||
|
||||
if "MAJOR" == r.group(1):
|
||||
major = r.group(2)
|
||||
elif "MINOR" == r.group(1):
|
||||
minor = r.group(2)
|
||||
elif "PATCH" == r.group(1):
|
||||
patch = r.group(2)
|
||||
else:
|
||||
"match but with something else!"
|
||||
exit(1)
|
||||
|
||||
is_ok = True
|
||||
for (filename, pattern, count) in file_pattern_count:
|
||||
num_found = 0
|
||||
with open(filename, "r") as f:
|
||||
for line in f:
|
||||
r = re.search(pattern, line)
|
||||
if r:
|
||||
num_found += 1
|
||||
if major != r.group(1) or minor != r.group(2) or patch != r.group(3):
|
||||
is_ok = False
|
||||
print(f"ERROR in {filename}: got '{line.strip()}' but version should be '{major}.{minor}.{patch}'")
|
||||
if num_found != count:
|
||||
is_ok = False
|
||||
print(f"ERROR in {filename}: expected {count} occurrences but found it {num_found} times")
|
||||
|
||||
if not is_ok:
|
||||
exit(1)
|
||||
|
39
third_party/unordered_dense/src/ankerl.unordered_dense.cpp
vendored
Normal file
39
third_party/unordered_dense/src/ankerl.unordered_dense.cpp
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
module;
|
||||
|
||||
// see https://github.com/fmtlib/fmt/blob/master/src/fmt.cc
|
||||
|
||||
// Put all implementation-provided headers into the global module fragment
|
||||
// to prevent attachment to this module.
|
||||
|
||||
#include <array> // for array
|
||||
#include <cstdint> // for uint64_t, uint32_t, uint8_t, UINT64_C
|
||||
#include <cstring> // for size_t, memcpy, memset
|
||||
#include <functional> // for equal_to, hash
|
||||
#include <initializer_list> // for initializer_list
|
||||
#include <iterator> // for pair, distance
|
||||
#include <limits> // for numeric_limits
|
||||
#include <memory> // for allocator, allocator_traits, shared_ptr
|
||||
#include <stdexcept> // for out_of_range
|
||||
#include <string> // for basic_string
|
||||
#include <string_view> // for basic_string_view, hash
|
||||
#include <tuple> // for forward_as_tuple
|
||||
#include <type_traits> // for enable_if_t, declval, conditional_t, ena...
|
||||
#include <utility> // for forward, exchange, pair, as_const, piece...
|
||||
#include <vector> // for vector
|
||||
#if defined(__has_include)
|
||||
# if __has_include(<memory_resource>)
|
||||
# include <memory_resource> // for polymorphic_allocator
|
||||
# elif __has_include(<experimental/memory_resource>)
|
||||
# include <experimental/memory_resource> // for polymorphic_allocator
|
||||
# endif
|
||||
#endif
|
||||
#if defined(_MSC_VER) && defined(_M_X64)
|
||||
# include <intrin.h>
|
||||
# pragma intrinsic(_umul128)
|
||||
#endif
|
||||
|
||||
export module ankerl.unordered_dense;
|
||||
|
||||
#define ANKERL_UNORDERED_DENSE_EXPORT export
|
||||
|
||||
#include "ankerl/unordered_dense.h"
|
2
third_party/unordered_dense/subprojects/.clang-tidy
vendored
Normal file
2
third_party/unordered_dense/subprojects/.clang-tidy
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
---
|
||||
Checks: '-*'
|
23
third_party/unordered_dense/subprojects/abseil-cpp.wrap
vendored
Normal file
23
third_party/unordered_dense/subprojects/abseil-cpp.wrap
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
[wrap-file]
|
||||
directory = abseil-cpp-20220623.0
|
||||
source_url = https://github.com/abseil/abseil-cpp/archive/20220623.0.tar.gz
|
||||
source_filename = abseil-cpp-20220623.0.tar.gz
|
||||
source_hash = 4208129b49006089ba1d6710845a45e31c59b0ab6bff9e5788a87f55c5abd602
|
||||
patch_filename = abseil-cpp_20220623.0-2_patch.zip
|
||||
patch_url = https://wrapdb.mesonbuild.com/v2/abseil-cpp_20220623.0-2/get_patch
|
||||
patch_hash = d19cb16610d9310658a815ebcd87a9e2966aafbd57964341c0d1a3a3778c03b6
|
||||
wrapdb_version = 20220623.0-2
|
||||
|
||||
[provide]
|
||||
absl_base = absl_base_dep
|
||||
absl_container = absl_container_dep
|
||||
absl_debugging = absl_debugging_dep
|
||||
absl_flags = absl_flags_dep
|
||||
absl_hash = absl_hash_dep
|
||||
absl_numeric = absl_numeric_dep
|
||||
absl_random = absl_random_dep
|
||||
absl_status = absl_status_dep
|
||||
absl_strings = absl_strings_dep
|
||||
absl_synchronization = absl_synchronization_dep
|
||||
absl_time = absl_time_dep
|
||||
absl_types = absl_types_dep
|
9
third_party/unordered_dense/subprojects/doctest.wrap
vendored
Normal file
9
third_party/unordered_dense/subprojects/doctest.wrap
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
[wrap-file]
|
||||
directory = doctest-2.4.9
|
||||
source_url = https://github.com/doctest/doctest/archive/refs/tags/v2.4.9.tar.gz
|
||||
source_filename = doctest-2.4.9.tar.gz
|
||||
source_hash = 19b2df757f2f3703a5e63cee553d85596875f06d91a3333acd80a969ef210856
|
||||
wrapdb_version = 2.4.9-1
|
||||
|
||||
[provide]
|
||||
dependency_names = doctest
|
12
third_party/unordered_dense/subprojects/fmt.wrap
vendored
Normal file
12
third_party/unordered_dense/subprojects/fmt.wrap
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
[wrap-file]
|
||||
directory = fmt-9.1.0
|
||||
source_url = https://github.com/fmtlib/fmt/archive/9.1.0.tar.gz
|
||||
source_filename = fmt-9.1.0.tar.gz
|
||||
source_hash = 5dea48d1fcddc3ec571ce2058e13910a0d4a6bab4cc09a809d8b1dd1c88ae6f2
|
||||
patch_filename = fmt_9.1.0-1_patch.zip
|
||||
patch_url = https://wrapdb.mesonbuild.com/v2/fmt_9.1.0-1/get_patch
|
||||
patch_hash = 4557b9ba87b3eb63694ed9b21d1a2117d4a97ca56b91085b10288e9a5294adf8
|
||||
wrapdb_version = 9.1.0-1
|
||||
|
||||
[provide]
|
||||
fmt = fmt_dep
|
75
third_party/unordered_dense/test/app/checksum.h
vendored
Normal file
75
third_party/unordered_dense/test/app/checksum.h
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
|
||||
#include <app/counter.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
|
||||
namespace checksum {
|
||||
|
||||
// final step from MurmurHash3
|
||||
[[nodiscard]] static inline auto mix(uint64_t k) -> uint64_t {
|
||||
k ^= k >> 33U;
|
||||
k *= 0xff51afd7ed558ccdULL;
|
||||
k ^= k >> 33U;
|
||||
k *= 0xc4ceb9fe1a85ec53ULL;
|
||||
k ^= k >> 33U;
|
||||
return k;
|
||||
}
|
||||
|
||||
[[maybe_unused]] [[nodiscard]] static inline auto mix(std::string_view data) -> uint64_t {
|
||||
static constexpr uint64_t fnv_offset_basis = UINT64_C(14695981039346656037);
|
||||
static constexpr uint64_t fnv_prime = UINT64_C(1099511628211);
|
||||
|
||||
uint64_t val = fnv_offset_basis;
|
||||
for (auto c : data) {
|
||||
val ^= static_cast<uint64_t>(c);
|
||||
val *= fnv_prime;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
[[maybe_unused]] [[nodiscard]] static inline auto mix(counter::obj const& cdv) -> uint64_t {
|
||||
return mix(cdv.get());
|
||||
}
|
||||
|
||||
// from boost::hash_combine, with additional fmix64 of value
|
||||
[[maybe_unused]] [[nodiscard]] static inline auto combine(uint64_t seed, uint64_t value) -> uint64_t {
|
||||
return seed ^ (value + 0x9e3779b9 + (seed << 6U) + (seed >> 2U));
|
||||
}
|
||||
|
||||
// calculates a hash of any iterable map. Order is irrelevant for the hash's result, as it simply
|
||||
// xors the elements together.
|
||||
template <typename M>
|
||||
[[nodiscard]] auto map(const M& map) -> uint64_t {
|
||||
uint64_t combined_hash = 1;
|
||||
|
||||
uint64_t num_elements = 0;
|
||||
for (auto const& entry : map) {
|
||||
auto entry_hash = combine(mix(entry.first), mix(entry.second));
|
||||
|
||||
combined_hash ^= entry_hash;
|
||||
++num_elements;
|
||||
}
|
||||
|
||||
return combine(combined_hash, num_elements);
|
||||
}
|
||||
|
||||
// map of maps
|
||||
template <typename MM>
|
||||
[[nodiscard]] auto mapmap(const MM& mapmap) -> uint64_t {
|
||||
uint64_t combined_hash = 1;
|
||||
|
||||
uint64_t num_elements = 0;
|
||||
for (auto const& entry : mapmap) {
|
||||
auto entry_hash = combine(mix(entry.first), map(entry.second));
|
||||
|
||||
combined_hash ^= entry_hash;
|
||||
++num_elements;
|
||||
}
|
||||
|
||||
return combine(combined_hash, num_elements);
|
||||
}
|
||||
|
||||
} // namespace checksum
|
254
third_party/unordered_dense/test/app/counter.cpp
vendored
Normal file
254
third_party/unordered_dense/test/app/counter.cpp
vendored
Normal file
@ -0,0 +1,254 @@
|
||||
#include <app/counter.h>
|
||||
|
||||
#include <app/print.h> // for print
|
||||
|
||||
#include <cstdlib> // for abort
|
||||
#include <ostream> // for ostream
|
||||
#include <stdexcept> // for runtime_error
|
||||
#include <unordered_set> // for unordered_set
|
||||
#include <utility> // for swap, pair
|
||||
|
||||
static inline constexpr bool counter_enable_unordered_set = true;
|
||||
|
||||
auto singleton_constructed_objects() -> std::unordered_set<counter::obj const*>& {
|
||||
static std::unordered_set<counter::obj const*> static_data{};
|
||||
return static_data;
|
||||
}
|
||||
|
||||
counter::obj::obj()
|
||||
: m_data(0)
|
||||
, m_counts(nullptr) {
|
||||
if constexpr (counter_enable_unordered_set) {
|
||||
if (!singleton_constructed_objects().emplace(this).second) {
|
||||
test::print("ERROR at {}({}): {}\n", __FILE__, __LINE__, __func__);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
++static_default_ctor;
|
||||
}
|
||||
|
||||
counter::obj::obj(const size_t& data, counter& counts)
|
||||
: m_data(data)
|
||||
, m_counts(&counts) {
|
||||
if constexpr (counter_enable_unordered_set) {
|
||||
if (!singleton_constructed_objects().emplace(this).second) {
|
||||
test::print("ERROR at {}({}): {}\n", __FILE__, __LINE__, __func__);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
++m_counts->m_data.m_ctor;
|
||||
}
|
||||
|
||||
counter::obj::obj(const counter::obj& o)
|
||||
: m_data(o.m_data)
|
||||
, m_counts(o.m_counts) {
|
||||
if constexpr (counter_enable_unordered_set) {
|
||||
if (1 != singleton_constructed_objects().count(&o)) {
|
||||
test::print("ERROR at {}({}): {}\n", __FILE__, __LINE__, __func__);
|
||||
std::abort();
|
||||
}
|
||||
if (!singleton_constructed_objects().emplace(this).second) {
|
||||
test::print("ERROR at {}({}): {}\n", __FILE__, __LINE__, __func__);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
if (nullptr != m_counts) {
|
||||
++m_counts->m_data.m_copy_ctor;
|
||||
}
|
||||
}
|
||||
|
||||
counter::obj::obj(counter::obj&& o) noexcept
|
||||
: m_data(o.m_data)
|
||||
, m_counts(o.m_counts) {
|
||||
if constexpr (counter_enable_unordered_set) {
|
||||
if (1 != singleton_constructed_objects().count(&o)) {
|
||||
test::print("ERROR at {}({}): {}\n", __FILE__, __LINE__, __func__);
|
||||
std::abort();
|
||||
}
|
||||
if (!singleton_constructed_objects().emplace(this).second) {
|
||||
test::print("ERROR at {}({}): {}\n", __FILE__, __LINE__, __func__);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
if (nullptr != m_counts) {
|
||||
++m_counts->m_data.m_move_ctor;
|
||||
}
|
||||
}
|
||||
|
||||
counter::obj::~obj() {
|
||||
if constexpr (counter_enable_unordered_set) {
|
||||
if (1 != singleton_constructed_objects().erase(this)) {
|
||||
test::print("ERROR at {}({}): {}\n", __FILE__, __LINE__, __func__);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
if (nullptr != m_counts) {
|
||||
++m_counts->m_data.m_dtor;
|
||||
} else {
|
||||
++static_dtor;
|
||||
}
|
||||
}
|
||||
|
||||
auto counter::obj::operator==(obj const& o) const -> bool {
|
||||
if constexpr (counter_enable_unordered_set) {
|
||||
if (1 != singleton_constructed_objects().count(this) || 1 != singleton_constructed_objects().count(&o)) {
|
||||
test::print("ERROR at {}({}): {}\n", __FILE__, __LINE__, __func__);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
if (nullptr != m_counts) {
|
||||
++m_counts->m_data.m_equals;
|
||||
}
|
||||
return m_data == o.m_data;
|
||||
}
|
||||
|
||||
auto counter::obj::operator<(obj const& o) const -> bool {
|
||||
if constexpr (counter_enable_unordered_set) {
|
||||
if (1 != singleton_constructed_objects().count(this) || 1 != singleton_constructed_objects().count(&o)) {
|
||||
test::print("ERROR at {}({}): {}\n", __FILE__, __LINE__, __func__);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
if (nullptr != m_counts) {
|
||||
++m_counts->m_data.m_less;
|
||||
}
|
||||
return m_data < o.m_data;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(bugprone-unhandled-self-assignment,cert-oop54-cpp)
|
||||
auto counter::obj::operator=(obj const& o) -> counter::obj& {
|
||||
if constexpr (counter_enable_unordered_set) {
|
||||
if (1 != singleton_constructed_objects().count(this) || 1 != singleton_constructed_objects().count(&o)) {
|
||||
test::print("ERROR at {}({}): {}\n", __FILE__, __LINE__, __func__);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
m_counts = o.m_counts;
|
||||
if (nullptr != m_counts) {
|
||||
++m_counts->m_data.m_assign;
|
||||
}
|
||||
m_data = o.m_data;
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto counter::obj::operator=(obj&& o) noexcept -> counter::obj& {
|
||||
if constexpr (counter_enable_unordered_set) {
|
||||
if (1 != singleton_constructed_objects().count(this) || 1 != singleton_constructed_objects().count(&o)) {
|
||||
test::print("ERROR at {}({}): {}\n", __FILE__, __LINE__, __func__);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
if (nullptr != o.m_counts) {
|
||||
m_counts = o.m_counts;
|
||||
}
|
||||
m_data = o.m_data;
|
||||
if (nullptr != m_counts) {
|
||||
++m_counts->m_data.m_move_assign;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto counter::obj::get() const -> size_t const& {
|
||||
if (nullptr != m_counts) {
|
||||
++m_counts->m_data.m_const_get;
|
||||
}
|
||||
return m_data;
|
||||
}
|
||||
|
||||
auto counter::obj::get() -> size_t& {
|
||||
if (nullptr != m_counts) {
|
||||
++m_counts->m_data.m_get;
|
||||
}
|
||||
return m_data;
|
||||
}
|
||||
|
||||
void counter::obj::swap(obj& other) {
|
||||
if constexpr (counter_enable_unordered_set) {
|
||||
if (1 != singleton_constructed_objects().count(this) || 1 != singleton_constructed_objects().count(&other)) {
|
||||
test::print("ERROR at {}({}): {}\n", __FILE__, __LINE__, __func__);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
using std::swap;
|
||||
swap(m_data, other.m_data);
|
||||
swap(m_counts, other.m_counts);
|
||||
if (nullptr != m_counts) {
|
||||
++m_counts->m_data.m_swaps;
|
||||
}
|
||||
}
|
||||
|
||||
auto counter::obj::get_for_hash() const -> size_t {
|
||||
if (nullptr != m_counts) {
|
||||
++m_counts->m_data.m_hash;
|
||||
}
|
||||
return m_data;
|
||||
}
|
||||
|
||||
counter::counter() {
|
||||
counter::static_default_ctor = 0;
|
||||
counter::static_dtor = 0;
|
||||
}
|
||||
|
||||
void counter::check_all_done() const {
|
||||
if constexpr (counter_enable_unordered_set) {
|
||||
// check that all are destructed
|
||||
if (!singleton_constructed_objects().empty()) {
|
||||
test::print("ERROR at ~counter(): got {} objects still alive!", singleton_constructed_objects().size());
|
||||
std::abort();
|
||||
}
|
||||
|
||||
if (m_data.m_dtor + static_dtor !=
|
||||
m_data.m_ctor + static_default_ctor + m_data.m_copy_ctor + m_data.m_default_ctor + m_data.m_move_ctor) {
|
||||
test::print("ERROR at ~counter(): number of counts does not match!\n");
|
||||
test::print(
|
||||
"{} dtor + {} staticDtor != {} ctor + {} staticDefaultCtor + {} copyCtor + {} defaultCtor + {} moveCtor\n",
|
||||
m_data.m_dtor,
|
||||
static_dtor,
|
||||
m_data.m_ctor,
|
||||
static_default_ctor,
|
||||
m_data.m_copy_ctor,
|
||||
m_data.m_default_ctor,
|
||||
m_data.m_move_ctor);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
counter::~counter() {
|
||||
check_all_done();
|
||||
}
|
||||
|
||||
auto counter::total() const -> size_t {
|
||||
return m_data.m_ctor + static_default_ctor + m_data.m_copy_ctor + (m_data.m_dtor + static_dtor) + m_data.m_equals +
|
||||
m_data.m_less + m_data.m_assign + m_data.m_swaps + m_data.m_get + m_data.m_const_get + m_data.m_hash +
|
||||
m_data.m_move_ctor + m_data.m_move_assign;
|
||||
}
|
||||
|
||||
void counter::operator()(std::string_view title) {
|
||||
m_records += fmt::format("{:9}{:9}{:9}{:9}{:9}{:9}{:9}{:9}{:9}{:9}{:9}{:9}{:9}|{:9}| {}\n",
|
||||
m_data.m_ctor,
|
||||
static_default_ctor,
|
||||
m_data.m_copy_ctor,
|
||||
m_data.m_dtor + static_dtor,
|
||||
m_data.m_assign,
|
||||
m_data.m_swaps,
|
||||
m_data.m_get,
|
||||
m_data.m_const_get,
|
||||
m_data.m_hash,
|
||||
m_data.m_equals,
|
||||
m_data.m_less,
|
||||
m_data.m_move_ctor,
|
||||
m_data.m_move_assign,
|
||||
total(),
|
||||
title);
|
||||
}
|
||||
|
||||
auto operator<<(std::ostream& os, counter const& c) -> std::ostream& {
|
||||
return os << c.m_records;
|
||||
}
|
||||
|
||||
auto operator new(size_t /*unused*/, counter::obj* /*unused*/) -> void* {
|
||||
throw std::runtime_error("operator new overload is taken! Cast to void* to ensure the void pointer overload is taken.");
|
||||
}
|
||||
size_t counter::static_default_ctor = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
size_t counter::static_dtor = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
170
third_party/unordered_dense/test/app/counter.h
vendored
Normal file
170
third_party/unordered_dense/test/app/counter.h
vendored
Normal file
@ -0,0 +1,170 @@
|
||||
#pragma once
|
||||
|
||||
#include <fmt/core.h> // for format_context, format_parse_context, format_to
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
#include <functional> // for hash
|
||||
#include <iosfwd> // for ostream
|
||||
#include <string> // for allocator, string
|
||||
#include <string_view> // for hash, string_view
|
||||
#include <type_traits>
|
||||
|
||||
class counter {
|
||||
public:
|
||||
struct data_t {
|
||||
size_t m_ctor{};
|
||||
size_t m_default_ctor{};
|
||||
size_t m_copy_ctor{};
|
||||
size_t m_dtor{};
|
||||
size_t m_assign{};
|
||||
size_t m_swaps{};
|
||||
size_t m_get{};
|
||||
size_t m_const_get{};
|
||||
size_t m_hash{};
|
||||
size_t m_equals{};
|
||||
size_t m_less{};
|
||||
size_t m_move_ctor{};
|
||||
size_t m_move_assign{};
|
||||
|
||||
friend auto operator==(data_t const& a, data_t const& b) -> bool {
|
||||
static_assert(std::has_unique_object_representations_v<data_t>);
|
||||
return 0 == std::memcmp(&a, &b, sizeof(data_t));
|
||||
}
|
||||
|
||||
friend auto operator!=(data_t const& a, data_t const& b) -> bool {
|
||||
return !(a == b);
|
||||
}
|
||||
};
|
||||
|
||||
counter(counter const&) = delete;
|
||||
counter(counter&&) = delete;
|
||||
auto operator=(counter const&) -> counter& = delete;
|
||||
auto operator=(counter&&) -> counter&& = delete;
|
||||
|
||||
// Obj for only swaps & equals. Used for optimizing.
|
||||
// Can't use static counters here because I want to do it in parallel.
|
||||
class obj {
|
||||
public:
|
||||
// required for operator[]
|
||||
obj();
|
||||
obj(const size_t& data, counter& counts);
|
||||
obj(const obj& o);
|
||||
obj(obj&& o) noexcept;
|
||||
~obj();
|
||||
|
||||
auto operator==(const obj& o) const -> bool;
|
||||
auto operator<(const obj& o) const -> bool;
|
||||
auto operator=(const obj& o) -> obj&;
|
||||
auto operator=(obj&& o) noexcept -> obj&;
|
||||
|
||||
[[nodiscard]] auto get() const -> size_t const&;
|
||||
auto get() -> size_t&;
|
||||
|
||||
void swap(obj& other);
|
||||
[[nodiscard]] auto get_for_hash() const -> size_t;
|
||||
|
||||
private:
|
||||
size_t m_data;
|
||||
counter* m_counts;
|
||||
};
|
||||
|
||||
counter();
|
||||
~counter();
|
||||
|
||||
void check_all_done() const;
|
||||
|
||||
[[nodiscard]] auto ctor() const -> size_t {
|
||||
return m_data.m_ctor;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto default_ctor() const -> size_t {
|
||||
return m_data.m_default_ctor;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto copy_ctor() const -> size_t {
|
||||
return m_data.m_copy_ctor;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto dtor() const -> size_t {
|
||||
return m_data.m_dtor;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto equals() const -> size_t {
|
||||
return m_data.m_equals;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto less() const -> size_t {
|
||||
return m_data.m_less;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto assign() const -> size_t {
|
||||
return m_data.m_assign;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto swaps() const -> size_t {
|
||||
return m_data.m_swaps;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get() const -> size_t {
|
||||
return m_data.m_get;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto const_get() const -> size_t {
|
||||
return m_data.m_const_get;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto hash() const -> size_t {
|
||||
return m_data.m_hash;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto move_ctor() const -> size_t {
|
||||
return m_data.m_move_ctor;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto move_assign() const -> size_t {
|
||||
return m_data.m_move_assign;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto data() const -> data_t const& {
|
||||
return m_data;
|
||||
}
|
||||
|
||||
friend auto operator<<(std::ostream& os, counter const& c) -> std::ostream&;
|
||||
|
||||
void operator()(std::string_view title);
|
||||
|
||||
[[nodiscard]] auto total() const -> size_t;
|
||||
|
||||
static size_t static_default_ctor; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
static size_t static_dtor; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
private:
|
||||
data_t m_data{};
|
||||
|
||||
std::string m_records =
|
||||
"\n ctor defctor cpyctor dtor assign swaps get cnstget hash equals less ctormv assignmv| total |\n";
|
||||
};
|
||||
|
||||
// Throws an exception, this overload should never be taken!
|
||||
inline auto operator new(size_t s, counter::obj* ptr) -> void*;
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct hash<counter::obj> {
|
||||
[[nodiscard]] auto operator()(const counter::obj& c) const noexcept -> size_t {
|
||||
return hash<size_t>{}(c.get_for_hash());
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<counter::obj> {
|
||||
static constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.end();
|
||||
}
|
||||
static auto format(counter::obj const& o, fmt::format_context& ctx) -> decltype(ctx.out()) {
|
||||
return fmt::format_to(ctx.out(), "{}", o.get());
|
||||
}
|
||||
};
|
138
third_party/unordered_dense/test/app/counting_allocator.h
vendored
Normal file
138
third_party/unordered_dense/test/app/counting_allocator.h
vendored
Normal file
@ -0,0 +1,138 @@
|
||||
#pragma once
|
||||
|
||||
#include <fmt/ostream.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
// Source: https://github.com/bitcoin/bitcoin/blob/master/src/memusage.h#L41-L61
|
||||
static inline auto malloc_usage(size_t alloc) -> size_t {
|
||||
static_assert(sizeof(void*) == 8 || sizeof(void*) == 4);
|
||||
|
||||
// Measured on libc6 2.19 on Linux.
|
||||
if constexpr (sizeof(void*) == 8U) {
|
||||
return ((alloc + 31U) >> 4U) << 4U;
|
||||
} else {
|
||||
return ((alloc + 15U) >> 3U) << 3U;
|
||||
}
|
||||
}
|
||||
|
||||
class counts_for_allocator {
|
||||
struct measurement_internal {
|
||||
std::chrono::steady_clock::time_point m_tp{};
|
||||
size_t m_diff{};
|
||||
};
|
||||
|
||||
struct measurement {
|
||||
std::chrono::steady_clock::duration m_duration{};
|
||||
size_t m_num_bytes_allocated{};
|
||||
};
|
||||
|
||||
std::vector<measurement_internal> m_measurements{};
|
||||
std::chrono::steady_clock::time_point m_start = std::chrono::steady_clock::now();
|
||||
|
||||
template <typename Op>
|
||||
void each_measurement(Op op) const {
|
||||
auto total_bytes = size_t();
|
||||
auto const start_time = m_start;
|
||||
for (auto const& m : m_measurements) {
|
||||
bool is_add = true;
|
||||
size_t bytes = m.m_diff;
|
||||
if (bytes > (0U - bytes)) {
|
||||
// negative number
|
||||
is_add = false;
|
||||
bytes = 0U - bytes;
|
||||
}
|
||||
|
||||
if (is_add) {
|
||||
total_bytes += malloc_usage(bytes);
|
||||
} else {
|
||||
total_bytes -= malloc_usage(bytes);
|
||||
}
|
||||
op(measurement{m.m_tp - start_time, total_bytes});
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void add(size_t count) {
|
||||
m_measurements.emplace_back(measurement_internal{std::chrono::steady_clock::now(), count});
|
||||
}
|
||||
|
||||
void sub(size_t count) {
|
||||
// overflow, but it's ok
|
||||
m_measurements.emplace_back(measurement_internal{std::chrono::steady_clock::now(), 0U - count});
|
||||
}
|
||||
|
||||
void save(std::filesystem::path const& filename) const {
|
||||
auto fout = std::ofstream(filename);
|
||||
each_measurement([&](measurement m) {
|
||||
fmt::print(fout, "{}; {}\n", std::chrono::duration<double>(m.m_duration).count(), m.m_num_bytes_allocated);
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] auto size() const -> size_t {
|
||||
return m_measurements.size();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
m_measurements.clear();
|
||||
m_start = std::chrono::steady_clock::now();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Forwards all allocations/deallocations to the counts
|
||||
*/
|
||||
template <class T>
|
||||
class counting_allocator {
|
||||
counts_for_allocator* m_counts;
|
||||
|
||||
template <typename U>
|
||||
friend class counting_allocator;
|
||||
|
||||
public:
|
||||
using value_type = T;
|
||||
|
||||
/**
|
||||
* Not explicit so we can easily construct it with the correct resource
|
||||
*/
|
||||
counting_allocator(counts_for_allocator* counts) noexcept // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
|
||||
: m_counts(counts) {}
|
||||
|
||||
/**
|
||||
* Not explicit so we can easily construct it with the correct resource
|
||||
*/
|
||||
template <class U>
|
||||
// NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions)
|
||||
counting_allocator(counting_allocator<U> const& other) noexcept
|
||||
: m_counts(other.m_counts) {}
|
||||
|
||||
counting_allocator(counting_allocator const& other) noexcept = default;
|
||||
counting_allocator(counting_allocator&& other) noexcept = default;
|
||||
auto operator=(counting_allocator const& other) noexcept -> counting_allocator& = default;
|
||||
auto operator=(counting_allocator&& other) noexcept -> counting_allocator& = default;
|
||||
~counting_allocator() = default;
|
||||
|
||||
auto allocate(size_t n) -> T* {
|
||||
m_counts->add(sizeof(T) * n);
|
||||
return std::allocator<T>{}.allocate(n);
|
||||
}
|
||||
|
||||
void deallocate(T* p, size_t n) noexcept {
|
||||
m_counts->sub(sizeof(T) * n);
|
||||
std::allocator<T>{}.deallocate(p, n);
|
||||
}
|
||||
|
||||
template <class U>
|
||||
friend auto operator==(counting_allocator const& a, counting_allocator<U> const& b) noexcept -> bool {
|
||||
return a.m_counts == b.m_counts;
|
||||
}
|
||||
|
||||
template <class U>
|
||||
friend auto operator!=(counting_allocator const& a, counting_allocator<U> const& b) noexcept -> bool {
|
||||
return a.m_counts != b.m_counts;
|
||||
}
|
||||
};
|
10
third_party/unordered_dense/test/app/doctest.cpp
vendored
Normal file
10
third_party/unordered_dense/test/app/doctest.cpp
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
|
||||
#include <doctest.h>
|
||||
|
||||
namespace doctest {
|
||||
|
||||
[[nodiscard]] auto current_test_name() -> char const* {
|
||||
return doctest::detail::g_cs->currentTest->m_name;
|
||||
}
|
||||
|
||||
} // namespace doctest
|
120
third_party/unordered_dense/test/app/doctest.h
vendored
Normal file
120
third_party/unordered_dense/test/app/doctest.h
vendored
Normal file
@ -0,0 +1,120 @@
|
||||
#pragma once
|
||||
|
||||
#include <ankerl/unordered_dense.h>
|
||||
#include <app/counter.h>
|
||||
|
||||
#include <doctest.h>
|
||||
|
||||
#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
|
||||
# undef DOCTEST_REQUIRE
|
||||
# define DOCTEST_REQUIRE(...) \
|
||||
do { \
|
||||
if (!(__VA_ARGS__)) { \
|
||||
std::abort(); \
|
||||
} \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
namespace doctest {
|
||||
|
||||
[[nodiscard]] auto current_test_name() -> char const*;
|
||||
|
||||
} // namespace doctest
|
||||
|
||||
#include <deque>
|
||||
#include <sstream>
|
||||
|
||||
template <class Key,
|
||||
class T,
|
||||
class Hash = ankerl::unordered_dense::hash<Key>,
|
||||
class KeyEqual = std::equal_to<Key>,
|
||||
class AllocatorOrContainer = std::deque<std::pair<Key, T>>,
|
||||
class Bucket = ankerl::unordered_dense::bucket_type::standard>
|
||||
class deque_map : public ankerl::unordered_dense::detail::table<Key, T, Hash, KeyEqual, AllocatorOrContainer, Bucket, false> {
|
||||
using base_t = ankerl::unordered_dense::detail::table<Key, T, Hash, KeyEqual, AllocatorOrContainer, Bucket, false>;
|
||||
using base_t::base_t;
|
||||
};
|
||||
|
||||
template <class Key,
|
||||
class Hash = ankerl::unordered_dense::hash<Key>,
|
||||
class KeyEqual = std::equal_to<Key>,
|
||||
class AllocatorOrContainer = std::deque<Key>,
|
||||
class Bucket = ankerl::unordered_dense::bucket_type::standard>
|
||||
class deque_set
|
||||
: public ankerl::unordered_dense::detail::table<Key, void, Hash, KeyEqual, AllocatorOrContainer, Bucket, false> {
|
||||
using base_t = ankerl::unordered_dense::detail::table<Key, void, Hash, KeyEqual, AllocatorOrContainer, Bucket, false>;
|
||||
using base_t::base_t;
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
#define TEST_CASE_MAP(name, ...) \
|
||||
TEST_CASE_TEMPLATE(name, \
|
||||
map_t, \
|
||||
ankerl::unordered_dense::map<__VA_ARGS__>, \
|
||||
ankerl::unordered_dense::segmented_map<__VA_ARGS__>, \
|
||||
deque_map<__VA_ARGS__>)
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
#define TEST_CASE_SET(name, ...) \
|
||||
TEST_CASE_TEMPLATE(name, \
|
||||
set_t, \
|
||||
ankerl::unordered_dense::set<__VA_ARGS__>, \
|
||||
ankerl::unordered_dense::segmented_set<__VA_ARGS__>, \
|
||||
deque_set<__VA_ARGS__>)
|
||||
|
||||
#define TYPE_TO_STRING_MAP(...) /*NOLINT*/ \
|
||||
TYPE_TO_STRING(ankerl::unordered_dense::map<__VA_ARGS__>); /*NOLINT*/ \
|
||||
TYPE_TO_STRING(ankerl::unordered_dense::segmented_map<__VA_ARGS__>); /*NOLINT*/ \
|
||||
TYPE_TO_STRING(deque_map<__VA_ARGS__>) /*NOLINT*/
|
||||
|
||||
#define TYPE_TO_STRING_SET(...) /*NOLINT*/ \
|
||||
TYPE_TO_STRING(ankerl::unordered_dense::set<__VA_ARGS__>); /*NOLINT*/ \
|
||||
TYPE_TO_STRING(ankerl::unordered_dense::segmented_set<__VA_ARGS__>); /*NOLINT*/ \
|
||||
TYPE_TO_STRING(deque_set<__VA_ARGS__>) /*NOLINT*/
|
||||
|
||||
#if defined(ANKERL_UNORDERED_DENSE_PMR)
|
||||
|
||||
// unfortunately there's no std::experimental::pmr::deque on macos, so just skip this here
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
# define TEST_CASE_PMR_MAP(name, ...) \
|
||||
TEST_CASE_TEMPLATE(name, \
|
||||
map_t, \
|
||||
ankerl::unordered_dense::pmr::map<__VA_ARGS__>, \
|
||||
ankerl::unordered_dense::pmr::segmented_map<__VA_ARGS__>)
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
# define TEST_CASE_PMR_SET(name, ...) \
|
||||
TEST_CASE_TEMPLATE(name, \
|
||||
set_t, \
|
||||
ankerl::unordered_dense::pmr::set<__VA_ARGS__>, \
|
||||
ankerl::unordered_dense::pmr::segmented_set<__VA_ARGS__>)
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
# define TYPE_TO_STRING_PMR_MAP(...) \
|
||||
TYPE_TO_STRING(ankerl::unordered_dense::pmr::map<__VA_ARGS__>); /*NOLINT*/ \
|
||||
TYPE_TO_STRING(ankerl::unordered_dense::pmr::segmented_map<__VA_ARGS__>) /*NOLINT*/
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
# define TYPE_TO_STRING_PMR_SET(...) \
|
||||
TYPE_TO_STRING(ankerl::unordered_dense::pmr::set<__VA_ARGS__>); /*NOLINT*/ \
|
||||
TYPE_TO_STRING(ankerl::unordered_dense::pmr::segmented_set<__VA_ARGS__>) /*NOLINT*/
|
||||
|
||||
#endif
|
||||
|
||||
// adds the most important type to strings here
|
||||
|
||||
TYPE_TO_STRING_MAP(counter::obj, counter::obj);
|
||||
TYPE_TO_STRING_MAP(int, char const*);
|
||||
TYPE_TO_STRING_MAP(int, int);
|
||||
TYPE_TO_STRING_MAP(int, std::string);
|
||||
TYPE_TO_STRING_MAP(std::string, size_t);
|
||||
TYPE_TO_STRING_MAP(std::string, std::string);
|
||||
TYPE_TO_STRING_MAP(uint64_t, uint64_t);
|
||||
TYPE_TO_STRING_MAP(uint32_t, int);
|
||||
TYPE_TO_STRING_MAP(uint64_t, int);
|
||||
TYPE_TO_STRING_SET(counter::obj);
|
||||
TYPE_TO_STRING_SET(int);
|
||||
TYPE_TO_STRING_SET(std::string);
|
||||
TYPE_TO_STRING_SET(uint32_t);
|
||||
TYPE_TO_STRING_SET(uint64_t);
|
24
third_party/unordered_dense/test/app/geomean.h
vendored
Normal file
24
third_party/unordered_dense/test/app/geomean.h
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
|
||||
template <typename It, typename Op>
|
||||
[[nodiscard]] auto geomean(It begin, It end, Op op) -> double {
|
||||
double sum = 0.0;
|
||||
size_t count = 0;
|
||||
while (begin != end) {
|
||||
sum += std::log(op(*begin));
|
||||
++begin;
|
||||
++count;
|
||||
}
|
||||
|
||||
sum /= static_cast<double>(count);
|
||||
return std::exp(sum);
|
||||
}
|
||||
|
||||
template <typename Container, typename Op>
|
||||
[[nodiscard]] auto geomean(Container&& c, Op op) -> double {
|
||||
return geomean(std::begin(c), std::end(c), std::move(op));
|
||||
}
|
31
third_party/unordered_dense/test/app/name_of_type.h
vendored
Normal file
31
third_party/unordered_dense/test/app/name_of_type.h
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename T>
|
||||
[[nodiscard]] constexpr auto name_of_type_raw() -> std::string_view {
|
||||
#if defined(_MSC_VER)
|
||||
return __FUNCSIG__; // NOLINT
|
||||
#else
|
||||
return __PRETTY_FUNCTION__; // NOLINT
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename T>
|
||||
[[nodiscard]] constexpr auto name_of_type() -> std::string_view {
|
||||
using namespace std::literals;
|
||||
|
||||
// idea from https://github.com/TheLartians/StaticTypeInfo/blob/master/include/static_type_info/type_name.h
|
||||
auto for_double = detail::name_of_type_raw<double>();
|
||||
auto n_before = for_double.find("double"sv);
|
||||
auto n_after = for_double.size() - (n_before + "double"sv.size());
|
||||
|
||||
auto str = detail::name_of_type_raw<T>();
|
||||
str.remove_prefix(n_before);
|
||||
str.remove_suffix(n_after);
|
||||
return str;
|
||||
}
|
2
third_party/unordered_dense/test/app/nanobench.cpp
vendored
Normal file
2
third_party/unordered_dense/test/app/nanobench.cpp
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
#define ANKERL_NANOBENCH_IMPLEMENT
|
||||
#include <third-party/nanobench.h>
|
19
third_party/unordered_dense/test/app/print.h
vendored
Normal file
19
third_party/unordered_dense/test/app/print.h
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
namespace test {
|
||||
|
||||
template <typename... Args>
|
||||
constexpr void print(fmt::format_string<Args...> f, Args&&... args) {
|
||||
fmt::print(f, std::forward<Args>(args)...);
|
||||
(void)std::fflush(stdout);
|
||||
}
|
||||
|
||||
#ifndef ENABLE_LOG_LINE
|
||||
# define LOG_LINE(what)
|
||||
#else
|
||||
# define LOG_LINE(what) ::test::print("{}({:3}) {}\n", __FILE__, __LINE__, what)
|
||||
#endif
|
||||
|
||||
} // namespace test
|
41
third_party/unordered_dense/test/app/stacktrace.cpp
vendored
Normal file
41
third_party/unordered_dense/test/app/stacktrace.cpp
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
#if __GNUC__
|
||||
|
||||
# include <fmt/format.h>
|
||||
|
||||
# include <array>
|
||||
# include <csignal>
|
||||
# include <cstdio>
|
||||
# include <cstdlib>
|
||||
# include <execinfo.h>
|
||||
# include <unistd.h>
|
||||
|
||||
namespace {
|
||||
|
||||
void handle(int sig) {
|
||||
fmt::print(stderr, "Error: signal {}:\n", sig);
|
||||
auto ary = std::array<void*, 50>();
|
||||
|
||||
// get void*'s for all entries on the stack
|
||||
auto size = backtrace(ary.data(), static_cast<int>(ary.size()));
|
||||
|
||||
// print out all the frames to stderr
|
||||
fmt::print(stderr, "Error: signal {}. See stacktrace with\n", sig);
|
||||
fmt::print(stderr, "addr2line -Cafpie ./test/udm-test");
|
||||
for (size_t i = 0; i < static_cast<size_t>(size); ++i) {
|
||||
fmt::print(stderr, " {}", ary[i]);
|
||||
}
|
||||
exit(1); // NOLINT(concurrency-mt-unsafe)
|
||||
}
|
||||
|
||||
class handler {
|
||||
public:
|
||||
handler() {
|
||||
(void)signal(SIGTERM, handle);
|
||||
}
|
||||
};
|
||||
|
||||
auto const global_h = handler();
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif
|
17
third_party/unordered_dense/test/app/ui/periodic.cpp
vendored
Normal file
17
third_party/unordered_dense/test/app/ui/periodic.cpp
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
#include "periodic.h"
|
||||
|
||||
namespace ui {
|
||||
|
||||
periodic::periodic(std::chrono::steady_clock::duration interval)
|
||||
: m_interval(interval) {}
|
||||
|
||||
periodic::operator bool() {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
if (now < m_next) {
|
||||
return false;
|
||||
}
|
||||
m_next = now + m_interval;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ui
|
16
third_party/unordered_dense/test/app/ui/periodic.h
vendored
Normal file
16
third_party/unordered_dense/test/app/ui/periodic.h
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace ui {
|
||||
|
||||
class periodic {
|
||||
std::chrono::steady_clock::time_point m_next{};
|
||||
std::chrono::steady_clock::duration m_interval{};
|
||||
|
||||
public:
|
||||
explicit periodic(std::chrono::steady_clock::duration interval);
|
||||
explicit operator bool();
|
||||
};
|
||||
|
||||
} // namespace ui
|
53
third_party/unordered_dense/test/app/ui/progress_bar.cpp
vendored
Normal file
53
third_party/unordered_dense/test/app/ui/progress_bar.cpp
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
#include "progress_bar.h"
|
||||
|
||||
#include <algorithm> // for min
|
||||
|
||||
namespace {
|
||||
|
||||
auto split(std::string_view symbols, char sep) -> std::vector<std::string> {
|
||||
auto s = std::vector<std::string>();
|
||||
while (true) {
|
||||
auto idx = symbols.find(sep);
|
||||
if (idx == std::string_view::npos) {
|
||||
break;
|
||||
}
|
||||
s.emplace_back(symbols.substr(0, idx));
|
||||
symbols.remove_prefix(idx + 1);
|
||||
}
|
||||
s.emplace_back(symbols);
|
||||
return s;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace ui {
|
||||
|
||||
progress_bar::progress_bar(size_t width, size_t total, std::string_view symbols)
|
||||
: m_width(width)
|
||||
, m_total(total)
|
||||
, m_symbols(split(symbols, ' ')) {}
|
||||
|
||||
auto progress_bar::operator()(size_t current) const -> std::string {
|
||||
auto const total_states = m_width * m_symbols.size() + 1;
|
||||
auto const current_state = total_states * current / m_total;
|
||||
std::string str;
|
||||
auto num_full = std::min(m_width, current_state / m_symbols.size());
|
||||
for (size_t i = 0; i < num_full; ++i) {
|
||||
str += m_symbols.back();
|
||||
}
|
||||
|
||||
if (num_full < m_width) {
|
||||
auto remaining = current_state - num_full * m_symbols.size();
|
||||
if (0U != remaining) {
|
||||
str += m_symbols[remaining - 1];
|
||||
}
|
||||
|
||||
auto num_fillers = m_width - num_full - (0U == remaining ? 0 : 1);
|
||||
for (size_t i = 0; i < num_fillers; ++i) {
|
||||
str.push_back(' ');
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
} // namespace ui
|
21
third_party/unordered_dense/test/app/ui/progress_bar.h
vendored
Normal file
21
third_party/unordered_dense/test/app/ui/progress_bar.h
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
#include <string> // for string, basic_string
|
||||
#include <string_view> // for string_view
|
||||
#include <vector> // for vector
|
||||
|
||||
namespace ui {
|
||||
|
||||
class progress_bar {
|
||||
size_t m_width;
|
||||
size_t m_total;
|
||||
std::vector<std::string> m_symbols;
|
||||
|
||||
public:
|
||||
progress_bar(size_t width, size_t total, std::string_view symbols = "⡀ ⡄ ⡆ ⡇ ⡏ ⡟ ⡿ ⣿");
|
||||
|
||||
auto operator()(size_t current) const -> std::string;
|
||||
};
|
||||
|
||||
} // namespace ui
|
3
third_party/unordered_dense/test/app/unordered_dense.cpp
vendored
Normal file
3
third_party/unordered_dense/test/app/unordered_dense.cpp
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
// this cpp file is only for include-what-you-use
|
47
third_party/unordered_dense/test/bench/copy.cpp
vendored
Normal file
47
third_party/unordered_dense/test/bench/copy.cpp
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
#include <ankerl/unordered_dense.h> // for map, operator==
|
||||
|
||||
#include <third-party/nanobench.h> // for Rng, Bench
|
||||
|
||||
#include <app/doctest.h> // for TestCase, skip, TEST_CASE, test_...
|
||||
#include <fmt/core.h> // for format
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
#include <cstdint> // for uint64_t
|
||||
#include <string_view> // for string_view
|
||||
#include <unordered_map> // for unordered_map, operator==
|
||||
|
||||
template <typename Map>
|
||||
void bench(std::string_view name) {
|
||||
auto a = Map();
|
||||
auto rng = ankerl::nanobench::Rng(123);
|
||||
for (size_t i = 0; i < 1000000; ++i) {
|
||||
a.try_emplace(rng(), rng());
|
||||
}
|
||||
|
||||
Map b;
|
||||
ankerl::nanobench::Bench().batch(a.size() * 2).run(fmt::format("copy {}", name), [&] {
|
||||
b = a;
|
||||
a = b;
|
||||
});
|
||||
REQUIRE(a == b);
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
||||
TEST_CASE("bench_copy_rhn" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
bench<robin_hood::unordered_node_map<uint64_t, uint64_t>>("robin_hood::unordered_node_map");
|
||||
}
|
||||
|
||||
TEST_CASE("bench_copy_rhf" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
bench<robin_hood::unordered_flat_map<uint64_t, uint64_t>>("robin_hood::unordered_flat_map");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
TEST_CASE_MAP("bench_copy_udm" * doctest::test_suite("bench") * doctest::skip(), uint64_t, uint64_t) {
|
||||
bench<map_t>("ankerl::unordered_dense::map");
|
||||
}
|
||||
|
||||
TEST_CASE("bench_copy_std" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
bench<std::unordered_map<uint64_t, uint64_t>>("std::unordered_map");
|
||||
}
|
101
third_party/unordered_dense/test/bench/find_random.cpp
vendored
Normal file
101
third_party/unordered_dense/test/bench/find_random.cpp
vendored
Normal file
@ -0,0 +1,101 @@
|
||||
#include <ankerl/unordered_dense.h> // for map
|
||||
|
||||
#include <app/name_of_type.h> // for name_of_type
|
||||
#include <third-party/nanobench.h> // for Rng
|
||||
|
||||
#include <app/doctest.h> // for TestCase, skip, ResultBuilder
|
||||
#include <fmt/core.h> // for format, print
|
||||
|
||||
#include <algorithm> // for fill_n
|
||||
#include <array> // for array
|
||||
#include <chrono> // for duration, operator-, steady_clock
|
||||
#include <cstddef> // for size_t
|
||||
#include <unordered_map> // for unordered_map, operator!=
|
||||
#include <vector> // for vector
|
||||
|
||||
template <typename Map>
|
||||
void bench() {
|
||||
static constexpr size_t num_total = 4;
|
||||
|
||||
auto required_checksum = std::array<size_t, 5>{200000, 25198620, 50197240, 75195862, 100194482};
|
||||
auto total = std::chrono::steady_clock::duration();
|
||||
|
||||
for (size_t num_found = 0; num_found < 5; ++num_found) {
|
||||
auto title = fmt::format("random find {}% success {}", num_found * 100 / num_total, name_of_type<Map>());
|
||||
auto rng = ankerl::nanobench::Rng(123);
|
||||
|
||||
size_t checksum = 0;
|
||||
|
||||
using ary_t = std::array<bool, num_total>;
|
||||
auto insert_random = ary_t();
|
||||
insert_random.fill(true);
|
||||
for (typename ary_t::size_type i = 0; i < num_found; ++i) {
|
||||
insert_random[i] = false;
|
||||
}
|
||||
|
||||
auto another_unrelated_rng = ankerl::nanobench::Rng(987654321);
|
||||
auto const another_unrelated_rng_initial_state = another_unrelated_rng.state();
|
||||
auto find_rng = ankerl::nanobench::Rng(another_unrelated_rng_initial_state);
|
||||
|
||||
{
|
||||
static constexpr size_t num_inserts = 200000;
|
||||
static constexpr size_t num_finds_per_insert = 500;
|
||||
static constexpr size_t num_finds_per_iter = num_finds_per_insert * num_total;
|
||||
|
||||
Map map;
|
||||
size_t i = 0;
|
||||
size_t find_count = 0;
|
||||
auto before = std::chrono::steady_clock::now();
|
||||
do {
|
||||
// insert numTotal entries: some random, some sequential.
|
||||
rng.shuffle(insert_random);
|
||||
for (bool const is_random_to_insert : insert_random) {
|
||||
auto val = another_unrelated_rng();
|
||||
if (is_random_to_insert) {
|
||||
map[static_cast<size_t>(rng())] = static_cast<size_t>(1);
|
||||
} else {
|
||||
map[static_cast<size_t>(val)] = static_cast<size_t>(1);
|
||||
}
|
||||
++i;
|
||||
}
|
||||
|
||||
// the actual benchmark code which should be as fast as possible
|
||||
for (size_t j = 0; j < num_finds_per_iter; ++j) {
|
||||
if (++find_count > i) {
|
||||
find_count = 0;
|
||||
find_rng = ankerl::nanobench::Rng(another_unrelated_rng_initial_state);
|
||||
}
|
||||
auto it = map.find(static_cast<size_t>(find_rng()));
|
||||
if (it != map.end()) {
|
||||
checksum += it->second;
|
||||
}
|
||||
}
|
||||
} while (i < num_inserts);
|
||||
checksum += map.size();
|
||||
auto after = std::chrono::steady_clock::now();
|
||||
total += after - before;
|
||||
fmt::print("{}s {}\n", std::chrono::duration<double>(after - before).count(), title);
|
||||
}
|
||||
REQUIRE(checksum == required_checksum[num_found]);
|
||||
}
|
||||
fmt::print("{}s total\n", std::chrono::duration<double>(total).count());
|
||||
}
|
||||
|
||||
// 26.81
|
||||
TEST_CASE("bench_find_random_uo" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
bench<std::unordered_map<size_t, size_t>>();
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
||||
// 10.55
|
||||
TEST_CASE("bench_find_random_rh" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
bench<robin_hood::unordered_flat_map<size_t, size_t>>();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// 8.87
|
||||
TEST_CASE_MAP("bench_find_random_udm" * doctest::test_suite("bench") * doctest::skip(), size_t, size_t) {
|
||||
bench<map_t>();
|
||||
}
|
174
third_party/unordered_dense/test/bench/game_of_life.cpp
vendored
Normal file
174
third_party/unordered_dense/test/bench/game_of_life.cpp
vendored
Normal file
@ -0,0 +1,174 @@
|
||||
#include <ankerl/unordered_dense.h> // for map
|
||||
|
||||
#include <app/counting_allocator.h>
|
||||
|
||||
#include <app/doctest.h> // for TestCase, skip, ResultBuilder
|
||||
#include <fmt/core.h> // for format, print
|
||||
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#if __has_include("boost/unordered/unordered_flat_map.hpp")
|
||||
# if defined(__clang__)
|
||||
# pragma clang diagnostic ignored "-Wold-style-cast"
|
||||
# endif
|
||||
# include "boost/unordered/unordered_flat_map.hpp"
|
||||
# define HAS_BOOST_UNORDERED_FLAT_MAP() 1 // NOLINT(cppcoreguidelines-macro-usage)
|
||||
#else
|
||||
# define HAS_BOOST_UNORDERED_FLAT_MAP() 0 // NOLINT(cppcoreguidelines-macro-usage)
|
||||
#endif
|
||||
|
||||
#if 0 && __has_include("absl/container/flat_hash_map.h")
|
||||
# if defined(__clang__)
|
||||
# pragma clang diagnostic ignored "-Wdeprecated-builtins"
|
||||
# pragma clang diagnostic ignored "-Wsign-conversion"
|
||||
# endif
|
||||
# include <absl/container/flat_hash_map.h>
|
||||
# define HAS_ABSL() 1 // NOLINT(cppcoreguidelines-macro-usage)
|
||||
#else
|
||||
# define HAS_ABSL() 0 // NOLINT(cppcoreguidelines-macro-usage)
|
||||
#endif
|
||||
|
||||
class vec2 {
|
||||
uint32_t m_xy;
|
||||
|
||||
public:
|
||||
constexpr vec2(uint16_t x, uint16_t y)
|
||||
: m_xy{static_cast<uint32_t>(x) << 16U | y} {}
|
||||
|
||||
constexpr explicit vec2(uint32_t xy)
|
||||
: m_xy(xy) {}
|
||||
|
||||
[[nodiscard]] constexpr auto pack() const -> uint32_t {
|
||||
return m_xy;
|
||||
};
|
||||
|
||||
[[nodiscard]] constexpr auto add_x(uint16_t x) const -> vec2 {
|
||||
return vec2{m_xy + (static_cast<uint32_t>(x) << 16U)};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto add_y(uint16_t y) const -> vec2 {
|
||||
return vec2{(m_xy & 0xffff0000) | ((m_xy + y) & 0xffff)};
|
||||
}
|
||||
|
||||
template <typename Op>
|
||||
constexpr void for_each_surrounding(Op&& op) const {
|
||||
uint32_t v = m_xy;
|
||||
|
||||
uint32_t upper = (v & 0xffff0000U) - 0x10000;
|
||||
uint32_t l1 = (v - 1) & 0xffffU;
|
||||
uint32_t l2 = v & 0xffffU;
|
||||
uint32_t l3 = (v + 1) & 0xffffU;
|
||||
|
||||
op(upper | l1);
|
||||
op(upper | l2);
|
||||
op(upper | l3);
|
||||
|
||||
upper += 0x10000;
|
||||
op(upper | l1);
|
||||
// op(upper | l2);
|
||||
op(upper | l3);
|
||||
|
||||
upper += 0x10000;
|
||||
op(upper | l1);
|
||||
op(upper | l2);
|
||||
op(upper | l3);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Map>
|
||||
auto game_of_life(std::string_view name, size_t nsteps, Map map1, std::vector<vec2> state) -> size_t {
|
||||
auto before = std::chrono::steady_clock::now();
|
||||
map1.clear();
|
||||
auto map2 = map1; // copy the empty map so we get the allocator
|
||||
|
||||
for (auto& v : state) {
|
||||
v = v.add_x(UINT16_MAX / 2).add_y(UINT16_MAX / 2);
|
||||
map1[v.pack()] = true;
|
||||
v.for_each_surrounding([&](uint32_t xy) {
|
||||
map1.emplace(xy, false);
|
||||
});
|
||||
}
|
||||
|
||||
auto* m1 = &map1;
|
||||
auto* m2 = &map2;
|
||||
for (size_t i = 0; i < nsteps; ++i) {
|
||||
for (auto const& kv : *m1) {
|
||||
auto const& pos = kv.first;
|
||||
auto alive = kv.second;
|
||||
int neighbors = 0;
|
||||
vec2{pos}.for_each_surrounding([&](uint32_t xy) {
|
||||
if (auto x = m1->find(xy); x != m1->end()) {
|
||||
neighbors += x->second;
|
||||
}
|
||||
});
|
||||
if ((alive && (neighbors == 2 || neighbors == 3)) || (!alive && neighbors == 3)) {
|
||||
(*m2)[pos] = true;
|
||||
vec2{pos}.for_each_surrounding([&](uint32_t xy) {
|
||||
m2->emplace(xy, false);
|
||||
});
|
||||
}
|
||||
}
|
||||
m1->clear();
|
||||
std::swap(m1, m2);
|
||||
}
|
||||
|
||||
size_t final_population = 0;
|
||||
for (auto const& kv : *m1) {
|
||||
final_population += kv.second;
|
||||
}
|
||||
auto after = std::chrono::steady_clock::now();
|
||||
fmt::print("{}s {}\n", std::chrono::duration<double>(after - before).count(), name);
|
||||
return final_population;
|
||||
}
|
||||
|
||||
TEST_CASE("gameoflife_gotts-dots" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
// https://conwaylife.com/wiki/Gotts_dots
|
||||
auto state = std::vector<vec2>{
|
||||
{0, 0}, {0, 1}, {0, 2}, // 1
|
||||
{4, 11}, {5, 12}, {6, 13}, {7, 12}, {8, 11}, // 2
|
||||
{9, 13}, {9, 14}, {9, 15}, // 3
|
||||
{185, 24}, {186, 25}, {186, 26}, {186, 27}, {185, 27}, {184, 27}, {183, 27}, {182, 26}, // 4
|
||||
{179, 28}, {180, 29}, {181, 29}, {179, 30}, // 5
|
||||
{182, 32}, {183, 31}, {184, 31}, {185, 31}, {186, 31}, {186, 32}, {186, 33}, {185, 34}, // 6
|
||||
{175, 35}, {176, 36}, {170, 37}, {176, 37}, {171, 38}, {172, 38}, {173, 38}, {174, 38}, {175, 38}, {176, 38}, // 7
|
||||
};
|
||||
|
||||
// size_t nsteps = 200;
|
||||
// size_t nsteps = 2000;
|
||||
size_t nsteps = 4000;
|
||||
// size_t nsteps = 10000;
|
||||
|
||||
auto pop = size_t();
|
||||
{
|
||||
using map_t = ankerl::unordered_dense::map<uint32_t, bool>;
|
||||
pop = game_of_life("ankerl::unordered_dense::map", nsteps, map_t(), state);
|
||||
}
|
||||
{
|
||||
using map_t = ankerl::unordered_dense::segmented_map<uint32_t, bool>;
|
||||
auto new_pop = game_of_life("ankerl::unordered_dense::segmented_map", nsteps, map_t(), state);
|
||||
REQUIRE(pop == new_pop);
|
||||
}
|
||||
|
||||
#if HAS_BOOST_UNORDERED_FLAT_MAP
|
||||
{
|
||||
using map_t = boost::unordered_flat_map<uint32_t, bool>;
|
||||
auto new_pop = game_of_life("boost::unordered_flat_map", nsteps, map_t(), state);
|
||||
REQUIRE(pop == new_pop);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if HAS_ABSL()
|
||||
{
|
||||
using map_t = absl::flat_hash_map<uint32_t, bool>;
|
||||
auto new_pop = game_of_life("absl::flat_hash_map", nsteps, map_t(), state);
|
||||
REQUIRE(pop == new_pop);
|
||||
}
|
||||
#endif
|
||||
|
||||
{
|
||||
using map_t = std::unordered_map<uint32_t, bool>;
|
||||
auto new_pop = game_of_life("std::unordered_map", nsteps, map_t(), state);
|
||||
REQUIRE(pop == new_pop);
|
||||
}
|
||||
}
|
344
third_party/unordered_dense/test/bench/quick_overall_map.cpp
vendored
Normal file
344
third_party/unordered_dense/test/bench/quick_overall_map.cpp
vendored
Normal file
@ -0,0 +1,344 @@
|
||||
#include <ankerl/unordered_dense.h> // for map, hash
|
||||
|
||||
#include <app/geomean.h> // for geomean
|
||||
#include <third-party/nanobench.h> // for Rng, doNotOptimizeAway, Bench
|
||||
|
||||
#include <doctest.h> // for TestCase, skip, ResultBuilder
|
||||
#include <fmt/core.h> // for print, format
|
||||
|
||||
#include <chrono> // for duration, operator-, high_resolu...
|
||||
#include <cstdint> // for uint64_t
|
||||
#include <cstring> // for size_t, memcpy
|
||||
#include <deque> // for deque
|
||||
#include <string> // for string, basic_string, operator==
|
||||
#include <string_view> // for string_view, literals
|
||||
#include <unordered_map> // for unordered_map, operator!=
|
||||
#include <vector> // for vector
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename K>
|
||||
inline auto init_key() -> K {
|
||||
return {};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void randomize_key(ankerl::nanobench::Rng* rng, int n, T* key) {
|
||||
// we limit ourselves to 32bit n
|
||||
auto limited = (((*rng)() >> 32U) * static_cast<uint64_t>(n)) >> 32U;
|
||||
*key = static_cast<T>(limited);
|
||||
}
|
||||
|
||||
template <>
|
||||
[[nodiscard]] inline auto init_key<std::string>() -> std::string {
|
||||
std::string str;
|
||||
str.resize(200);
|
||||
return str;
|
||||
}
|
||||
|
||||
inline void randomize_key(ankerl::nanobench::Rng* rng, int n, std::string* key) {
|
||||
uint64_t k{};
|
||||
randomize_key(rng, n, &k);
|
||||
std::memcpy(key->data(), &k, sizeof(k));
|
||||
}
|
||||
|
||||
// Random insert & erase
|
||||
template <typename Map>
|
||||
void bench_random_insert_erase(ankerl::nanobench::Bench* bench, std::string_view name) {
|
||||
bench->run(fmt::format("{} random insert erase", name), [&] {
|
||||
ankerl::nanobench::Rng rng(123);
|
||||
size_t verifier{};
|
||||
Map map;
|
||||
auto key = init_key<typename Map::key_type>();
|
||||
for (int n = 1; n < 20000; ++n) {
|
||||
for (int i = 0; i < 200; ++i) {
|
||||
randomize_key(&rng, n, &key);
|
||||
map[key];
|
||||
randomize_key(&rng, n, &key);
|
||||
verifier += map.erase(key);
|
||||
}
|
||||
}
|
||||
CHECK(verifier == 1994641U);
|
||||
CHECK(map.size() == 9987U);
|
||||
});
|
||||
}
|
||||
|
||||
// iterate
|
||||
template <typename Map>
|
||||
void bench_iterate(ankerl::nanobench::Bench* bench, std::string_view name) {
|
||||
size_t const num_elements = 5000;
|
||||
|
||||
auto key = init_key<typename Map::key_type>();
|
||||
|
||||
// insert
|
||||
bench->run(fmt::format("{} iterate while adding then removing", name), [&] {
|
||||
ankerl::nanobench::Rng rng(555);
|
||||
Map map;
|
||||
size_t result = 0;
|
||||
for (size_t n = 0; n < num_elements; ++n) {
|
||||
randomize_key(&rng, 1000000, &key);
|
||||
map[key] = n;
|
||||
for (auto const& key_val : map) {
|
||||
result += key_val.second;
|
||||
}
|
||||
}
|
||||
|
||||
rng = ankerl::nanobench::Rng(555);
|
||||
do {
|
||||
randomize_key(&rng, 1000000, &key);
|
||||
map.erase(key);
|
||||
for (auto const& key_val : map) {
|
||||
result += key_val.second;
|
||||
}
|
||||
} while (!map.empty());
|
||||
|
||||
CHECK(result == 62282755409U);
|
||||
});
|
||||
}
|
||||
|
||||
// 111.903 222
|
||||
// 112.023 123123
|
||||
template <typename Map>
|
||||
void bench_random_find(ankerl::nanobench::Bench* bench, std::string_view name) {
|
||||
|
||||
bench->run(fmt::format("{} 50% probability to find", name), [&] {
|
||||
uint64_t const seed = 123123;
|
||||
ankerl::nanobench::Rng numbers_insert_rng(seed);
|
||||
size_t numbers_insert_rng_calls = 0;
|
||||
|
||||
ankerl::nanobench::Rng numbers_search_rng(seed);
|
||||
size_t numbers_search_rng_calls = 0;
|
||||
|
||||
ankerl::nanobench::Rng insertion_rng(123);
|
||||
|
||||
size_t checksum = 0;
|
||||
size_t found = 0;
|
||||
size_t not_found = 0;
|
||||
|
||||
Map map;
|
||||
auto key = init_key<typename Map::key_type>();
|
||||
for (size_t i = 0; i < 100000; ++i) {
|
||||
randomize_key(&numbers_insert_rng, 1000000, &key);
|
||||
++numbers_insert_rng_calls;
|
||||
|
||||
if (insertion_rng() & 1U) {
|
||||
map[key] = i;
|
||||
}
|
||||
|
||||
// search 100 entries in the map
|
||||
for (size_t search = 0; search < 100; ++search) {
|
||||
randomize_key(&numbers_search_rng, 1000000, &key);
|
||||
++numbers_search_rng_calls;
|
||||
|
||||
auto it = map.find(key);
|
||||
if (it != map.end()) {
|
||||
checksum += it->second;
|
||||
++found;
|
||||
} else {
|
||||
++not_found;
|
||||
}
|
||||
if (numbers_insert_rng_calls == numbers_search_rng_calls) {
|
||||
numbers_search_rng = ankerl::nanobench::Rng(seed);
|
||||
numbers_search_rng_calls = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
ankerl::nanobench::doNotOptimizeAway(checksum);
|
||||
ankerl::nanobench::doNotOptimizeAway(found);
|
||||
ankerl::nanobench::doNotOptimizeAway(not_found);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Map>
|
||||
void bench_all(ankerl::nanobench::Bench* bench, std::string_view name) {
|
||||
bench->title("benchmarking");
|
||||
bench->minEpochTime(100ms);
|
||||
bench_iterate<Map>(bench, name);
|
||||
bench_random_insert_erase<Map>(bench, name);
|
||||
bench_random_find<Map>(bench, name);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto geomean1(ankerl::nanobench::Bench const& bench) -> double {
|
||||
return geomean(bench.results(), [](ankerl::nanobench::Result const& result) {
|
||||
return result.median(ankerl::nanobench::Result::Measure::elapsed);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#if 0
|
||||
|
||||
// A relatively quick benchmark that should get a relatively good single number of how good the map
|
||||
// is. It calculates geometric mean of several benchmarks.
|
||||
TEST_CASE("bench_quick_overall_rhf" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
ankerl::nanobench::Bench bench;
|
||||
bench_all<robin_hood::unordered_flat_map<uint64_t, size_t>>(&bench, "robin_hood::unordered_flat_map<uint64_t, size_t>");
|
||||
bench_all<robin_hood::unordered_flat_map<std::string, size_t>>(&bench,
|
||||
"robin_hood::unordered_flat_map<std::string, size_t>");
|
||||
fmt::print("{} bench_quick_overall_rhf\n", geomean1(bench));
|
||||
|
||||
# ifdef ROBIN_HOOD_COUNT_ENABLED
|
||||
std::cout << robin_hood::counts() << std::endl;
|
||||
# endif
|
||||
}
|
||||
|
||||
TEST_CASE("bench_quick_overall_rhn" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
ankerl::nanobench::Bench bench;
|
||||
bench_all<robin_hood::unordered_node_map<uint64_t, size_t>>(&bench, "robin_hood::unordered_node_map<uint64_t, size_t>");
|
||||
bench_all<robin_hood::unordered_node_map<std::string, size_t>>(&bench,
|
||||
"robin_hood::unordered_node_map<std::string, size_t>");
|
||||
fmt::print("{} bench_quick_overall_rhn\n", geomean1(bench));
|
||||
|
||||
# ifdef ROBIN_HOOD_COUNT_ENABLED
|
||||
std::cout << robin_hood::counts() << std::endl;
|
||||
# endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
using hash_t = ankerl::unordered_dense::hash<uint64_t>;
|
||||
using eq_t = std::equal_to<uint64_t>;
|
||||
using pair_t = std::pair<uint64_t, size_t>;
|
||||
|
||||
using hash_str_t = ankerl::unordered_dense::hash<std::string>;
|
||||
using eq_str_t = std::equal_to<std::string>;
|
||||
using pair_str_t = std::pair<std::string, size_t>;
|
||||
|
||||
TEST_CASE("bench_quick_overall_std" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
ankerl::nanobench::Bench bench;
|
||||
bench_all<std::unordered_map<uint64_t, size_t>>(&bench, "std::unordered_map<uint64_t, size_t>");
|
||||
bench_all<std::unordered_map<std::string, size_t>>(&bench, "std::unordered_map<std::string, size_t>");
|
||||
fmt::print("{} bench_quick_overall_map_std\n", geomean1(bench));
|
||||
}
|
||||
|
||||
TEST_CASE("bench_quick_overall_udm" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
ankerl::nanobench::Bench bench;
|
||||
// bench.minEpochTime(1s);
|
||||
|
||||
using map_t = ankerl::unordered_dense::map<uint64_t, size_t>;
|
||||
bench_all<map_t>(&bench, "ankerl::unordered_dense::map<uint64_t, size_t>");
|
||||
|
||||
using map_str_t = ankerl::unordered_dense::map<std::string, size_t, hash_str_t>;
|
||||
bench_all<map_str_t>(&bench, "ankerl::unordered_dense::map<std::string, size_t>");
|
||||
|
||||
fmt::print("{} bench_quick_overall_map_udm\n", geomean1(bench));
|
||||
}
|
||||
|
||||
TEST_CASE("bench_quick_overall_segmented_vector" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
ankerl::nanobench::Bench bench;
|
||||
// bench.minEpochTime(1s);
|
||||
using vec_t = ankerl::unordered_dense::segmented_vector<pair_t>;
|
||||
using map_t = ankerl::unordered_dense::segmented_map<uint64_t, size_t, hash_t, eq_t, vec_t>;
|
||||
bench_all<map_t>(&bench, "ankerl::unordered_dense::map<uint64_t, size_t> segmented_vector");
|
||||
|
||||
using vec_str_t = ankerl::unordered_dense::segmented_vector<pair_str_t>;
|
||||
using map_str_t = ankerl::unordered_dense::map<std::string, size_t, hash_str_t, eq_str_t, vec_str_t>;
|
||||
bench_all<map_str_t>(&bench, "ankerl::unordered_dense::map<std::string, size_t> segmented_vector");
|
||||
|
||||
fmt::print("{} bench_quick_overall_segmented_vector\n", geomean1(bench));
|
||||
}
|
||||
|
||||
TEST_CASE("bench_quick_overall_deque" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
ankerl::nanobench::Bench bench;
|
||||
// bench.minEpochTime(1s);
|
||||
|
||||
using vec_t = std::deque<pair_t>;
|
||||
using map_t = ankerl::unordered_dense::map<uint64_t, size_t, hash_t, eq_t, vec_t>;
|
||||
bench_all<map_t>(&bench, "ankerl::unordered_dense::map<uint64_t, size_t> deque");
|
||||
|
||||
using vec_str_t = std::deque<pair_str_t>;
|
||||
using map_str_t = ankerl::unordered_dense::map<std::string, size_t, hash_str_t, eq_str_t, vec_str_t>;
|
||||
bench_all<map_str_t>(&bench, "ankerl::unordered_dense::map<std::string, size_t> deque");
|
||||
|
||||
fmt::print("{} bench_quick_overall_deque\n", geomean1(bench));
|
||||
}
|
||||
|
||||
TEST_CASE("bench_quick_overall_udm_bigbucket" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
using bucket_t = ankerl::unordered_dense::bucket_type::big;
|
||||
|
||||
ankerl::nanobench::Bench bench;
|
||||
// bench.minEpochTime(1s);
|
||||
|
||||
using alloc_t = std::allocator<pair_t>;
|
||||
using map_t = ankerl::unordered_dense::map<uint64_t, size_t, hash_t, eq_t, alloc_t, bucket_t>;
|
||||
bench_all<map_t>(&bench, "ankerl::unordered_dense::map<uint64_t, size_t>");
|
||||
|
||||
using alloc_str_t = std::allocator<pair_str_t>;
|
||||
using map_str_t = ankerl::unordered_dense::map<std::string, size_t, hash_str_t, eq_str_t, alloc_str_t, bucket_t>;
|
||||
bench_all<map_str_t>(&bench, "ankerl::unordered_dense::map<std::string, size_t>");
|
||||
|
||||
fmt::print("{} bench_quick_overall_map_udm\n", geomean1(bench));
|
||||
}
|
||||
|
||||
template <typename Map>
|
||||
void test_big() {
|
||||
Map map;
|
||||
auto rng = ankerl::nanobench::Rng();
|
||||
for (uint64_t n = 0; n < 20000000; ++n) {
|
||||
map[rng()];
|
||||
map[rng()];
|
||||
map[rng()];
|
||||
map[rng()];
|
||||
}
|
||||
fmt::print("{} map.size()\n", map.size());
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
||||
// 3346376 max RSS, 0:12.40
|
||||
TEST_CASE("memory_map_huge_rhf" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
test_big<robin_hood::unordered_flat_map<uint64_t, size_t>>();
|
||||
}
|
||||
|
||||
// 2616352 max RSS, 0:24.72
|
||||
TEST_CASE("memory_map_huge_rhn" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
test_big<robin_hood::unordered_node_map<uint64_t, size_t>>();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// 3296524 max RSS, 0:50.76
|
||||
TEST_CASE("memory_map_huge_uo" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
test_big<std::unordered_map<uint64_t, size_t>>();
|
||||
}
|
||||
|
||||
// 3149724 max RSS, 0:10.58
|
||||
TEST_CASE("memory_map_huge_udm" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
test_big<ankerl::unordered_dense::map<uint64_t, size_t>>();
|
||||
}
|
||||
|
||||
template <typename Set>
|
||||
void bench_consecutive_insert(char const* name) {
|
||||
auto before = std::chrono::high_resolution_clock::now();
|
||||
Set s{};
|
||||
for (uint64_t x = 0; x < 100000000; ++x) {
|
||||
s.insert(x);
|
||||
}
|
||||
auto after = std::chrono::high_resolution_clock::now();
|
||||
fmt::print("\t{}s, size={} for {}\n", std::chrono::duration<double>(after - before).count(), s.size(), name);
|
||||
}
|
||||
|
||||
template <typename Set>
|
||||
void bench_random_insert(char const* name) {
|
||||
ankerl::nanobench::Rng rng(23);
|
||||
auto before = std::chrono::high_resolution_clock::now();
|
||||
Set s{};
|
||||
for (uint64_t x = 0; x < 100000000; ++x) {
|
||||
s.insert(rng());
|
||||
}
|
||||
auto after = std::chrono::high_resolution_clock::now();
|
||||
fmt::print("\t{}s, size={} for {}\n", std::chrono::duration<double>(after - before).count(), s.size(), name);
|
||||
}
|
||||
|
||||
template <typename Set>
|
||||
void bench_shifted_insert(char const* name) {
|
||||
auto before = std::chrono::high_resolution_clock::now();
|
||||
Set s{};
|
||||
for (uint64_t x = 0; x < 100000000; ++x) {
|
||||
s.insert(x << 4U);
|
||||
}
|
||||
auto after = std::chrono::high_resolution_clock::now();
|
||||
fmt::print("\t{}s, size={} for {}\n", std::chrono::duration<double>(after - before).count(), s.size(), name);
|
||||
}
|
114
third_party/unordered_dense/test/bench/show_allocations.cpp
vendored
Normal file
114
third_party/unordered_dense/test/bench/show_allocations.cpp
vendored
Normal file
@ -0,0 +1,114 @@
|
||||
// #include "absl/container/flat_hash_map.h"
|
||||
#include <ankerl/unordered_dense.h> // for map, operator==
|
||||
|
||||
#include <app/counting_allocator.h>
|
||||
|
||||
#include <third-party/nanobench.h>
|
||||
|
||||
#if __has_include("boost/unordered/unordered_flat_map.hpp")
|
||||
# if defined(__clang__)
|
||||
# pragma clang diagnostic push
|
||||
# pragma clang diagnostic ignored "-Wold-style-cast"
|
||||
# endif
|
||||
# include "boost/unordered/unordered_flat_map.hpp"
|
||||
# define HAS_BOOST_UNORDERED_FLAT_MAP() 1 // NOLINT(cppcoreguidelines-macro-usage)
|
||||
#else
|
||||
# define HAS_BOOST_UNORDERED_FLAT_MAP() 0 // NOLINT(cppcoreguidelines-macro-usage)
|
||||
#endif
|
||||
|
||||
#include <doctest.h>
|
||||
#include <fmt/ostream.h>
|
||||
|
||||
#include <deque>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <unordered_map>
|
||||
|
||||
template <typename Map>
|
||||
void evaluate_map(Map& map) {
|
||||
auto rng = ankerl::nanobench::Rng{1234};
|
||||
|
||||
auto num_elements = size_t{10'000'000};
|
||||
for (uint64_t i = 0; i < num_elements; ++i) {
|
||||
map[rng()] = i;
|
||||
}
|
||||
REQUIRE(map.size() == num_elements);
|
||||
}
|
||||
|
||||
using hash_t = ankerl::unordered_dense::hash<uint64_t>;
|
||||
using eq_t = std::equal_to<uint64_t>;
|
||||
using pair_t = std::pair<uint64_t, uint64_t>;
|
||||
using pair_const_t = std::pair<const uint64_t, uint64_t>;
|
||||
using alloc_t = counting_allocator<pair_t>;
|
||||
using alloc_const_t = counting_allocator<pair_const_t>;
|
||||
|
||||
TEST_CASE("allocated_memory_std_vector" * doctest::skip()) {
|
||||
auto counters = counts_for_allocator{};
|
||||
{
|
||||
using vec_t = std::vector<pair_t, alloc_t>;
|
||||
using map_t = ankerl::unordered_dense::map<uint64_t, uint64_t, hash_t, eq_t, vec_t>;
|
||||
auto map = map_t(0, hash_t{}, eq_t{}, alloc_t{&counters});
|
||||
evaluate_map(map);
|
||||
}
|
||||
counters.save("allocated_memory_std_vector.txt");
|
||||
}
|
||||
|
||||
#if HAS_BOOST_UNORDERED_FLAT_MAP()
|
||||
|
||||
TEST_CASE("allocated_memory_boost_flat_map" * doctest::skip()) {
|
||||
auto counters = counts_for_allocator{};
|
||||
{
|
||||
using map_t = boost::unordered_flat_map<uint64_t, uint64_t, hash_t, eq_t, alloc_t>;
|
||||
auto map = map_t(alloc_t{&counters});
|
||||
evaluate_map(map);
|
||||
}
|
||||
counters.save("allocated_memory_unordered_flat_map.txt");
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE("allocated_memory_std_deque" * doctest::skip()) {
|
||||
auto counters = counts_for_allocator{};
|
||||
{
|
||||
using vec_t = std::deque<pair_t, alloc_t>;
|
||||
using map_t = ankerl::unordered_dense::map<uint64_t, uint64_t, hash_t, eq_t, vec_t>;
|
||||
auto map = map_t(0, hash_t{}, eq_t{}, alloc_t{&counters});
|
||||
evaluate_map(map);
|
||||
}
|
||||
counters.save("allocated_memory_std_deque.txt");
|
||||
}
|
||||
|
||||
TEST_CASE("allocated_memory_segmented_vector" * doctest::skip()) {
|
||||
auto counters = counts_for_allocator{};
|
||||
{
|
||||
using vec_t = ankerl::unordered_dense::segmented_vector<pair_t, alloc_t>;
|
||||
using map_t = ankerl::unordered_dense::segmented_map<uint64_t, uint64_t, hash_t, eq_t, vec_t>;
|
||||
auto map = map_t{0, hash_t{}, eq_t{}, alloc_t{&counters}};
|
||||
evaluate_map(map);
|
||||
}
|
||||
counters.save("allocated_memory_segmented_vector.txt");
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
||||
TEST_CASE("allocated_memory_std_unordered_map" * doctest::skip()) {
|
||||
auto counters = counts_for_allocator{};
|
||||
{
|
||||
using map_t = std::unordered_map<uint64_t, uint64_t, hash_t, eq_t, alloc_const_t>;
|
||||
auto map = map_t(0, alloc_t{&counters});
|
||||
evaluate_map(map);
|
||||
}
|
||||
counters.save("allocated_memory_std_unordered_map.txt");
|
||||
}
|
||||
|
||||
TEST_CASE("allocated_memory_boost_unordered_flat_map" * doctest::skip()) {
|
||||
auto counters = counts_for_allocator{};
|
||||
{
|
||||
using map_t = absl::
|
||||
flat_hash_map<uint64_t, uint64_t, absl::container_internal::hash_default_hash<uint64_t>, eq_t, alloc_const_t>;
|
||||
auto map = map_t(0, alloc_t{&counters});
|
||||
evaluate_map(map);
|
||||
}
|
||||
counters.save("allocated_memory_absl_flat_hash_map.txt");
|
||||
}
|
||||
|
||||
#endif
|
53
third_party/unordered_dense/test/bench/swap.cpp
vendored
Normal file
53
third_party/unordered_dense/test/bench/swap.cpp
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
#include <ankerl/unordered_dense.h> // for map
|
||||
#include <third-party/nanobench.h> // for Rng, doNotOptimizeAway, Bench
|
||||
|
||||
#include <doctest.h> // for TestCase, skip, TEST_CASE, test_...
|
||||
#include <fmt/core.h> // for format
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
#include <cstdint> // for uint64_t
|
||||
#include <string_view> // for string_view
|
||||
#include <unordered_map> // for unordered_map, swap
|
||||
#include <utility> // for swap
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename Map>
|
||||
void bench(std::string_view name) {
|
||||
Map a;
|
||||
Map b;
|
||||
ankerl::nanobench::Rng rng(123);
|
||||
|
||||
ankerl::nanobench::Bench bench;
|
||||
for (size_t j = 0; j < 10000; ++j) {
|
||||
a[rng()];
|
||||
b[rng()];
|
||||
}
|
||||
bench.run(fmt::format("swap {}", name), [&] {
|
||||
std::swap(a, b);
|
||||
});
|
||||
ankerl::nanobench::doNotOptimizeAway(&a);
|
||||
ankerl::nanobench::doNotOptimizeAway(&b);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#if 0
|
||||
|
||||
TEST_CASE("bench_swap_rhn" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
bench<robin_hood::unordered_node_map<uint64_t, uint64_t>>("robin_hood::unordered_node_map");
|
||||
}
|
||||
|
||||
TEST_CASE("bench_swap_rhf" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
bench<robin_hood::unordered_flat_map<uint64_t, uint64_t>>("robin_hood::unordered_flat_map");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
TEST_CASE("bench_swap_std" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
bench<std::unordered_map<uint64_t, uint64_t>>("std::unordered_map");
|
||||
}
|
||||
|
||||
TEST_CASE("bench_swap_udm" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
bench<ankerl::unordered_dense::map<uint64_t, uint64_t>>("ankerl::unordered_dense::map");
|
||||
}
|
205
third_party/unordered_dense/test/fuzz/provider.h
vendored
Normal file
205
third_party/unordered_dense/test/fuzz/provider.h
vendored
Normal file
@ -0,0 +1,205 @@
|
||||
#pragma once
|
||||
|
||||
#include <climits>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
namespace fuzz {
|
||||
|
||||
// Helper to provide a little bit more convenient interface than FuzzedDataProvider itself
|
||||
class provider {
|
||||
uint8_t const* m_data;
|
||||
size_t m_remaining_bytes;
|
||||
|
||||
// Reads one byte and returns a bool, or false when no data remains.
|
||||
[[nodiscard]] inline auto consume_bool() -> bool {
|
||||
return (1U & consume_integral<uint8_t>()) != 0U;
|
||||
}
|
||||
|
||||
// Returns a number in the range [Type's min, Type's max]. The value might
|
||||
// not be uniformly distributed in the given range. If there's no input data
|
||||
// left, always returns |min|.
|
||||
template <typename T>
|
||||
[[nodiscard]] auto consume_integral() -> T {
|
||||
return consume_integral_in_range(std::numeric_limits<T>::min(), std::numeric_limits<T>::max());
|
||||
}
|
||||
|
||||
// Returns a number in the range [min, max] by consuming bytes from the
|
||||
// input data. The value might not be uniformly distributed in the given
|
||||
// range. If there's no input data left, always returns |min|. |min| must
|
||||
// be less than or equal to |max|.
|
||||
template <typename T>
|
||||
[[nodiscard]] auto consume_integral_in_range(T min, T max) -> T {
|
||||
static_assert(std::is_integral<T>::value, "An integral type is required.");
|
||||
static_assert(sizeof(T) <= sizeof(uint64_t), "Unsupported integral type.");
|
||||
|
||||
if (min > max) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
// Use the biggest type possible to hold the range and the result.
|
||||
uint64_t range = static_cast<uint64_t>(max) - static_cast<uint64_t>(min);
|
||||
uint64_t result = 0;
|
||||
size_t offset = 0;
|
||||
|
||||
while (offset < sizeof(T) * CHAR_BIT && (range >> offset) > 0 && m_remaining_bytes != 0) {
|
||||
// Pull bytes off the end of the seed data. Experimentally, this seems to
|
||||
// allow the fuzzer to more easily explore the input space. This makes
|
||||
// sense, since it works by modifying inputs that caused new code to run,
|
||||
// and this data is often used to encode length of data read by
|
||||
// |ConsumeBytes|. Separating out read lengths makes it easier modify the
|
||||
// contents of the data that is actually read.
|
||||
--m_remaining_bytes;
|
||||
result = (result << CHAR_BIT) | m_data[m_remaining_bytes];
|
||||
offset += CHAR_BIT;
|
||||
}
|
||||
|
||||
// Avoid division by 0, in case |range + 1| results in overflow.
|
||||
if (range != std::numeric_limits<decltype(range)>::max()) {
|
||||
result = result % (range + 1);
|
||||
}
|
||||
|
||||
return static_cast<T>(static_cast<uint64_t>(min) + result);
|
||||
}
|
||||
|
||||
inline void advance_unchecked(size_t num_bytes) {
|
||||
m_data += num_bytes;
|
||||
m_remaining_bytes -= num_bytes;
|
||||
}
|
||||
|
||||
// Returns a std::string of length from 0 to |max_length|. When it runs out of
|
||||
// input data, returns what remains of the input. Designed to be more stable
|
||||
// with respect to a fuzzer inserting characters than just picking a random
|
||||
// length and then consuming that many bytes with |ConsumeBytes|.
|
||||
[[nodiscard]] inline auto consume_random_length_string(size_t max_length) -> std::string {
|
||||
// Reads bytes from the start of |data_ptr_|. Maps "\\" to "\", and maps "\"
|
||||
// followed by anything else to the end of the string. As a result of this
|
||||
// logic, a fuzzer can insert characters into the string, and the string
|
||||
// will be lengthened to include those new characters, resulting in a more
|
||||
// stable fuzzer than picking the length of a string independently from
|
||||
// picking its contents.
|
||||
std::string result;
|
||||
|
||||
// Reserve the anticipated capacity to prevent several reallocations.
|
||||
result.reserve(std::min(max_length, m_remaining_bytes));
|
||||
for (size_t i = 0; i < max_length && m_remaining_bytes != 0; ++i) {
|
||||
auto next = m_data[0];
|
||||
advance_unchecked(1);
|
||||
if (next == '\\' && m_remaining_bytes != 0) {
|
||||
next = m_data[0];
|
||||
advance_unchecked(1);
|
||||
if (next != '\\') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
result += static_cast<char>(next);
|
||||
}
|
||||
|
||||
result.shrink_to_fit();
|
||||
return result;
|
||||
}
|
||||
|
||||
provider(provider const&) = default;
|
||||
auto operator=(provider const&) -> provider& = default;
|
||||
|
||||
public:
|
||||
provider(provider&&) = default;
|
||||
auto operator=(provider&&) -> provider& = default;
|
||||
~provider() = default;
|
||||
|
||||
[[nodiscard]] auto copy() const -> provider {
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline explicit provider(void const* data, size_t size)
|
||||
: m_data(reinterpret_cast<uint8_t const*>(data)) /* NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) */
|
||||
, m_remaining_bytes(size) /* NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) */ {}
|
||||
|
||||
// random number in inclusive range [min, max]
|
||||
template <typename T>
|
||||
auto range(T min, T max) -> T {
|
||||
return consume_integral_in_range<T>(min, max);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto bounded(T max_exclusive) -> T {
|
||||
if (0 == max_exclusive) {
|
||||
return {};
|
||||
}
|
||||
return consume_integral_in_range<T>(0, max_exclusive - 1);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto integral() -> T {
|
||||
if constexpr (std::is_same_v<bool, T>) {
|
||||
return consume_bool();
|
||||
} else {
|
||||
return consume_integral<T>();
|
||||
}
|
||||
}
|
||||
|
||||
inline auto string(size_t max_length) -> std::string {
|
||||
return consume_random_length_string(max_length);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
auto pick(Args&&... args) -> std::common_type_t<decltype(args)...>& {
|
||||
static constexpr auto num_ops = sizeof...(args);
|
||||
|
||||
auto idx = size_t{};
|
||||
auto const chosen_idx = consume_integral_in_range<size_t>(0, num_ops - 1);
|
||||
std::common_type_t<decltype(args)...>* result = nullptr;
|
||||
((idx++ == chosen_idx ? (result = &args, true) : false) || ...);
|
||||
return *result;
|
||||
}
|
||||
|
||||
template <typename... Ops>
|
||||
void repeat_oneof(Ops&&... op) {
|
||||
static constexpr auto num_ops = sizeof...(op);
|
||||
|
||||
do {
|
||||
if constexpr (num_ops == 1) {
|
||||
(op(), ...);
|
||||
} else {
|
||||
auto chosen_op_idx = range<size_t>(0, num_ops - 1);
|
||||
auto op_idx = size_t{};
|
||||
((op_idx++ == chosen_op_idx ? op() : void()), ...);
|
||||
}
|
||||
} while (0 != m_remaining_bytes);
|
||||
}
|
||||
|
||||
template <typename... Ops>
|
||||
void limited_repeat_oneof(size_t min, size_t max, Ops&&... op) {
|
||||
static constexpr auto num_ops = sizeof...(op);
|
||||
|
||||
size_t const num_evaluations = consume_integral_in_range(min, max);
|
||||
for (size_t i = 0; i < num_evaluations; ++i) {
|
||||
if constexpr (num_ops == 1) {
|
||||
(op(), ...);
|
||||
} else {
|
||||
auto chosen_op_idx = range<size_t>(0, num_ops - 1);
|
||||
auto op_idx = size_t{};
|
||||
((op_idx++ == chosen_op_idx ? op() : void()), ...);
|
||||
}
|
||||
if (m_remaining_bytes == 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] auto has_remaining_bytes() const -> bool {
|
||||
return 0U != m_remaining_bytes;
|
||||
}
|
||||
|
||||
static inline void require(bool b) {
|
||||
if (!b) {
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace fuzz
|
133
third_party/unordered_dense/test/fuzz/run.cpp
vendored
Normal file
133
third_party/unordered_dense/test/fuzz/run.cpp
vendored
Normal file
@ -0,0 +1,133 @@
|
||||
#include <fuzz/run.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace fuzz::detail {
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] constexpr auto is_alpha(char c) -> bool {
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto is_digit(char c) -> bool {
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto is_alnum(char c) -> bool {
|
||||
return is_alpha(c) || is_digit(c);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto contains(std::string_view haystack, char needle) -> bool {
|
||||
return std::string_view::npos != haystack.find_first_of(needle);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto is_valid_filename(std::string_view name) -> bool {
|
||||
using namespace std::literals;
|
||||
for (auto c : name) {
|
||||
if (!is_alnum(c) && !contains("_-+", c)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
auto env(char const* varname) -> std::optional<std::string> {
|
||||
#ifdef _MSC_VER
|
||||
char* pValue = nullptr;
|
||||
size_t len = 0;
|
||||
errno_t err = _dupenv_s(&pValue, &len, varname);
|
||||
if (err || nullptr == pValue) {
|
||||
return {};
|
||||
}
|
||||
auto str = std::string(pValue);
|
||||
free(pValue);
|
||||
return str;
|
||||
#else
|
||||
char const* val = std::getenv(varname); // NOLINT(concurrency-mt-unsafe,clang-analyzer-cplusplus.StringChecker)
|
||||
if (nullptr == val) {
|
||||
return {};
|
||||
}
|
||||
return val;
|
||||
#endif
|
||||
}
|
||||
|
||||
[[nodiscard]] auto read_file(std::filesystem::path const& p) -> std::optional<std::string> {
|
||||
auto f = std::ifstream(p);
|
||||
if (!f) {
|
||||
return {};
|
||||
}
|
||||
auto content = std::string((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
|
||||
if (f.bad()) {
|
||||
return {};
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto find_fuzz_corpus_base_dir() -> std::optional<std::filesystem::path> {
|
||||
auto corpus_base_dir = env("FUZZ_CORPUS_BASE_DIR");
|
||||
if (corpus_base_dir) {
|
||||
return corpus_base_dir.value();
|
||||
}
|
||||
|
||||
auto p = std::filesystem::current_path();
|
||||
while (true) {
|
||||
auto const filename = p / ".fuzz-corpus-base-dir";
|
||||
// INFO(fmt::format("trying '{}'", filename.string()));
|
||||
if (std::filesystem::exists(filename)) {
|
||||
if (auto file_content = read_file(p / ".fuzz-corpus-base-dir"); file_content) {
|
||||
auto f = std::filesystem::path(file_content.value()).make_preferred();
|
||||
// INFO(fmt::format("got it! p='{}, f='{}', p/f='{}'\n", p.string(), f.string(), (p / f).string()));
|
||||
return p / f;
|
||||
}
|
||||
// could not read file
|
||||
throw std::runtime_error(fmt::format("could not read '{}'", filename.string()));
|
||||
}
|
||||
if (p == p.root_path()) {
|
||||
return {};
|
||||
}
|
||||
p = p.parent_path();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void evaluate_corpus(std::function<void(provider)> const& op) {
|
||||
if (!is_valid_filename(doctest::current_test_name())) {
|
||||
throw std::runtime_error("test case name needs to be a valid filename. only [a-zA-Z0-9_-+] are allowed");
|
||||
}
|
||||
|
||||
// 2 ways
|
||||
|
||||
auto corpus_base_dir = find_fuzz_corpus_base_dir();
|
||||
if (!corpus_base_dir) {
|
||||
throw std::runtime_error("could not find corpus base dir :-(");
|
||||
}
|
||||
|
||||
auto path = std::filesystem::path(corpus_base_dir.value()) / doctest::current_test_name();
|
||||
INFO("path=\"" << path.string() << "\"");
|
||||
auto num_files = size_t();
|
||||
for (auto const& dir_entry : std::filesystem::directory_iterator(path)) {
|
||||
++num_files;
|
||||
auto const& test_file = dir_entry.path();
|
||||
CAPTURE(test_file);
|
||||
|
||||
auto f = std::ifstream(test_file);
|
||||
auto content = std::string((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
op(provider(content.data(), content.size()));
|
||||
}
|
||||
REQUIRE(num_files > 1);
|
||||
}
|
||||
|
||||
} // namespace fuzz::detail
|
47
third_party/unordered_dense/test/fuzz/run.h
vendored
Normal file
47
third_party/unordered_dense/test/fuzz/run.h
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <fuzz/provider.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
|
||||
extern "C" {
|
||||
void HF_ITER(const uint8_t** buf_ptr, size_t* len_ptr);
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace fuzz {
|
||||
|
||||
namespace detail {
|
||||
|
||||
void evaluate_corpus(std::function<void(provider)> const& op);
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* There are 2 modes how this the op() will be executed:
|
||||
*
|
||||
* Driven by honggfuzz: this is enabled when compiling with -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION.
|
||||
* This is done to fuzz a particular test.
|
||||
*
|
||||
* Otherwise, this is run in "corpus" mode, where all files in a directory named by the testname are evaluated
|
||||
* This should be done in normal unit testing. The location of the corpus base directory is determined in this order:
|
||||
* 1. Use FUZZ_CORPUS_BASE_DIR environment variable
|
||||
* 2. If this is not set, look in the working directory for a ".fuzz-corpus-base-dir" file which should contain
|
||||
* the path to the base directory (relative to that particular file)
|
||||
*/
|
||||
template <typename Op>
|
||||
void run(Op const& op) {
|
||||
#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
|
||||
size_t len = 0;
|
||||
uint8_t const* buf = nullptr;
|
||||
while (true) {
|
||||
::HF_ITER(&buf, &len);
|
||||
op(provider(buf, len));
|
||||
}
|
||||
#else
|
||||
detail::evaluate_corpus(op);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace fuzz
|
192
third_party/unordered_dense/test/meson.build
vendored
Normal file
192
third_party/unordered_dense/test/meson.build
vendored
Normal file
@ -0,0 +1,192 @@
|
||||
test_sources = [
|
||||
'app/counter.cpp',
|
||||
'app/doctest.cpp',
|
||||
'app/nanobench.cpp',
|
||||
'app/stacktrace.cpp',
|
||||
'app/ui/periodic.cpp',
|
||||
'app/ui/progress_bar.cpp',
|
||||
'app/unordered_dense.cpp',
|
||||
|
||||
'bench/swap.cpp',
|
||||
'bench/show_allocations.cpp',
|
||||
'bench/quick_overall_map.cpp',
|
||||
'bench/game_of_life.cpp',
|
||||
'bench/find_random.cpp',
|
||||
'bench/copy.cpp',
|
||||
|
||||
'fuzz/run.cpp',
|
||||
|
||||
'unit/assign_to_move.cpp',
|
||||
'unit/assignment_combinations.cpp',
|
||||
'unit/at.cpp',
|
||||
'unit/bucket.cpp',
|
||||
'unit/contains.cpp',
|
||||
'unit/copy_and_assign_maps.cpp',
|
||||
'unit/copyassignment.cpp',
|
||||
'unit/count.cpp',
|
||||
'unit/ctors.cpp',
|
||||
'unit/custom_container_boost.cpp',
|
||||
'unit/custom_container.cpp',
|
||||
'unit/custom_hash.cpp',
|
||||
'unit/deduction_guides.cpp',
|
||||
'unit/diamond.cpp',
|
||||
'unit/empty.cpp',
|
||||
'unit/equal_range.cpp',
|
||||
'unit/erase_if.cpp',
|
||||
'unit/erase_range.cpp',
|
||||
'unit/erase.cpp',
|
||||
'unit/explicit.cpp',
|
||||
'unit/extract.cpp',
|
||||
'unit/fuzz_api.cpp',
|
||||
'unit/fuzz_insert_erase.cpp',
|
||||
'unit/fuzz_replace_map.cpp',
|
||||
'unit/fuzz_string.cpp',
|
||||
'unit/hash_char_types.cpp',
|
||||
'unit/hash_smart_ptr.cpp',
|
||||
'unit/hash_string_view.cpp',
|
||||
'unit/hash.cpp',
|
||||
'unit/include_only.cpp',
|
||||
'unit/initializer_list.cpp',
|
||||
'unit/insert_or_assign.cpp',
|
||||
'unit/insert.cpp',
|
||||
'unit/iterators_empty.cpp',
|
||||
'unit/iterators_erase.cpp',
|
||||
'unit/iterators_insert.cpp',
|
||||
'unit/load_factor.cpp',
|
||||
'unit/maps_of_maps.cpp',
|
||||
'unit/max.cpp',
|
||||
'unit/move_to_moved.cpp',
|
||||
'unit/multiple_apis.cpp',
|
||||
'unit/namespace.cpp',
|
||||
'unit/not_copyable.cpp',
|
||||
'unit/not_moveable.cpp',
|
||||
'unit/pmr_move_with_allocators.cpp',
|
||||
'unit/pmr.cpp',
|
||||
'unit/rehash.cpp',
|
||||
'unit/replace.cpp',
|
||||
'unit/reserve_and_assign.cpp',
|
||||
'unit/reserve.cpp',
|
||||
'unit/segmented_vector.cpp',
|
||||
'unit/set_or_map_types.cpp',
|
||||
'unit/set.cpp',
|
||||
'unit/std_hash.cpp',
|
||||
'unit/swap.cpp',
|
||||
'unit/transparent.cpp',
|
||||
'unit/try_emplace.cpp',
|
||||
'unit/tuple_hash.cpp',
|
||||
'unit/unique_ptr.cpp',
|
||||
'unit/unordered_set.cpp',
|
||||
'unit/vectorofmaps.cpp',
|
||||
'unit/windows_include.cpp',
|
||||
]
|
||||
|
||||
# additional compile options
|
||||
# see https://mesonbuild.com/Reference-tables.html
|
||||
cpp_args = []
|
||||
compiler = meson.get_compiler('cpp')
|
||||
foreach arg : [
|
||||
# gcc
|
||||
'-Wno-stringop-overflow', # g++ error in fmtlib
|
||||
'-Warith-conversion',
|
||||
'-Wshadow=global',
|
||||
'-Wno-array-bounds', # gcc 13 gives incorrect warning
|
||||
|
||||
# gcc / clang
|
||||
'-Wconversion',
|
||||
'-Wextra',
|
||||
'-Wunreachable-code',
|
||||
'-Wuninitialized',
|
||||
'-pedantic-errors',
|
||||
'-Wold-style-cast',
|
||||
'-Wno-unused-function',
|
||||
# '-Weffc++', doesn't work with fmt
|
||||
|
||||
# '-march=native',
|
||||
]
|
||||
if compiler.has_argument(arg)
|
||||
cpp_args += [arg]
|
||||
endif
|
||||
endforeach
|
||||
|
||||
if compiler.get_id() == 'msvc'
|
||||
add_global_arguments(
|
||||
'/wd4189', # fmt: 'zero': local variable is initialized but not referenced, fixed in https://github.com/fmtlib/fmt/issues/2891
|
||||
'/wd4251', # 'fmt::v8::ostream::file_': class 'fmt::v8::file' needs to have dll-interface to be used by clients of class 'fmt::v8::ostream'
|
||||
language: 'cpp')
|
||||
endif
|
||||
|
||||
# for include-what-you-use
|
||||
#cpp_args += '-isystem'
|
||||
#cpp_args += '/usr/lib64/clang/14.0.0/include/'
|
||||
|
||||
fmt_method = 'auto'
|
||||
if get_option('cpp_args').contains('-m32')
|
||||
# use builtin so we can compile it for 32bit.
|
||||
# Can't use it as a default or sanitizer doesn't work...
|
||||
fmt_method = 'builtin'
|
||||
endif
|
||||
|
||||
# use e.g.
|
||||
# CXX='ccache clang++' BOOST_ROOT=/home/martinus/dev/boost_1_81_0/ meson setup --buildtype release -Dcpp_std=c++17 build
|
||||
opt_boost = dependency('boost', required: false)
|
||||
link_args = []
|
||||
if opt_boost.found()
|
||||
add_global_arguments('-DANKERL_UNORDERED_DENSE_HAS_BOOST=1', language: 'cpp')
|
||||
link_args += ['-lrt']
|
||||
else
|
||||
add_global_arguments('-DANKERL_UNORDERED_DENSE_HAS_BOOST=0', language: 'cpp')
|
||||
endif
|
||||
|
||||
#opt_absl = dependency('absl_container', required: true, )
|
||||
#if opt_boost.found()
|
||||
# add_global_arguments('-DANKERL_UNORDERED_DENSE_HAS_ABSL=1', language: 'cpp')
|
||||
#else
|
||||
# add_global_arguments('-DANKERL_UNORDERED_DENSE_HAS_ABSL=0', language: 'cpp')
|
||||
#endif
|
||||
|
||||
cpp_args += [
|
||||
#'-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION',
|
||||
#'-fsanitize-undefined-trap-on-error',
|
||||
#'-fsanitize=undefined,address',
|
||||
#'-fno-sanitize=thread',
|
||||
#'-ftrivial-auto-var-init=pattern',
|
||||
#'-g',
|
||||
]
|
||||
|
||||
link_args += [
|
||||
#'-fsanitize=undefined,address',
|
||||
#'-fno-sanitize=thread'
|
||||
#'-Wl,-shuffle-sections' # for benchmarking with mold linker
|
||||
]
|
||||
|
||||
test_exe = executable(
|
||||
'udm-test',
|
||||
test_sources,
|
||||
include_directories: incdir,
|
||||
cpp_args: cpp_args,
|
||||
link_args: link_args,
|
||||
dependencies: [
|
||||
dependency('threads'), # add dependency for threads (-lpthread, see https://mesonbuild.com/howtox.html),
|
||||
|
||||
# see what's in the [provide] sections for the dependency names
|
||||
dependency('doctest'),
|
||||
dependency('fmt', method: fmt_method),
|
||||
|
||||
# disable these two if you don't want them
|
||||
#dependency('boost'),
|
||||
#dependency('absl_container', default_options: ['warning_level=0', 'werror=false'])
|
||||
# dependency('absl_hash', method: 'builtin', default_options: ['warning_level=0', 'werror=false'])
|
||||
],
|
||||
)
|
||||
|
||||
benchmark(
|
||||
'bench',
|
||||
test_exe,
|
||||
args: ['-ns', '-ts=bench'],
|
||||
verbose: true)
|
||||
|
||||
test(
|
||||
'unit',
|
||||
test_exe,
|
||||
verbose: true)
|
||||
|
20
third_party/unordered_dense/test/modules/module_test.cpp
vendored
Normal file
20
third_party/unordered_dense/test/modules/module_test.cpp
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
import ankerl.unordered_dense;
|
||||
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
|
||||
int main() {
|
||||
ankerl::unordered_dense::map<std::string, int> m;
|
||||
m["24535"] = 4;
|
||||
assert(m.size() == 1);
|
||||
|
||||
auto h_int = ankerl::unordered_dense::hash<int>();
|
||||
assert(h_int(123) != 123);
|
||||
|
||||
auto h_str = ankerl::unordered_dense::hash<std::string>();
|
||||
assert(h_str("123") != 123);
|
||||
|
||||
auto h_ptr = ankerl::unordered_dense::hash<int*>();
|
||||
int i = 0;
|
||||
assert(h_ptr(&i) != 0);
|
||||
}
|
12
third_party/unordered_dense/test/modules/test.sh
vendored
Executable file
12
third_party/unordered_dense/test/modules/test.sh
vendored
Executable file
@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
set -xe
|
||||
|
||||
rm -f *.o *.pcm a.out
|
||||
|
||||
clang++ -std=c++20 -I ../../include --precompile -x c++-module ../../src/ankerl.unordered_dense.cpp
|
||||
clang++ -std=c++20 -c ankerl.unordered_dense.pcm
|
||||
clang++ -std=c++20 -fprebuilt-module-path=. ankerl.unordered_dense.o module_test.cpp -o a.out
|
||||
|
||||
./a.out
|
||||
|
||||
rm -f *.o *.pcm a.out
|
7
third_party/unordered_dense/test/third-party/.clang-tidy
vendored
Normal file
7
third_party/unordered_dense/test/third-party/.clang-tidy
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
Checks: '-*,
|
||||
bugprone-infinite-loop
|
||||
'
|
||||
WarningsAsErrors: ''
|
||||
HeaderFilterRegex: ''
|
||||
...
|
397
third_party/unordered_dense/test/third-party/FuzzedDataProvider.h
vendored
Normal file
397
third_party/unordered_dense/test/third-party/FuzzedDataProvider.h
vendored
Normal file
@ -0,0 +1,397 @@
|
||||
//===- FuzzedDataProvider.h - Utility header for fuzz targets ---*- C++ -* ===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
// A single header library providing an utility class to break up an array of
|
||||
// bytes. Whenever run on the same input, provides the same output, as long as
|
||||
// its methods are called in the same order, with the same arguments.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_FUZZER_FUZZED_DATA_PROVIDER_H_
|
||||
#define LLVM_FUZZER_FUZZED_DATA_PROVIDER_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <climits>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <initializer_list>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
// In addition to the comments below, the API is also briefly documented at
|
||||
// https://github.com/google/fuzzing/blob/master/docs/split-inputs.md#fuzzed-data-provider
|
||||
class FuzzedDataProvider {
|
||||
public:
|
||||
// |data| is an array of length |size| that the FuzzedDataProvider wraps to
|
||||
// provide more granular access. |data| must outlive the FuzzedDataProvider.
|
||||
FuzzedDataProvider(const uint8_t *data, size_t size)
|
||||
: data_ptr_(data), remaining_bytes_(size) {}
|
||||
~FuzzedDataProvider() = default;
|
||||
|
||||
// See the implementation below (after the class definition) for more verbose
|
||||
// comments for each of the methods.
|
||||
|
||||
// Methods returning std::vector of bytes. These are the most popular choice
|
||||
// when splitting fuzzing input into pieces, as every piece is put into a
|
||||
// separate buffer (i.e. ASan would catch any under-/overflow) and the memory
|
||||
// will be released automatically.
|
||||
template <typename T> std::vector<T> ConsumeBytes(size_t num_bytes);
|
||||
template <typename T>
|
||||
std::vector<T> ConsumeBytesWithTerminator(size_t num_bytes, T terminator = 0);
|
||||
template <typename T> std::vector<T> ConsumeRemainingBytes();
|
||||
|
||||
// Methods returning strings. Use only when you need a std::string or a null
|
||||
// terminated C-string. Otherwise, prefer the methods returning std::vector.
|
||||
std::string ConsumeBytesAsString(size_t num_bytes);
|
||||
std::string ConsumeRandomLengthString(size_t max_length);
|
||||
std::string ConsumeRandomLengthString();
|
||||
std::string ConsumeRemainingBytesAsString();
|
||||
|
||||
// Methods returning integer values.
|
||||
template <typename T> T ConsumeIntegral();
|
||||
template <typename T> T ConsumeIntegralInRange(T min, T max);
|
||||
|
||||
// Methods returning floating point values.
|
||||
template <typename T> T ConsumeFloatingPoint();
|
||||
template <typename T> T ConsumeFloatingPointInRange(T min, T max);
|
||||
|
||||
// 0 <= return value <= 1.
|
||||
template <typename T> T ConsumeProbability();
|
||||
|
||||
bool ConsumeBool();
|
||||
|
||||
// Returns a value chosen from the given enum.
|
||||
template <typename T> T ConsumeEnum();
|
||||
|
||||
// Returns a value from the given array.
|
||||
template <typename T, size_t size> T PickValueInArray(const T (&array)[size]);
|
||||
template <typename T, size_t size>
|
||||
T PickValueInArray(const std::array<T, size> &array);
|
||||
template <typename T> T PickValueInArray(std::initializer_list<const T> list);
|
||||
|
||||
// Writes data to the given destination and returns number of bytes written.
|
||||
size_t ConsumeData(void *destination, size_t num_bytes);
|
||||
|
||||
// Reports the remaining bytes available for fuzzed input.
|
||||
size_t remaining_bytes() { return remaining_bytes_; }
|
||||
|
||||
private:
|
||||
FuzzedDataProvider(const FuzzedDataProvider &) = delete;
|
||||
FuzzedDataProvider &operator=(const FuzzedDataProvider &) = delete;
|
||||
|
||||
void CopyAndAdvance(void *destination, size_t num_bytes);
|
||||
|
||||
void Advance(size_t num_bytes);
|
||||
|
||||
template <typename T>
|
||||
std::vector<T> ConsumeBytes(size_t size, size_t num_bytes);
|
||||
|
||||
template <typename TS, typename TU> TS ConvertUnsignedToSigned(TU value);
|
||||
|
||||
const uint8_t *data_ptr_;
|
||||
size_t remaining_bytes_;
|
||||
};
|
||||
|
||||
// Returns a std::vector containing |num_bytes| of input data. If fewer than
|
||||
// |num_bytes| of data remain, returns a shorter std::vector containing all
|
||||
// of the data that's left. Can be used with any byte sized type, such as
|
||||
// char, unsigned char, uint8_t, etc.
|
||||
template <typename T>
|
||||
std::vector<T> FuzzedDataProvider::ConsumeBytes(size_t num_bytes) {
|
||||
num_bytes = std::min(num_bytes, remaining_bytes_);
|
||||
return ConsumeBytes<T>(num_bytes, num_bytes);
|
||||
}
|
||||
|
||||
// Similar to |ConsumeBytes|, but also appends the terminator value at the end
|
||||
// of the resulting vector. Useful, when a mutable null-terminated C-string is
|
||||
// needed, for example. But that is a rare case. Better avoid it, if possible,
|
||||
// and prefer using |ConsumeBytes| or |ConsumeBytesAsString| methods.
|
||||
template <typename T>
|
||||
std::vector<T> FuzzedDataProvider::ConsumeBytesWithTerminator(size_t num_bytes,
|
||||
T terminator) {
|
||||
num_bytes = std::min(num_bytes, remaining_bytes_);
|
||||
std::vector<T> result = ConsumeBytes<T>(num_bytes + 1, num_bytes);
|
||||
result.back() = terminator;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Returns a std::vector containing all remaining bytes of the input data.
|
||||
template <typename T>
|
||||
std::vector<T> FuzzedDataProvider::ConsumeRemainingBytes() {
|
||||
return ConsumeBytes<T>(remaining_bytes_);
|
||||
}
|
||||
|
||||
// Returns a std::string containing |num_bytes| of input data. Using this and
|
||||
// |.c_str()| on the resulting string is the best way to get an immutable
|
||||
// null-terminated C string. If fewer than |num_bytes| of data remain, returns
|
||||
// a shorter std::string containing all of the data that's left.
|
||||
inline std::string FuzzedDataProvider::ConsumeBytesAsString(size_t num_bytes) {
|
||||
static_assert(sizeof(std::string::value_type) == sizeof(uint8_t),
|
||||
"ConsumeBytesAsString cannot convert the data to a string.");
|
||||
|
||||
num_bytes = std::min(num_bytes, remaining_bytes_);
|
||||
std::string result(
|
||||
reinterpret_cast<const std::string::value_type *>(data_ptr_), num_bytes);
|
||||
Advance(num_bytes);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Returns a std::string of length from 0 to |max_length|. When it runs out of
|
||||
// input data, returns what remains of the input. Designed to be more stable
|
||||
// with respect to a fuzzer inserting characters than just picking a random
|
||||
// length and then consuming that many bytes with |ConsumeBytes|.
|
||||
inline std::string
|
||||
FuzzedDataProvider::ConsumeRandomLengthString(size_t max_length) {
|
||||
// Reads bytes from the start of |data_ptr_|. Maps "\\" to "\", and maps "\"
|
||||
// followed by anything else to the end of the string. As a result of this
|
||||
// logic, a fuzzer can insert characters into the string, and the string
|
||||
// will be lengthened to include those new characters, resulting in a more
|
||||
// stable fuzzer than picking the length of a string independently from
|
||||
// picking its contents.
|
||||
std::string result;
|
||||
|
||||
// Reserve the anticipated capaticity to prevent several reallocations.
|
||||
result.reserve(std::min(max_length, remaining_bytes_));
|
||||
for (size_t i = 0; i < max_length && remaining_bytes_ != 0; ++i) {
|
||||
char next = ConvertUnsignedToSigned<char>(data_ptr_[0]);
|
||||
Advance(1);
|
||||
if (next == '\\' && remaining_bytes_ != 0) {
|
||||
next = ConvertUnsignedToSigned<char>(data_ptr_[0]);
|
||||
Advance(1);
|
||||
if (next != '\\')
|
||||
break;
|
||||
}
|
||||
result += next;
|
||||
}
|
||||
|
||||
result.shrink_to_fit();
|
||||
return result;
|
||||
}
|
||||
|
||||
// Returns a std::string of length from 0 to |remaining_bytes_|.
|
||||
inline std::string FuzzedDataProvider::ConsumeRandomLengthString() {
|
||||
return ConsumeRandomLengthString(remaining_bytes_);
|
||||
}
|
||||
|
||||
// Returns a std::string containing all remaining bytes of the input data.
|
||||
// Prefer using |ConsumeRemainingBytes| unless you actually need a std::string
|
||||
// object.
|
||||
inline std::string FuzzedDataProvider::ConsumeRemainingBytesAsString() {
|
||||
return ConsumeBytesAsString(remaining_bytes_);
|
||||
}
|
||||
|
||||
// Returns a number in the range [Type's min, Type's max]. The value might
|
||||
// not be uniformly distributed in the given range. If there's no input data
|
||||
// left, always returns |min|.
|
||||
template <typename T> T FuzzedDataProvider::ConsumeIntegral() {
|
||||
return ConsumeIntegralInRange(std::numeric_limits<T>::min(),
|
||||
std::numeric_limits<T>::max());
|
||||
}
|
||||
|
||||
// Returns a number in the range [min, max] by consuming bytes from the
|
||||
// input data. The value might not be uniformly distributed in the given
|
||||
// range. If there's no input data left, always returns |min|. |min| must
|
||||
// be less than or equal to |max|.
|
||||
template <typename T>
|
||||
T FuzzedDataProvider::ConsumeIntegralInRange(T min, T max) {
|
||||
static_assert(std::is_integral<T>::value, "An integral type is required.");
|
||||
static_assert(sizeof(T) <= sizeof(uint64_t), "Unsupported integral type.");
|
||||
|
||||
if (min > max)
|
||||
abort();
|
||||
|
||||
// Use the biggest type possible to hold the range and the result.
|
||||
uint64_t range = static_cast<uint64_t>(max) - static_cast<uint64_t>(min);
|
||||
uint64_t result = 0;
|
||||
size_t offset = 0;
|
||||
|
||||
while (offset < sizeof(T) * CHAR_BIT && (range >> offset) > 0 &&
|
||||
remaining_bytes_ != 0) {
|
||||
// Pull bytes off the end of the seed data. Experimentally, this seems to
|
||||
// allow the fuzzer to more easily explore the input space. This makes
|
||||
// sense, since it works by modifying inputs that caused new code to run,
|
||||
// and this data is often used to encode length of data read by
|
||||
// |ConsumeBytes|. Separating out read lengths makes it easier modify the
|
||||
// contents of the data that is actually read.
|
||||
--remaining_bytes_;
|
||||
result = (result << CHAR_BIT) | data_ptr_[remaining_bytes_];
|
||||
offset += CHAR_BIT;
|
||||
}
|
||||
|
||||
// Avoid division by 0, in case |range + 1| results in overflow.
|
||||
if (range != std::numeric_limits<decltype(range)>::max())
|
||||
result = result % (range + 1);
|
||||
|
||||
return static_cast<T>(static_cast<uint64_t>(min) + result);
|
||||
}
|
||||
|
||||
// Returns a floating point value in the range [Type's lowest, Type's max] by
|
||||
// consuming bytes from the input data. If there's no input data left, always
|
||||
// returns approximately 0.
|
||||
template <typename T> T FuzzedDataProvider::ConsumeFloatingPoint() {
|
||||
return ConsumeFloatingPointInRange<T>(std::numeric_limits<T>::lowest(),
|
||||
std::numeric_limits<T>::max());
|
||||
}
|
||||
|
||||
// Returns a floating point value in the given range by consuming bytes from
|
||||
// the input data. If there's no input data left, returns |min|. Note that
|
||||
// |min| must be less than or equal to |max|.
|
||||
template <typename T>
|
||||
T FuzzedDataProvider::ConsumeFloatingPointInRange(T min, T max) {
|
||||
if (min > max)
|
||||
abort();
|
||||
|
||||
T range = .0;
|
||||
T result = min;
|
||||
constexpr T zero(.0);
|
||||
if (max > zero && min < zero && max > min + std::numeric_limits<T>::max()) {
|
||||
// The diff |max - min| would overflow the given floating point type. Use
|
||||
// the half of the diff as the range and consume a bool to decide whether
|
||||
// the result is in the first of the second part of the diff.
|
||||
range = (max / 2.0) - (min / 2.0);
|
||||
if (ConsumeBool()) {
|
||||
result += range;
|
||||
}
|
||||
} else {
|
||||
range = max - min;
|
||||
}
|
||||
|
||||
return result + range * ConsumeProbability<T>();
|
||||
}
|
||||
|
||||
// Returns a floating point number in the range [0.0, 1.0]. If there's no
|
||||
// input data left, always returns 0.
|
||||
template <typename T> T FuzzedDataProvider::ConsumeProbability() {
|
||||
static_assert(std::is_floating_point<T>::value,
|
||||
"A floating point type is required.");
|
||||
|
||||
// Use different integral types for different floating point types in order
|
||||
// to provide better density of the resulting values.
|
||||
using IntegralType =
|
||||
typename std::conditional<(sizeof(T) <= sizeof(uint32_t)), uint32_t,
|
||||
uint64_t>::type;
|
||||
|
||||
T result = static_cast<T>(ConsumeIntegral<IntegralType>());
|
||||
result /= static_cast<T>(std::numeric_limits<IntegralType>::max());
|
||||
return result;
|
||||
}
|
||||
|
||||
// Reads one byte and returns a bool, or false when no data remains.
|
||||
inline bool FuzzedDataProvider::ConsumeBool() {
|
||||
return 1 & ConsumeIntegral<uint8_t>();
|
||||
}
|
||||
|
||||
// Returns an enum value. The enum must start at 0 and be contiguous. It must
|
||||
// also contain |kMaxValue| aliased to its largest (inclusive) value. Such as:
|
||||
// enum class Foo { SomeValue, OtherValue, kMaxValue = OtherValue };
|
||||
template <typename T> T FuzzedDataProvider::ConsumeEnum() {
|
||||
static_assert(std::is_enum<T>::value, "|T| must be an enum type.");
|
||||
return static_cast<T>(
|
||||
ConsumeIntegralInRange<uint32_t>(0, static_cast<uint32_t>(T::kMaxValue)));
|
||||
}
|
||||
|
||||
// Returns a copy of the value selected from the given fixed-size |array|.
|
||||
template <typename T, size_t size>
|
||||
T FuzzedDataProvider::PickValueInArray(const T (&array)[size]) {
|
||||
static_assert(size > 0, "The array must be non empty.");
|
||||
return array[ConsumeIntegralInRange<size_t>(0, size - 1)];
|
||||
}
|
||||
|
||||
template <typename T, size_t size>
|
||||
T FuzzedDataProvider::PickValueInArray(const std::array<T, size> &array) {
|
||||
static_assert(size > 0, "The array must be non empty.");
|
||||
return array[ConsumeIntegralInRange<size_t>(0, size - 1)];
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T FuzzedDataProvider::PickValueInArray(std::initializer_list<const T> list) {
|
||||
// TODO(Dor1s): switch to static_assert once C++14 is allowed.
|
||||
if (!list.size())
|
||||
abort();
|
||||
|
||||
return *(list.begin() + ConsumeIntegralInRange<size_t>(0, list.size() - 1));
|
||||
}
|
||||
|
||||
// Writes |num_bytes| of input data to the given destination pointer. If there
|
||||
// is not enough data left, writes all remaining bytes. Return value is the
|
||||
// number of bytes written.
|
||||
// In general, it's better to avoid using this function, but it may be useful
|
||||
// in cases when it's necessary to fill a certain buffer or object with
|
||||
// fuzzing data.
|
||||
inline size_t FuzzedDataProvider::ConsumeData(void *destination,
|
||||
size_t num_bytes) {
|
||||
num_bytes = std::min(num_bytes, remaining_bytes_);
|
||||
CopyAndAdvance(destination, num_bytes);
|
||||
return num_bytes;
|
||||
}
|
||||
|
||||
// Private methods.
|
||||
inline void FuzzedDataProvider::CopyAndAdvance(void *destination,
|
||||
size_t num_bytes) {
|
||||
std::memcpy(destination, data_ptr_, num_bytes);
|
||||
Advance(num_bytes);
|
||||
}
|
||||
|
||||
inline void FuzzedDataProvider::Advance(size_t num_bytes) {
|
||||
if (num_bytes > remaining_bytes_)
|
||||
abort();
|
||||
|
||||
data_ptr_ += num_bytes;
|
||||
remaining_bytes_ -= num_bytes;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::vector<T> FuzzedDataProvider::ConsumeBytes(size_t size, size_t num_bytes) {
|
||||
static_assert(sizeof(T) == sizeof(uint8_t), "Incompatible data type.");
|
||||
|
||||
// The point of using the size-based constructor below is to increase the
|
||||
// odds of having a vector object with capacity being equal to the length.
|
||||
// That part is always implementation specific, but at least both libc++ and
|
||||
// libstdc++ allocate the requested number of bytes in that constructor,
|
||||
// which seems to be a natural choice for other implementations as well.
|
||||
// To increase the odds even more, we also call |shrink_to_fit| below.
|
||||
std::vector<T> result(size);
|
||||
if (size == 0) {
|
||||
if (num_bytes != 0)
|
||||
abort();
|
||||
return result;
|
||||
}
|
||||
|
||||
CopyAndAdvance(result.data(), num_bytes);
|
||||
|
||||
// Even though |shrink_to_fit| is also implementation specific, we expect it
|
||||
// to provide an additional assurance in case vector's constructor allocated
|
||||
// a buffer which is larger than the actual amount of data we put inside it.
|
||||
result.shrink_to_fit();
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename TS, typename TU>
|
||||
TS FuzzedDataProvider::ConvertUnsignedToSigned(TU value) {
|
||||
static_assert(sizeof(TS) == sizeof(TU), "Incompatible data types.");
|
||||
static_assert(!std::numeric_limits<TU>::is_signed,
|
||||
"Source type must be unsigned.");
|
||||
|
||||
// TODO(Dor1s): change to `if constexpr` once C++17 becomes mainstream.
|
||||
if (std::numeric_limits<TS>::is_modulo)
|
||||
return static_cast<TS>(value);
|
||||
|
||||
// Avoid using implementation-defined unsigned to signed conversions.
|
||||
// To learn more, see https://stackoverflow.com/questions/13150449.
|
||||
if (value <= std::numeric_limits<TS>::max()) {
|
||||
return static_cast<TS>(value);
|
||||
} else {
|
||||
constexpr auto TS_min = std::numeric_limits<TS>::min();
|
||||
return static_cast<TS>(TS_min + static_cast<TS>(value - TS_min));
|
||||
}
|
||||
}
|
||||
|
||||
#endif // LLVM_FUZZER_FUZZED_DATA_PROVIDER_H_
|
3365
third_party/unordered_dense/test/third-party/nanobench.h
vendored
Normal file
3365
third_party/unordered_dense/test/third-party/nanobench.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2527
third_party/unordered_dense/test/third-party/robin_hood.h
vendored
Normal file
2527
third_party/unordered_dense/test/third-party/robin_hood.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
84
third_party/unordered_dense/test/unit/assign_to_move.cpp
vendored
Normal file
84
third_party/unordered_dense/test/unit/assign_to_move.cpp
vendored
Normal file
@ -0,0 +1,84 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#define ENABLE_LOG_LINE
|
||||
#include <app/print.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <type_traits> // for remove_reference, remove_referen...
|
||||
#include <utility> // for move
|
||||
#include <vector> // for vector
|
||||
|
||||
TEST_CASE_MAP("assign_to_moved", int, int) {
|
||||
auto a = map_t();
|
||||
a[1] = 2;
|
||||
auto moved = std::move(a);
|
||||
REQUIRE(moved.size() == 1U);
|
||||
|
||||
auto c = map_t();
|
||||
c[3] = 4;
|
||||
|
||||
// assign to a moved map
|
||||
a = c;
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("move_to_moved", int, int) {
|
||||
auto a = map_t();
|
||||
a[1] = 2;
|
||||
auto moved = std::move(a);
|
||||
|
||||
auto c = map_t();
|
||||
c[3] = 4;
|
||||
|
||||
// assign to a moved map
|
||||
a = std::move(c);
|
||||
|
||||
a[5] = 6;
|
||||
moved[6] = 7;
|
||||
REQUIRE(moved[6] == 7);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("swap", int, int) {
|
||||
{
|
||||
auto b = map_t();
|
||||
{
|
||||
auto a = map_t();
|
||||
b[1] = 2;
|
||||
|
||||
a.swap(b);
|
||||
REQUIRE(a.end() != a.find(1));
|
||||
REQUIRE(b.end() == b.find(1));
|
||||
}
|
||||
REQUIRE(b.end() == b.find(1));
|
||||
b[2] = 3;
|
||||
REQUIRE(b.end() != b.find(2));
|
||||
REQUIRE(b.size() == 1);
|
||||
}
|
||||
|
||||
{
|
||||
auto a = map_t();
|
||||
{
|
||||
auto b = map_t();
|
||||
b[1] = 2;
|
||||
|
||||
a.swap(b);
|
||||
REQUIRE(a.end() != a.find(1));
|
||||
REQUIRE(b.end() == b.find(1));
|
||||
}
|
||||
REQUIRE(a.end() != a.find(1));
|
||||
a[2] = 3;
|
||||
REQUIRE(a.end() != a.find(2));
|
||||
REQUIRE(a.size() == 2);
|
||||
}
|
||||
|
||||
{
|
||||
auto a = map_t();
|
||||
{
|
||||
auto b = map_t();
|
||||
a.swap(b);
|
||||
REQUIRE(a.end() == a.find(1));
|
||||
REQUIRE(b.end() == b.find(1));
|
||||
}
|
||||
REQUIRE(a.end() == a.find(1));
|
||||
}
|
||||
}
|
167
third_party/unordered_dense/test/unit/assignment_combinations.cpp
vendored
Normal file
167
third_party/unordered_dense/test/unit/assignment_combinations.cpp
vendored
Normal file
@ -0,0 +1,167 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#define ENABLE_LOG_LINE
|
||||
#include <app/doctest.h>
|
||||
#include <app/print.h>
|
||||
|
||||
#include <cstdint> // for uint64_t
|
||||
#include <utility> // for pair
|
||||
#include <vector> // for vector
|
||||
|
||||
TEST_CASE_MAP("assignment_combinations_1", uint64_t, uint64_t) {
|
||||
map_t a;
|
||||
map_t b;
|
||||
b = a;
|
||||
REQUIRE(b == a);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("assignment_combinations_2", uint64_t, uint64_t) {
|
||||
map_t a;
|
||||
map_t const& a_const = a;
|
||||
map_t b;
|
||||
a[123] = 321;
|
||||
b = a;
|
||||
|
||||
REQUIRE(a.find(123)->second == 321);
|
||||
REQUIRE(a_const.find(123)->second == 321);
|
||||
|
||||
REQUIRE(b.find(123)->second == 321);
|
||||
a[123] = 111;
|
||||
REQUIRE(a.find(123)->second == 111);
|
||||
REQUIRE(a_const.find(123)->second == 111);
|
||||
REQUIRE(b.find(123)->second == 321);
|
||||
b[123] = 222;
|
||||
REQUIRE(a.find(123)->second == 111);
|
||||
REQUIRE(a_const.find(123)->second == 111);
|
||||
REQUIRE(b.find(123)->second == 222);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("assignment_combinations_3", uint64_t, uint64_t) {
|
||||
map_t a;
|
||||
map_t b;
|
||||
a[123] = 321;
|
||||
a.clear();
|
||||
b = a;
|
||||
|
||||
REQUIRE(a.size() == 0);
|
||||
REQUIRE(b.size() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("assignment_combinations_4", uint64_t, uint64_t) {
|
||||
map_t a;
|
||||
map_t b;
|
||||
b[123] = 321;
|
||||
b = a;
|
||||
|
||||
REQUIRE(a.size() == 0);
|
||||
REQUIRE(b.size() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("assignment_combinations_5", uint64_t, uint64_t) {
|
||||
map_t a;
|
||||
map_t b;
|
||||
b[123] = 321;
|
||||
b.clear();
|
||||
b = a;
|
||||
|
||||
REQUIRE(a.size() == 0);
|
||||
REQUIRE(b.size() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("assignment_combinations_6", uint64_t, uint64_t) {
|
||||
map_t a;
|
||||
a[1] = 2;
|
||||
map_t b;
|
||||
b[3] = 4;
|
||||
b = a;
|
||||
|
||||
REQUIRE(a.size() == 1);
|
||||
REQUIRE(b.size() == 1);
|
||||
REQUIRE(b.find(1)->second == 2);
|
||||
a[1] = 123;
|
||||
REQUIRE(a.size() == 1);
|
||||
REQUIRE(b.size() == 1);
|
||||
REQUIRE(b.find(1)->second == 2);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("assignment_combinations_7", uint64_t, uint64_t) {
|
||||
map_t a;
|
||||
a[1] = 2;
|
||||
a.clear();
|
||||
map_t b;
|
||||
REQUIRE(a == b);
|
||||
b[3] = 4;
|
||||
REQUIRE(a != b);
|
||||
b = a;
|
||||
REQUIRE(a == b);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("assignment_combinations_7", uint64_t, uint64_t) {
|
||||
map_t a;
|
||||
a[1] = 2;
|
||||
map_t b;
|
||||
REQUIRE(a != b);
|
||||
b[3] = 4;
|
||||
b.clear();
|
||||
REQUIRE(a != b);
|
||||
b = a;
|
||||
REQUIRE(a == b);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("assignment_combinations_8", uint64_t, uint64_t) {
|
||||
map_t a;
|
||||
a[1] = 2;
|
||||
a.clear();
|
||||
map_t b;
|
||||
b[3] = 4;
|
||||
REQUIRE(a != b);
|
||||
b.clear();
|
||||
REQUIRE(a == b);
|
||||
b = a;
|
||||
REQUIRE(a == b);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("assignment_combinations_9", uint64_t, uint64_t) {
|
||||
map_t a;
|
||||
a[1] = 2;
|
||||
|
||||
// self assignment should work too
|
||||
map_t* b = &a;
|
||||
a = *b;
|
||||
REQUIRE(a == a);
|
||||
REQUIRE(a.size() == 1);
|
||||
REQUIRE(a.find(1) != a.end());
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("assignment_combinations_10", uint64_t, uint64_t) {
|
||||
map_t a;
|
||||
a[1] = 2;
|
||||
map_t b;
|
||||
b[2] = 1;
|
||||
|
||||
// maps have the same number of elements, but are not equal.
|
||||
REQUIRE(!(a == b));
|
||||
REQUIRE(a != b);
|
||||
REQUIRE(b != a);
|
||||
REQUIRE(!(a == b));
|
||||
REQUIRE(!(b == a));
|
||||
|
||||
map_t c;
|
||||
c[1] = 3;
|
||||
REQUIRE(a != c);
|
||||
REQUIRE(c != a);
|
||||
REQUIRE(!(a == c));
|
||||
REQUIRE(!(c == a));
|
||||
|
||||
b.clear();
|
||||
REQUIRE(a != b);
|
||||
REQUIRE(b != a);
|
||||
REQUIRE(!(a == b));
|
||||
REQUIRE(!(b == a));
|
||||
|
||||
map_t empty;
|
||||
REQUIRE(b == empty);
|
||||
REQUIRE(empty == b);
|
||||
REQUIRE(!(b != empty));
|
||||
REQUIRE(!(empty != b));
|
||||
}
|
32
third_party/unordered_dense/test/unit/at.cpp
vendored
Normal file
32
third_party/unordered_dense/test/unit/at.cpp
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <stdexcept> // for out_of_range
|
||||
|
||||
TEST_CASE_MAP("at", int, int) {
|
||||
map_t map;
|
||||
map_t const& cmap = map;
|
||||
|
||||
// NOLINTNEXTLINE(llvm-else-after-return,readability-else-after-return)
|
||||
REQUIRE_THROWS_AS(map.at(123), std::out_of_range);
|
||||
|
||||
// NOLINTNEXTLINE(llvm-else-after-return,readability-else-after-return)
|
||||
REQUIRE_THROWS_AS(static_cast<void>(map.at(0)), std::out_of_range);
|
||||
|
||||
// NOLINTNEXTLINE(llvm-else-after-return,readability-else-after-return)
|
||||
REQUIRE_THROWS_AS(static_cast<void>(cmap.at(123)), std::out_of_range);
|
||||
|
||||
// NOLINTNEXTLINE(llvm-else-after-return,readability-else-after-return)
|
||||
REQUIRE_THROWS_AS(static_cast<void>(cmap.at(0)), std::out_of_range);
|
||||
|
||||
map[123] = 333;
|
||||
REQUIRE(map.at(123) == 333);
|
||||
REQUIRE(cmap.at(123) == 333);
|
||||
|
||||
// NOLINTNEXTLINE(llvm-else-after-return,readability-else-after-return)
|
||||
REQUIRE_THROWS_AS(static_cast<void>(map.at(0)), std::out_of_range);
|
||||
|
||||
// NOLINTNEXTLINE(llvm-else-after-return,readability-else-after-return)
|
||||
REQUIRE_THROWS_AS(static_cast<void>(cmap.at(0)), std::out_of_range);
|
||||
}
|
82
third_party/unordered_dense/test/unit/bucket.cpp
vendored
Normal file
82
third_party/unordered_dense/test/unit/bucket.cpp
vendored
Normal file
@ -0,0 +1,82 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/counter.h>
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <limits>
|
||||
#include <stdexcept> // for out_of_range
|
||||
|
||||
using map_default_t = ankerl::unordered_dense::map<std::string, size_t>;
|
||||
|
||||
// big bucket type allows 2^64 elements, but has more memory & CPU overhead.
|
||||
using map_big_t = ankerl::unordered_dense::map<std::string,
|
||||
size_t,
|
||||
ankerl::unordered_dense::hash<std::string>,
|
||||
std::equal_to<std::string>,
|
||||
std::allocator<std::pair<std::string, size_t>>,
|
||||
ankerl::unordered_dense::bucket_type::big>;
|
||||
|
||||
static_assert(sizeof(map_default_t::bucket_type) == 8U);
|
||||
static_assert(sizeof(map_big_t::bucket_type) == sizeof(size_t) + 4U);
|
||||
static_assert(map_default_t::max_size() == map_default_t::max_bucket_count());
|
||||
|
||||
#if SIZE_MAX == UINT32_MAX
|
||||
static_assert(map_default_t::max_size() == uint64_t{1} << 31U);
|
||||
static_assert(map_big_t::max_size() == uint64_t{1} << 31U);
|
||||
#else
|
||||
static_assert(map_default_t::max_size() == uint64_t{1} << 32U);
|
||||
static_assert(map_big_t::max_size() == uint64_t{1} << 63U);
|
||||
#endif
|
||||
|
||||
struct bucket_micro {
|
||||
static constexpr uint8_t dist_inc = 1U << 1U; // 1 bits for fingerprint
|
||||
static constexpr uint8_t fingerprint_mask = dist_inc - 1; // 11 bit = 2048 positions for distance
|
||||
|
||||
uint8_t m_dist_and_fingerprint;
|
||||
uint8_t m_value_idx;
|
||||
};
|
||||
|
||||
TYPE_TO_STRING_MAP(counter::obj,
|
||||
counter::obj,
|
||||
ankerl::unordered_dense::hash<counter::obj>,
|
||||
std::equal_to<counter::obj>,
|
||||
std::allocator<std::pair<counter::obj, counter::obj>>,
|
||||
bucket_micro);
|
||||
|
||||
TEST_CASE_MAP("bucket_micro",
|
||||
counter::obj,
|
||||
counter::obj,
|
||||
ankerl::unordered_dense::hash<counter::obj>,
|
||||
std::equal_to<counter::obj>,
|
||||
std::allocator<std::pair<counter::obj, counter::obj>>,
|
||||
bucket_micro) {
|
||||
counter counts;
|
||||
INFO(counts);
|
||||
|
||||
auto map = map_t();
|
||||
INFO("map_t::max_size()=" << map_t::max_size());
|
||||
for (size_t i = 0; i < map_t::max_size(); ++i) {
|
||||
if (i == 255) {
|
||||
INFO("i=" << i);
|
||||
}
|
||||
auto const r = map.try_emplace({i, counts}, i, counts);
|
||||
REQUIRE(r.second);
|
||||
|
||||
auto it = map.find({0, counts});
|
||||
REQUIRE(it != map.end());
|
||||
}
|
||||
// NOLINTNEXTLINE(llvm-else-after-return,readability-else-after-return)
|
||||
REQUIRE_THROWS_AS(map.try_emplace({map_t::max_size(), counts}, map_t::max_size(), counts), std::overflow_error);
|
||||
|
||||
// check that all elements are there
|
||||
REQUIRE(map.size() == map_t::max_size());
|
||||
for (size_t i = 0; i < map_t::max_size(); ++i) {
|
||||
INFO(i);
|
||||
auto it = map.find({i, counts});
|
||||
REQUIRE(it != map.end());
|
||||
REQUIRE(it->first.get() == i);
|
||||
REQUIRE(it->second.get() == i);
|
||||
}
|
||||
}
|
20
third_party/unordered_dense/test/unit/contains.cpp
vendored
Normal file
20
third_party/unordered_dense/test/unit/contains.cpp
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <cstdint> // for uint64_t
|
||||
|
||||
TEST_CASE_MAP("contains", uint64_t, uint64_t) {
|
||||
static_assert(std::is_same_v<bool, decltype(map_t{}.contains(1))>);
|
||||
|
||||
auto map = map_t();
|
||||
|
||||
REQUIRE(!map.contains(0));
|
||||
REQUIRE(!map.contains(123));
|
||||
map[123];
|
||||
REQUIRE(!map.contains(0));
|
||||
REQUIRE(map.contains(123));
|
||||
map.clear();
|
||||
REQUIRE(!map.contains(0));
|
||||
REQUIRE(!map.contains(123));
|
||||
}
|
81
third_party/unordered_dense/test/unit/copy_and_assign_maps.cpp
vendored
Normal file
81
third_party/unordered_dense/test/unit/copy_and_assign_maps.cpp
vendored
Normal file
@ -0,0 +1,81 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#define ENABLE_LOG_LINE
|
||||
#include <app/doctest.h>
|
||||
#include <app/print.h>
|
||||
|
||||
#include <utility> // for pair
|
||||
#include <vector> // for vector
|
||||
|
||||
// creates a map with some data in it
|
||||
template <class M>
|
||||
[[nodiscard]] auto create_map(int num_elements) -> M {
|
||||
M m;
|
||||
for (int i = 0; i < num_elements; ++i) {
|
||||
m[static_cast<typename M::key_type>((i + 123) * 7)] = static_cast<typename M::mapped_type>(i);
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("copy_and_assign_maps_1", int, int) {
|
||||
auto a = create_map<map_t>(15);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("copy_and_assign_maps_2", int, int) {
|
||||
auto a = create_map<map_t>(100);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("copy_and_assign_maps_3", int, int) {
|
||||
auto a = create_map<map_t>(1);
|
||||
// NOLINTNEXTLINE(performance-unnecessary-copy-initialization)
|
||||
auto b = a;
|
||||
REQUIRE(a == b);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("copy_and_assign_maps_4", int, int) {
|
||||
map_t a;
|
||||
REQUIRE(a.empty());
|
||||
a.clear();
|
||||
REQUIRE(a.empty());
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("copy_and_assign_maps_5", int, int) {
|
||||
auto a = create_map<map_t>(100);
|
||||
// NOLINTNEXTLINE(performance-unnecessary-copy-initialization)
|
||||
auto b = a;
|
||||
REQUIRE(b == a);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("copy_and_assign_maps_6", int, int) {
|
||||
map_t a;
|
||||
a[123] = 321;
|
||||
a.clear();
|
||||
auto const maps = std::vector<map_t>(10, a);
|
||||
|
||||
for (auto const& map : maps) {
|
||||
REQUIRE(map.empty());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("copy_and_assign_maps_7", int, int) {
|
||||
auto const maps = std::vector<map_t>(10);
|
||||
REQUIRE(maps.size() == 10U);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("copy_and_assign_maps_8", int, int) {
|
||||
map_t a;
|
||||
auto const maps = std::vector<map_t>(12, a);
|
||||
REQUIRE(maps.size() == 12U);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("copy_and_assign_maps_9", int, int) {
|
||||
map_t a;
|
||||
a[123] = 321;
|
||||
auto const maps = std::vector<map_t>(10, a);
|
||||
a[123] = 1;
|
||||
|
||||
for (auto const& map : maps) {
|
||||
REQUIRE(map.size() == 1);
|
||||
REQUIRE(map.find(123)->second == 321);
|
||||
}
|
||||
}
|
28
third_party/unordered_dense/test/unit/copyassignment.cpp
vendored
Normal file
28
third_party/unordered_dense/test/unit/copyassignment.cpp
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
TEST_CASE_MAP("copyassignment", std::string, std::string) {
|
||||
auto map = map_t();
|
||||
auto tmp = map_t();
|
||||
|
||||
map.emplace("a", "b");
|
||||
map = tmp;
|
||||
map.emplace("c", "d");
|
||||
|
||||
REQUIRE(map.size() == 1);
|
||||
REQUIRE(map["c"] == "d");
|
||||
REQUIRE(map.size() == 1);
|
||||
|
||||
REQUIRE(tmp.size() == 0);
|
||||
|
||||
map["e"] = "f";
|
||||
REQUIRE(map.size() == 2);
|
||||
REQUIRE(tmp.size() == 0);
|
||||
|
||||
tmp["g"] = "h";
|
||||
REQUIRE(map.size() == 2);
|
||||
REQUIRE(tmp.size() == 1);
|
||||
}
|
12
third_party/unordered_dense/test/unit/count.cpp
vendored
Normal file
12
third_party/unordered_dense/test/unit/count.cpp
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
TEST_CASE_MAP("count", int, int) {
|
||||
auto map = map_t();
|
||||
REQUIRE(map.count(123) == 0);
|
||||
REQUIRE(map.count(0) == 0);
|
||||
map[123];
|
||||
REQUIRE(map.count(123) == 1);
|
||||
REQUIRE(map.count(0) == 0);
|
||||
}
|
89
third_party/unordered_dense/test/unit/ctors.cpp
vendored
Normal file
89
third_party/unordered_dense/test/unit/ctors.cpp
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/counter.h>
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
#include <utility> // for pair
|
||||
|
||||
// very minimal input iterator
|
||||
// https://en.cppreference.com/w/cpp/named_req/InputIterator#Concept
|
||||
class it {
|
||||
std::pair<counter::obj, counter::obj> m_kv;
|
||||
|
||||
public:
|
||||
it(size_t val, counter& counts)
|
||||
: m_kv({val, counts}, {val, counts}) {}
|
||||
|
||||
auto operator++() -> it& {
|
||||
++m_kv.first.get();
|
||||
++m_kv.second.get();
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto operator*() -> std::pair<counter::obj, counter::obj> const& {
|
||||
return m_kv;
|
||||
}
|
||||
|
||||
auto operator!=(it const& other) const -> bool {
|
||||
return other.m_kv.first.get() != m_kv.first.get() || other.m_kv.second.get() != m_kv.second.get();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CASE_MAP("ctors_map", counter::obj, counter::obj) {
|
||||
using alloc_t = typename map_t::allocator_type;
|
||||
using hash_t = typename map_t::hasher;
|
||||
using key_eq_t = typename map_t::key_equal;
|
||||
|
||||
auto counts = counter();
|
||||
INFO(counts);
|
||||
|
||||
{ auto m = map_t{}; }
|
||||
{ auto m = map_t{0, alloc_t{}}; }
|
||||
{ auto m = map_t{0, hash_t{}, alloc_t{}}; }
|
||||
{ auto m = map_t{alloc_t{}}; }
|
||||
REQUIRE(counts.dtor() == 0);
|
||||
|
||||
{
|
||||
auto begin_it = it{size_t{0}, counts};
|
||||
auto end_it = it{size_t{10}, counts};
|
||||
auto m = map_t{begin_it, end_it};
|
||||
REQUIRE(m.size() == 10);
|
||||
}
|
||||
{
|
||||
auto begin_it = it{size_t{0}, counts};
|
||||
auto end_it = it{size_t{10}, counts};
|
||||
auto m = map_t{begin_it, end_it, 0, alloc_t{}};
|
||||
REQUIRE(m.size() == 10);
|
||||
}
|
||||
{
|
||||
auto begin_it = it{size_t{0}, counts};
|
||||
auto end_it = it{size_t{10}, counts};
|
||||
auto m = map_t{begin_it, end_it, 0, hash_t{}, alloc_t{}};
|
||||
REQUIRE(m.size() == 10);
|
||||
}
|
||||
{
|
||||
auto begin_it = it{size_t{0}, counts};
|
||||
auto end_it = it{size_t{10}, counts};
|
||||
auto m = map_t{begin_it, end_it, 0, hash_t{}, key_eq_t{}};
|
||||
REQUIRE(m.size() == 10);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("ctor_bucket_count_map", counter::obj, counter::obj) {
|
||||
{
|
||||
auto m = map_t{};
|
||||
// depends on initial bucket count, could also be 0
|
||||
// REQUIRE(m.bucket_count() == 2U);
|
||||
}
|
||||
{
|
||||
auto m = map_t{150U};
|
||||
REQUIRE(m.bucket_count() == 256U);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_SET("ctor_bucket_count_set", int) {
|
||||
auto m = ankerl::unordered_dense::set<int>{{1, 2, 3, 4}, 300U};
|
||||
REQUIRE(m.size() == 4U);
|
||||
REQUIRE(m.bucket_count() == 512U);
|
||||
}
|
34
third_party/unordered_dense/test/unit/custom_container.cpp
vendored
Normal file
34
third_party/unordered_dense/test/unit/custom_container.cpp
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <deque>
|
||||
|
||||
static_assert(
|
||||
ankerl::unordered_dense::detail::is_detected_v<ankerl::unordered_dense::detail::detect_iterator, std::deque<int>>);
|
||||
|
||||
static_assert(
|
||||
!ankerl::unordered_dense::detail::is_detected_v<ankerl::unordered_dense::detail::detect_iterator, std::allocator<int>>);
|
||||
|
||||
TEST_CASE_MAP("custom_container",
|
||||
int,
|
||||
std::string,
|
||||
ankerl::unordered_dense::hash<int>,
|
||||
std::equal_to<int>,
|
||||
std::deque<std::pair<int, std::string>>) {
|
||||
auto map = map_t();
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
map[i] = std::to_string(i);
|
||||
}
|
||||
|
||||
REQUIRE(std::is_same_v<std::deque<std::pair<int, std::string>>, typename map_t::value_container_type>);
|
||||
std::deque<std::pair<int, std::string>> const container = std::move(map).extract();
|
||||
|
||||
auto m2 = map_t();
|
||||
// we allow use-after-move
|
||||
m2 = map; // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved)
|
||||
|
||||
auto map2 = map;
|
||||
std::swap(map2, map);
|
||||
}
|
78
third_party/unordered_dense/test/unit/custom_container_boost.cpp
vendored
Normal file
78
third_party/unordered_dense/test/unit/custom_container_boost.cpp
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
#if ANKERL_UNORDERED_DENSE_HAS_BOOST
|
||||
|
||||
# if __clang__
|
||||
# pragma clang diagnostic push
|
||||
# pragma clang diagnostic ignored "-Wold-style-cast"
|
||||
# endif
|
||||
# include <ankerl/unordered_dense.h>
|
||||
|
||||
# include <app/doctest.h>
|
||||
|
||||
# include <boost/container/vector.hpp>
|
||||
# include <boost/interprocess/allocators/allocator.hpp>
|
||||
# include <boost/interprocess/allocators/node_allocator.hpp>
|
||||
# include <boost/interprocess/containers/vector.hpp>
|
||||
# include <boost/interprocess/managed_shared_memory.hpp>
|
||||
|
||||
# include <deque>
|
||||
|
||||
// Alias an STL-like allocator of ints that allocates ints from the segment
|
||||
using shmem_allocator =
|
||||
boost::interprocess::allocator<std::pair<int, std::string>, boost::interprocess::managed_shared_memory::segment_manager>;
|
||||
using shmem_vector = boost::interprocess::vector<std::pair<int, std::string>, shmem_allocator>;
|
||||
|
||||
// Remove shared memory on construction and destruction
|
||||
struct shm_remove {
|
||||
shm_remove() {
|
||||
boost::interprocess::shared_memory_object::remove("MySharedMemory");
|
||||
}
|
||||
~shm_remove() {
|
||||
boost::interprocess::shared_memory_object::remove("MySharedMemory");
|
||||
}
|
||||
|
||||
shm_remove(shm_remove const&) = delete;
|
||||
shm_remove(shm_remove&&) = delete;
|
||||
auto operator=(shm_remove const&) -> shm_remove = delete;
|
||||
auto operator=(shm_remove&&) -> shm_remove = delete;
|
||||
};
|
||||
|
||||
TYPE_TO_STRING_MAP(int, std::string, ankerl::unordered_dense::hash<int>, std::equal_to<int>, shmem_vector);
|
||||
|
||||
// See https://www.boost.org/doc/libs/1_80_0/doc/html/interprocess/allocators_containers.html
|
||||
TEST_CASE_TEMPLATE(
|
||||
"boost_container_vector",
|
||||
map_t,
|
||||
ankerl::unordered_dense::map<int, std::string, ankerl::unordered_dense::hash<int>, std::equal_to<int>, shmem_vector>,
|
||||
ankerl::unordered_dense::
|
||||
segmented_map<int, std::string, ankerl::unordered_dense::hash<int>, std::equal_to<int>, shmem_allocator>) {
|
||||
|
||||
auto remover = shm_remove();
|
||||
|
||||
// Create shared memory
|
||||
auto segment = boost::interprocess::managed_shared_memory(boost::interprocess::create_only, "MySharedMemory", 1024 * 1024);
|
||||
auto map = map_t{shmem_allocator{segment.get_segment_manager()}};
|
||||
|
||||
int total = 10000;
|
||||
|
||||
for (int i = 0; i < total; ++i) {
|
||||
map.try_emplace(i, std::to_string(i));
|
||||
}
|
||||
|
||||
REQUIRE(map.size() == static_cast<size_t>(total));
|
||||
for (int i = 0; i < total; ++i) {
|
||||
auto it = map.find(i);
|
||||
REQUIRE(it != map.end());
|
||||
REQUIRE(it->first == i);
|
||||
REQUIRE(it->second == std::to_string(i));
|
||||
}
|
||||
|
||||
map.erase(total + 123);
|
||||
REQUIRE(map.size() == static_cast<size_t>(total));
|
||||
map.erase(29);
|
||||
REQUIRE(map.size() == static_cast<size_t>(total - 1));
|
||||
|
||||
map.emplace(std::pair<int, std::string>(9999999, "hello"));
|
||||
REQUIRE(map.size() == static_cast<size_t>(total));
|
||||
}
|
||||
|
||||
#endif // ANKERL_UNORDERED_DENSE_HAS_BOOST
|
94
third_party/unordered_dense/test/unit/custom_hash.cpp
vendored
Normal file
94
third_party/unordered_dense/test/unit/custom_hash.cpp
vendored
Normal file
@ -0,0 +1,94 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
namespace {
|
||||
|
||||
struct id {
|
||||
uint64_t value{}; // NOLINT
|
||||
|
||||
auto operator==(id const& other) const -> bool {
|
||||
return value == other.value;
|
||||
}
|
||||
};
|
||||
|
||||
struct custom_hash_simple {
|
||||
[[nodiscard]] auto operator()(id const& x) const noexcept -> uint64_t {
|
||||
return x.value;
|
||||
}
|
||||
};
|
||||
|
||||
struct custom_hash_avalanching {
|
||||
using is_avalanching = void;
|
||||
|
||||
auto operator()(id const& x) const noexcept -> uint64_t {
|
||||
return ankerl::unordered_dense::detail::wyhash::hash(x.value);
|
||||
}
|
||||
};
|
||||
|
||||
struct point {
|
||||
int x{}; // NOLINT
|
||||
int y{}; // NOLINT
|
||||
|
||||
auto operator==(point const& other) const -> bool {
|
||||
return x == other.x && y == other.y;
|
||||
}
|
||||
};
|
||||
|
||||
struct custom_hash_unique_object_representation {
|
||||
using is_avalanching = void;
|
||||
|
||||
[[nodiscard]] auto operator()(point const& f) const noexcept -> uint64_t {
|
||||
static_assert(std::has_unique_object_representations_v<point>);
|
||||
return ankerl::unordered_dense::detail::wyhash::hash(&f, sizeof(f));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
template <>
|
||||
struct ankerl::unordered_dense::hash<id> {
|
||||
using is_avalanching = void;
|
||||
|
||||
[[nodiscard]] auto operator()(id const& x) const noexcept -> uint64_t {
|
||||
return detail::wyhash::hash(x.value);
|
||||
}
|
||||
};
|
||||
|
||||
TYPE_TO_STRING_SET(id);
|
||||
TYPE_TO_STRING_SET(id, custom_hash_simple);
|
||||
TYPE_TO_STRING_SET(id, custom_hash_avalanching);
|
||||
TYPE_TO_STRING_SET(point, custom_hash_unique_object_representation);
|
||||
|
||||
TEST_CASE_SET("custom_hash_simple", id, custom_hash_simple) {
|
||||
auto set = set_t();
|
||||
set.insert(id{124});
|
||||
}
|
||||
|
||||
TEST_CASE_SET("custom_hash_avalanching", id, custom_hash_avalanching) {
|
||||
auto set = set_t();
|
||||
set.insert(id{124});
|
||||
}
|
||||
|
||||
TEST_CASE_SET("custom_hash_unique", point, custom_hash_unique_object_representation) {
|
||||
auto set = set_t();
|
||||
set.insert(point{123, 321});
|
||||
}
|
||||
|
||||
TEST_CASE_SET("custom_hash_default", id) {
|
||||
auto set = set_t();
|
||||
set.insert(id{124});
|
||||
}
|
||||
|
||||
static_assert(
|
||||
!ankerl::unordered_dense::detail::is_detected_v<ankerl::unordered_dense::detail::detect_avalanching, custom_hash_simple>);
|
||||
|
||||
static_assert(ankerl::unordered_dense::detail::is_detected_v<ankerl::unordered_dense::detail::detect_avalanching,
|
||||
custom_hash_avalanching>);
|
||||
static_assert(ankerl::unordered_dense::detail::is_detected_v<ankerl::unordered_dense::detail::detect_avalanching,
|
||||
custom_hash_unique_object_representation>);
|
||||
|
||||
static_assert(!ankerl::unordered_dense::detail::is_detected_v<ankerl::unordered_dense::detail::detect_avalanching,
|
||||
ankerl::unordered_dense::hash<point>>);
|
7
third_party/unordered_dense/test/unit/deduction_guides.cpp
vendored
Normal file
7
third_party/unordered_dense/test/unit/deduction_guides.cpp
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
#include <doctest.h> // for TestCase, skip, TEST_CASE, test_...
|
||||
|
||||
TEST_CASE("deduction_guide") {
|
||||
// TODO(martinus) not yet possible, only in c++20. See
|
||||
// https://stackoverflow.com/a/41008415
|
||||
// https://en.cppreference.com/w/cpp/language/class_template_argument_deduction
|
||||
}
|
27
third_party/unordered_dense/test/unit/diamond.cpp
vendored
Normal file
27
third_party/unordered_dense/test/unit/diamond.cpp
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
#include <vector> // for vector
|
||||
|
||||
// struct that provides both hash and equals operator
|
||||
struct hash_with_equal {
|
||||
auto operator()(int x) const -> size_t {
|
||||
return static_cast<size_t>(x);
|
||||
}
|
||||
|
||||
auto operator()(int a, int b) const -> bool {
|
||||
return a == b;
|
||||
}
|
||||
};
|
||||
|
||||
TYPE_TO_STRING_MAP(int, int, hash_with_equal, hash_with_equal);
|
||||
|
||||
// make sure the map works with the same type (check that it handles the diamond problem)
|
||||
TEST_CASE_MAP("diamond_problem", int, int, hash_with_equal, hash_with_equal) {
|
||||
auto map = map_t();
|
||||
map[1] = 2;
|
||||
REQUIRE(map.size() == 1);
|
||||
REQUIRE(map.find(1) != map.end());
|
||||
}
|
44
third_party/unordered_dense/test/unit/empty.cpp
vendored
Normal file
44
third_party/unordered_dense/test/unit/empty.cpp
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
TEST_CASE_MAP("empty_map_operations", int, int) {
|
||||
map_t m;
|
||||
|
||||
REQUIRE(m.end() == m.find(123));
|
||||
REQUIRE(m.end() == m.begin());
|
||||
m[32];
|
||||
REQUIRE(m.end() != m.begin());
|
||||
REQUIRE(m.end() == m.find(123));
|
||||
REQUIRE(m.end() != m.find(32));
|
||||
|
||||
m = map_t();
|
||||
REQUIRE(m.end() == m.begin());
|
||||
REQUIRE(m.end() == m.find(123));
|
||||
REQUIRE(m.end() == m.find(32));
|
||||
|
||||
map_t m2(m);
|
||||
REQUIRE(m2.end() == m2.begin());
|
||||
REQUIRE(m2.end() == m2.find(123));
|
||||
REQUIRE(m2.end() == m2.find(32));
|
||||
m2[32];
|
||||
REQUIRE(m2.end() != m2.begin());
|
||||
REQUIRE(m2.end() == m2.find(123));
|
||||
REQUIRE(m2.end() != m2.find(32));
|
||||
|
||||
map_t empty;
|
||||
map_t m3(empty);
|
||||
REQUIRE(m3.end() == m3.begin());
|
||||
REQUIRE(m3.end() == m3.find(123));
|
||||
REQUIRE(m3.end() == m3.find(32));
|
||||
m3[32];
|
||||
REQUIRE(m3.end() != m3.begin());
|
||||
REQUIRE(m3.end() == m3.find(123));
|
||||
REQUIRE(m3.end() != m3.find(32));
|
||||
|
||||
map_t m4(std::move(empty));
|
||||
REQUIRE(m4.count(123) == 0);
|
||||
REQUIRE(m4.end() == m4.begin());
|
||||
REQUIRE(m4.end() == m4.find(123));
|
||||
REQUIRE(m4.end() == m4.find(32));
|
||||
}
|
34
third_party/unordered_dense/test/unit/equal_range.cpp
vendored
Normal file
34
third_party/unordered_dense/test/unit/equal_range.cpp
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <type_traits> // for add_const_t
|
||||
#include <utility> // for pair, as_const
|
||||
#include <vector> // for vector
|
||||
|
||||
TEST_CASE_MAP("equal_range", int, int) {
|
||||
auto map = map_t();
|
||||
// auto map = std::unordered_map<int, int>();
|
||||
|
||||
auto range = map.equal_range(123);
|
||||
REQUIRE(range.first == map.end());
|
||||
REQUIRE(range.second == map.end());
|
||||
|
||||
map.try_emplace(1, 1);
|
||||
range = map.equal_range(123);
|
||||
REQUIRE(range.first == map.end());
|
||||
REQUIRE(range.second == map.end());
|
||||
|
||||
int const x = 1;
|
||||
auto const_range = std::as_const(map).equal_range(x);
|
||||
REQUIRE(const_range.first == map.begin());
|
||||
REQUIRE(const_range.second == map.end());
|
||||
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
map.try_emplace(i, i);
|
||||
}
|
||||
range = map.equal_range(50);
|
||||
auto after_first = ++range.first;
|
||||
REQUIRE(range.second == after_first);
|
||||
REQUIRE(range.second != map.end());
|
||||
}
|
49
third_party/unordered_dense/test/unit/erase.cpp
vendored
Normal file
49
third_party/unordered_dense/test/unit/erase.cpp
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <third-party/nanobench.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <algorithm> // for all_of
|
||||
#include <cstddef> // for size_t
|
||||
#include <cstdint> // for uint32_t
|
||||
#include <unordered_set> // for unordered_set, operator!=
|
||||
#include <vector> // for vector
|
||||
|
||||
template <typename A, typename B>
|
||||
[[nodiscard]] auto is_eq(A const& a, B const& b) -> bool {
|
||||
if (a.size() != b.size()) {
|
||||
return false;
|
||||
}
|
||||
return std::all_of(a.begin(), a.end(), [&b](auto const& k) {
|
||||
return b.end() != b.find(k);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_CASE_SET("insert_erase_random", uint32_t) {
|
||||
auto uds = set_t();
|
||||
auto us = std::unordered_set<uint32_t>();
|
||||
auto rng = ankerl::nanobench::Rng(123);
|
||||
for (size_t i = 0; i < 10000; ++i) {
|
||||
auto key = rng.bounded(1000);
|
||||
uds.insert(key);
|
||||
us.insert(key);
|
||||
REQUIRE(uds.size() == us.size());
|
||||
|
||||
key = rng.bounded(1000);
|
||||
REQUIRE(uds.erase(key) == us.erase(key));
|
||||
REQUIRE(uds.size() == us.size());
|
||||
}
|
||||
REQUIRE(is_eq(uds, us));
|
||||
auto k = *uds.begin();
|
||||
uds.erase(k);
|
||||
REQUIRE(!is_eq(uds, us));
|
||||
us.erase(k);
|
||||
REQUIRE(is_eq(uds, us));
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("erase", int, int) {
|
||||
auto map = ankerl::unordered_dense::map<int, int>();
|
||||
REQUIRE(0 == map.erase(123));
|
||||
REQUIRE(0 == map.count(0));
|
||||
}
|
54
third_party/unordered_dense/test/unit/erase_if.cpp
vendored
Normal file
54
third_party/unordered_dense/test/unit/erase_if.cpp
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/counter.h>
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
#include <cstdint> // for uint64_t
|
||||
#include <utility> // for pair
|
||||
|
||||
TEST_CASE_SET("erase_if_set", counter::obj) {
|
||||
auto counts = counter();
|
||||
INFO(counts);
|
||||
auto set = set_t();
|
||||
|
||||
for (size_t i = 0; i < 1000; ++i) {
|
||||
set.emplace(i, counts);
|
||||
}
|
||||
REQUIRE(set.size() == 1000);
|
||||
auto num_erased = std::erase_if(set, [](counter::obj const& obj) {
|
||||
return 0 == obj.get() % 3;
|
||||
});
|
||||
REQUIRE(num_erased == 334);
|
||||
|
||||
REQUIRE(set.size() == 666);
|
||||
for (size_t i = 0; i < 1000; ++i) {
|
||||
if (0 == i % 3) {
|
||||
REQUIRE(!set.contains({i, counts}));
|
||||
} else {
|
||||
REQUIRE(set.contains({i, counts}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("erase_if_map", uint64_t, uint64_t) {
|
||||
auto map = map_t();
|
||||
for (size_t i = 0; i < 1000; ++i) {
|
||||
map.try_emplace(i, i);
|
||||
}
|
||||
|
||||
REQUIRE(map.size() == 1000);
|
||||
auto num_erased = std::erase_if(map, [](std::pair<uint64_t, uint64_t> const& x) {
|
||||
return 0 == x.second % 2;
|
||||
});
|
||||
|
||||
REQUIRE(num_erased == 500);
|
||||
REQUIRE(map.size() == 500);
|
||||
for (size_t i = 0; i < 1000; ++i) {
|
||||
if (0 == i % 2) {
|
||||
REQUIRE(!map.contains(i));
|
||||
} else {
|
||||
REQUIRE(map.contains(i));
|
||||
}
|
||||
}
|
||||
}
|
32
third_party/unordered_dense/test/unit/erase_range.cpp
vendored
Normal file
32
third_party/unordered_dense/test/unit/erase_range.cpp
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/counter.h>
|
||||
#include <app/doctest.h>
|
||||
#include <third-party/nanobench.h>
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
#include <vector> // for vector
|
||||
|
||||
TEST_CASE_MAP("erase_range", counter::obj, counter::obj) {
|
||||
int const num_elements = 10;
|
||||
|
||||
for (int first_pos = 0; first_pos <= num_elements; ++first_pos) {
|
||||
for (int last_pos = first_pos; last_pos <= num_elements; ++last_pos) {
|
||||
auto counts = counter();
|
||||
INFO(counts);
|
||||
|
||||
auto map = map_t();
|
||||
|
||||
for (size_t i = 0; i < num_elements; ++i) {
|
||||
auto key = i;
|
||||
auto val = i * 1000;
|
||||
map.try_emplace({key, counts}, val, counts);
|
||||
}
|
||||
REQUIRE(map.size() == num_elements);
|
||||
|
||||
auto it_ret = map.erase(map.cbegin() + first_pos, map.cbegin() + last_pos);
|
||||
REQUIRE(map.size() == static_cast<size_t>(num_elements - (last_pos - first_pos)));
|
||||
REQUIRE(it_ret == map.begin() + first_pos);
|
||||
}
|
||||
}
|
||||
}
|
33
third_party/unordered_dense/test/unit/explicit.cpp
vendored
Normal file
33
third_party/unordered_dense/test/unit/explicit.cpp
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
struct texture {
|
||||
int m_width;
|
||||
int m_height;
|
||||
void* m_data;
|
||||
};
|
||||
|
||||
struct per_image {
|
||||
std::unordered_map<void*, texture*> m_texture_index;
|
||||
};
|
||||
|
||||
template <typename Map>
|
||||
struct scene {
|
||||
std::vector<per_image> m_per_image;
|
||||
Map m_textures_per_key;
|
||||
};
|
||||
|
||||
template <typename Map>
|
||||
struct app_state {
|
||||
scene<Map> m_scene;
|
||||
};
|
||||
|
||||
TYPE_TO_STRING_MAP(void*, texture*);
|
||||
|
||||
TEST_CASE_MAP("unit_create_AppState_issue_97", void*, texture*) {
|
||||
app_state<map_t> const app_state{};
|
||||
}
|
62
third_party/unordered_dense/test/unit/extract.cpp
vendored
Normal file
62
third_party/unordered_dense/test/unit/extract.cpp
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/counter.h>
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
TEST_CASE_MAP("extract", counter::obj, counter::obj) {
|
||||
auto counts = counter();
|
||||
INFO(counts);
|
||||
|
||||
auto container = typename map_t::value_container_type();
|
||||
{
|
||||
auto map = map_t();
|
||||
|
||||
for (size_t i = 0; i < 100; ++i) {
|
||||
map.try_emplace(counter::obj{i, counts}, i, counts);
|
||||
}
|
||||
|
||||
container = std::move(map).extract();
|
||||
}
|
||||
|
||||
REQUIRE(container.size() == 100U);
|
||||
for (size_t i = 0; i < container.size(); ++i) {
|
||||
REQUIRE(container[i].first.get() == i);
|
||||
REQUIRE(container[i].second.get() == i);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("extract_element", counter::obj, counter::obj) {
|
||||
auto counts = counter();
|
||||
INFO(counts);
|
||||
|
||||
counts("init");
|
||||
auto map = map_t();
|
||||
for (size_t i = 0; i < 100; ++i) {
|
||||
map.try_emplace(counter::obj{i, counts}, i, counts);
|
||||
}
|
||||
|
||||
// extract(key)
|
||||
for (size_t i = 0; i < 20; ++i) {
|
||||
auto query = counter::obj{i, counts};
|
||||
counts("before remove 1");
|
||||
auto opt = map.extract(query);
|
||||
counts("after remove 1");
|
||||
REQUIRE(opt);
|
||||
REQUIRE(opt->first.get() == i);
|
||||
REQUIRE(opt->second.get() == i);
|
||||
}
|
||||
REQUIRE(map.size() == 80);
|
||||
|
||||
// extract iterator
|
||||
for (size_t i = 20; i < 100; ++i) {
|
||||
auto query = counter::obj{i, counts};
|
||||
auto it = map.find(query);
|
||||
REQUIRE(it != map.end());
|
||||
auto opt = map.extract(it);
|
||||
REQUIRE(opt.first.get() == i);
|
||||
REQUIRE(opt.second.get() == i);
|
||||
}
|
||||
REQUIRE(map.empty());
|
||||
}
|
121
third_party/unordered_dense/test/unit/fuzz_api.cpp
vendored
Normal file
121
third_party/unordered_dense/test/unit/fuzz_api.cpp
vendored
Normal file
@ -0,0 +1,121 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
#include <app/doctest.h>
|
||||
#include <fuzz/provider.h>
|
||||
#include <fuzz/run.h>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
template <typename Map>
|
||||
void do_fuzz_api(fuzz::provider p) {
|
||||
auto counts = counter();
|
||||
auto map = Map();
|
||||
p.repeat_oneof(
|
||||
[&] {
|
||||
auto key = p.integral<size_t>();
|
||||
auto it = map.try_emplace(counter::obj(key, counts), counter::obj(key, counts)).first;
|
||||
REQUIRE(it != map.end());
|
||||
REQUIRE(it->first.get() == key);
|
||||
},
|
||||
[&] {
|
||||
auto key = p.integral<size_t>();
|
||||
map.emplace(std::piecewise_construct, std::forward_as_tuple(key, counts), std::forward_as_tuple(key + 77, counts));
|
||||
},
|
||||
[&] {
|
||||
auto key = p.integral<size_t>();
|
||||
map[counter::obj(key, counts)] = counter::obj(key + 123, counts);
|
||||
},
|
||||
[&] {
|
||||
auto key = p.integral<size_t>();
|
||||
map.insert(std::pair<counter::obj, counter::obj>(counter::obj(key, counts), counter::obj(key, counts)));
|
||||
},
|
||||
[&] {
|
||||
auto key = p.integral<size_t>();
|
||||
map.insert_or_assign(counter::obj(key, counts), counter::obj(key + 1, counts));
|
||||
},
|
||||
[&] {
|
||||
auto key = p.integral<size_t>();
|
||||
map.erase(counter::obj(key, counts));
|
||||
},
|
||||
[&] {
|
||||
map = Map{};
|
||||
},
|
||||
[&] {
|
||||
auto m = Map{};
|
||||
m.swap(map);
|
||||
},
|
||||
[&] {
|
||||
map.clear();
|
||||
},
|
||||
[&] {
|
||||
auto s = p.bounded<size_t>(1025);
|
||||
map.rehash(s);
|
||||
},
|
||||
[&] {
|
||||
auto s = p.bounded<size_t>(1025);
|
||||
map.reserve(s);
|
||||
},
|
||||
[&] {
|
||||
auto key = p.integral<size_t>();
|
||||
auto it = map.find(counter::obj(key, counts));
|
||||
auto d = std::distance(map.begin(), it);
|
||||
REQUIRE(0 <= d);
|
||||
REQUIRE(d <= static_cast<std::ptrdiff_t>(map.size()));
|
||||
},
|
||||
[&] {
|
||||
if (!map.empty()) {
|
||||
auto idx = p.bounded(static_cast<int>(map.size()));
|
||||
auto it = map.cbegin() + idx;
|
||||
auto const& key = it->first;
|
||||
auto found_it = map.find(key);
|
||||
REQUIRE(it == found_it);
|
||||
}
|
||||
},
|
||||
[&] {
|
||||
if (!map.empty()) {
|
||||
auto it = map.begin() + p.bounded(static_cast<int>(map.size()));
|
||||
map.erase(it);
|
||||
}
|
||||
},
|
||||
[&] {
|
||||
auto tmp = Map();
|
||||
std::swap(tmp, map);
|
||||
},
|
||||
[&] {
|
||||
map = std::initializer_list<std::pair<counter::obj, counter::obj>>{
|
||||
{{1, counts}, {2, counts}},
|
||||
{{3, counts}, {4, counts}},
|
||||
{{5, counts}, {6, counts}},
|
||||
};
|
||||
REQUIRE(map.size() == 3);
|
||||
},
|
||||
[&] {
|
||||
auto first_idx = 0;
|
||||
auto last_idx = 0;
|
||||
if (!map.empty()) {
|
||||
first_idx = p.bounded(static_cast<int>(map.size()));
|
||||
last_idx = p.bounded(static_cast<int>(map.size()));
|
||||
if (first_idx > last_idx) {
|
||||
std::swap(first_idx, last_idx);
|
||||
}
|
||||
}
|
||||
map.erase(map.cbegin() + first_idx, map.cbegin() + last_idx);
|
||||
},
|
||||
[&] {
|
||||
map.~Map();
|
||||
counts.check_all_done();
|
||||
new (&map) Map();
|
||||
},
|
||||
[&] {
|
||||
std::erase_if(map, [&](typename Map::value_type const& /*v*/) {
|
||||
return p.integral<bool>();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
TEST_CASE("fuzz_api" * doctest::test_suite("fuzz")) {
|
||||
fuzz::run([](fuzz::provider p) {
|
||||
do_fuzz_api<ankerl::unordered_dense::map<counter::obj, counter::obj>>(p.copy());
|
||||
do_fuzz_api<ankerl::unordered_dense::segmented_map<counter::obj, counter::obj>>(p.copy());
|
||||
do_fuzz_api<deque_map<counter::obj, counter::obj>>(p.copy());
|
||||
});
|
||||
}
|
52
third_party/unordered_dense/test/unit/fuzz_insert_erase.cpp
vendored
Normal file
52
third_party/unordered_dense/test/unit/fuzz_insert_erase.cpp
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
#include <fuzz/run.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace {
|
||||
|
||||
// Using DummyHash to make it easier for the fuzzer
|
||||
struct dummy_hash {
|
||||
using is_avalanching = void;
|
||||
|
||||
auto operator()(uint64_t x) const noexcept -> size_t {
|
||||
return static_cast<size_t>(x);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Map>
|
||||
void insert_erase(fuzz::provider p) {
|
||||
auto ank = Map();
|
||||
auto ref = std::unordered_map<uint64_t, uint64_t>();
|
||||
|
||||
auto c = uint64_t();
|
||||
while (p.has_remaining_bytes()) {
|
||||
auto key = p.integral<uint64_t>();
|
||||
ank[key] = c;
|
||||
ref[key] = c;
|
||||
++c;
|
||||
|
||||
key = p.integral<uint64_t>();
|
||||
REQUIRE(ank.erase(key) == ref.erase(key));
|
||||
REQUIRE(ank.size() == ref.size());
|
||||
}
|
||||
auto cpy = std::unordered_map(ank.begin(), ank.end());
|
||||
REQUIRE(cpy == ref);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("fuzz_insert_erase" * doctest::test_suite("fuzz")) {
|
||||
fuzz::run([](fuzz::provider p) {
|
||||
// try all 3 different map styles with the same input
|
||||
insert_erase<ankerl::unordered_dense::map<uint64_t, uint64_t, dummy_hash>>(p.copy());
|
||||
insert_erase<ankerl::unordered_dense::segmented_map<uint64_t, uint64_t, dummy_hash>>(p.copy());
|
||||
insert_erase<ankerl::unordered_dense::map<uint64_t,
|
||||
uint64_t,
|
||||
ankerl::unordered_dense::hash<uint64_t>,
|
||||
std::equal_to<uint64_t>,
|
||||
std::deque<std::pair<uint64_t, uint64_t>>>>(p.copy());
|
||||
});
|
||||
}
|
71
third_party/unordered_dense/test/unit/fuzz_replace_map.cpp
vendored
Normal file
71
third_party/unordered_dense/test/unit/fuzz_replace_map.cpp
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
#include <app/doctest.h>
|
||||
#include <fuzz/run.h>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename Map>
|
||||
void replace_map(fuzz::provider p) {
|
||||
auto counts = counter{};
|
||||
|
||||
using map_t = Map;
|
||||
|
||||
auto initial_size = p.bounded<size_t>(100);
|
||||
auto map = map_t{};
|
||||
for (size_t i = 0; i < initial_size; ++i) {
|
||||
map.try_emplace(counter::obj{i, counts}, counter::obj{i, counts});
|
||||
}
|
||||
|
||||
// create a container with data in it provided by fuzzer
|
||||
auto container = typename map_t::value_container_type{};
|
||||
auto comparison_container = std::vector<std::pair<size_t, size_t>>();
|
||||
auto v = size_t{};
|
||||
while (p.has_remaining_bytes()) {
|
||||
auto key = p.integral<size_t>();
|
||||
container.emplace_back(counter::obj{key, counts}, counter::obj{v, counts});
|
||||
comparison_container.emplace_back(key, v);
|
||||
++v;
|
||||
}
|
||||
|
||||
// create comparison map with the same move-back-forward algorithm
|
||||
auto comparison_map = std::unordered_map<size_t, size_t>{};
|
||||
size_t idx = 0;
|
||||
while (idx != comparison_container.size()) {
|
||||
auto [key, val] = comparison_container[idx];
|
||||
if (comparison_map.try_emplace(key, val).second) {
|
||||
++idx;
|
||||
} else {
|
||||
comparison_container[idx] = comparison_container.back();
|
||||
comparison_container.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
map.replace(std::move(container));
|
||||
|
||||
// now check if the data in the map is exactly what we expect
|
||||
REQUIRE(map.size() == comparison_map.size());
|
||||
for (auto [key, val] : comparison_map) {
|
||||
auto key_obj = counter::obj{key, counts};
|
||||
auto val_obj = counter::obj{val, counts};
|
||||
auto it = map.find(key_obj);
|
||||
REQUIRE(it != map.end());
|
||||
REQUIRE(it->first == key_obj);
|
||||
REQUIRE(it->second == val_obj);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("fuzz_replace_map" * doctest::test_suite("fuzz")) {
|
||||
fuzz::run([](fuzz::provider p) {
|
||||
replace_map<ankerl::unordered_dense::map<counter::obj, counter::obj>>(p.copy());
|
||||
replace_map<ankerl::unordered_dense::segmented_map<counter::obj, counter::obj>>(p.copy());
|
||||
replace_map<ankerl::unordered_dense::map<counter::obj,
|
||||
counter::obj,
|
||||
ankerl::unordered_dense::hash<counter::obj>,
|
||||
std::equal_to<counter::obj>,
|
||||
std::deque<std::pair<counter::obj, counter::obj>>>>(p.copy());
|
||||
});
|
||||
}
|
53
third_party/unordered_dense/test/unit/fuzz_string.cpp
vendored
Normal file
53
third_party/unordered_dense/test/unit/fuzz_string.cpp
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
#include <fuzz/provider.h>
|
||||
#include <fuzz/run.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename Map>
|
||||
void do_string(fuzz::provider p) {
|
||||
auto ank = Map();
|
||||
auto ref = std::unordered_map<std::string, std::string>();
|
||||
|
||||
while (p.has_remaining_bytes()) {
|
||||
auto str = p.string(32);
|
||||
REQUIRE(ank.try_emplace(str, "hello!").second == ref.try_emplace(str, "hello!").second);
|
||||
|
||||
str = p.string(32);
|
||||
auto it_ank = ank.find(str);
|
||||
auto it_ref = ref.find(str);
|
||||
REQUIRE((it_ank == ank.end()) == (it_ref == ref.end()));
|
||||
|
||||
if (it_ank != ank.end()) {
|
||||
ank.erase(it_ank);
|
||||
ref.erase(it_ref);
|
||||
}
|
||||
REQUIRE(ank.size() == ref.size());
|
||||
|
||||
str = p.string(32);
|
||||
REQUIRE(ank.try_emplace(str, "huh").second == ref.try_emplace(str, "huh").second);
|
||||
|
||||
str = p.string(32);
|
||||
REQUIRE(ank.erase(str) == ref.erase(str));
|
||||
}
|
||||
|
||||
REQUIRE(std::unordered_map(ank.begin(), ank.end()) == ref);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("fuzz_string" * doctest::test_suite("fuzz")) {
|
||||
fuzz::run([](fuzz::provider p) {
|
||||
do_string<ankerl::unordered_dense::map<std::string, std::string>>(p.copy());
|
||||
do_string<ankerl::unordered_dense::segmented_map<std::string, std::string>>(p.copy());
|
||||
do_string<ankerl::unordered_dense::map<std::string,
|
||||
std::string,
|
||||
ankerl::unordered_dense::hash<std::string>,
|
||||
std::equal_to<std::string>,
|
||||
std::deque<std::pair<std::string, std::string>>>>(p.copy());
|
||||
});
|
||||
}
|
20
third_party/unordered_dense/test/unit/hash.cpp
vendored
Normal file
20
third_party/unordered_dense/test/unit/hash.cpp
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <doctest.h> // for ResultBuilder, TestCase, REQUIRE
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
#include <cstdint> // for uint64_t
|
||||
#include <string> // for string, basic_string
|
||||
#include <unordered_set> // for unordered_set
|
||||
|
||||
TEST_CASE("hash_string") {
|
||||
auto h = ankerl::unordered_dense::hash<std::string>();
|
||||
|
||||
auto set = std::unordered_set<uint64_t>();
|
||||
auto str = std::string();
|
||||
for (size_t l = 0; l < 100; ++l) {
|
||||
set.insert(h(str));
|
||||
str.push_back('x');
|
||||
}
|
||||
REQUIRE(set.size() == 100);
|
||||
}
|
6
third_party/unordered_dense/test/unit/hash_char_types.cpp
vendored
Normal file
6
third_party/unordered_dense/test/unit/hash_char_types.cpp
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
#include <doctest.h>
|
||||
|
||||
TEST_CASE("hash_char_types") {
|
||||
// TODO(martinus) make hash generic?
|
||||
// REQUIRE(123 != ankerl::hash<wchar_t>{}(123));
|
||||
}
|
21
third_party/unordered_dense/test/unit/hash_smart_ptr.cpp
vendored
Normal file
21
third_party/unordered_dense/test/unit/hash_smart_ptr.cpp
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <doctest.h> // for ResultBuilder, TestCase, REQUIRE
|
||||
|
||||
#include <cstdint> // for uint64_t
|
||||
#include <memory> // for shared_ptr, __unique_ptr_t, make...
|
||||
#include <type_traits> // for declval
|
||||
|
||||
template <typename Ptr>
|
||||
void check(Ptr const& ptr) {
|
||||
REQUIRE(ankerl::unordered_dense::hash<Ptr>{}(ptr) ==
|
||||
ankerl::unordered_dense::hash<decltype(std::declval<Ptr>().get())>{}(ptr.get()));
|
||||
}
|
||||
|
||||
TEST_CASE("hash_smart_ptr") {
|
||||
check(std::unique_ptr<uint64_t>{});
|
||||
check(std::shared_ptr<uint64_t>{});
|
||||
check(std::make_shared<uint64_t>(123U));
|
||||
check(std::make_unique<uint64_t>(123U));
|
||||
check(std::make_unique<uint64_t>(uint64_t{123U}));
|
||||
}
|
24
third_party/unordered_dense/test/unit/hash_string_view.cpp
vendored
Normal file
24
third_party/unordered_dense/test/unit/hash_string_view.cpp
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <doctest.h>
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
TEST_CASE("hash_string_view") {
|
||||
auto const* cstr = "The ships hung in the sky in much the same way that bricks don't.";
|
||||
REQUIRE(ankerl::unordered_dense::hash<std::string>{}(std::string{cstr}) ==
|
||||
ankerl::unordered_dense::hash<std::string_view>{}(std::string_view{cstr}));
|
||||
}
|
||||
|
||||
TEST_CASE("unit_hash_u32string") {
|
||||
auto str = std::u32string{};
|
||||
str.push_back(1);
|
||||
str.push_back(2);
|
||||
str.push_back(3);
|
||||
str.push_back(4);
|
||||
str.push_back(5);
|
||||
|
||||
REQUIRE(ankerl::unordered_dense::hash<std::u32string>{}(str) == ankerl::unordered_dense::hash<std::u32string_view>{}(str));
|
||||
REQUIRE(ankerl::unordered_dense::hash<std::u32string>{}(str) != std::hash<std::u32string>{}(str));
|
||||
}
|
1
third_party/unordered_dense/test/unit/include_only.cpp
vendored
Normal file
1
third_party/unordered_dense/test/unit/include_only.cpp
vendored
Normal file
@ -0,0 +1 @@
|
||||
#include <ankerl/unordered_dense.h>
|
81
third_party/unordered_dense/test/unit/initializer_list.cpp
vendored
Normal file
81
third_party/unordered_dense/test/unit/initializer_list.cpp
vendored
Normal file
@ -0,0 +1,81 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
#include <string> // for string, operator==, allocator
|
||||
#include <string_view> // for basic_string_view, operator""sv
|
||||
#include <utility> // for pair
|
||||
#include <vector> // for vector
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
template <class Map, class First, class Second>
|
||||
auto has(Map const& map, First const& first, Second const& second) -> bool {
|
||||
auto it = map.find(first);
|
||||
return it != map.end() && it->second == second;
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("insert_initializer_list", int, int) {
|
||||
auto m = map_t();
|
||||
m.insert({{1, 2}, {3, 4}, {5, 6}});
|
||||
REQUIRE(m.size() == 3U);
|
||||
REQUIRE(m[1] == 2);
|
||||
REQUIRE(m[3] == 4);
|
||||
REQUIRE(m[5] == 6);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("initializerlist_string", std::string, size_t) {
|
||||
size_t const n1 = 17;
|
||||
size_t const n2 = 10;
|
||||
|
||||
auto m1 = map_t{{"duck", n1}, {"lion", n2}};
|
||||
auto m2 = map_t{{"duck", n1}, {"lion", n2}};
|
||||
|
||||
REQUIRE(m1.size() == 2);
|
||||
REQUIRE(m1["duck"] == n1);
|
||||
REQUIRE(m1["lion"] == n2);
|
||||
|
||||
REQUIRE(m2.size() == 2);
|
||||
auto it = m2.find("duck");
|
||||
REQUIRE((it != m2.end() && it->second == n1));
|
||||
REQUIRE(m2["lion"] == n2);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("insert_initializer_list_string", int, std::string) {
|
||||
auto m = map_t();
|
||||
m.insert({{1, "a"}, {3, "b"}, {5, "c"}});
|
||||
REQUIRE(m.size() == 3U);
|
||||
REQUIRE(m[1] == "a");
|
||||
REQUIRE(m[3] == "b");
|
||||
REQUIRE(m[5] == "c");
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("initializer_list_assign", int, char const*) {
|
||||
auto map = map_t();
|
||||
map[3] = "nope";
|
||||
map = {{1, "a"}, {2, "hello"}, {12346, "world!"}};
|
||||
REQUIRE(map.size() == 3);
|
||||
REQUIRE(has(map, 1, "a"sv));
|
||||
REQUIRE(has(map, 2, "hello"sv));
|
||||
REQUIRE(has(map, 12346, "world!"sv));
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("initializer_list_ctor_alloc", int, char const*) {
|
||||
using alloc_t = std::allocator<std::pair<int, char const*>>;
|
||||
auto map = map_t({{1, "a"}, {2, "hello"}, {12346, "world!"}}, 0, alloc_t{});
|
||||
REQUIRE(map.size() == 3);
|
||||
REQUIRE(has(map, 1, "a"sv));
|
||||
REQUIRE(has(map, 2, "hello"sv));
|
||||
REQUIRE(has(map, 12346, "world!"sv));
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("initializer_list_ctor_hash_alloc", int, char const*) {
|
||||
using hash_t = ankerl::unordered_dense::hash<int>;
|
||||
using alloc_t = std::allocator<std::pair<int, char const*>>;
|
||||
auto map = map_t({{1, "a"}, {2, "hello"}, {12346, "world!"}}, 0, hash_t{}, alloc_t{});
|
||||
REQUIRE(map.size() == 3);
|
||||
REQUIRE(has(map, 1, "a"sv));
|
||||
REQUIRE(has(map, 2, "hello"sv));
|
||||
REQUIRE(has(map, 12346, "world!"sv));
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user