Compare commits

...

35 Commits

Author SHA1 Message Date
Siarhei Fedartsou
8b878a841e Merge branch 'master' into sf-ankerl 2024-06-26 18:39:02 +02:00
Siarhei Fedartsou
8aade382bb wip 2024-06-26 18:10:53 +02:00
Siarhei Fedartsou
a522016953 wip 2024-06-26 18:10:19 +02:00
Siarhei Fedartsou
6f444be1de ankerl 2024-06-25 18:27:53 +02:00
Siarhei Fedartsou
72527d63a0 Merge branch 'master' into sf-ankerl 2024-06-25 17:53:09 +02:00
Siarhei Fedartsou
3ff2819922 wip 2024-06-13 18:31:19 +02:00
Siarhei Fedartsou
559768c351 wip 2024-06-12 21:47:58 +02:00
Siarhei Fedartsou
316b8dbec2 wip 2024-06-12 21:05:39 +02:00
Siarhei Fedartsou
1724c3a7fa wip 2024-06-12 21:03:13 +02:00
Siarhei Fedartsou
112e089166 wip 2024-06-12 20:01:37 +02:00
Siarhei Fedartsou
250c406098 wip 2024-06-12 17:18:47 +02:00
Siarhei Fedartsou
11c3369cf4 wip 2024-06-12 13:33:18 +02:00
Siarhei Fedartsou
f4ce1dc8f6 wip 2024-06-12 09:21:20 +02:00
Siarhei Fedartsou
49f07cbb8e wip 2024-06-11 22:41:49 +02:00
Siarhei Fedartsou
68d5dbc919 wip 2024-06-11 16:27:21 +02:00
Siarhei Fedartsou
068a1da098 wip 2024-06-11 09:14:57 +02:00
Siarhei Fedartsou
ff02ae92f4 wip 2024-06-10 21:43:49 +02:00
Siarhei Fedartsou
a65c9cbcb1 wip 2024-06-10 12:33:53 +02:00
Siarhei Fedartsou
13d8eebbca wip 2024-06-09 22:57:31 +02:00
Siarhei Fedartsou
93820be50e Avoid copy of intersection in totalTurnAngle 2024-06-09 19:37:44 +02:00
Siarhei Fedartsou
47d1630e7f wip 2024-06-09 18:17:27 +02:00
Siarhei Fedartsou
79da3793c1 wip 2024-06-09 17:07:35 +02:00
Siarhei Fedartsou
c5cf8c31ac Merge branch 'master' into sf-ankerl 2024-06-09 17:03:53 +02:00
Siarhei Fedartsou
67558b796f wip 2024-06-09 13:11:09 +02:00
Siarhei Fedartsou
2a3f539bb2 wip 2024-06-09 11:13:31 +02:00
Siarhei Fedartsou
ada954cd8d wip 2024-06-09 10:43:28 +02:00
Siarhei Fedartsou
9398bbc382 wip 2024-06-09 10:39:20 +02:00
Siarhei Fedartsou
2e3f3e90ef Merge branch 'master' into sf-ankerl 2024-06-08 22:29:49 +02:00
Siarhei Fedartsou
2e54842ce8 Merge branch 'master' into sf-ankerl 2024-06-05 21:48:14 +02:00
Siarhei Fedartsou
cf4141dffd Try to use ankerl::unordered_dense::map instead of std::unordered_map in UnorderedMapStorage 2024-05-30 19:49:06 +02:00
Siarhei Fedartsou
c3683201e6 Try to use ankerl::unordered_dense::map instead of std::unordered_map in UnorderedMapStorage 2024-05-30 19:21:38 +02:00
Siarhei Fedartsou
0f5ffc2a84 Try to use ankerl::unordered_dense::map instead of std::unordered_map in UnorderedMapStorage 2024-05-30 19:19:30 +02:00
Siarhei Fedartsou
fafe1d4f81 Merge commit 'f1087e81ecdca5a59ba5ffca684c955c5b38f7c2' as 'third_party/unordered_dense' 2024-05-30 19:06:16 +02:00
Siarhei Fedartsou
f1087e81ec Squashed 'third_party/unordered_dense/' content from commit 231e48c94
git-subtree-dir: third_party/unordered_dense
git-subtree-split: 231e48c9426bd21c273669e5fdcd042c146975cf
2024-05-30 19:06:16 +02:00
Siarhei Fedartsou
178bcb974e Add ankerl 2024-05-30 19:06:09 +02:00
132 changed files with 15797 additions and 73 deletions

View File

@ -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

View File

@ -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")

View File

@ -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,

View File

@ -102,4 +102,4 @@ def main():
if __name__ == "__main__":
main()
main()

View File

@ -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

View 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
View 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_'
...

View File

@ -0,0 +1 @@
data/fuzz

View 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']

View 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.

View 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.

View 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
View 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

View 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()

View 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

View 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
View 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
View File

@ -0,0 +1,374 @@
<a id="top"></a>
[![Release](https://img.shields.io/github/release/martinus/unordered_dense.svg)](https://github.com/martinus/unordered_dense/releases)
[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/martinus/unordered_dense/main/LICENSE)
[![meson_build_test](https://github.com/martinus/unordered_dense/actions/workflows/main.yml/badge.svg)](https://github.com/martinus/unordered_dense/actions)
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/6220/badge)](https://bestpractices.coreinfrastructure.org/projects/6220)
[![Sponsors](https://img.shields.io/github/sponsors/martinus?style=social)](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
![allocated memory](doc/allocated_memory.png)
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 NavierStokes 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)

View File

@ -0,0 +1,4 @@
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")
check_required_components("@PROJECT_NAME@")

View 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"

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View 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)

View 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
```

View 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;
}
}

File diff suppressed because it is too large Load Diff

32
third_party/unordered_dense/meson.build vendored Normal file
View 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
View 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]

View 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}

View 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}

View 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)

View 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)

View 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)

View 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"

View File

@ -0,0 +1,2 @@
---
Checks: '-*'

View 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

View 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

View 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

View 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

View 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)

View 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());
}
};

View 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;
}
};

View 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

View 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);

View 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));
}

View 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;
}

View File

@ -0,0 +1,2 @@
#define ANKERL_NANOBENCH_IMPLEMENT
#include <third-party/nanobench.h>

View 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

View 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

View 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

View 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

View 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

View 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

View File

@ -0,0 +1,3 @@
#include <ankerl/unordered_dense.h>
// this cpp file is only for include-what-you-use

View 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");
}

View 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>();
}

View 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);
}
}

View 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);
}

View 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

View 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");
}

View 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

View 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

View 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

View 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)

View 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);
}

View 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

View File

@ -0,0 +1,7 @@
---
Checks: '-*,
bugprone-infinite-loop
'
WarningsAsErrors: ''
HeaderFilterRegex: ''
...

View 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_

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View 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));
}
}

View 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));
}

View 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);
}

View 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);
}
}

View 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));
}

View 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);
}
}

View 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);
}

View 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);
}

View 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);
}

View 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);
}

View 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

View 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>>);

View 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
}

View 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());
}

View 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));
}

View 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());
}

View 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));
}

View 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));
}
}
}

View 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);
}
}
}

View 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{};
}

View 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());
}

View 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());
});
}

View 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());
});
}

View 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());
});
}

View 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());
});
}

View 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);
}

View 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));
}

View 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}));
}

View 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));
}

View File

@ -0,0 +1 @@
#include <ankerl/unordered_dense.h>

View 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