├── benchs ├── pdqsort.hpp ├── libcuckoo │ ├── libcuckoo-config.cmake │ ├── mainpage.dox │ ├── cuckoohash_config.hh │ ├── CMakeLists.txt │ └── cuckoohash_util.hh ├── gtl │ ├── stopwatch.hpp │ ├── combinators.hpp │ ├── vec_utils.hpp │ ├── adv_utils.hpp │ ├── lru_cache.hpp │ ├── debug_vis │ │ ├── gtl_gdb.py │ │ └── gtl.natvis │ ├── meminfo.hpp │ ├── soa.hpp │ └── utils.hpp ├── bench_format.cpp ├── CMakeLists.txt ├── bench_sequence.cpp ├── bench_sort.cpp └── zipfian_int_distribution.h ├── tests ├── CMakeLists.txt └── test_seq │ ├── test_all_maps.cpp │ ├── test_algorithm.cpp │ ├── CMakeLists.txt │ ├── tests.hpp │ └── test_charconv.cpp ├── cmake └── seqConfig.cmake.in ├── seq.pc.in ├── .clang-format ├── LICENSES ├── plf.txt ├── robin_hood.txt ├── komihash.txt ├── quadsort.txt └── boost.txt ├── .github └── workflows │ ├── cmake.yml │ ├── build-windows.yml │ ├── build-linux.yml │ └── build-macos.yml ├── LICENSE ├── docs ├── bits.md ├── hash.md ├── devector.md ├── algorithm.md ├── containers.md ├── charconv.md ├── v2.md ├── tiered_vector.md ├── flat_set.md └── tiny_string.md ├── seq_config.hpp.in ├── seq ├── seq_config.hpp ├── internal │ ├── binary_search.hpp │ └── hash_utils.hpp ├── timer.hpp ├── lock.hpp └── tagged_pointer.hpp ├── CMakeLists.txt └── README.md /benchs/pdqsort.hpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thermadiag/seq/HEAD/benchs/pdqsort.hpp -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | add_subdirectory(test_seq) 4 | -------------------------------------------------------------------------------- /benchs/libcuckoo/libcuckoo-config.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # libcuckoo-config.cmake 3 | # 4 | 5 | include (CMakeFindDependencyMacro) 6 | 7 | set(THREADS_PREFER_PTHREAD_FLAG ON) 8 | find_dependency(Threads) 9 | 10 | include ("${CMAKE_CURRENT_LIST_DIR}/libcuckoo-targets.cmake") 11 | -------------------------------------------------------------------------------- /cmake/seqConfig.cmake.in: -------------------------------------------------------------------------------- 1 | 2 | @PACKAGE_INIT@ 3 | include(${CMAKE_CURRENT_LIST_DIR}/seq.cmake) 4 | 5 | set(SEQ_FOUND TRUE) 6 | set(SEQ_VERSION_MAJOR @PROJECT_VERSION_MAJOR@) 7 | set(SEQ_VERSION_MINOR @PROJECT_VERSION_MINOR@) 8 | set(SEQ_VERSION "@PROJECT_VERSION@") 9 | 10 | set_and_check(SEQ_INCLUDE_DIR "${PACKAGE_PREFIX_DIR}/include") 11 | 12 | # Automatic addition of include dir 13 | include_directories(${SEQ_INCLUDE_DIR}) -------------------------------------------------------------------------------- /seq.pc.in: -------------------------------------------------------------------------------- 1 | # this template is filled-in by CMake `configure_file(... @ONLY)` 2 | # the `@....@` are filled in by CMake configure_file(), 3 | # from variables set in your CMakeLists.txt or by CMake itself 4 | 5 | prefix="@CMAKE_INSTALL_PREFIX@" 6 | exec_prefix="${prefix}" 7 | includedir="${prefix}/include" 8 | 9 | Name: @PROJECT_NAME@ 10 | Description: @CMAKE_PROJECT_DESCRIPTION@ 11 | URL: @CMAKE_PROJECT_HOMEPAGE_URL@ 12 | Version: @PROJECT_VERSION@ 13 | Cflags: -I"${includedir}" 14 | -------------------------------------------------------------------------------- /benchs/libcuckoo/mainpage.dox: -------------------------------------------------------------------------------- 1 | /*! \mainpage libcuckoo Documentation 2 | * 3 | * libcuckoo is a high-performance, memory efficient hash table that 4 | * supports concurrent reads and writes. 5 | * 6 | * \ref cuckoohash_map is the class of the hash table. Its interface 7 | * resembles that of STL's unordered_map but does contain some 8 | * important differences. 9 | * 10 | * Internally, the hash table is partitioned into an array of 11 | * buckets, each of which contains \c SLOT_PER_BUCKET slots to 12 | * store items. 13 | * 14 | * Each bucket has a lock to ensure multiple threads don't modify the 15 | * same elements. Most operations will lock no more than two buckets 16 | * at a time, thereby allowing for concurrent reads and writes. 17 | */ 18 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | # We'll use defaults from the Mozilla style, but with 8 columns indentation. 3 | BasedOnStyle: Mozilla 4 | ColumnLimit: 200 5 | UseTab: Always 6 | IndentWidth: 8 7 | TabWidth: 8 8 | AlignTrailingComments: true 9 | AccessModifierOffset: -8 10 | AlwaysBreakAfterDefinitionReturnType: None 11 | AlwaysBreakAfterReturnType: None 12 | NamespaceIndentation: All 13 | SortIncludes: false 14 | 15 | BraceWrapping: 16 | AfterClass: true 17 | AfterControlStatement: false 18 | AfterEnum: true 19 | AfterFunction: true 20 | AfterNamespace: true 21 | AfterObjCDeclaration: true 22 | AfterStruct: true 23 | AfterUnion: true 24 | BeforeCatch: true 25 | BeforeElse: true 26 | IndentBraces: false 27 | BreakBeforeBraces: Custom 28 | 29 | --- 30 | Language: Cpp 31 | # Force pointers to the type for C++. 32 | -------------------------------------------------------------------------------- /LICENSES/plf.txt: -------------------------------------------------------------------------------- 1 | zlib License 2 | 3 | (C) 2019 mattreecebentley 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 2. Altered source versions must be plainly marked as such, and must not be 18 | misrepresented as being the original software. 19 | 3. This notice may not be removed or altered from any source distribution. -------------------------------------------------------------------------------- /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: 3 | 4 | push: 5 | branches: [ "main", "v2dev" ] 6 | pull_request: 7 | branches: [ "main", "v2dev", "releases/**" ] 8 | workflow_dispatch: 9 | workflow_call: 10 | 11 | 12 | jobs: 13 | build-linux: 14 | strategy: 15 | matrix: 16 | compiler: [gcc-13.2.0] 17 | uses: ./.github/workflows/build-linux.yml 18 | with: 19 | compiler: ${{ matrix.compiler }} 20 | 21 | 22 | build-windows: 23 | strategy: 24 | matrix: 25 | compiler: [ msvc] 26 | uses: ./.github/workflows/build-windows.yml 27 | with: 28 | compiler: ${{ matrix.compiler }} 29 | 30 | build-macos: 31 | strategy: 32 | matrix: 33 | compiler: [clang] 34 | uses: ./.github/workflows/build-macos.yml 35 | with: 36 | compiler: ${{ matrix.compiler }} -------------------------------------------------------------------------------- /.github/workflows/build-windows.yml: -------------------------------------------------------------------------------- 1 | name: Build on Windows 2 | on: 3 | 4 | push: 5 | branches: [ "main", "dev" ] 6 | pull_request: 7 | branches: [ "main", "dev", "releases/**" ] 8 | workflow_dispatch: 9 | inputs: 10 | compiler: 11 | required: false 12 | type: string 13 | default: msvc 14 | 15 | workflow_call: 16 | inputs: 17 | compiler: 18 | required: true 19 | type: string 20 | default: msvc 21 | 22 | jobs: 23 | build-windows: 24 | runs-on: windows-latest 25 | 26 | steps: 27 | - uses: actions/checkout@v4 28 | 29 | - name: Build on Windows 30 | run: | 31 | mkdir build 32 | cd build 33 | cmake .. -DCMAKE_BUILD_TYPE=Debug -DSEQ_NO_WARNINGS=ON -DSEQ_BUILD_TESTS=ON -DSEQ_BUILD_BENCHS=ON -DCMAKE_INSTALL_PREFIX=./install 34 | cmake --build ./ 35 | cmake --build . --target install 36 | 37 | 38 | -------------------------------------------------------------------------------- /.github/workflows/build-linux.yml: -------------------------------------------------------------------------------- 1 | name: Build on Linux 2 | on: 3 | 4 | push: 5 | branches: [ "main", "dev" ] 6 | pull_request: 7 | branches: [ "main", "dev", "releases/**" ] 8 | workflow_dispatch: 9 | inputs: 10 | compiler: 11 | required: false 12 | type: string 13 | default: gcc 14 | 15 | workflow_call: 16 | inputs: 17 | compiler: 18 | required: true 19 | type: string 20 | default: gcc 21 | 22 | jobs: 23 | build-linux: 24 | runs-on: ubuntu-22.04 25 | 26 | steps: 27 | - uses: actions/checkout@v4 28 | 29 | - name: Build on Linux 30 | run: | 31 | mkdir build 32 | cd build 33 | cmake .. -DCMAKE_BUILD_TYPE=Debug -DSEQ_NO_WARNINGS=ON -DSEQ_BUILD_TESTS=ON -DSEQ_BUILD_BENCHS=ON -DCMAKE_INSTALL_PREFIX=./install 34 | make -j8 35 | cmake --build . --target install 36 | shell: bash 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /.github/workflows/build-macos.yml: -------------------------------------------------------------------------------- 1 | name: Build on MacOS 2 | on: 3 | 4 | push: 5 | branches: [ "main", "dev" ] 6 | pull_request: 7 | branches: [ "main", "dev", "releases/**" ] 8 | workflow_dispatch: 9 | inputs: 10 | compiler: 11 | required: false 12 | type: string 13 | default: clang 14 | 15 | workflow_call: 16 | inputs: 17 | compiler: 18 | required: true 19 | type: string 20 | default: clang 21 | 22 | jobs: 23 | build-macos: 24 | runs-on: macos-latest 25 | 26 | steps: 27 | - uses: actions/checkout@v4 28 | 29 | - name: install coreutils 30 | run: brew install coreutils 31 | 32 | - name: Build on MacOS 33 | run: | 34 | mkdir build 35 | cd build 36 | cmake .. -DCMAKE_BUILD_TYPE=Debug -DSEQ_NO_WARNINGS=ON -DSEQ_BUILD_TESTS=ON -DSEQ_BUILD_BENCHS=ON -DCMAKE_INSTALL_PREFIX=./install 37 | cmake --build ./ 38 | cmake --build . --target install 39 | 40 | 41 | -------------------------------------------------------------------------------- /LICENSES/robin_hood.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2021 Martin Ankerl 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Victor Moncada 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /LICENSES/komihash.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-2023 Aleksey Vaneev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/bits.md: -------------------------------------------------------------------------------- 1 | # Bits: collection of functions for low level bits manipulation. 2 | 3 | The *bits* module provides several portable low-level functions for bits manipulation: 4 | - `seq::popcnt64`: population count on a 64 bits word 5 | - `seq::popcnt32`: population count on a 32 bits word 6 | - `seq::popcnt16`: population count on a 16 bits word 7 | - `seq::popcnt8`: population count on a 8 bits word 8 | - `seq::bit_scan_forward_32`: index of the lowest set bit in a 32 bits word 9 | - `seq::bit_scan_forward_64`: index of the lowest set bit in a 64 bits word 10 | - `seq::bit_scan_reverse_32`: index of the highest set bit in a 32 bits word 11 | - `seq::bit_scan_reverse_64`: index of the highest set bit in a 32 bits word 12 | - `seq::bit_scan_forward`: index of the lowest set bit in a size_t word 13 | - `seq::bit_scan_reverse`: index of the highest set bit in a size_t word 14 | - `seq::static_bit_scan_reverse`: index of the highest set bit at compile time 15 | - `seq::count_digits_base_10`: number of digits to represent an integer in base 10 16 | - `seq::byte_swap_16`: byte swap for 16 bits word 17 | - `seq::byte_swap_32`: byte swap for 32 bits word 18 | - `seq::byte_swap_64`: byte swap for 64 bits word 19 | 20 | -------------------------------------------------------------------------------- /benchs/libcuckoo/cuckoohash_config.hh: -------------------------------------------------------------------------------- 1 | /** \file */ 2 | 3 | #ifndef _CUCKOOHASH_CONFIG_HH 4 | #define _CUCKOOHASH_CONFIG_HH 5 | 6 | #include 7 | #include 8 | 9 | namespace libcuckoo { 10 | 11 | //! The default maximum number of keys per bucket 12 | constexpr size_t DEFAULT_SLOT_PER_BUCKET = 4; 13 | 14 | //! The default number of elements in an empty hash table 15 | constexpr size_t DEFAULT_SIZE = 16 | (1U << 16) * DEFAULT_SLOT_PER_BUCKET; 17 | 18 | //! The default minimum load factor that the table allows for automatic 19 | //! expansion. It must be a number between 0.0 and 1.0. The table will throw 20 | //! load_factor_too_low if the load factor falls below this value 21 | //! during an automatic expansion. 22 | constexpr double DEFAULT_MINIMUM_LOAD_FACTOR = 0.05; 23 | 24 | //! An alias for the value that sets no limit on the maximum hashpower. If this 25 | //! value is set as the maximum hashpower limit, there will be no limit. This 26 | //! is also the default initial value for the maximum hashpower in a table. 27 | constexpr size_t NO_MAXIMUM_HASHPOWER = 28 | std::numeric_limits::max(); 29 | 30 | //! set LIBCUCKOO_DEBUG to 1 to enable debug output 31 | #define LIBCUCKOO_DEBUG 0 32 | 33 | } // namespace libcuckoo 34 | 35 | #endif // _CUCKOOHASH_CONFIG_HH 36 | -------------------------------------------------------------------------------- /LICENSES/quadsort.txt: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to -------------------------------------------------------------------------------- /tests/test_seq/test_all_maps.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | template 9 | void test_map() 10 | { 11 | using value_type = typename Map::value_type; 12 | //using key_type = typename Map::key_type; 13 | //using mapped_type = typename Map::mapped_type; 14 | Map m; 15 | 16 | // Make sure all of these compile 17 | 18 | value_type v("tata", "ok"); 19 | // copy insert 20 | m.insert(v); 21 | //move insert 22 | m.insert(value_type("toto", "ok")); 23 | //insert( P&&) 24 | m.insert(std::make_pair("titi", "ok")); 25 | 26 | m.emplace(v); 27 | m.emplace(value_type("toto", "ok")); 28 | m.emplace(std::make_pair("titi", "ok")); 29 | 30 | 31 | m.emplace("toto", "ok"); 32 | m.try_emplace("toto", "ok"); 33 | } 34 | 35 | SEQ_PROTOTYPE( int test_all_maps(int, char** const)) 36 | { 37 | test_map >(); 38 | test_map >(); 39 | test_map >(); 40 | test_map >(); 41 | test_map >(); 42 | test_map >(); 43 | return 0; 44 | } 45 | -------------------------------------------------------------------------------- /LICENSES/boost.txt: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /docs/hash.md: -------------------------------------------------------------------------------- 1 | # Hash: small collection of hash utilities 2 | 3 | The *hash* module provides several hash-related functions: 4 | 5 | - `seq::hash_finalize`: mix input hash value for better avalanching 6 | - `seq::hash_combine`: combine 2 hash values 7 | - `seq::hash_bytes_murmur64`: murmurhash2 algorithm 8 | - `seq::hash_bytes_fnv1a`: fnv1a hash algorithm 9 | - `seq::hash_bytes_komihash`: simplified [komihash](https://github.com/avaneev/komihash) hash function. 10 | 11 | 12 | The *hash* module also provides its own hashing class called `seq::hasher` that, by default, inherits `std::hash`. `seq::hasher` is specialized for: 13 | 14 | - All arithmetic types as well character types (use 128 bits multiplication) 15 | - Enumerations 16 | - Pointers 17 | - `std::unique_ptr` and `std::shared_ptr` 18 | - `std::tuple` and `std::pair` 19 | - `std::basic_string`, `std::basic_string_view` and `seq::tiny_string` (`` must be included). 20 | 21 | For string types, `seq::hasher` uses a seeded version of [komihash](https://github.com/avaneev/komihash). komihash is a very fast hash function that passes all [SMhasher](https://github.com/rurban/smhasher) tests, and is especially efficient on small strings. 22 | 23 | Note that `seq::hasher` is the default hash function for all hash tables within the *seq* library: [`seq::ordered_set/map`](ordered_set.md), [`seq::radix_hash_set/map`](radix_tree.md) and [`seq::concurrent_set/map`](concurrent_map.md). -------------------------------------------------------------------------------- /seq_config.hpp.in: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2025 Victor Moncada 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #ifndef SEQ_CONFIG_HPP 26 | #define SEQ_CONFIG_HPP 27 | 28 | 29 | /** @file */ 30 | 31 | 32 | #define SEQ_VERSION_MAJOR @PROJECT_VERSION_MAJOR@ 33 | #define SEQ_VERSION_MINOR @PROJECT_VERSION_MINOR@ 34 | #define SEQ_VERSION "@PROJECT_VERSION@" 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /seq/seq_config.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2025 Victor Moncada 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #ifndef SEQ_CONFIG_HPP 26 | #define SEQ_CONFIG_HPP 27 | 28 | /** @file */ 29 | 30 | /// This is the default config file, just in case people will use the seq folder directly without installing the library 31 | 32 | #define SEQ_VERSION_MAJOR 0 33 | #define SEQ_VERSION_MINOR 0 34 | #define SEQ_VERSION "0.0" 35 | 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /docs/devector.md: -------------------------------------------------------------------------------- 1 | # Double ended vector 2 | 3 | `seq::devector` is a double-ending vector class that mixes the behavior and performances of `std::deque` and `std::vector`. 4 | Elements are stored in a contiguous memory chunk exatcly like a vector, but might contain free space at the front in addition to free space at the back in order to provide O(1) insertion at the front. 5 | 6 | `seq::devector` provides a similar interface as std::vector with the following additional members: 7 | - `push_front()` and `emplace_front()`: insert an element at the front of the devector 8 | - `resize_front()`: resize the devector from the front instead of the back of the container 9 | - `back_capacity()`: returns the capacity (free slots) at the back of the devector 10 | - `front_capacity()`: returns the capacity (free slots) at the front of the devector 11 | 12 | Almost all members provide basic exception guarantee, except if the value type has a noexcept move constructor and move assignment operator, in which case members provide strong exception guarantee. 13 | References and iterators are invalidated by insertion/removal of elements. 14 | `seq::devector` is used by [seq::tiered_vector](tiered_vector.md) for bucket storage. 15 | 16 | 17 | ## Performances 18 | 19 | Internal benchmarks show that devector is as fast as `std::vector` when inserting at the back. `seq::devector` is also faster than `std::vector` for relocatable types (where `seq::is_relocatable::value` is true) as memcpy and memmove can be used instead of `std::copy` or `std::move` on reallocation. 20 | Inserting a new element in the middle of a devector is on average twice as fast as on `std::vector`, since the values can be pushed to either ends, whichever is faster. 21 | -------------------------------------------------------------------------------- /benchs/libcuckoo/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # for write_basic_package_version_file() 2 | include(CMakePackageConfigHelpers) 3 | 4 | # we require the use of threads 5 | set(THREADS_PREFER_PTHREAD_FLAG ON) 6 | find_package(Threads REQUIRED) 7 | 8 | # generate a version for cmake to use with find_package(libcuckoo) 9 | set (libcuckoo_VERSION "${libcuckoo_VERSION_MAJOR}.${libcuckoo_VERSION_MINOR}") 10 | set (libcuckoo_VERSION "${libcuckoo_VERSION}.${libcuckoo_VERSION_PATCH}") 11 | 12 | # libcuckoo is an interface (all headers) library target 13 | add_library(libcuckoo INTERFACE) 14 | add_library(libcuckoo::libcuckoo ALIAS libcuckoo) 15 | 16 | # tag libcuckoo target with a c++11 feature so that libcuckoo users 17 | # will have c++11 turned on in their compile when they use this target. 18 | # XXX: newer cmakes have a "cxx_std_11" feature that could be used 19 | target_compile_features (libcuckoo INTERFACE cxx_constexpr) 20 | 21 | # Include relative to the base directory 22 | target_include_directories(libcuckoo INTERFACE 23 | $ 24 | $ 25 | ) 26 | 27 | # switch on threading for all targets that link with libcuckoo 28 | target_link_libraries(libcuckoo INTERFACE Threads::Threads) 29 | 30 | # cmake packaging 31 | set (libcuckoo_pkgloc "share/cmake/libcuckoo") 32 | 33 | write_basic_package_version_file( 34 | "libcuckoo-config-version.cmake" VERSION ${libcuckoo_VERSION} 35 | COMPATIBILITY AnyNewerVersion) 36 | 37 | install(TARGETS libcuckoo EXPORT libcuckoo-targets) 38 | install(EXPORT libcuckoo-targets 39 | NAMESPACE libcuckoo:: 40 | DESTINATION ${libcuckoo_pkgloc} 41 | FILE "libcuckoo-targets.cmake") 42 | install(FILES libcuckoo-config.cmake 43 | ${CMAKE_CURRENT_BINARY_DIR}/libcuckoo-config-version.cmake 44 | DESTINATION ${libcuckoo_pkgloc}) 45 | install( 46 | FILES 47 | cuckoohash_config.hh 48 | cuckoohash_map.hh 49 | cuckoohash_util.hh 50 | bucket_container.hh 51 | DESTINATION 52 | ${CMAKE_INSTALL_PREFIX}/include/libcuckoo 53 | ) 54 | -------------------------------------------------------------------------------- /benchs/gtl/stopwatch.hpp: -------------------------------------------------------------------------------- 1 | #ifndef gtl_stopwatch_hpp_guard 2 | #define gtl_stopwatch_hpp_guard 3 | 4 | // --------------------------------------------------------------------------- 5 | // Copyright (c) 2022, Gregory Popovitch - greg7mdp@gmail.com 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // https://www.apache.org/licenses/LICENSE-2.0 12 | // --------------------------------------------------------------------------- 13 | 14 | #include 15 | 16 | namespace gtl { 17 | // ------------------------------------------------------------------------------- 18 | template 19 | class stopwatch { 20 | public: 21 | stopwatch(bool do_start = true) { 22 | if (do_start) 23 | start(); 24 | } 25 | 26 | void start() { _start = _snap = clock::now(); } 27 | void snap() { _snap = clock::now(); } 28 | 29 | float since_start() const { return get_diff(_start, clock::now()); } 30 | float since_snap() const { return get_diff(_snap, clock::now()); } 31 | float start_to_snap() const { return get_diff(_start, _snap); } 32 | 33 | private: 34 | using clock = std::chrono::high_resolution_clock; 35 | using point = std::chrono::time_point; 36 | 37 | template 38 | static T get_diff(const point& start, const point& end) { 39 | using duration_t = std::chrono::duration; 40 | return std::chrono::duration_cast(end - start).count(); 41 | } 42 | 43 | point _start; 44 | point _snap; 45 | }; 46 | 47 | // ------------------------------------------------------------------------------- 48 | template 49 | class start_snap { 50 | public: 51 | start_snap(StopWatch& sw) 52 | : _sw(sw) { 53 | _sw.start(); 54 | } 55 | ~start_snap() { _sw.snap(); } 56 | 57 | private: 58 | StopWatch& _sw; 59 | }; 60 | 61 | } 62 | 63 | #endif // gtl_stopwatch_hpp_guard 64 | -------------------------------------------------------------------------------- /benchs/gtl/combinators.hpp: -------------------------------------------------------------------------------- 1 | pragma once 2 | 3 | /* 4 | * from https://github.com/codereport/blackbird - Copyright (c) 2022 Conor Hoekstra 5 | * 6 | * MIT License: https://github.com/codereport/blackbird/blob/main/LICENSE 7 | */ 8 | 9 | #include 10 | 11 | namespace combinators { 12 | 13 | ///////////////// 14 | // combinators // 15 | ///////////////// 16 | 17 | // B (The Bluebird) 18 | auto _b = [](auto f, auto g) { return [=](auto x) { return f(g(x)); }; }; 19 | 20 | // C (The Cardinal) aka `flip` in Haskell 21 | auto _c = [](auto f) { return [=](auto x, auto y) { return f(y, x); }; }; 22 | 23 | // K (Kestrel) 24 | auto _l_ = [](auto x, auto y) { return x; }; 25 | 26 | // KI 27 | auto _r_ = [](auto x, auto y) { return y; }; 28 | 29 | // Phi (The Phoenix) 30 | auto _phi = [](auto f, auto g, auto h) { return [=](auto x) { return g(f(x), h(x)); }; }; 31 | 32 | // Phi1 (The Pheasant) 33 | auto _phi1_ = [](auto f, auto g, auto h) { return [=](auto x, auto y) { return g(f(x, y), h(x, y)); }; }; 34 | 35 | // Psi (The Psi Bird) 36 | auto _psi = [](auto f, auto g) { return [=](auto x, auto y) { return f(g(x), g(y)); }; }; 37 | 38 | ///////////////////////////////////////////// 39 | // more convenient binary/unary operations // 40 | ///////////////////////////////////////////// 41 | 42 | auto _eq = [](auto x) { return [x](auto y) { return x == y; }; }; 43 | auto _eq_ = std::equal_to{}; 44 | auto _neq_ = std::not_equal_to{}; 45 | auto _lt = [](auto x) { return [x](auto y) { return x > y; }; }; 46 | auto lt_ = [](auto x) { return [x](auto y) { return y < x; }; }; 47 | auto _lt_ = std::less{}; 48 | auto _gte = [](auto x) { return [x](auto y) { return x >= y; }; }; 49 | auto _plus = [](auto x) { return [x](auto y) { return x + y; }; }; 50 | auto _plus_ = std::plus{}; 51 | auto _mul = [](auto x) { return [x](auto y) { return x * y; }; }; 52 | auto _mul_ = std::multiplies{}; 53 | auto _sub = [](auto x) { return [x](auto y) { return x - y; }; }; 54 | auto sub_ = [](auto x) { return [x](auto y) { return y - x; }; }; 55 | auto _sub_ = std::minus{}; 56 | auto _or_ = std::logical_or{}; 57 | auto _and_ = std::logical_and{}; 58 | auto _not = std::logical_not{}; 59 | auto _min_ = [](auto a, auto b) { return std::min(a, b); }; 60 | auto _max_ = [](auto a, auto b) { return std::max(a, b); }; 61 | auto _fst = [](auto t) { return std::get<0>(t); }; 62 | auto _snd = [](auto t) { return std::get<1>(t); }; 63 | 64 | } // namespace combinators 65 | -------------------------------------------------------------------------------- /docs/algorithm.md: -------------------------------------------------------------------------------- 1 | # Algorithm 2 | `algorithm` is a small module providing a few iterator based algorithms: 3 | 4 | - `seq::merge`: similar to `std::merge` but providing a better handling of consecutive ordered values, and has a special case for unbalanced merging (one range is way smaller than the other) 5 | 6 | - `seq::inplace_merge`: similar to `std::inplace_merge` but relying on `seq::merge` and using a user-provided buffer. `seq::inplace_merge` never allocates memory unless one of the following constant buffer is provided: 7 | - `seq::default_buffer`: uses as much memory as `std::inplace_merge` 8 | - `seq::medium_buffer`: uses 8 times less memory than with `seq::default_buffer` 9 | - `small_buffer` : uses 32 times less memory than with `seq::default_buffer` 10 | - `tiny_buffer`: uses 128 times less memory than with `seq::default_buffer` 11 | 12 | Note that a buffer of size 0 is supported, the algorithm will just be way slower. The inplace merging is based on the following [article](https://www.jmeiners.com/efficient-programming-with-components/15_merge_inplace.html) and was first published in 1981. 13 | 14 | - `seq::reverse_descending`: reverse a range sorted in descending order in a stable way: consecutive equal values are not reversed. 15 | 16 | - `seq::unique`: removed duplicates from a range in a stable way. This is very similar to `std::unique` except that the range does not need to be sorted. It uses a hash table under the hood to find duplicate values. A custom hash function and comparison function can be passed for custom types. 17 | 18 | - `seq::net_sort` and `seq::net_sort_size`: "new" generic stable sorting algorithm that is used everywhere within the seq library. `seq::net_sort` is a merge sort algorithm with the following specificities: 19 | - Bottom-up merging instead of the more traditional top-down approach, 20 | - Small blocks of 8 elements are sorted using a sorting network, 21 | - Bidirectional merging is used for relocatable types, 22 | - Ping-pong merge is used to merge 4 sorted ranges, 23 | - Can work without allocating memory through a (potentially null) user provided buffer, 24 | - Also works on bidirectional iterators. 25 | 26 | If provided buffer is one of `seq::default_buffer`, `seq::medium_buffer`, `seq::small_buffer` or `seq::tiny_buffer`, this function will try to allocate memory. 27 | 28 | From my tests on multiple input types, net_sort() is always faster than std::stable_sort(). 29 | 30 | net_sort_size() and net_sort() work on bidirectional iterators. Using net_sort_size() instead of net_sort() is faster when the range size is already known. 31 | 32 | Full credits to scandum (https://github.com/scandum) for its quadsort algorithm from which I took several ideas (bidirectional merge and ping-pong merge). -------------------------------------------------------------------------------- /benchs/bench_format.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #ifdef SEQ_HAS_CPP_20 7 | #include 8 | #endif 9 | 10 | int bench_format(int, char ** const) 11 | { 12 | using namespace seq; 13 | 14 | // Generate 4M double values 15 | using ftype = double; 16 | random_float_genertor rgn; 17 | std::vector vec_d; 18 | for (int i = 0; i < 4000000; ++i) 19 | vec_d.push_back(rgn()); 20 | 21 | // Null ostream object 22 | nullbuf n; 23 | std::ostream oss(&n); 24 | oss.sync_with_stdio(false); 25 | 26 | // Build a table of 4 * 1000000 double values separated by a '|'. All values are centered on a 20 characters space 27 | tick(); 28 | oss << std::setprecision(6); 29 | for (size_t i = 0; i < vec_d.size() / 4; ++i) 30 | { 31 | oss << std::left << std::setw(20) << vec_d[i * 4] << "|"; 32 | oss << std::left << std::setw(20) << vec_d[i * 4 + 1] << "|"; 33 | oss << std::left << std::setw(20) << vec_d[i * 4 + 2] << "|"; 34 | oss << std::left << std::setw(20) << vec_d[i * 4 + 3] << "|"; 35 | oss << std::endl; 36 | } 37 | size_t el = tock_ms(); 38 | std::cout << "Write table with streams: " << el << " ms" << std::endl; 39 | 40 | 41 | // Build the same table with format module 42 | 43 | // Create the format object 44 | auto slot = _g().p(6).l(20); // floating point slot with a precision of 6 and left-aligned on a 20 characters width 45 | auto f = join("|",slot, slot, slot, slot, ""); 46 | tick(); 47 | for (size_t i = 0; i < vec_d.size() / 4; ++i) 48 | oss << f(vec_d[i * 4], vec_d[i * 4 + 1], vec_d[i * 4 + 2], vec_d[i * 4 + 3]) << std::endl; 49 | el = tock_ms(); 50 | std::cout << "Write table with seq formatting module: " << el << " ms" << std::endl; 51 | 52 | 53 | // Compare to std::format for C++20 compilers 54 | #ifdef SEQ_HAS_CPP_20 55 | tick(); 56 | for (size_t i = 0; i < vec_d.size() / 4; ++i) 57 | std::format_to( 58 | std::ostreambuf_iterator(oss), 59 | "{:^20.6g} | {:^20.6g} | {:^20.6g} | {:^20.6g}\n", 60 | vec_d[i * 4], vec_d[i * 4 + 1], vec_d[i * 4 + 2], vec_d[i * 4 + 3]); 61 | el = tock_ms(); 62 | std::cout << "Write table with std::format : " << el << " ms" << std::endl; 63 | #endif 64 | 65 | // Just for comparison, directly dump the double values without the '|' character (but keeping alignment) 66 | 67 | tick(); 68 | auto f2 = g().l(20); 69 | for (size_t i = 0; i < vec_d.size(); ++i) 70 | oss << f2(vec_d[i]); 71 | el = tock_ms(); 72 | std::cout << "Write left-aligned double with seq::fmt: " << el << " ms" << std::endl; 73 | 74 | 75 | 76 | // use std::ostream::bad() to make sure the above tests are not simply ignored by the compiler 77 | if (oss.bad()) 78 | std::cout << "error" << std::endl; 79 | 80 | 81 | return 0; 82 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | project(seq 4 | VERSION 2.1 5 | DESCRIPTION "Collection of C++17 original containers" 6 | HOMEPAGE_URL "https://github.com/Thermadiag/seq" 7 | LANGUAGES CXX 8 | ) 9 | 10 | set(CMAKE_CXX_STANDARD 17) 11 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 12 | include(GNUInstallDirs) 13 | 14 | # project options 15 | option(SEQ_BUILD_TESTS "Build tests" OFF) 16 | option(SEQ_BUILD_BENCHS "Build benchmarks" OFF) 17 | option(SEQ_NO_WARNINGS "Treat warnings as errors" OFF) 18 | 19 | 20 | set(SEQ_INSTALL_LIBDIR "${CMAKE_INSTALL_LIBDIR}") 21 | set(SEQ_INSTALL_DATAROOTDIR "${CMAKE_INSTALL_DATAROOTDIR}") 22 | set(SEQ_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/seq") 23 | 24 | # create source file list 25 | file(GLOB SeqSources 26 | "seq/*.hpp" 27 | ) 28 | 29 | add_library(seq INTERFACE ${SeqSources}) 30 | 31 | target_include_directories(seq INTERFACE 32 | "$" 33 | "$" 34 | "$") 35 | 36 | install(TARGETS seq EXPORT seq ) 37 | install(EXPORT seq DESTINATION ${SEQ_CMAKEDIR}) 38 | 39 | 40 | # add tests 41 | enable_testing() 42 | 43 | # add tests 44 | if(SEQ_BUILD_TESTS) 45 | add_subdirectory(tests) 46 | endif() 47 | 48 | # add benchmarks 49 | if(SEQ_BUILD_BENCHS) 50 | add_subdirectory(benchs) 51 | endif() 52 | 53 | # Install headers and sources (for header-only mode) 54 | install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/seq" 55 | DESTINATION "${CMAKE_INSTALL_PREFIX}/include") 56 | 57 | 58 | # Configure and install seq.pc 59 | configure_file(seq.pc.in seq.pc @ONLY) 60 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/seq.pc 61 | DESTINATION ${SEQ_INSTALL_DATAROOTDIR}/pkgconfig) 62 | 63 | 64 | # Configure and install seq_config.hpp 65 | configure_file(seq_config.hpp.in seq_config.hpp @ONLY) 66 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/seq_config.hpp 67 | DESTINATION "${CMAKE_INSTALL_PREFIX}/include/seq") 68 | 69 | # Configure and install seqConfig.cmake and seqConfigVersion.cmake 70 | include(CMakePackageConfigHelpers) 71 | 72 | configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/seqConfig.cmake.in" 73 | "${CMAKE_CURRENT_BINARY_DIR}/seqConfig.cmake" 74 | INSTALL_DESTINATION ${SEQ_INSTALL_LIBDIR}/cmake/seq 75 | PATH_VARS ) 76 | 77 | 78 | # Generate seqConfigVersion.cmake 79 | write_basic_package_version_file( 80 | ${CMAKE_CURRENT_BINARY_DIR}/seqConfigVersion.cmake 81 | VERSION ${PROJECT_VERSION} 82 | COMPATIBILITY SameMajorVersion ) 83 | 84 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/seqConfig.cmake 85 | ${CMAKE_CURRENT_BINARY_DIR}/seqConfigVersion.cmake 86 | DESTINATION ${SEQ_INSTALL_LIBDIR}/cmake/seq ) 87 | 88 | -------------------------------------------------------------------------------- /docs/containers.md: -------------------------------------------------------------------------------- 1 | # Containers: original STL-like containers 2 | 3 | The *containers* module defines several container classes as alternatives to STL containers or providing features not present in the STL. 4 | These containers generally adhere to the properties of STL containers (in C++17 version), though there are often some associated API differences and/or implementation details which differ from the standard library. 5 | 6 | The *seq* containers are not necessarly drop-in replacement for their STL counterparts as they usually provide different iterator/reference statibility rules or different exception guarantees. 7 | 8 | Currently, the *containers* module provide 5 types of containers: 9 | - Sequential random-access containers: 10 | - [seq::devector](devector.md): double ended vector that can be optimized for front operations, back operations or both. Similar interface to `std::deque`. 11 | - [seq::tiered_vector](tiered_vector.md): tiered vector implementation optimized for fast insertion and deletion in the middle. Similar interface to `std::deque`. 12 | - [seq::cvector](cvector.md): vector-like class storing its values in a compressed way to reduce program memory footprint. Similar interface to `std::vector`. 13 | - Sequential stable non random-access container: `seq::sequence`, fast stable list-like container. 14 | - Sorted containers: 15 | - [seq::flat_set](flat_set.md) : flat set container similar to boost::flat_set but based on seq::tiered_vector and providing fast insertion/deletion of single elements. 16 | - `seq::flat_map`: associative version of `seq::flat_set`. 17 | - `seq::flat_multiset`: similar to `seq::flat_set` but supporting duplicate keys. 18 | - `seq::flat_multimap`: similar to `seq::flat_map` but supporting duplicate keys. 19 | - [seq::radix_set](radix_tree.md) : radix based sorted container with a similar interface to std::set. Provides very fast lookup. 20 | - `seq::radix_map`: associative version of `seq::radix_set`. 21 | - Hash tables: 22 | - [seq::ordered_set](ordered_set.md): Ordered robin-hood hash table with backward shift deletion. Drop-in replacement for `std::unordered_set` (except for the bucket interface) with iterator/reference stability, and additional features (see class documentation). 23 | - `seq::ordered_map`: associative version of `seq::ordered_set`. 24 | - [seq::radix_hash_set](radix_tree.md): radix based hash table with a similar interface to `std::unordered_set`. Uses incremental rehash, no memory peak. 25 | - `seq::radix_hash_map`: associative version of `seq::radix_hash_set`. 26 | - [seq::concurrent_map](concurrent_map.md) and `seq::concurrent_set`: higly scalable concurrent hash tables. 27 | - Strings: 28 | - [seq::tiny_string](tiny_string.md): string-like class with configurable Small String Optimization and tiny memory footprint. Makes most string containers faster. 29 | 30 | -------------------------------------------------------------------------------- /benchs/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | #include(CTest) # We include CTest which is part of CMake 3 | 4 | 5 | enable_testing() 6 | # create the testing file and list of tests 7 | create_test_sourcelist (Benchs 8 | seq_benchs.cpp 9 | bench_format.cpp 10 | bench_hash.cpp 11 | bench_concurrent_hash.cpp 12 | bench_map.cpp 13 | bench_sequence.cpp 14 | bench_text_stream.cpp 15 | bench_tiered_vector.cpp 16 | bench_tiny_string.cpp 17 | bench_boost_unordered_benchmarks.cpp 18 | bench_sort.cpp 19 | ) 20 | 21 | # add the executable 22 | add_executable (seq_benchs ${Benchs}) 23 | 24 | set_property(TARGET seq_benchs PROPERTY CXX_STANDARD 17) 25 | 26 | # find boost for flat_set and concurrent_flat_map 27 | find_package(Boost) 28 | if(${Boost_FOUND}) 29 | message(STATUS "Boost library found!") 30 | target_compile_definitions(seq_benchs PRIVATE -DBOOST_FOUND) 31 | if(${Boost_VERSION_STRING} VERSION_GREATER_EQUAL "1.83") 32 | message(STATUS "Boost library version high enough to test concurrent_flat_map!") 33 | target_compile_definitions(seq_benchs PRIVATE -DBOOST_CONCURRENT_MAP_FOUND) 34 | #target_compile_features(seq_benchs PRIVATE cxx_std_17) 35 | #set_property(TARGET seq_benchs PROPERTY CXX_STANDARD 20) 36 | endif() 37 | if(${Boost_VERSION_STRING} VERSION_GREATER_EQUAL "1.81") 38 | message(STATUS "Boost library version high enough to test unordered_flat_map!") 39 | target_compile_definitions(seq_benchs PRIVATE -DBOOST_UNORDERED_MAP_FOUND) 40 | #target_compile_features(seq_benchs PRIVATE cxx_std_17) 41 | #set_property(TARGET seq_benchs PROPERTY CXX_STANDARD 20) 42 | endif() 43 | target_include_directories(seq_benchs PRIVATE ${Boost_INCLUDE_DIRS}) 44 | endif() 45 | 46 | # find TBB for concurrent_hash_map and concurrent_unordered_map 47 | find_package(TBB COMPONENTS tbb) 48 | if(${TBB_FOUND}) 49 | message(STATUS "TBB library found! ${TBB_VERSION}") 50 | 51 | target_compile_definitions(seq_benchs PRIVATE -DTBB_FOUND) 52 | target_include_directories(seq_benchs PRIVATE ${TBB_INCLUDE_DIRS}) 53 | target_link_libraries(seq_benchs PRIVATE TBB::tbb) 54 | endif() 55 | 56 | 57 | 58 | # Test boost::container::flat_set insert and erase performances (very slow) 59 | if(TEST_BOOST_INSERT_ERASE) 60 | target_compile_definitions(seq_benchs PRIVATE -DTEST_BOOST_INSERT_ERASE) 61 | endif() 62 | 63 | # msvc warnings generated by seq_benchs.cpp 64 | target_compile_definitions(seq_benchs PRIVATE -D_CRT_SECURE_NO_WARNINGS) 65 | 66 | if (WIN32 AND CMAKE_CXX_COMPILER_ID MATCHES "GNU") 67 | # mingw 68 | target_link_options(seq_benchs PRIVATE -lKernel32 -lpsapi -lBcrypt ) 69 | elseif(UNIX AND (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU")) 70 | target_link_options(seq_benchs PRIVATE -lpthread ) 71 | endif() 72 | 73 | if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") 74 | target_compile_options(seq_benchs PRIVATE /bigobj) 75 | endif() 76 | 77 | target_include_directories(seq_benchs PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) 78 | target_include_directories(seq_benchs PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/../..) 79 | 80 | 81 | # remove the test driver source file 82 | set (BenchsToRun ${Benchs}) 83 | remove (BenchsToRun seq_benchs.cpp) 84 | 85 | # Add all the ADD_TEST for each test 86 | foreach (bench ${BenchsToRun}) 87 | get_filename_component (TName ${bench} NAME_WE) 88 | add_test (NAME ${TName} COMMAND seq_benchs ${TName}) 89 | endforeach () 90 | 91 | install (TARGETS seq_benchs RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) -------------------------------------------------------------------------------- /benchs/gtl/vec_utils.hpp: -------------------------------------------------------------------------------- 1 | #ifndef vec_utils_hpp_guard_ 2 | #define vec_utils_hpp_guard_ 3 | 4 | // --------------------------------------------------------------------------- 5 | // Copyright (c) 2022, Gregory Popovitch - greg7mdp@gmail.com 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // https://www.apache.org/licenses/LICENSE-2.0 12 | // --------------------------------------------------------------------------- 13 | #include 14 | #include 15 | #include 16 | 17 | // --------------------------------------------------------------------------- 18 | // Some utilities to more easily code recursive algorithms using std::vector 19 | // when you are not concerned with performance, and miss the simplicity 20 | // of python lists 21 | // --------------------------------------------------------------------------- 22 | 23 | namespace gtl { 24 | 25 | template 26 | concept VectorLike = requires(T v) { 27 | /* v.reserve(1); */ 28 | v.begin(); 29 | v.end(); 30 | (void)v[0]; 31 | }; 32 | 33 | // ---------------------------------------------------------------------------------- 34 | // returns a new vector which is the concatenation of the vectors passed as arguments 35 | // ---------------------------------------------------------------------------------- 36 | template 37 | auto cat(Vs&&... vs) { 38 | std::common_type_t res; 39 | res.reserve((0 + ... + vs.size())); 40 | (..., (res.insert(res.end(), std::begin(std::forward(vs)), std::end(std::forward(vs))))); 41 | return res; 42 | } 43 | 44 | // ------------------------------------------------------------------------------- 45 | // implements python-like slicing for vectors, negative indices start from the end 46 | // ------------------------------------------------------------------------------- 47 | template 48 | auto slice(V&& v, int first = 0, int last = -1, int stride = 1) { 49 | std::remove_const_t> res; 50 | auto first_iter = 51 | (first >= 0 ? std::begin(std::forward(v)) + first : std::end(std::forward(v)) + (first + 1)); 52 | auto last_iter = (last >= 0 ? std::begin(std::forward(v)) + last : std::end(std::forward(v)) + (last + 1)); 53 | if (last_iter > first_iter) { 54 | std::size_t cnt = (last_iter - first_iter) / stride; 55 | res.reserve(cnt); 56 | for (; cnt-- != 0; first_iter += stride) 57 | res.push_back(*first_iter); 58 | } 59 | return res; 60 | } 61 | 62 | // --------------------------------------------------------------------------------------- 63 | // apply a unary function to every element of a vector, returning the vector of results 64 | // --------------------------------------------------------------------------------------- 65 | template class V> 66 | #ifndef _LIBCPP_VERSION // until this is available 67 | requires std::invocable 68 | #endif 69 | auto map(F&& f, const V& v) { 70 | using result_type = std::invoke_result_t; 71 | V> res; 72 | res.reserve(v.size()); 73 | for (const auto& x : v) 74 | res.push_back(std::forward(f)(x)); 75 | return res; 76 | } 77 | 78 | } // namespace gtl 79 | 80 | #endif // vec_utils_hpp_guard_ 81 | -------------------------------------------------------------------------------- /tests/test_seq/test_algorithm.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | void test_stability() 9 | { 10 | // Test stability 11 | using ptr_type = std::shared_ptr; 12 | 13 | std::vector vec(1000000); 14 | for (size_t i = 0; i < vec.size(); ++i) 15 | vec[i] = std::make_shared(i % 100); 16 | 17 | auto le = [](const auto& l, const auto& r) { return *l < *r; }; 18 | 19 | auto v = vec; 20 | std::stable_sort(v.begin(), v.end(), le); 21 | SEQ_TEST(std::is_sorted(v.begin(), v.end(), le)); 22 | 23 | auto v2 = vec; 24 | seq::net_sort(v2.begin(), v2.end(), le); 25 | SEQ_TEST(std::is_sorted(v2.begin(), v2.end(), le)); 26 | 27 | bool eq = std::equal(v.begin(), v.end(), v2.begin(), v2.end()); 28 | SEQ_TEST(eq); 29 | } 30 | 31 | void test_reverse_sort_stability() 32 | { 33 | // Test stability on vector sorted in descending order. 34 | // This tests the validity of seq::reverse_sort 35 | using ptr_type = std::shared_ptr; 36 | 37 | std::vector vec(1000000); 38 | for (size_t i = 0; i < vec.size(); ++i) 39 | vec[i] = std::make_shared(i % 100); 40 | 41 | auto le = [](const auto& l, const auto& r) { return *l < *r; }; 42 | auto ge = [](const auto& l, const auto& r) { return *l > *r; }; 43 | 44 | // sort in reverse order 45 | std::sort(vec.begin(), vec.end(), ge); 46 | 47 | auto v = vec; 48 | std::stable_sort(v.begin(), v.end(), le); 49 | SEQ_TEST(std::is_sorted(v.begin(), v.end(), le)); 50 | 51 | auto v2 = vec; 52 | seq::net_sort(v2.begin(), v2.end(), le); 53 | SEQ_TEST(std::is_sorted(v2.begin(), v2.end(), le)); 54 | 55 | bool eq = std::equal(v.begin(), v.end(), v2.begin(), v2.end()); 56 | SEQ_TEST(eq); 57 | } 58 | 59 | void test_move_only() 60 | { 61 | // Test net_sort on move only type 62 | using ptr_type = std::unique_ptr; 63 | 64 | std::vector vec(1000000); 65 | for (size_t i = 0; i < vec.size(); ++i) 66 | vec[i] = std::make_unique(i % 100); 67 | 68 | auto le = [](const auto& l, const auto& r) { return *l < *r; }; 69 | 70 | seq::net_sort(vec.begin(), vec.end(), le); 71 | SEQ_TEST(std::is_sorted(vec.begin(), vec.end(), le)); 72 | } 73 | 74 | void test_unique() 75 | { 76 | // Test seq::unique validity and stability 77 | 78 | using ptr_type = std::shared_ptr; 79 | std::vector vec(1000000); 80 | for (size_t i = 0; i < vec.size(); ++i) 81 | vec[i] = std::make_shared(i % 100); 82 | 83 | auto v = vec; 84 | auto end = seq::unique(v.begin(), v.end(), [](const auto& v) { return seq::hasher{}(*v); }, [](const auto& l, const auto& r) { return *l == *r; }); 85 | v.erase(end, v.end()); 86 | SEQ_TEST(v.size() == 100); 87 | SEQ_TEST(std::equal(v.begin(), v.end(), vec.begin(), vec.begin() + 100)); 88 | } 89 | 90 | void test_unique_move_only() 91 | { 92 | // Test seq::unique on move only type 93 | 94 | using ptr_type = std::unique_ptr; 95 | std::vector vec(1000000); 96 | for (size_t i = 0; i < vec.size(); ++i) 97 | vec[i] = std::make_unique(i % 100); 98 | 99 | auto end = seq::unique(vec.begin(), vec.end(), [](const auto& v) { return seq::hasher{}(*v); }, [](const auto& l, const auto& r) { return *l == *r; }); 100 | vec.erase(end, vec.end()); 101 | SEQ_TEST(vec.size() == 100); 102 | } 103 | 104 | SEQ_PROTOTYPE(int test_algorithm(int, char** const)) 105 | { 106 | test_reverse_sort_stability(); 107 | test_unique(); 108 | test_unique_move_only(); 109 | test_stability(); 110 | test_move_only(); 111 | 112 | return 0; 113 | } -------------------------------------------------------------------------------- /benchs/gtl/adv_utils.hpp: -------------------------------------------------------------------------------- 1 | #ifndef gtl_adv_utils_hpp_guard 2 | #define gtl_adv_utils_hpp_guard 3 | 4 | // --------------------------------------------------------------------------- 5 | // Copyright (c) 2023, Gregory Popovitch - greg7mdp@gmail.com 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // https://www.apache.org/licenses/LICENSE-2.0 12 | // --------------------------------------------------------------------------- 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | namespace gtl { 22 | 23 | // --------------------------------------------------------------------------- 24 | // Generalized binary search 25 | // inspired by the fun post from Brent Yorgey. 26 | // https://byorgey.wordpress.com/2023/01/01/competitive-programming-in-haskell-better-binary-search/ 27 | // also see: https://julesjacobs.com/notes/binarysearch/binarysearch.pdf 28 | // --------------------------------------------------------------------------- 29 | 30 | // Concept for "Middle" function. 31 | // compute a potential next value to look at, given the current interval. When 32 | // mid l r returns a value, it must be strictly in between l and r 33 | // --------------------------------------------------------------------------- 34 | template 35 | concept MiddlePredicate = requires(MiddleFn fn, T l, T r) { 36 | { 37 | fn(l, r) 38 | } -> std::same_as>; 39 | }; 40 | 41 | // Concept for a Boolean predicate function 42 | // 43 | template 44 | concept BooleanPredicate = requires(BoolPredFn fn, T val) { 45 | { 46 | fn(val) 47 | } -> std::convertible_to; 48 | }; 49 | 50 | // returns a pair such that pred(first) == false and pred(second) == true 51 | // in the interval [l, r], pred should switch from false to true exactly once. 52 | template 53 | std::pair binary_search(Middle&& middle, Pred&& pred, T l, T r) 54 | requires MiddlePredicate && BooleanPredicate 55 | { 56 | assert(std::forward(pred)(l) == false && std::forward(pred)(r) == true); 57 | auto m = std::forward(middle)(l, r); 58 | if (!m) 59 | return { l, r }; 60 | return std::forward(pred)(*m) 61 | ? binary_search(std::forward(middle), std::forward(pred), l, *m) 62 | : binary_search(std::forward(middle), std::forward(pred), *m, r); 63 | } 64 | 65 | // --------------------------------------------------------------------------- 66 | // Some middle functions 67 | // --------------------------------------------------------------------------- 68 | template 69 | concept is_double = std::same_as; 70 | 71 | // We stop when l and r are exactly one apart 72 | // ------------------------------------------ 73 | template 74 | requires std::integral || is_double 75 | struct middle {}; 76 | 77 | template 78 | struct middle { 79 | std::optional operator()(T l, T r) { 80 | if (r - l > 1) 81 | return std::midpoint(l, r); 82 | return {}; 83 | } 84 | }; 85 | 86 | // Compare doubles using the binary representation 87 | // ----------------------------------------------- 88 | template 89 | struct middle { 90 | std::optional operator()(T l, T r) { 91 | uint64_t* il = reinterpret_cast(&l); 92 | uint64_t* ir = reinterpret_cast(&r); 93 | auto m = middle()(*il, *ir); 94 | if (m) { 95 | uint64_t med = *m; 96 | return *(T*)&med; 97 | } 98 | return {}; 99 | } 100 | }; 101 | 102 | } // namespace gtl 103 | 104 | #endif // gtl_adv_utils_hpp_guard 105 | -------------------------------------------------------------------------------- /seq/internal/binary_search.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2025 Victor Moncada 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | 26 | #ifndef SEQ_BINARY_SEARCH_HPP 27 | #define SEQ_BINARY_SEARCH_HPP 28 | 29 | #include "../type_traits.hpp" 30 | #include 31 | 32 | namespace seq 33 | { 34 | 35 | 36 | template 37 | std::pair lower_bound(Iter ptr, SizeType size, const U& value, const Less& le) 38 | { 39 | using T = typename std::iterator_traits::value_type; 40 | 41 | if constexpr (std::is_arithmetic_v) { 42 | static constexpr SizeType end_of_probe = (sizeof(T) > 16U ? 8 : (sizeof(T) > 8U ? 16 : 32)); 43 | SizeType low = 0; 44 | while (size > end_of_probe) { 45 | SizeType half = size / 2; 46 | low = le(ptr[low + half], value) ? (low + size - half) : low; 47 | size = half; 48 | 49 | half = size / 2; 50 | low = le(ptr[low + half], value) ? (low + size - half) : low; 51 | size = half; 52 | 53 | } 54 | // Finish with linear probing 55 | size += low; 56 | while (low < size && le(ptr[low], value)) 57 | ++low; 58 | return { low, false }; 59 | } 60 | else if constexpr (has_comparable::value) { 61 | SizeType s = 0; 62 | SizeType e = size; 63 | if constexpr (Multi) { 64 | bool exact_match = false; 65 | while (s != e) { 66 | const SizeType mid = (s + e) >> 1; 67 | const int c = le.compare(ptr[mid], value); 68 | if (c < 0) { 69 | s = mid + 1; 70 | } 71 | else { 72 | e = mid; 73 | if (c == 0) { 74 | // Need to return the first value whose key is not less than value, 75 | // which requires continuing the binary search if this is a 76 | // multi-container. 77 | exact_match = true; 78 | } 79 | } 80 | } 81 | return { s, exact_match }; 82 | } 83 | else { // Not a multi-container. 84 | while (s != e) { 85 | const SizeType mid = (s + e) >> 1; 86 | const int c = le.compare(ptr[mid], value); 87 | if (c < 0) { 88 | s = mid + 1; 89 | } 90 | else if (c > 0) { 91 | e = mid; 92 | } 93 | else { 94 | return { mid, true }; 95 | } 96 | } 97 | return { s, false }; 98 | } 99 | } 100 | else { 101 | auto p = ptr; 102 | SizeType count = size; 103 | while (0 < count) { 104 | const SizeType half = count / 2; 105 | if (le(p[half], value)) { 106 | p += half + 1; 107 | count -= half + 1; 108 | } 109 | else 110 | count = half; 111 | } 112 | return { static_cast(p - ptr), false }; 113 | } 114 | } 115 | 116 | template 117 | SEQ_ALWAYS_INLINE SizeType upper_bound(Iter ptr, SizeType size, const U& value, const Le& le) 118 | { 119 | return lower_bound(ptr, size, value, [&le](const auto& a, const auto& b) { return !le(b, a); }).first; 120 | } 121 | 122 | } // end seq 123 | 124 | #endif 125 | -------------------------------------------------------------------------------- /seq/internal/hash_utils.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2025 Victor Moncada 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #ifndef SEQ_HASH_UTILS_HPP 26 | #define SEQ_HASH_UTILS_HPP 27 | 28 | /** @file */ 29 | 30 | #include 31 | #include 32 | #include "../bits.hpp" 33 | 34 | namespace seq 35 | { 36 | namespace detail 37 | { 38 | 39 | /// @brief Gather hash class and equal_to class in the same struct. Inherits both for non empty classes. 40 | /// This is a simple way to handle statefull hash function or equality comparison function. 41 | template 42 | struct HashEqual 43 | : private Hash 44 | , private Equal 45 | { 46 | HashEqual() {} 47 | HashEqual(const Hash& h, const Equal& e) 48 | : Hash(h) 49 | , Equal(e) 50 | { 51 | } 52 | HashEqual(const HashEqual& other) noexcept(std::is_nothrow_copy_constructible_v && std::is_nothrow_copy_constructible_v) 53 | : Hash(other) 54 | , Equal(other) 55 | { 56 | } 57 | HashEqual(HashEqual&& other) noexcept(std::is_nothrow_move_constructible_v && std::is_nothrow_move_constructible_v) 58 | : Hash(std::move(other)) 59 | , Equal(std::move(other)) 60 | { 61 | } 62 | 63 | auto operator=(const HashEqual& other) noexcept(std::is_nothrow_copy_assignable_v && std::is_nothrow_copy_assignable_v) -> HashEqual& 64 | { 65 | static_cast(*this) = static_cast(other); 66 | static_cast(*this) = static_cast(other); 67 | return *this; 68 | } 69 | auto operator=(HashEqual&& other) noexcept(std::is_nothrow_move_assignable_v && std::is_nothrow_move_assignable_v) -> HashEqual& 70 | { 71 | static_cast(*this) = std::move(static_cast(other)); 72 | static_cast(*this) = std::move(static_cast(other)); 73 | return *this; 74 | } 75 | 76 | void swap(HashEqual& other) noexcept(std::is_nothrow_move_assignable_v && std::is_nothrow_move_assignable_v&& 77 | std::is_nothrow_move_constructible_v&& std::is_nothrow_move_constructible_v) 78 | { 79 | std::swap(static_cast(*this), static_cast(other)); 80 | std::swap(static_cast(*this), static_cast(other)); 81 | } 82 | 83 | SEQ_ALWAYS_INLINE auto hash_function() const noexcept -> const Hash& { return (*this); } 84 | SEQ_ALWAYS_INLINE auto key_eq() const noexcept -> const Equal& { return (*this); } 85 | 86 | template 87 | SEQ_ALWAYS_INLINE auto hash(Args&&... args) const noexcept(noexcept(std::declval().operator()(std::forward(args)...))) -> size_t 88 | { 89 | return (Hash::operator()(std::forward(args)...)); 90 | } 91 | template 92 | SEQ_ALWAYS_INLINE bool operator()(Args&&... args) const noexcept(noexcept(std::declval().operator()(std::forward(args)...))) 93 | { 94 | return Equal::operator()(std::forward(args)...); 95 | } 96 | }; 97 | 98 | } 99 | } 100 | 101 | #endif 102 | -------------------------------------------------------------------------------- /docs/charconv.md: -------------------------------------------------------------------------------- 1 | # Charconv: arithmetic value convertion from/to string 2 | 3 | The charconv module provides low-level fast routines to convert numerical values from/to string. 4 | This module was initially developped for very fast containers dump in files or strings, where C++17 is not available. 5 | 6 | This module is less mandatory now that `seq` requires C++17, but is still usefull for: 7 | - Very fast reading/writing numerical values (when exact round trip is not mandatory for floating point values) 8 | - Read/write numerical values from/to ANY kind of character type (including wchar_t, char16_t, char32_t...) 9 | 10 | ## Low level functions 11 | 12 | 13 | The main functions of charconv module are `seq::to_chars` and `seq::from_chars` which provide a similar interface to C++17 functions `std::from_chars` and `std::to_chars`. 14 | They aim to provide a faster alternative to C++ streams for converting floating point and integer types from/to string. Note that they were developped to accomodate my needs, and might not be used in all circumstances. 15 | 16 | `seq::from_chars()` is similar to `std::from_chars` with the following differences: 17 | - Leading spaces are consumed. 18 | - For integral types, a leading '0x' prefix is considered valid. 19 | - For floating point values: if the pattern is a valid floating point text representation too large or too small to be stored in given output value, value will be set to (+-)inf or (+-)0, 20 | and the full pattern will be consumed. Therefore, std::errc::result_out_of_range is never returned. 21 | - Leading '+' sign is considered valid. 22 | - Custom 'dot' character can be passed as argument. 23 | - For floating point values: this function IS NOT AN EXACT PARSER. In some cases it relies on unprecise floating point arithmetic wich might produce different roundings than strtod() function. 24 | Note that the result is almost always equal to the result of strtod(), and potential differences are located in the last digits. Use this function when the speed factor is more important than 100% perfect exactitude. 25 | 26 | `seq::to_chars` is similar to `std::to_chars` with the following differences 27 | - For integral types, additional options on the form of a `seq::integral_chars_format` object can be passed. They add the possibility to output a leading '0x' for hexadecimal 28 | formatting, a minimum width (with zeros padding), upper case outputs for hexadecimal formatting. 29 | - For floating point values, the 'dot' character can be specified. 30 | - For floating point values, this function is NOT AN EXACT FORMATTER. 31 | There are currently a lot of different algorithms to provide fast convertion of floating point values to strings with round-trip guarantees: ryu, grisu-exact, dragonbox... 32 | This function tries to provide a faster and lighter alternative when perfect round-trip is not a requirement (which is my case). 33 | When converting double values, obtained strings are similar to the result of printf in 100% of the cases when the required precision is below 12. 34 | After that, the ratio decreases to 86% of exactitude for a precision of 17. Converting a very high (or very small) value with the 'f' specifier will usually produce slightly different output, especially in the "garbage" digits. 35 | 36 | 37 | ## Working with C++ streams 38 | 39 | 40 | To write numerical values to C++ `std::ostream` objects, see the [format](format.md) module. 41 | 42 | To read numerical values from `std::istream` object, the charconv module provides the stream adaptor `seq::std_input_stream`. 43 | It was developped to read huge tables or matrices from ascii files or strings. 44 | 45 | Basic usage: 46 | 47 | ```cpp 48 | 49 | std::ifstream fin("my_huge_file.txt"); 50 | seq::std_input_stream<> istream(fin); 51 | 52 | // Read trailing lines 53 | std::string trailer; 54 | seq::read_line_from_stream(istream, trailer); 55 | //... 56 | 57 | // Read words 58 | std::string word; 59 | seq::from_stream(istream, word); 60 | //... 61 | 62 | // Read all numeric values into a vector 63 | std::vector vec; 64 | while (true) { 65 | double v; 66 | seq::from_stream(istream, v); 67 | if (istream) 68 | vec.push_back(v); 69 | else 70 | break; 71 | } 72 | 73 | ``` 74 | 75 | Internal benchmarks show that using seq::from_stream() is around 10 times faster than using *std::istream::operator>>()* when reading floating point values from a huge string. 76 | 77 | In additional to `seq::std_input_stream`, *charconv* module provides the similar `seq::buffer_input_stream` and `seq::file_input_stream`. -------------------------------------------------------------------------------- /seq/timer.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2025 Victor Moncada 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #ifndef SEQ_TIMER_HPP 26 | #define SEQ_TIMER_HPP 27 | 28 | #ifdef __APPLE__ 29 | #include 30 | #include 31 | #elif defined(_MSC_VER) || defined(__MINGW32__) 32 | #include "Windows.h" 33 | #else 34 | #include 35 | #endif 36 | #include 37 | 38 | #include "bits.hpp" 39 | 40 | namespace seq 41 | { 42 | /// @class timer 43 | /// 44 | /// @brief Precise timer class returning elapsed time in nanoseconds 45 | /// 46 | /// 47 | 48 | #ifdef __APPLE__ 49 | 50 | class timer 51 | { 52 | std::uint64_t start = 0; 53 | mach_timebase_info_data_t rate; 54 | 55 | public: 56 | timer() 57 | { 58 | mach_timebase_info_data_t rate; 59 | mach_timebase_info(&rate); 60 | } 61 | timer(const timer&) = delete; 62 | timer& operator=(const timer&) = delete; 63 | 64 | void tick() noexcept { start = mach_absolute_time(); } 65 | std::uint64_t tock() const noexcept 66 | { 67 | std::uint64_t elapsed = mach_absolute_time() - start; 68 | return (elapsed * rate.numer) / rate.denom; 69 | } 70 | }; 71 | 72 | #elif defined(_MSC_VER) || defined(__MINGW32__) 73 | 74 | class timer 75 | { 76 | LARGE_INTEGER StartingTime{ 0, 0 }; 77 | uint64_t start = 0; 78 | uint64_t ok = 0; 79 | 80 | public: 81 | timer() noexcept {} 82 | timer(const timer&) = delete; 83 | timer& operator=(const timer&) = delete; 84 | 85 | void tick() noexcept 86 | { 87 | start = std::chrono::high_resolution_clock::now().time_since_epoch().count(); 88 | ok = ::QueryPerformanceCounter(&StartingTime); 89 | } 90 | std::uint64_t tock() const noexcept 91 | { 92 | LARGE_INTEGER EndingTime, Elapsed, Frequency; 93 | if (ok) 94 | if (::QueryPerformanceCounter(&EndingTime) == TRUE) 95 | if (::QueryPerformanceFrequency(&Frequency) == TRUE) { 96 | Elapsed.QuadPart = EndingTime.QuadPart - StartingTime.QuadPart; 97 | Elapsed.QuadPart *= 1000000000LL; 98 | Elapsed.QuadPart /= Frequency.QuadPart; 99 | return static_cast(Elapsed.QuadPart); 100 | } 101 | return (std::chrono::high_resolution_clock::now().time_since_epoch().count() - start); 102 | } 103 | }; 104 | 105 | #else 106 | 107 | // Unix systems 108 | 109 | class timer 110 | { 111 | timespec start; 112 | timespec diff(timespec start, timespec end) const noexcept 113 | { 114 | timespec temp; 115 | if ((end.tv_nsec - start.tv_nsec) < 0) { 116 | temp.tv_sec = end.tv_sec - start.tv_sec - 1; 117 | temp.tv_nsec = 1000000000 + end.tv_nsec - start.tv_nsec; 118 | } 119 | else { 120 | temp.tv_sec = end.tv_sec - start.tv_sec; 121 | temp.tv_nsec = end.tv_nsec - start.tv_nsec; 122 | } 123 | return temp; 124 | } 125 | 126 | public: 127 | timer() {} 128 | timer(const timer&) = delete; 129 | timer& operator=(const timer&) = delete; 130 | 131 | void tick() noexcept { clock_gettime(CLOCK_MONOTONIC, &start); } 132 | std::uint64_t tock() const noexcept 133 | { 134 | timespec end; 135 | clock_gettime(CLOCK_MONOTONIC, &end); 136 | 137 | timespec d = diff(start, end); 138 | return d.tv_sec * 1000000000ull + d.tv_nsec; 139 | } 140 | }; 141 | #endif 142 | 143 | } 144 | 145 | #endif 146 | -------------------------------------------------------------------------------- /tests/test_seq/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | #include(CTest) # We include CTest which is part of CMake 3 | 4 | option(SEQ_TEST_CVECTOR "Test cvector class" ON) 5 | 6 | 7 | enable_testing() 8 | # create the testing file and list of tests 9 | create_test_sourcelist (Tests 10 | seq_tests.cpp 11 | test_any.cpp 12 | test_charconv.cpp 13 | test_format.cpp 14 | test_devector.cpp 15 | test_flat_map.cpp 16 | test_ordered_map.cpp 17 | test_radix_hash_map.cpp 18 | test_radix_tree.cpp 19 | test_sequence.cpp 20 | test_tiered_vector.cpp 21 | test_tiny_string.cpp 22 | test_all_maps.cpp 23 | test_concurrent_map.cpp 24 | test_algorithm.cpp 25 | ) 26 | 27 | 28 | # add the executable 29 | add_executable (seq_tests ${Tests}) 30 | 31 | 32 | target_compile_features(seq_tests PRIVATE cxx_std_17) 33 | set_property(TARGET seq_tests PROPERTY CXX_STANDARD 17) 34 | 35 | 36 | # msvc warnings generated by seq_tests.cpp 37 | target_compile_definitions(seq_tests PRIVATE -D_CRT_SECURE_NO_WARNINGS) 38 | 39 | if(SEQ_NO_WARNINGS) 40 | if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") 41 | target_compile_options(seq_tests PRIVATE /WX /W3 ) 42 | elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") 43 | target_compile_options(seq_tests PRIVATE -Werror -Wall -Wno-c++98-compat -Wno-c++98-compat-pedantic) 44 | else() 45 | target_compile_options(seq_tests PRIVATE -Werror -Wall) 46 | endif() 47 | endif() 48 | 49 | #set the most drastic flags 50 | if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") 51 | target_compile_options(seq_tests PRIVATE /bigobj /WX /W3 /UNDEBUG -D_SCL_SECURE_NO_WARNINGS) 52 | set_source_files_properties(seq_tests.cpp PROPERTIES COMPILE_OPTIONS /W0) 53 | elseif(WIN32 AND CMAKE_CXX_COMPILER_ID MATCHES "Clang") 54 | #target_compile_options(seq_tests PRIVATE -Wno-unused-template -Wno-missing-prototypes -Wno-documentation-unknown-command -Wno-sizeof-pointer-memaccess -Wno-format-nonliteral -Wno-microsoft-cast -Wno-reserved-id-macro -Wno-float-equal -Wno-extra-semi-stmt -Wno-exit-time-destructors -Wno-documentation -Wno-c++98-compat -Wno-c++98-compat-pedantic -Werror -Wall -Wextra -Wold-style-cast -march=native -UNDEBUG ) 55 | target_compile_options(seq_tests PRIVATE -Werror -Wall -Wno-c++98-compat -Wno-c++98-compat-pedantic -march=native -UNDEBUG) 56 | set_source_files_properties(seq_tests.cpp PROPERTIES COMPILE_OPTIONS -Wno-error) 57 | elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") 58 | #target_compile_options(seq_tests PRIVATE -Wno-unused-template -Wno-missing-prototypes -Wno-documentation-unknown-command -Wno-sizeof-pointer-memaccess -Wno-format-nonliteral -Wno-reserved-id-macro -Wno-float-equal -Wno-error,-Wextra-semi-stmt -Wno-exit-time-destructors -Wno-documentation -Wno-c++98-compat -Wno-c++98-compat-pedantic -Werror -Wall -Wextra -Wold-style-cast -march=native -UNDEBUG ) 59 | target_compile_options(seq_tests PRIVATE -Werror -Wall -Wno-c++98-compat -Wno-c++98-compat-pedantic -march=native -UNDEBUG) 60 | set_source_files_properties(seq_tests.cpp PROPERTIES COMPILE_OPTIONS -Wno-error) 61 | else() 62 | #target_compile_options(seq_tests PRIVATE -Werror -Wall -Wextra -Wold-style-cast -march=native -UNDEBUG ) 63 | target_compile_options(seq_tests PRIVATE -Werror -Wall -march=native -UNDEBUG) 64 | set_source_files_properties(seq_tests.cpp PROPERTIES COMPILE_OPTIONS -Wno-error) 65 | endif() 66 | 67 | target_compile_definitions(seq_tests PRIVATE -DSEQ_USE_INTERNAL_STD_TO_CHARS) 68 | 69 | if (WIN32 AND CMAKE_CXX_COMPILER_ID MATCHES "GNU") 70 | # mingw 71 | target_link_options(seq_tests PRIVATE -lKernel32 -lpsapi -lBcrypt) 72 | elseif(UNIX AND (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU")) 73 | target_link_options(seq_tests PRIVATE -lpthread ) 74 | endif() 75 | 76 | 77 | target_include_directories(seq_tests PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) 78 | target_include_directories(seq_tests PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/..) 79 | target_include_directories(seq_tests PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/../..) 80 | target_include_directories(seq_tests PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/../../..) 81 | #target_include_directories(seq_tests PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/mimalloc/include) 82 | 83 | 84 | # remove the test driver source file 85 | set (TestsToRun ${Tests}) 86 | remove (TestsToRun seq_tests.cpp) 87 | 88 | # Add all the ADD_TEST for each test 89 | foreach (test ${TestsToRun}) 90 | get_filename_component (TName ${test} NAME_WE) 91 | add_test (NAME ${TName} COMMAND seq_tests ${TName}) 92 | endforeach () 93 | 94 | install (TARGETS seq_tests RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) -------------------------------------------------------------------------------- /docs/v2.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Notes on version 2 of seq library 5 | --------------------------------- 6 | 7 | The version 2 of `seq` introduced several changes on all modules, which are listed below. 8 | 9 | Library wide changes 10 | -------------------- 11 | 12 | - The biggest change is the library requirement which was upgraded to C++17. Indeed, working with C++14 was painfull and all compilers I now work with support at least C++17. 13 | - Another big change is the full removal of the `cvector` class (compressed vector-like container). Indeed, `cvector` relied on a compression algorithm that was heavily refactored and upgraded, up to the point where it did not belong to a library about containers... 14 | Therefore, the compression algorithm and the `cvector` class were moved to a new open source project called stenos. 15 | - The library is now header-only library thanks to the removal of `cvector` class. 16 | - [Pdqsort](https://github.com/orlp/pdqsort) is not used anymore within the library. Instead the `net_sort` algorithm (from [algorithm](algorithm.md) module) is used everywhere. 17 | - The `memory` module (deprecated in v1.3) was removed. 18 | 19 | [bits](bits.md) 20 | -------------------- 21 | 22 | - Internal refactoring. 23 | - Updated SEQ_LIKELY/SEQ_UNLIKELY to use c++20 [[likely]]/[[unlikely]] attributes if available. 24 | - Added class `fast_rand`: fast 32 bits random number generator. 25 | 26 | [hash](hash.md) 27 | -------------------- 28 | 29 | - Internal refactoring. 30 | - Moved implementation which was in `hash.cpp` to `hash_impl.hpp` 31 | 32 | 33 | [charconv](charconv.md) 34 | ---------------------------- 35 | 36 | - Internal refactoring 37 | - Removed file `charconv.cpp` 38 | - All functions to read/write integral/floating numbers are now template, and work on any character type instead of just `char`. 39 | 40 | [format](format.md) 41 | ------------------------ 42 | 43 | - Full refactoring of the module. 44 | - All functions now work with any character type including `wchar_t`, `char16_t`, `char32_t` and `char8_t` (if available). 45 | 46 | [any](any.md) 47 | ------------------ 48 | 49 | - Minor refactoring to simplify the code. 50 | - Now relies on seq::hasher instead of std::hash. 51 | 52 | [containers](containers.md) 53 | -------------------------------- 54 | 55 | - Sequential random-access containers: 56 | - [seq::devector](devector.md): 57 | - Minor refactoring. 58 | - Corrected a memory leak in shrink_to_fit(). 59 | - Updated the internal strategy when growing from front or back: the data are not anymore moved in the middle of the array in case of front/back growing. 60 | This allows to get rid of the last template parameter `DEVectorFlag`. devector now behaves almost exactly like the QVector class. 61 | - [seq::tiered_vector](tiered_vector.md): 62 | - Minor refactoring and optimizatoins. 63 | - Update of the iterator class which was not detected as random access by c++20 concept random_access_iterator. 64 | - Sequential stable non random-access container: `seq::sequence`: minor refactoring. Switched from pdqsort to net_sort as sorting algorithm. 65 | - Sorted containers: 66 | - [seq::flat_set](flat_set.md), `seq::flat_map`, `seq::flat_multiset`, `seq::flat_multimap`: 67 | - Internal refactoring and optimizations, huge code simplification. 68 | - Better support of transparent comparison functions. 69 | - Switched from pdqsort to net_sort as sorting algorithm. 70 | - Removed the possibility to modify the underlying tiered_vector container. 71 | - Member functions `tvector()` and `ctvector()` where gathered and renamed in a single const member `container()` 72 | - Added C++23 members extract() and replace() 73 | - [seq::radix_set](radix_tree.md) and `seq::radix_map`: 74 | - Full refactoring to simplify the class. 75 | - It now supports any kind of key that has `data()` and `size()` members (all string classes, std::vector, std::array...) 76 | - The tree is now rebalanced when erasing keys in order to reduce its memory footprint. 77 | - Hash tables: 78 | - [seq::ordered_set](ordered_set.md) and `seq::ordered_map`: minor refactoring 79 | - [seq::radix_hash_set](radix_tree.md) and `seq::radix_hash_map`: 80 | - Same huge refactoring as [seq::radix_set](radix_tree.md). 81 | - Better handling of transparent hash/comparison functions. 82 | - [seq::concurrent_map](concurrent_map.md) and `seq::concurrent_set`: 83 | - Internal refactoring and optimizations. 84 | - Added mechanisms to avoid busy wait on shard rehash. This increases performances on insert operations. 85 | - Strings: 86 | - [seq::tiny_string](tiny_string.md): minor changes in the `find_*` functions. Most of them now use std::basic_string_view. 87 | - Added several type traits to help detect string types and character types. 88 | 89 | 90 | [algorithm](algorithm.md) 91 | ------------------------- 92 | 93 | New module, provides several iterator based algorithms including the `net_sort` sorting one. 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /benchs/gtl/lru_cache.hpp: -------------------------------------------------------------------------------- 1 | #ifndef gtl_lru_cache_hpp_ 2 | #define gtl_lru_cache_hpp_ 3 | 4 | // --------------------------------------------------------------------------- 5 | // Copyright (c) 2022, Gregory Popovitch - greg7mdp@gmail.com 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // https://www.apache.org/licenses/LICENSE-2.0 12 | // --------------------------------------------------------------------------- 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | namespace gtl { 21 | 22 | // ------------------------------------------------------------------------------ 23 | // ------------------------------------------------------------------------------ 24 | template, 28 | class Eq = std::equal_to, 29 | class Mutex = std::mutex> 30 | class lru_cache_impl { 31 | public: 32 | using key_type = K; 33 | using result_type = V; 34 | using value_type = typename std::pair; 35 | 36 | using list_type = std::list; 37 | using list_iter = typename list_type::iterator; 38 | 39 | using map_type = gtl::parallel_flat_hash_map>, 44 | N, 45 | Mutex, 46 | list_type>; 47 | 48 | static constexpr size_t num_submaps = map_type::subcnt(); 49 | 50 | // because the cache is sharded (multiple submaps and sublists) 51 | // the max_size is an approximation. 52 | // ------------------------------------------------------------ 53 | lru_cache_impl(size_t max_size = 65536) { 54 | reserve(max_size); 55 | set_cache_size(max_size); 56 | assert(_max_size > 2); 57 | } 58 | 59 | bool exists(const K& k) { 60 | return _cache.if_contains(k, [&](const auto&, list_type&) {}); 61 | } 62 | 63 | std::optional get(const K& k) { 64 | if (result_type res; _cache.if_contains(k, [&](const auto& v, list_type& l) { 65 | res = v.second->second; 66 | l.splice(l.begin(), l, v.second); 67 | })) 68 | return { res }; 69 | return std::nullopt; 70 | } 71 | 72 | template 73 | void insert(const K& key, Val&& value) { 74 | _cache.lazy_emplace_l( 75 | key, 76 | [&](typename map_type::value_type& v, list_type& l) { 77 | // called only when key was already present 78 | v.second->second = std::forward(value); 79 | l.splice(l.begin(), l, v.second); 80 | }, 81 | [&](const typename map_type::constructor& ctor, list_type& l) { 82 | // construct value_type in place when key not present 83 | l.push_front(value_type(key, std::forward(value))); 84 | ctor(key, l.begin()); 85 | if (l.size() > _max_size) { 86 | // remove oldest 87 | auto last = l.end(); 88 | last--; 89 | auto to_delete = std::move(last->first); 90 | l.pop_back(); 91 | return std::optional{ to_delete }; 92 | } 93 | return std::optional{}; 94 | }); 95 | } 96 | 97 | void clear() { _cache.clear(); } 98 | void reserve(size_t n) { _cache.reserve(size_t(n * 1.1f)); } 99 | void set_cache_size(size_t max_size) { _max_size = max_size / num_submaps; } 100 | size_t size() const { return _cache.size(); } 101 | 102 | private: 103 | size_t _max_size; 104 | map_type _cache; 105 | }; 106 | 107 | // ------------------------------------------------------------------------------ 108 | // test lru_cache_test.cpp will only pass if N==0 as it checks exact numbers 109 | // of items remaining in (or expunged from) cache. 110 | // 111 | // but it is fine to use a larger N for real-life applications, although it is 112 | // mostly useful in multi threaded contexts (when using std::mutex). 113 | // ------------------------------------------------------------------------------ 114 | template, class Eq = std::equal_to> 115 | using lru_cache = lru_cache_impl; 116 | 117 | // ------------------------------------------------------------------------------ 118 | // uses std::mutex by default 119 | // ------------------------------------------------------------------------------ 120 | template, class Eq = std::equal_to> 121 | using mt_lru_cache = lru_cache_impl; 122 | 123 | } // namespace gtl 124 | 125 | #endif // gtl_lru_cache_hpp_ 126 | -------------------------------------------------------------------------------- /tests/test_seq/tests.hpp: -------------------------------------------------------------------------------- 1 | #ifndef TESTS_HPP 2 | #define TESTS_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | 11 | namespace test_detail 12 | { 13 | struct Unused 14 | { 15 | static inline std::int64_t& get_count() 16 | { 17 | static std::int64_t count = 0; 18 | return count; 19 | } 20 | }; 21 | static inline std::int64_t& get_count() 22 | { 23 | return Unused::get_count(); 24 | } 25 | } 26 | 27 | template 28 | class TestDestroy 29 | { 30 | static_assert(std::is_arithmetic_v, "TestDestroy only supports arithmetic types"); 31 | 32 | T value; 33 | 34 | public: 35 | static std::int64_t count() { 36 | return test_detail::get_count(); 37 | } 38 | TestDestroy() 39 | :value(){ 40 | ++test_detail::get_count(); 41 | } 42 | TestDestroy(const T& val) 43 | :value(val) { 44 | ++test_detail::get_count(); 45 | } 46 | template 47 | TestDestroy(const U& val, typename std::enable_if,void>::type* =nullptr) 48 | :value(static_cast(val)) { 49 | ++test_detail::get_count(); 50 | } 51 | TestDestroy(const TestDestroy& val) 52 | :value(val.value) { 53 | ++test_detail::get_count(); 54 | } 55 | TestDestroy( TestDestroy&& val) noexcept 56 | :value(std::move(val.value)) { 57 | ++test_detail::get_count(); 58 | } 59 | ~TestDestroy() noexcept { 60 | --test_detail::get_count(); 61 | } 62 | TestDestroy& operator=(const T& other) { 63 | value = other; 64 | return *this; 65 | } 66 | TestDestroy& operator=( T&& other) noexcept { 67 | value = std::move(other); 68 | return *this; 69 | } 70 | TestDestroy& operator=(const TestDestroy& other) { 71 | value = other.value; 72 | return *this; 73 | } 74 | TestDestroy& operator=( TestDestroy&& other) { 75 | value = std::move( other.value); 76 | return *this; 77 | } 78 | TestDestroy& operator++() { 79 | ++value; 80 | return *this; 81 | } 82 | TestDestroy& operator++(int) { 83 | TestDestroy t = *this; 84 | ++value; 85 | return t; 86 | } 87 | TestDestroy& operator--() { 88 | --value; 89 | return *this; 90 | } 91 | TestDestroy& operator--(int) { 92 | TestDestroy t = *this; 93 | --value; 94 | return t; 95 | } 96 | operator T() const { return value; } 97 | const T& val() const { return value; } 98 | }; 99 | 100 | template inline bool operator==(const TestDestroy& l, const TestDestroy& r) { return l.val() == r.val(); } 101 | template inline bool operator!=(const TestDestroy& l, const TestDestroy& r) { return l.val() != r.val(); } 102 | template inline bool operator<(const TestDestroy& l, const TestDestroy& r) { return l.val() < r.val(); } 103 | template inline bool operator>(const TestDestroy& l, const TestDestroy& r) { return l.val() > r.val(); } 104 | template inline bool operator<=(const TestDestroy& l, const TestDestroy& r) { return l.val() <= r.val(); } 105 | template inline bool operator>=(const TestDestroy& l, const TestDestroy& r) { return l.val() >= r.val(); } 106 | 107 | template inline TestDestroy operator*(const TestDestroy& v, T val) { return TestDestroy(v.val() * val); } 108 | 109 | namespace seq 110 | { 111 | template 112 | struct is_relocatable > { 113 | static constexpr bool value = is_relocatable::value && R; 114 | }; 115 | } 116 | 117 | namespace std 118 | { 119 | template 120 | struct hash> 121 | { 122 | size_t operator()(const TestDestroy& v) const { 123 | return std::hash{}(static_cast(v)); 124 | } 125 | }; 126 | } 127 | 128 | 129 | 130 | 131 | 132 | template 133 | struct CountAlloc 134 | { 135 | typedef T value_type; 136 | typedef T* pointer; 137 | typedef const T* const_pointer; 138 | typedef T& reference; 139 | typedef const T& const_reference; 140 | using size_type = size_t; 141 | using difference_type = ptrdiff_t; 142 | using is_always_equal = std::false_type; 143 | using propagate_on_container_swap = std::true_type; 144 | using propagate_on_container_copy_assignment = std::true_type; 145 | using propagate_on_container_move_assignment = std::true_type; 146 | 147 | template 148 | struct rebind { 149 | using other = CountAlloc; 150 | }; 151 | 152 | std::shared_ptr d_count; 153 | 154 | CountAlloc() :d_count(new std::int64_t(0)) {} 155 | CountAlloc(const CountAlloc& other) 156 | :d_count(other.d_count) {} 157 | template 158 | CountAlloc(const CountAlloc& other) 159 | : d_count(other.d_count) {} 160 | ~CountAlloc() {} 161 | CountAlloc& operator=(const CountAlloc& other) { 162 | d_count = other.d_count; 163 | return *this; 164 | } 165 | 166 | bool operator==(const CountAlloc& other) const { return d_count == other.d_count; } 167 | bool operator!=(const CountAlloc& other) const { return d_count != other.d_count; } 168 | 169 | void deallocate(T* p, const size_t count) { 170 | std::allocator{}.deallocate(p, count); 171 | (*d_count) -= count * sizeof(T); 172 | } 173 | T* allocate(const size_t count) { 174 | T* p = std::allocator{}.allocate(count); 175 | (*d_count) += count * sizeof(T); 176 | return p; 177 | } 178 | T* allocate(const size_t count, const void*) { return allocate(count); } 179 | size_t max_size() const noexcept { return static_cast(-1) / sizeof(T); } 180 | }; 181 | 182 | template 183 | std::int64_t get_alloc_bytes(const CountAlloc& al) 184 | { 185 | return *al.d_count; 186 | } 187 | template 188 | std::int64_t get_alloc_bytes(const std::allocator& ) 189 | { 190 | return 0; 191 | } 192 | 193 | #endif 194 | -------------------------------------------------------------------------------- /benchs/gtl/debug_vis/gtl_gdb.py: -------------------------------------------------------------------------------- 1 | # Python GDB formatters for parallel-hashmap 2 | # tested with GCC 10.2 / GDB 9.2 3 | # to install it, ensure the script location is in the Python path 4 | # and type the following command (or put it in $HOME/.gdbinit): 5 | 6 | # python 7 | # import phmap_gdb 8 | # end 9 | 10 | 11 | import gdb.printing 12 | 13 | 14 | def counter(): 15 | i = 0 16 | while(True): 17 | yield str(i) 18 | i += 1 19 | 20 | 21 | def slot_iterator(base_obj): 22 | index = -1 23 | n_items = 0 24 | size = int(base_obj["size_"]) 25 | while n_items < size: 26 | index += 1 27 | if int(base_obj["ctrl_"][index]) < 0: 28 | continue 29 | 30 | n_items += 1 31 | yield base_obj["slots_"][index] 32 | 33 | 34 | def parallel_slot_iterator(base_obj): 35 | array = base_obj["sets_"] 36 | array_len = int(array.type.template_argument(1)) 37 | for index in range(array_len): 38 | obj = array["_M_elems"][index]["set_"] 39 | yield from slot_iterator(obj) 40 | 41 | 42 | def flat_map_iterator(name, item): 43 | yield (next(name), item["value"]["first"]) 44 | yield (next(name), item["value"]["second"]) 45 | 46 | 47 | def flat_set_iterator(name, item): 48 | yield (next(name), item) 49 | 50 | 51 | def node_map_iterator(name, item): 52 | yield (next(name), item.dereference()["first"]) 53 | yield (next(name), item.dereference()["second"]) 54 | 55 | 56 | def node_set_iterator(name, item): 57 | yield (next(name), item.dereference()) 58 | 59 | 60 | def traverse(iterator, slot_type_iterator): 61 | name = counter() 62 | for item in iterator: 63 | yield from slot_type_iterator(name, item) 64 | 65 | 66 | def parallel_size(parallel_hash_obj): 67 | array = parallel_hash_obj["sets_"] 68 | array_len = int(array.type.template_argument(1)) 69 | size = 0 70 | for index in range(array_len): 71 | size += array["_M_elems"][index]["set_"]["size_"] 72 | 73 | return size 74 | 75 | 76 | class FlatMapPrinter: 77 | def __init__(self, val): 78 | self.val = val 79 | 80 | def children(self): 81 | return traverse(slot_iterator(self.val), flat_map_iterator) 82 | 83 | def to_string(self): 84 | return f"gtl::flat_hash_map with {int(self.val['size_'])} elements" 85 | 86 | def display_hint(self): 87 | return "map" 88 | 89 | 90 | class FlatSetPrinter: 91 | def __init__(self, val): 92 | self.val = val 93 | 94 | def children(self): 95 | return traverse(slot_iterator(self.val), flat_set_iterator) 96 | 97 | def to_string(self): 98 | return f"gtl::flat_hash_set with {int(self.val['size_'])} elements" 99 | 100 | def display_hint(self): 101 | return "array" 102 | 103 | 104 | class NodeMapPrinter: 105 | def __init__(self, val): 106 | self.val = val 107 | 108 | def children(self): 109 | return traverse(slot_iterator(self.val), node_map_iterator) 110 | 111 | def to_string(self): 112 | return f"gtl::node_hash_map with {int(self.val['size_'])} elements" 113 | 114 | def display_hint(self): 115 | return "map" 116 | 117 | 118 | class NodeSetPrinter: 119 | def __init__(self, val): 120 | self.val = val 121 | 122 | def children(self): 123 | return traverse(slot_iterator(self.val), node_set_iterator) 124 | 125 | def to_string(self): 126 | return f"gtl::node_hash_set with {int(self.val['size_'])} elements" 127 | 128 | def display_hint(self): 129 | return "array" 130 | 131 | 132 | class ParallelFlatMapPrinter: 133 | def __init__(self, val): 134 | self.val = val 135 | 136 | def children(self): 137 | return traverse(parallel_slot_iterator(self.val), flat_map_iterator) 138 | 139 | def to_string(self): 140 | return f"gtl::parallel_flat_hash_map with {parallel_size(self.val)} elements" 141 | 142 | def display_hint(self): 143 | return "map" 144 | 145 | 146 | class ParallelFlatSetPrinter: 147 | def __init__(self, val): 148 | self.val = val 149 | 150 | def children(self): 151 | return traverse(parallel_slot_iterator(self.val), flat_set_iterator) 152 | 153 | def to_string(self): 154 | return f"gtl::parallel_flat_hash_set with {parallel_size(self.val)} elements" 155 | 156 | def display_hint(self): 157 | return "array" 158 | 159 | 160 | class ParallelNodeMapPrinter: 161 | def __init__(self, val): 162 | self.val = val 163 | 164 | def children(self): 165 | return traverse(parallel_slot_iterator(self.val), node_map_iterator) 166 | 167 | def to_string(self): 168 | return f"gtl::parallel_node_hash_map with {parallel_size(self.val)} elements" 169 | 170 | def display_hint(self): 171 | return "map" 172 | 173 | 174 | class ParallelNodeSetPrinter: 175 | def __init__(self, val): 176 | self.val = val 177 | 178 | def children(self): 179 | return traverse(parallel_slot_iterator(self.val), node_set_iterator) 180 | 181 | def to_string(self): 182 | return f"gtl::parallel_node_hash_set with {parallel_size(self.val)} elements" 183 | 184 | def display_hint(self): 185 | return "array" 186 | 187 | 188 | def build_pretty_printer(): 189 | pp = gdb.printing.RegexpCollectionPrettyPrinter("phmap") 190 | pp.add_printer('flat_hash_map', '^gtl::flat_hash_map<.*>$', FlatMapPrinter) 191 | pp.add_printer('flat_hash_set', '^gtl::flat_hash_set<.*>$', FlatSetPrinter) 192 | pp.add_printer('node_hash_map', '^gtl::node_hash_map<.*>$', NodeMapPrinter) 193 | pp.add_printer('node_hash_set', '^gtl::node_hash_set<.*>$', NodeSetPrinter) 194 | pp.add_printer('parallel_flat_hash_map', '^gtl::parallel_flat_hash_map<.*>$', ParallelFlatMapPrinter) 195 | pp.add_printer('parallel_flat_hash_set', '^gtl::parallel_flat_hash_set<.*>$', ParallelFlatSetPrinter) 196 | pp.add_printer('parallel_node_hash_map', '^gtl::parallel_node_hash_map<.*>$', ParallelNodeMapPrinter) 197 | pp.add_printer('parallel_node_hash_set', '^gtl::parallel_node_hash_set<.*>$', ParallelNodeSetPrinter) 198 | return pp 199 | 200 | 201 | gdb.printing.register_pretty_printer(gdb.current_objfile(), build_pretty_printer()) 202 | -------------------------------------------------------------------------------- /docs/tiered_vector.md: -------------------------------------------------------------------------------- 1 | # Tiered vector 2 | 3 | `seq::tiered_vector` is a random-access, bucket based container providing a similar interface to std::deque. 4 | Its internals are however very different as it is implemented as a tiered vector. 5 | Instead of maintaining a vector of fixed size buckets, `seq::tiered_vector` uses a bucket size close to sqrt(size()). The bucket size is always a power of 2 for fast division and modulo. 6 | Furtheremore, the bucket is not a linear buffer but is instead implemented as a circular buffer. 7 | This allows a complexity of O(sqrt(N)) for insertion and deletion in the middle of the tiered_vector instead of O(N) for std::deque. Inserting and deleting elements at both ends is still O(1). 8 | 9 | `seq::tiered_vector` internally uses `seq::devector` to store the buckets. 10 | `seq::tiered_vector` is used as the backend container for [seq::flat_set](flat_set.md), `seq::flat_map`, `seq::flat_multiset` and `seq::flat_multimap`. 11 | 12 | 13 | ## Interface 14 | 15 | `seq::tiered_vector` interface is the same as `std::deque`, with the additional members: 16 | - for_each() providing a faster way to walk through the tiered_vector than iterators, 17 | - resize_front() to resize the tiered_vector from the front instead of the back of the container, 18 | 19 | 20 | ## Bucket managment 21 | 22 | The `seq::tiered_vector` maintains internally an array of circular buffers (or buckets). At any moment, all buckets have the same size which is a power of 2. At container initialization, the bucket size is given by template parameter *MinBSize* which is, by default, between 64 and 8 depending on value_type size. 23 | Whenever `seq::tiered_vector` grows (through push_back(), push_front(), insert(), resize() ...), the new bucket size is computed using the template parameter *FindBSize*. *FindBSize* must provide the member 24 | 25 | ```cpp 26 | unsigned FindBSize::operator() (size_t size, unsigned MinBSize, unsigned MaxBSize) const noexcept ; 27 | ``` 28 | 29 | returning the new bucket size based on the container size, the minimum and maximum bucket size. Default implementation returns a value close to sqrt(size()) rounded up to the next power of 2. 30 | 31 | If the new bucket size is different than the current one, new buckets are created and elements from the old buckets are moved to the new ones. This has the practical consequence to invalidate all iterators and references on growing or shrinking , as opposed to std::deque which maintains references when inserting/deleting at both ends. 32 | 33 | 34 | ## Inserting and deleting elements 35 | 36 | Inserting or deleting elements at the back or the front behaves the same way as for std::deque, except if the bucket size is updated (as explained above). 37 | 38 | Inerting an element in the middle of `seq::tiered_vector` follows these steps: 39 | - The bucket index and the element position within the bucket are first computed 40 | - The back element of the bucket is extracted and removed from the bucket 41 | - The new element is inserted at the right position. Since the bucket is implemented as a dense circular buffer, at most half of the bucket elements must be moved (toward the left or the right, whichever is the shortest path) 42 | - The back value that was previously removed is inserted at the front of the next bucket 43 | - The next bucket back value is extracted and inserted at the front of the following bucket 44 | - .... 45 | - And so forth until we reach the last bucket. 46 | 47 | This operation of insert front/pop back is very fast on a circular buffer as it involves only two element moves and shifting the begin index of the buffer. If the bucket size is exactly sqrt(size()), inserting an element in the middle performs in O(sqrt(N)) as it involves as many element moves within a single bucket than between buckets. 48 | 49 | In practice the buckets size should be greater than sqrt(size()) as moving elements within a bucket is much faster than between buckets due to cache locality. 50 | Note that, depending on the insertion location, elements can be shifted toward the front bucket instead of the back one if this is the shortest path. This practically divides by 2 (on average) the number of moved elements. 51 | Erasing an element in the middle follows the exact same logic. 52 | Note that inserting/removing relocatable types (where `seq::is_relocatable::value` is true) is faster than for other types. 53 | 54 | 55 | ## Exception guarantee 56 | 57 | All insertion/deletion operations on a `seq::tiered_vector` are much more complex than for a std::deque. Especially, each operation might change the bucket size, and therefore trigger the allocation of new buckets plus moving all elements within the new buckets. 58 | Although possible, providing strong exception guarantee on `seq::tiered_vector` operations would have added a very complex layer hurting its performances. Therefore, all `seq::tiered_vector` operations only provide basic exception guarantee. 59 | 60 | 61 | ## Iterators and references invalidation 62 | 63 | As explained above, all `seq::tiered_vector` operations invalidate iterators and references, except for swapping two `seq::tiered_vector`. 64 | 65 | The only exception is when providing a minimum bucket size (*MinBSize*) equals to the maximum bucket size *MaxBSize*). 66 | In this case, inserting/deleting elements will never change the bucket size and move all elements within new buckets. This affects the members emplace_back(), push_back(), emplace_front() and push_front() that provide the same invalidation rules as for std::deque. 67 | 68 | 69 | ## Performances 70 | 71 | `seq::tiered_vector` was optimized to match libstdc++ `std::deque` performances as close as possible. 72 | 73 | Usually, iterating through a `seq::tiered_vector` is faster than through a std::deque, and the random-access `operator[](size_t)` is also faster. Making a lot of random access based on iterators can be slightly slower with `seq::tiered_vector` depending on the use case. For instance, sorting a `seq::tiered_vector` is slower than sorting a `std::deque`. 74 | 75 | Inserting/deleting single elements in the middle of a `seq::tiered_vector` is several order of magnitudes faster than std::deque due to the tiered-vector implementation. 76 | 77 | `seq::tiered_vector` is faster when working with relocatable types (where `seq::is_relocatable::value == true`). 78 | 79 | The standard conlusion is: you should always benchmark with your own use cases. 80 | -------------------------------------------------------------------------------- /benchs/gtl/meminfo.hpp: -------------------------------------------------------------------------------- 1 | #ifndef gtl_meminfo_hpp_guard 2 | #define gtl_meminfo_hpp_guard 3 | 4 | // --------------------------------------------------------------------------- 5 | // Copyright (c) 2017-2022, Gregory Popovitch - greg7mdp@gmail.com 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // https://www.apache.org/licenses/LICENSE-2.0 12 | // --------------------------------------------------------------------------- 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #if defined(_WIN32) || defined(__CYGWIN__) 20 | #define GTL_WIN 21 | #endif 22 | 23 | #ifdef GTL_WIN 24 | // clang-format off 25 | #include 26 | #include 27 | // clang-format on 28 | 29 | #undef min 30 | #undef max 31 | #elif defined(__linux__) 32 | #include 33 | #include 34 | #elif defined(__FreeBSD__) 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #endif 42 | 43 | namespace gtl { 44 | uint64_t GetSystemMemory(); 45 | uint64_t GetTotalMemoryUsed(); 46 | uint64_t GetProcessMemoryUsed(); 47 | uint64_t GetPhysicalMemory(); 48 | 49 | uint64_t GetSystemMemory() { 50 | #ifdef GTL_WIN 51 | MEMORYSTATUSEX memInfo; 52 | memInfo.dwLength = sizeof(MEMORYSTATUSEX); 53 | GlobalMemoryStatusEx(&memInfo); 54 | return static_cast(memInfo.ullTotalPageFile); 55 | #elif defined(__linux__) 56 | struct sysinfo memInfo; 57 | sysinfo(&memInfo); 58 | auto totalVirtualMem = memInfo.totalram; 59 | 60 | totalVirtualMem += memInfo.totalswap; 61 | totalVirtualMem *= memInfo.mem_unit; 62 | return static_cast(totalVirtualMem); 63 | #elif defined(__FreeBSD__) 64 | kvm_t* kd; 65 | u_int pageCnt; 66 | size_t pageCntLen = sizeof(pageCnt); 67 | u_int pageSize; 68 | struct kvm_swap kswap; 69 | uint64_t totalVirtualMem; 70 | 71 | pageSize = static_cast(getpagesize()); 72 | 73 | sysctlbyname("vm.stats.vm.v_page_count", &pageCnt, &pageCntLen, NULL, 0); 74 | totalVirtualMem = pageCnt * pageSize; 75 | 76 | kd = kvm_open(NULL, _PATH_DEVNULL, NULL, O_RDONLY, "kvm_open"); 77 | kvm_getswapinfo(kd, &kswap, 1, 0); 78 | kvm_close(kd); 79 | totalVirtualMem += kswap.ksw_total * pageSize; 80 | 81 | return totalVirtualMem; 82 | #else 83 | return 0; 84 | #endif 85 | } 86 | 87 | uint64_t GetTotalMemoryUsed() { 88 | #ifdef GTL_WIN 89 | MEMORYSTATUSEX memInfo; 90 | memInfo.dwLength = sizeof(MEMORYSTATUSEX); 91 | GlobalMemoryStatusEx(&memInfo); 92 | return static_cast(memInfo.ullTotalPageFile - memInfo.ullAvailPageFile); 93 | #elif defined(__linux__) 94 | struct sysinfo memInfo; 95 | sysinfo(&memInfo); 96 | auto virtualMemUsed = memInfo.totalram - memInfo.freeram; 97 | 98 | virtualMemUsed += memInfo.totalswap - memInfo.freeswap; 99 | virtualMemUsed *= memInfo.mem_unit; 100 | 101 | return static_cast(virtualMemUsed); 102 | #elif defined(__FreeBSD__) 103 | kvm_t* kd; 104 | u_int pageSize; 105 | u_int pageCnt, freeCnt; 106 | size_t pageCntLen = sizeof(pageCnt); 107 | size_t freeCntLen = sizeof(freeCnt); 108 | struct kvm_swap kswap; 109 | uint64_t virtualMemUsed; 110 | 111 | pageSize = static_cast(getpagesize()); 112 | 113 | sysctlbyname("vm.stats.vm.v_page_count", &pageCnt, &pageCntLen, NULL, 0); 114 | sysctlbyname("vm.stats.vm.v_free_count", &freeCnt, &freeCntLen, NULL, 0); 115 | virtualMemUsed = (pageCnt - freeCnt) * pageSize; 116 | 117 | kd = kvm_open(NULL, _PATH_DEVNULL, NULL, O_RDONLY, "kvm_open"); 118 | kvm_getswapinfo(kd, &kswap, 1, 0); 119 | kvm_close(kd); 120 | virtualMemUsed += kswap.ksw_used * pageSize; 121 | 122 | return virtualMemUsed; 123 | #else 124 | return 0; 125 | #endif 126 | } 127 | 128 | uint64_t GetProcessMemoryUsed() { 129 | #ifdef GTL_WIN 130 | PROCESS_MEMORY_COUNTERS_EX pmc; 131 | GetProcessMemoryInfo(GetCurrentProcess(), reinterpret_cast(&pmc), sizeof(pmc)); 132 | return static_cast(pmc.PrivateUsage); 133 | #elif defined(__linux__) 134 | auto parseLine = [](char* line) -> int { 135 | auto i = strlen(line); 136 | 137 | while (*line < '0' || *line > '9') { 138 | line++; 139 | } 140 | 141 | line[i - 3] = '\0'; 142 | i = atoi(line); 143 | return i; 144 | }; 145 | 146 | auto file = fopen("/proc/self/status", "r"); 147 | auto result = -1; 148 | char line[128]; 149 | 150 | while (fgets(line, 128, file) != nullptr) { 151 | if (strncmp(line, "VmSize:", 7) == 0) { 152 | result = parseLine(line); 153 | break; 154 | } 155 | } 156 | 157 | fclose(file); 158 | return static_cast(result) * 1024; 159 | #elif defined(__FreeBSD__) 160 | struct kinfo_proc info; 161 | size_t infoLen = sizeof(info); 162 | int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid() }; 163 | 164 | sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &infoLen, NULL, 0); 165 | return static_cast(info.ki_rssize * getpagesize()); 166 | #else 167 | return 0; 168 | #endif 169 | } 170 | 171 | uint64_t GetPhysicalMemory() { 172 | #ifdef GTL_WIN 173 | MEMORYSTATUSEX memInfo; 174 | memInfo.dwLength = sizeof(MEMORYSTATUSEX); 175 | GlobalMemoryStatusEx(&memInfo); 176 | return static_cast(memInfo.ullTotalPhys); 177 | #elif defined(__linux__) 178 | struct sysinfo memInfo; 179 | sysinfo(&memInfo); 180 | 181 | auto totalPhysMem = memInfo.totalram; 182 | 183 | totalPhysMem *= memInfo.mem_unit; 184 | return static_cast(totalPhysMem); 185 | #elif defined(__FreeBSD__) 186 | u_long physMem; 187 | size_t physMemLen = sizeof(physMem); 188 | int mib[] = { CTL_HW, HW_PHYSMEM }; 189 | 190 | sysctl(mib, sizeof(mib) / sizeof(*mib), &physMem, &physMemLen, NULL, 0); 191 | return physMem; 192 | #else 193 | return 0; 194 | #endif 195 | } 196 | 197 | } 198 | 199 | #endif // gtl_meminfo_hpp_guard 200 | -------------------------------------------------------------------------------- /benchs/bench_sequence.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2025 Victor Moncada 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | 26 | 27 | #define NOMINMAX 28 | #include 29 | #include 30 | #include 31 | #include "plf/plf_colony.hpp" 32 | 33 | #include 34 | 35 | #undef min 36 | #undef max 37 | 38 | using namespace seq; 39 | 40 | /// @brief Compare performances of seq::sequence, plf::colony and std::list 41 | /// @tparam T 42 | /// @param count 43 | template 44 | void test_sequence_vs_colony(size_t count) 45 | { 46 | using seq_type = sequence>; 47 | using colony_type = plf::colony; 48 | using list_type = std::list; 49 | 50 | std::cout << std::endl; 51 | std::cout << "Compare performances of seq::sequence, plf::colony and std::list " << std::endl; 52 | std::cout << std::endl; 53 | 54 | 55 | std::cout << fmt(fmt("method").l(30), "|", fmt("plf::colony").c(20), "|", fmt("seq::sequence").c(20), "|", fmt("std::list").c(20), "|") << std::endl; 56 | std::cout << fmt(str().l(30).f('-'), "|", str().c(20).f('-'), "|", str().c(20).f('-'), "|", str().c(20).f('-'), "|") << std::endl; 57 | 58 | auto f = fmt(pos<0, 2, 4, 6>(), str().l(30), "|", fmt(pos<0>(), size_t(), " ms").c(20), "|", fmt(pos<0>(), size_t(), " ms").c(20), "|", fmt(pos<0>(), size_t(), " ms").c(20), "|"); 59 | 60 | 61 | 62 | std::vector< T> shufle; 63 | 64 | colony_type col1; 65 | seq_type seq1; 66 | list_type lst; 67 | 68 | size_t col_t, seq_t, lst_t; 69 | 70 | for (size_t i = 0; i < count; ++i) 71 | shufle.push_back((T)i); 72 | seq::random_shuffle(shufle.begin(), shufle.end()); 73 | 74 | 75 | 76 | tick(); 77 | col1.reserve(count); 78 | for (int i = 0; i < count; ++i) 79 | col1.insert((shufle[i])); 80 | col_t = tock_ms(); 81 | 82 | tick(); 83 | seq1.reserve(count); 84 | for (int i = 0; i < count; ++i) 85 | seq1.push_back((shufle[i])); 86 | seq_t = tock_ms(); 87 | 88 | tick(); 89 | for (int i = 0; i < count; ++i) 90 | lst.push_back((shufle[i])); 91 | lst_t = tock_ms(); 92 | 93 | std::cout << f("insert(reserve)", col_t, seq_t, lst_t) << std::endl; 94 | 95 | col1 = colony_type{}; 96 | seq1 = seq_type{}; 97 | 98 | 99 | tick(); 100 | for (int i = 0; i < count; ++i) 101 | col1.insert((shufle[i])); 102 | col_t = tock_ms(); 103 | 104 | tick(); 105 | for (int i = 0; i < count; ++i) 106 | seq1.insert((shufle[i])); 107 | seq_t = tock_ms(); 108 | 109 | tick(); 110 | for (int i = 0; i < count; ++i) 111 | lst.push_back((shufle[i])); 112 | lst_t = tock_ms(); 113 | 114 | std::cout << f("insert", col_t, seq_t, lst_t) << std::endl; 115 | 116 | 117 | 118 | tick(); 119 | col1.clear(); 120 | col_t = tock_ms(); 121 | 122 | tick(); 123 | seq1.clear(); 124 | seq_t = tock_ms(); 125 | 126 | tick(); 127 | lst.clear(); 128 | lst_t = tock_ms(); 129 | 130 | SEQ_TEST(seq1.size() == 0 && col1.size() == 0 && lst.size() == 0); 131 | std::cout << f("clear", col_t, seq_t, lst_t) << std::endl; 132 | 133 | for (int i = 0; i < count; ++i) { 134 | col1.insert((shufle[i])); 135 | seq1.insert((shufle[i])); 136 | lst.push_back((shufle[i])); 137 | } 138 | 139 | tick(); 140 | col1.erase(col1.begin(), col1.end()); 141 | col_t = tock_ms(); 142 | 143 | tick(); 144 | seq1.erase(seq1.begin(), seq1.end()); 145 | seq_t = tock_ms(); 146 | 147 | tick(); 148 | lst.erase(lst.begin(), lst.end()); 149 | lst_t = tock_ms(); 150 | 151 | SEQ_TEST(seq1.size() == 0 && col1.size() == 0 && lst.size() == 0); 152 | std::cout << f("erase(begin(),end())", col_t, seq_t, lst_t) << std::endl; 153 | 154 | 155 | 156 | for (int i = 0; i < count; ++i) { 157 | col1.insert((shufle[i])); 158 | seq1.insert((shufle[i])); 159 | lst.push_back((shufle[i])); 160 | } 161 | T sum = 0; 162 | 163 | tick(); 164 | for (auto it = col1.begin(); it != col1.end(); ++it) 165 | sum += *it; 166 | col_t = tock_ms(); 167 | 168 | tick(); 169 | for (auto it = seq1.begin(); it != seq1.end(); ++it) 170 | sum += *it; 171 | seq_t = tock_ms(); 172 | 173 | tick(); 174 | for (auto it = lst.begin(); it != lst.end(); ++it) 175 | sum += *it; 176 | lst_t = tock_ms(); 177 | 178 | std::cout << f("iterate", col_t, seq_t, lst_t) << std::endl; print_null(sum); 179 | 180 | 181 | 182 | 183 | tick(); 184 | for (auto it = col1.begin(); it != col1.end(); ++it) { 185 | it = col1.erase(it); 186 | } 187 | col_t = tock_ms(); 188 | 189 | 190 | tick(); 191 | for (auto it = seq1.begin(); it != seq1.end(); ++it) { 192 | it = seq1.erase(it); 193 | } 194 | seq_t = tock_ms(); 195 | 196 | tick(); 197 | for (auto it = lst.begin(); it != lst.end(); ++it) { 198 | it = lst.erase(it); 199 | } 200 | lst_t = tock_ms(); 201 | 202 | SEQ_TEST(seq1.size() == col1.size() && col1.size() == lst.size() ); 203 | std::cout << f("erase half", col_t, seq_t, lst_t) << std::endl; 204 | 205 | 206 | tick(); 207 | for (int i = 0; i < shufle.size(); ++i) 208 | col1.insert(shufle[i]); 209 | col_t = tock_ms(); 210 | 211 | tick(); 212 | for (int i = 0; i < shufle.size(); ++i) 213 | seq1.insert(shufle[i]); 214 | seq_t = tock_ms(); 215 | 216 | tick(); 217 | for (int i = 0; i < shufle.size(); ++i) 218 | lst.push_back(shufle[i]); 219 | lst_t = tock_ms(); 220 | 221 | std::cout << f("insert again", col_t, seq_t, lst_t) << std::endl; 222 | 223 | 224 | 225 | col1.clear(); 226 | seq1.clear(); 227 | lst.clear(); 228 | for (int i = 0; i < count; ++i) { 229 | seq1.push_back((shufle[i])); 230 | col1.insert((shufle[i])); 231 | lst.push_back((shufle[i])); 232 | } 233 | 234 | 235 | tick(); 236 | col1.sort(); 237 | col_t = tock_ms(); 238 | 239 | tick(); 240 | seq1.sort(); 241 | seq_t = tock_ms(); 242 | 243 | tick(); 244 | lst.sort(); 245 | lst_t = tock_ms(); 246 | 247 | std::cout << f("sort", col_t, seq_t, lst_t) << std::endl; 248 | 249 | } 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | int bench_sequence(int, char** const) 259 | { 260 | test_sequence_vs_colony(5000000); 261 | return 0; 262 | } -------------------------------------------------------------------------------- /benchs/bench_sort.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #ifdef BOOST_FOUND 10 | #include 11 | #endif 12 | 13 | #include "pdqsort.hpp" 14 | 15 | template 16 | void indisort(T start, size_t size, Cmp c) 17 | { 18 | using value_type = typename std::iterator_traits::value_type; 19 | 20 | std::vector buffer(size * 3u); 21 | uintptr_t* sort_array = buffer.data(); 22 | uintptr_t* indexes = buffer.data() + size; 23 | value_type** ptrs = (value_type**)(buffer.data() + size * 2); 24 | size_t index = 0; 25 | for (auto it = start; index != size; ++it, ++index) { 26 | 27 | sort_array[index] = (index); 28 | indexes[index] = (index); 29 | ptrs[index] = (&(*it)); 30 | } 31 | // pdqsort(sort_array, sort_array + size, [&ptrs,c](size_t l, size_t r) { return c(*ptrs[l] , *ptrs[r]); }); 32 | auto le = [&ptrs, c](size_t l, size_t r) noexcept { return c(*ptrs[l], *ptrs[r]); }; 33 | seq::net_sort(sort_array, sort_array + size, le); 34 | 35 | index = 0; 36 | 37 | for (auto current_index = sort_array; current_index != sort_array + size; ++current_index, ++index) { 38 | 39 | size_t idx = indexes[*current_index]; 40 | while (idx != indexes[idx]) 41 | idx = indexes[idx]; 42 | value_type tmp = std::move(*ptrs[index]); 43 | *ptrs[index] = std::move(*ptrs[idx]); 44 | *ptrs[idx] = std::move(tmp); 45 | 46 | indexes[index] = idx; 47 | } 48 | } 49 | 50 | template 51 | std::vector generate_random_numbers(size_t count, Arithmetic max = std::numeric_limits::max()) 52 | { 53 | std::mt19937 rng(0); 54 | std::vector vec(count); 55 | if constexpr (std::is_integral_v) { 56 | std::uniform_int_distribution dist(0, max); 57 | for (size_t i = 0; i < vec.size(); ++i) 58 | vec[i] = (dist(rng)); 59 | } 60 | else { 61 | std::uniform_real_distribution dist(0, max); 62 | for (size_t i = 0; i < vec.size(); ++i) 63 | vec[i] = (dist(rng)); 64 | } 65 | return vec; 66 | } 67 | 68 | template 69 | std::vector generate_random_strings(size_t count, size_t max_size) 70 | { 71 | std::vector res(count); 72 | for (String& s : res) 73 | s = seq::generate_random_string((int)max_size, true); 74 | return res; 75 | } 76 | 77 | template 78 | std::vector generate_random(size_t count, size_t max_size_or_val) 79 | { 80 | if constexpr (std::is_arithmetic_v) 81 | return generate_random_numbers(count, max_size_or_val); 82 | else 83 | return generate_random_strings(count, max_size_or_val); 84 | } 85 | 86 | template 87 | std::vector generate_waves(size_t count, size_t max_wave_len, size_t max_val) 88 | { 89 | std::mt19937 rng(0); 90 | std::uniform_int_distribution dist(0, max_wave_len); 91 | 92 | std::vector res; 93 | while (res.size() < count) { 94 | std::vector tmp; 95 | size_t size = dist(rng); 96 | tmp = generate_random(size, max_val); 97 | 98 | // Sort and potentially reverse 99 | std::sort(tmp.begin(), tmp.end()); 100 | if (dist(rng) & 1) 101 | std::reverse(tmp.begin(), tmp.end()); 102 | 103 | res.insert(res.end(), tmp.begin(), tmp.end()); 104 | } 105 | 106 | res.resize(count); 107 | return res; 108 | } 109 | 110 | enum Method 111 | { 112 | StdSort, 113 | StdStableSort, 114 | Pdqsort, 115 | #ifdef BOOST_FOUND 116 | BoostSpinSort, 117 | #endif 118 | NetSort, 119 | NetSortTiny 120 | }; 121 | 122 | template 123 | bool sort(Vec& v, Cmp c, Method m) 124 | { 125 | switch (m) { 126 | case StdSort: 127 | std::sort(v.begin(), v.end(), c); 128 | break; 129 | case StdStableSort: 130 | std::stable_sort(v.begin(), v.end(), c); 131 | break; 132 | case Pdqsort: 133 | pdqsort(v.begin(), v.end(), c); 134 | break; 135 | #ifdef BOOST_FOUND 136 | case BoostSpinSort: 137 | boost::sort::spinsort(v.begin(), v.end(), c); 138 | break; 139 | #endif 140 | case NetSort: 141 | seq::net_sort(v.begin(), v.end(), c); 142 | break; 143 | case NetSortTiny: 144 | seq::net_sort(v.begin(), v.end(), c, seq::tiny_buffer); 145 | break; 146 | } 147 | return std::is_sorted(v.begin(), v.end()); 148 | } 149 | 150 | template 151 | void test_pattern(const Vec& v, const char* name, F f) 152 | { 153 | using namespace seq; 154 | 155 | auto h = join("|", _str().l(20), _str().c(20), _fmt("is_sorted").c(20)); 156 | std::cout << std::endl; 157 | std::cout << h(name, "time", null) << std::endl; 158 | std::cout << h(seq::fill('-'), seq::fill('-'), seq::fill('-')) << std::endl; 159 | 160 | auto vec = v; 161 | size_t el; 162 | bool sorted; 163 | 164 | tick(); 165 | sorted = sort(vec, std::less<>{}, StdSort); 166 | el = tock_ms(); 167 | std::cout << f("std::sort", el, sorted) << std::endl; 168 | 169 | vec = v; 170 | tick(); 171 | sorted = sort(vec, std::less<>{}, StdStableSort); 172 | el = tock_ms(); 173 | std::cout << f("std::stable_sort", el, sorted) << std::endl; 174 | 175 | vec = v; 176 | tick(); 177 | sorted = sort(vec, std::less<>{}, Pdqsort); 178 | el = tock_ms(); 179 | std::cout << f("pdqsort", el, sorted) << std::endl; 180 | 181 | #ifdef BOOST_FOUND 182 | vec = v; 183 | tick(); 184 | sorted = sort(vec, std::less<>{}, BoostSpinSort); 185 | el = tock_ms(); 186 | std::cout << f("boost::spinsort", el, sorted) << std::endl; 187 | #endif 188 | 189 | vec = v; 190 | tick(); 191 | sorted = sort(vec, std::less<>{}, NetSort); 192 | el = tock_ms(); 193 | std::cout << f("seq::net_sort", el, sorted) << std::endl; 194 | 195 | vec = v; 196 | tick(); 197 | sorted = sort(vec, std::less<>{}, NetSortTiny); 198 | el = tock_ms(); 199 | std::cout << f("seq::net_sort_tiny", el, sorted) << std::endl; 200 | } 201 | 202 | template 203 | void test_patterns_for_type(size_t count, size_t max_val) 204 | { 205 | auto f = seq::join("|", seq::_str().l(20), seq::_fmt(seq::_u(), " ms").c(20), seq::_fmt().c(20)); 206 | 207 | auto v = generate_random(count, max_val); 208 | std::sort(v.begin(), v.end()); 209 | 210 | test_pattern(v, "sorted", f); 211 | 212 | std::sort(v.begin(), v.end(), std::greater<>{}); 213 | test_pattern(v, "reverse", f); 214 | 215 | test_pattern(generate_random(count, max_val), "random", f); 216 | 217 | test_pattern(generate_waves(count, 1000, max_val), "wave", f); 218 | } 219 | 220 | int bench_sort(int, char** const) 221 | { 222 | std::cout << "Test uint64_t" << std::endl; 223 | test_patterns_for_type(10000000, std::numeric_limits::max()); 224 | 225 | std::cout << std::endl; 226 | std::cout << "Test uint64_t % 100" << std::endl; 227 | test_patterns_for_type(10000000, 100); 228 | 229 | std::cout << "Test double" << std::endl; 230 | test_patterns_for_type(10000000, std::numeric_limits::max()); 231 | 232 | using string = std::string; 233 | 234 | std::cout << std::endl; 235 | std::cout << "Test string length 4" << std::endl; 236 | test_patterns_for_type(1000000, 4); 237 | 238 | std::cout << std::endl; 239 | std::cout << "Test string length 15" << std::endl; 240 | test_patterns_for_type(1000000, 15); 241 | 242 | std::cout << std::endl; 243 | std::cout << "Test string length 70" << std::endl; 244 | test_patterns_for_type(1000000, 70); 245 | 246 | return 0; 247 | } -------------------------------------------------------------------------------- /benchs/libcuckoo/cuckoohash_util.hh: -------------------------------------------------------------------------------- 1 | /** \file */ 2 | 3 | #ifndef _CUCKOOHASH_UTIL_HH 4 | #define _CUCKOOHASH_UTIL_HH 5 | 6 | #include "cuckoohash_config.hh" // for LIBCUCKOO_DEBUG 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace libcuckoo { 13 | 14 | #if LIBCUCKOO_DEBUG 15 | //! When \ref LIBCUCKOO_DEBUG is 0, LIBCUCKOO_DBG will printing out status 16 | //! messages in various situations 17 | #define LIBCUCKOO_DBG(fmt, ...) \ 18 | fprintf(stderr, "\x1b[32m" \ 19 | "[libcuckoo:%s:%d:%lu] " fmt "" \ 20 | "\x1b[0m", \ 21 | __FILE__, __LINE__, \ 22 | std::hash()(std::this_thread::get_id()), \ 23 | __VA_ARGS__) 24 | #else 25 | //! When \ref LIBCUCKOO_DEBUG is 0, LIBCUCKOO_DBG does nothing 26 | #define LIBCUCKOO_DBG(fmt, ...) \ 27 | do { \ 28 | } while (0) 29 | #endif 30 | 31 | /** 32 | * alignas() requires GCC >= 4.9, so we stick with the alignment attribute for 33 | * GCC. 34 | */ 35 | #ifdef __GNUC__ 36 | #define LIBCUCKOO_ALIGNAS(x) __attribute__((aligned(x))) 37 | #else 38 | #define LIBCUCKOO_ALIGNAS(x) alignas(x) 39 | #endif 40 | 41 | /** 42 | * At higher warning levels, MSVC produces an annoying warning that alignment 43 | * may cause wasted space: "structure was padded due to __declspec(align())". 44 | */ 45 | #ifdef _MSC_VER 46 | #define LIBCUCKOO_SQUELCH_PADDING_WARNING __pragma(warning(suppress : 4324)) 47 | #else 48 | #define LIBCUCKOO_SQUELCH_PADDING_WARNING 49 | #endif 50 | 51 | /** 52 | * At higher warning levels, MSVC may issue a deadcode warning which depends on 53 | * the template arguments given. For certain other template arguments, the code 54 | * is not really "dead". 55 | */ 56 | #ifdef _MSC_VER 57 | #define LIBCUCKOO_SQUELCH_DEADCODE_WARNING_BEGIN \ 58 | do { \ 59 | __pragma(warning(push)); \ 60 | __pragma(warning(disable : 4702)) \ 61 | } while (0) 62 | #define LIBCUCKOO_SQUELCH_DEADCODE_WARNING_END __pragma(warning(pop)) 63 | #else 64 | #define LIBCUCKOO_SQUELCH_DEADCODE_WARNING_BEGIN 65 | #define LIBCUCKOO_SQUELCH_DEADCODE_WARNING_END 66 | #endif 67 | 68 | /** 69 | * Thrown when an automatic expansion is triggered, but the load factor of the 70 | * table is below a minimum threshold, which can be set by the \ref 71 | * cuckoohash_map::minimum_load_factor method. This can happen if the hash 72 | * function does not properly distribute keys, or for certain adversarial 73 | * workloads. 74 | */ 75 | class load_factor_too_low : public std::exception { 76 | public: 77 | /** 78 | * Constructor 79 | * 80 | * @param lf the load factor of the table when the exception was thrown 81 | */ 82 | load_factor_too_low(const double lf) noexcept : load_factor_(lf) {} 83 | 84 | /** 85 | * @return a descriptive error message 86 | */ 87 | virtual const char *what() const noexcept override { 88 | return "Automatic expansion triggered when load factor was below " 89 | "minimum threshold"; 90 | } 91 | 92 | /** 93 | * @return the load factor of the table when the exception was thrown 94 | */ 95 | double load_factor() const noexcept { return load_factor_; } 96 | 97 | private: 98 | const double load_factor_; 99 | }; 100 | 101 | /** 102 | * Thrown when an expansion is triggered, but the hashpower specified is greater 103 | * than the maximum, which can be set with the \ref 104 | * cuckoohash_map::maximum_hashpower method. 105 | */ 106 | class maximum_hashpower_exceeded : public std::exception { 107 | public: 108 | /** 109 | * Constructor 110 | * 111 | * @param hp the hash power we were trying to expand to 112 | */ 113 | maximum_hashpower_exceeded(const size_t hp) noexcept : hashpower_(hp) {} 114 | 115 | /** 116 | * @return a descriptive error message 117 | */ 118 | virtual const char *what() const noexcept override { 119 | return "Expansion beyond maximum hashpower"; 120 | } 121 | 122 | /** 123 | * @return the hashpower we were trying to expand to 124 | */ 125 | size_t hashpower() const noexcept { return hashpower_; } 126 | 127 | private: 128 | const size_t hashpower_; 129 | }; 130 | 131 | /** 132 | * This enum indicates whether an insertion took place, or whether the 133 | * key-value pair was already in the table. See \ref cuckoohash_map::uprase_fn 134 | * for usage details. 135 | */ 136 | enum class UpsertContext { 137 | NEWLY_INSERTED, 138 | ALREADY_EXISTED, 139 | }; 140 | 141 | namespace internal { 142 | 143 | // Used to invoke the \ref uprase_fn functor with or without an \ref 144 | // UpsertContext enum. Note that if we cannot pass an upsert context and the 145 | // desired context is UpsertContext:::NEWLY_INSERTED, then we do not 146 | // invoke the functor at all. 147 | // 148 | // We implement this utility using C++11-style SFINAE, for maximum 149 | // compatibility. 150 | template 151 | class CanInvokeWithUpsertContext { 152 | private: 153 | template ()( 155 | std::declval(), std::declval()))> 156 | static std::true_type test(int); 157 | 158 | // Note: The argument type needs to be less-preferable than the first 159 | // overload so that it is picked only if the first overload cannot be 160 | // instantiated. 161 | template 162 | static std::false_type test(float); 163 | 164 | public: 165 | using type = decltype(test(0)); 166 | }; 167 | 168 | template 169 | bool InvokeUpraseFn(F& f, MappedType& mapped, UpsertContext context, 170 | std::true_type) { 171 | return f(mapped, context); 172 | } 173 | 174 | template 175 | bool InvokeUpraseFn(F& f, MappedType& mapped, UpsertContext context, 176 | std::false_type) { 177 | if (context == UpsertContext::ALREADY_EXISTED) { 178 | return f(mapped); 179 | } else { 180 | // Returning false indicates no deletion, making this a no-op. 181 | return false; 182 | } 183 | } 184 | 185 | // Upgrades an upsert functor to an uprase functor, which always returns false, 186 | // so that we never erase the element. 187 | template 188 | struct UpsertToUpraseFn; 189 | 190 | template 191 | struct UpsertToUpraseFn { 192 | F& f; 193 | 194 | bool operator()(MappedType& mapped, UpsertContext context) const { 195 | f(mapped, context); 196 | return false; 197 | } 198 | }; 199 | 200 | template 201 | struct UpsertToUpraseFn { 202 | F& f; 203 | 204 | bool operator()(MappedType& mapped) { 205 | f(mapped); 206 | return false; 207 | } 208 | }; 209 | 210 | } // namespace internal 211 | 212 | } // namespace libcuckoo 213 | 214 | #endif // _CUCKOOHASH_UTIL_HH 215 | -------------------------------------------------------------------------------- /docs/flat_set.md: -------------------------------------------------------------------------------- 1 | # Flat set 2 | 3 | ``seq::flat_set`` is a sorted associative container similar to boost::flat_set, but relying on a [seq::tiered_vector](tiered_vector.md) instead of a flat array. 4 | Therefore, it inherits seq::tiered_vector properties: faster insertion and deletion of individual values (thanks to its tiered-vector based implementation), fast iteration, random access, invalidation of references/iterators on insertion and deletion. 5 | 6 | All keys in a ``seq::flat_set`` are unique. 7 | All references and iterators are invalidated when inserting/removing keys. 8 | 9 | ## Interface 10 | 11 | ``seq::flat_set`` provides a similar interface to std::set (C++17) and std::flat_set (C++20) with the following differences: 12 | - The node related functions are not implemented, 13 | - The member `flat_set::pos()` is used to access elements at a random location, 14 | - The members `flat_set::container()` returns a reference to the underlying seq::tiered_vector object, 15 | - Its iterator and const_iterator types are random access iterators. 16 | 17 | The underlying tiered_vector object stores plain non const Key objects, and `seq::flat_map` iterators return objects of type `std::pair`. 18 | 19 | In addition to members returning (const_)iterator(s), the flat_set provides the same members ending with the '_pos' prefix and returning positions within the tiered_vector instead of iterators. These functions are slightly faster than the iterator based members. 20 | 21 | 22 | ## Direct access to tiered_vector 23 | 24 | Like `std::flat_set`, `seq::flat_set/map/multiset/multimap` provide the members `extract()` to retrieve (move) the underlying seq::tiered_vector object. 25 | The vector can be modified at will and then moved to the flat_set using `replace()` member. In this case, the vector must already be sorted and unique (unless multiset/multimap). 26 | 27 | ## Range insertion 28 | 29 | Inserting a range of values using `flat_set::insert(first, last)` is faster than inserting keys one by one, and should be favored if possible. 30 | Range insertion works the following way: 31 | - New keys are first appended to the underlying tiered_vector 32 | - These new keys are sorted directly within the tiered_vector 33 | - Old keys and new keys are merged using `std::inplace_merge` 34 | - Duplicate values are removed if necessary using `std::unique`. 35 | 36 | If you need to keep stability when inserting range of values, you must set the Stable template parameter to true. 37 | 38 | 39 | ## Exception guarantee 40 | 41 | All ``seq::flat_set`` operations only provide *basic exception guarantee*, exactly like the underlying `seq::tiered_vector`. 42 | 43 | 44 | ## Performances 45 | 46 | Performances of `seq::flat_set` has been measured and compared to std::set, std::unordered_set, boost::flat_set and phmap::btree_set (based on abseil btree_set). 47 | The following table show the results when compiled with gcc 10.1.0 (-O3) for msys2 on Windows 10, using Intel(R) Core(TM) i7-10850H at 2.70GHz. Measured operations are: 48 | - Insert successfully a range of 1M unique double randomly shuffled using set_class::insert(first,last) 49 | - Insert successfully 1M unique double randomly shuffled one by one using set_class::insert(const Key&) 50 | - Insert 1M double randomly shuffled one by one and already present in the set (failed insertion) 51 | - Successfully search for 1M double in the set using set_class::find(const Key&), or flat_set::find_pos(const Key&) 52 | - Search for 1M double not present in the set (failed lookup) 53 | - Walk through the full set (1M double) using iterators 54 | - Successfull find and erase all 1M double one by one using set_class::erase(iterator) 55 | 56 | Note the the given memory is NOT the memory footprint of the container, but the one of the full program. It should be used relatively to compare memory usage difference between each container. 57 | 58 | Set name | Insert(range) | Insert |Insert(failed) |Find (success) | Find (failed) | Iterate | Erase | 59 | ------------------------------|--------------------|--------------------|---------------|---------------|---------------|---------------|---------------| 60 | seq::flat_set | 46 ms/15 MO | 408 ms/16 MO | 128 ms | 130 ms | 122 ms | 1 ms | 413 ms | 61 | phmap::btree_set | 135 ms/18 MO | 118 ms/18 MO | 118 ms | 119 ms | 120 ms | 3 ms | 131 ms | 62 | boost::flat_set | 57 ms/15 MO | 49344 ms/17 MO | 133 ms | 132 ms | 127 ms | 1 ms | 131460 ms | 63 | std::set | 457 ms/54 MO | 457 ms/54 MO | 449 ms | 505 ms | 502 ms | 92 ms | 739 ms | 64 | std::unordered_set | 187 ms/46 MO | 279 ms/50 MO | 100 ms | 116 ms | 155 ms | 29 ms | 312 ms | 65 | 66 | Below is the same benchmark using random seq::tstring of length 13 (using Small String Optimization): 67 | 68 | Set name | Insert(range) | Insert |Insert(failed) |Find (success) | Find (failed) | Iterate | Erase | 69 | ------------------------------|--------------------|--------------------|---------------|---------------|---------------|---------------|---------------| 70 | seq::flat_set | 127 ms/30 MO | 891 ms/31 MO | 252 ms | 245 ms | 240 ms | 1 ms | 904 ms | 71 | phmap::btree_set | 280 ms/37 MO | 278 ms/37 MO | 266 ms | 292 ms | 279 ms | 10 ms | 292 ms | 72 | boost::flat_set | 107 ms/30 MO | 585263 ms/32 MO | 228 ms | 232 ms | 232 ms | 0 ms | 601541 ms | 73 | std::set | 646 ms/77 MO | 640 ms/77 MO | 611 ms | 672 ms | 710 ms | 87 ms | 798 ms | 74 | std::unordered_set | 205 ms/54 MO | 319 ms/57 MO | 157 ms | 192 ms | 220 ms | 34 ms | 380 ms | 75 | 76 | 77 | These benchmarks are available in file 'seq/benchs/bench_map.cpp'. 78 | `seq::flat_set` behaves quite well compared to phmap::btree_set or boost::flat_set, and is even faster for single insertion/deletion than std::set for double type. 79 | 80 | `seq::flat_set` insertion/deletion performs in O(sqrt(N)) on average, as compared to std::set that performs in O(log(N)). 81 | `seq::flat_set` is therfore slower for inserting and deleting elements than std::set when dealing with several millions of elements. 82 | Lookup functions (find, lower_bound, upper_bound...) still perform in O(log(N)) and remain faster that std::set couterparts because of the underlying seq::tiered_vector cache friendliness. 83 | flat_set will almost always be slower for element lookup than boost::flat_set wich uses a single dense array, except for very small keys (like in above benchmark). 84 | 85 | Several factors will impact the performances of `seq::flat_set`: 86 | - Relocatable types (where `seq::is_relocatable::value` is true) are faster than other types for insertion/deletion, as tiered_vector will use memmove to move around objects. Therefore, a flat_set of seq::tstring will always be faster than std::string. 87 | - Performances of insertion/deletion decrease as `sizeof(value_type)` increases. This is especially true for insertion/deletion, much less for lookup functions which remain (more or less) as fast as boost::flat_set. 88 | - All members using the '_pos' prefix are usually slightly faster than their iterator based counterparts. 89 | -------------------------------------------------------------------------------- /tests/test_seq/test_charconv.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2025 Victor Moncada 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include "seq/charconv.hpp" 29 | #include "seq/utils.hpp" 30 | #include "seq/testing.hpp" 31 | 32 | inline double promote(float val) { return static_cast(val); } 33 | inline double promote(double val) { return val; } 34 | inline long double promote(long double val) { return val; } 35 | 36 | template 37 | int __float_to_string_seq(T val, seq::chars_format fmt, int prec, char *dst, char * end) 38 | { 39 | using namespace seq; 40 | int size = static_cast(seq::to_chars(dst, end, val, fmt, prec).ptr - dst); 41 | dst[size] = 0; 42 | return size; 43 | } 44 | template 45 | int __float_to_string_printf(T val, seq::chars_format fmt, int prec, char* dst, char* end) 46 | { 47 | using namespace seq; 48 | std::ostringstream oss; 49 | if (fmt == seq::general) { 50 | if(std::is_same::value) 51 | oss <<"%." << prec << "Lg"; 52 | else 53 | oss << "%." << prec << "g" ; 54 | } 55 | else if (fmt == seq::scientific) { 56 | if (std::is_same::value) 57 | oss << "%." << prec << "Le" ; 58 | else 59 | oss << "%." << prec << "e" ; 60 | } 61 | else { 62 | if (std::is_same::value) 63 | oss << "%." << prec<< "Lf" ; 64 | else 65 | oss << "%." << prec<< "f"; 66 | } 67 | 68 | snprintf(dst,static_cast(end-dst), oss.str().c_str(), promote(val)); 69 | return static_cast(strlen(dst)); 70 | } 71 | 72 | template 73 | bool __test_read_val(const char* src) 74 | { 75 | using namespace seq; 76 | std::istringstream iss(src); 77 | T v; 78 | iss >> v; 79 | if (!iss) { 80 | if (strcmp(src, "inf") == 0) return true; 81 | if (strcmp(src, "-inf") == 0) return true; 82 | if (strcmp(src, "nan") == 0) return true; 83 | return false; 84 | } 85 | return iss.peek() == EOF; 86 | } 87 | 88 | template 89 | int exponent(T v) 90 | { 91 | T exp = std::log10(std::abs(v)); 92 | exp = std::floor(exp); 93 | return static_cast(exp); 94 | } 95 | 96 | template 97 | bool __test_equal(const char* s1, const char* s2, seq::chars_format fmt, int prec) 98 | { 99 | using namespace seq; 100 | int l1 = static_cast(strlen(s1)); 101 | //int l2 = (int)strlen(s2); 102 | 103 | if (strcmp(s1, s2) == 0) 104 | return true; 105 | 106 | //check for inf and nan 107 | T v1; 108 | if (from_chars(s1, s1 + l1, v1).ec != std::errc()) 109 | return false; 110 | T v2; 111 | { 112 | std::istringstream iss(s2); 113 | iss >> v2; 114 | } 115 | 116 | T saved1 = v1; 117 | T saved2 = v2; 118 | 119 | //equal values: no need to go further 120 | if (v1 == v2) 121 | return true; 122 | if (std::isnan(v1) && std::isnan(v2)) 123 | return true; 124 | 125 | 126 | int exp1 = exponent(v1); 127 | int exp2 = exponent(v2); 128 | if (fmt == seq::fixed) { 129 | // for fixed specifier, harder to compare, for now just check the exponents 130 | return exp1 == exp2; 131 | } 132 | v1 *= static_cast(std::pow(static_cast(10), static_cast(-exp1))); 133 | v2 *= static_cast(std::pow(static_cast(10), static_cast(-exp1))); 134 | 135 | int p = prec; 136 | if (p > 14) p = 14; 137 | if (std::is_same::value && p > 6) 138 | p = 6; 139 | if (p > 0) --p; 140 | T error = static_cast(std::pow(static_cast(10.), static_cast(-p))) *4; 141 | T diff = std::abs(v1 - v2); 142 | if (diff <= error) 143 | return true; 144 | 145 | std::cout << std::setprecision(prec + 6) << "read vals: " << saved1 << " and " << saved2 << std::endl; 146 | std::cout << std::setprecision(prec + 6) << "normalized: " << v1 << " and " << v2 << std::endl; 147 | 148 | std::cout << std::setprecision(prec + 6) << "diff is " << diff << " and max error is " << error << std::endl; 149 | return false; 150 | 151 | /*int p = prec; 152 | if (p > 15) p = 15; 153 | if (std::is_same::value && p > 6) 154 | p = 6; 155 | if (p > 0) --p; 156 | T error = (T)std::pow((T)10., (T)-p); 157 | T pow = std::log10(std::abs(v1)); 158 | T mul = std::pow((T)10., pow < 0 ? std::ceil(pow) : std::floor(pow)); 159 | error *= mul*4; 160 | 161 | T diff = std::abs(v1 - v2); 162 | if (diff <= error) 163 | return true; 164 | 165 | std::cout << std::setprecision(prec+6)<< "diff is " << diff << " and max error is " << error << std::endl; 166 | return false;*/ 167 | } 168 | 169 | 170 | 171 | 172 | 173 | template 174 | void test_to_chars(unsigned count, seq::chars_format fmt, int p) 175 | { 176 | using namespace seq; 177 | std::cout << "test charconv for " << count << " random " << typeid(T).name() << " with precision " << p << " and type " << (fmt == seq::scientific ? "scientific" : (fmt == seq::fixed ? "fixed" : "general")) << std::endl; 178 | 179 | random_float_genertor rgn; 180 | std::vector vals; 181 | for (unsigned i = 0; i < count; ++i) 182 | vals.push_back(rgn()); 183 | 184 | char dst1[1000]; 185 | char dst2[1000]; 186 | 187 | for (unsigned i = 0; i < count; ++i) 188 | { 189 | 190 | 191 | __float_to_string_seq(vals[i], fmt, p, dst1, dst1 + sizeof(dst1)); 192 | __float_to_string_printf(vals[i], fmt, p, dst2, dst2 + sizeof(dst2)); 193 | 194 | SEQ_TEST(__test_read_val(dst1)); 195 | SEQ_TEST(__test_read_val(dst2)); 196 | 197 | try { 198 | 199 | SEQ_TEST(__test_equal(dst1, dst2,fmt,p)); 200 | } 201 | catch (...) 202 | { 203 | //SEQ_TEST(__test_equal(dst1, dst2,fmt, p)); 204 | std::cout << "error while parsing " << dst1 << " (seq) and " << dst2 << " (printf) for value " << std::setprecision(static_cast(p+6)) << vals[i]<< std::endl; 205 | throw; 206 | } 207 | 208 | } 209 | } 210 | 211 | 212 | inline void test_charconv(unsigned count = 100000, int max_precision = 50) 213 | { 214 | 215 | for (int p = 0; p < max_precision; ++p) 216 | test_to_chars(count, seq::general, p); 217 | for (int p = 0; p < max_precision; ++p) 218 | test_to_chars(count, seq::scientific, p); 219 | 220 | 221 | for (int p = 0; p < max_precision; ++p) 222 | test_to_chars(count, seq::general, p); 223 | for (int p = 0; p < max_precision; ++p) 224 | test_to_chars(count, seq::scientific, p); 225 | 226 | for (int p = 0; p < max_precision; ++p) 227 | test_to_chars(count, seq::general, p); 228 | for (int p = 0; p < max_precision; ++p) 229 | test_to_chars(count, seq::scientific, p); 230 | 231 | } 232 | 233 | 234 | SEQ_PROTOTYPE(int test_charconv(int , char*[])) 235 | { 236 | SEQ_TEST_MODULE_RETURN(charconv, 1, test_charconv(10000, 30)); 237 | return 0; 238 | } 239 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [![CTest](https://github.com/Thermadiag/seq/actions/workflows/build-linux.yml/badge.svg?branch=main)](https://github.com/Thermadiag/seq/actions/workflows/build-linux.yml) 4 | [![CTest](https://github.com/Thermadiag/seq/actions/workflows/build-macos.yml/badge.svg?branch=main)](https://github.com/Thermadiag/seq/actions/workflows/build-macos.yml) 5 | [![CTest](https://github.com/Thermadiag/seq/actions/workflows/build-windows.yml/badge.svg?branch=main)](https://github.com/Thermadiag/seq/actions/workflows/build-windows.yml) 6 | 7 | Transitioning to v2.0 8 | --------------------- 9 | 10 | *seq* v2.0 introduced a lot of changes. See this [note](docs/v2.md) for more details and explanations. 11 | 12 | Purpose 13 | ------- 14 | 15 | The *seq* library is a header-only collection of original C++17 STL-like containers and related tools. 16 | 17 | *seq* library does not try to reimplement already existing container classes present in other libraries like folly, abseil, boost and (of course) std. Instead, it provides new features (or a combination of features) that are usually not present in other libraries. Some low level API like bits manipulation or hashing functions are not new, but must be defined to keep the seq library self dependent. 18 | 19 | Among other things (see modules below), the *seq* library defines several container classes as alternatives to STL containers or providing features not present in the STL. 20 | These containers generally adhere to the properties of STL containers (in C++17 version), though there are often some associated API differences and/or implementation details which differ from the standard library. 21 | 22 | The *seq* containers are not necessarly drop-in replacement for their STL counterparts as they usually provide different iterator/reference statibility rules or different exception guarantees. 23 | 24 | Currently, the *containers* module provide 5 types of containers: 25 | - Sequential random-access containers: 26 | - [seq::devector](docs/devector.md): double ended vector that optimized for front and back operations. Similar interface to `std::deque`. 27 | - [seq::tiered_vector](docs/tiered_vector.md): tiered vector implementation optimized for fast insertion and deletion in the middle. Similar interface to `std::deque`. 28 | - Sequential stable non random-access container: `seq::sequence`, fast stable list-like container. 29 | - Sorted containers: 30 | - [seq::flat_set](docs/flat_set.md) : flat set container similar to boost::flat_set but based on seq::tiered_vector and providing fast insertion/deletion of single elements, while keeping the fast lookup performances of flat containers. 31 | - `seq::flat_map`: associative version of `seq::flat_set`. 32 | - `seq::flat_multiset`: similar to `seq::flat_set` but supporting duplicate keys. 33 | - `seq::flat_multimap`: similar to `seq::flat_map` but supporting duplicate keys. 34 | - [seq::radix_set](docs/radix_tree.md) : radix based (derived from the *Burst Trie*) sorted container with a similar interface to `std::set`. One of the fastest `std::set` like data structure for all operations, especially point lookup. 35 | - `seq::radix_map`: associative version of `seq::radix_set`. 36 | - Hash tables: 37 | - [seq::ordered_set](docs/ordered_set.md): Ordered robin-hood hash table with backward shift deletion. Drop-in replacement for `std::unordered_set` (except for the bucket and node interface) with iterator/reference stability, with performances close to 'flat' hash maps. `seq::ordered_set` preserves the insertion order. 38 | - `seq::ordered_map`: associative version of `seq::ordered_set`. 39 | - [seq::radix_hash_set](docs/radix_tree.md): radix based hash table with a similar interface to `std::unordered_set`. Uses incremental rehash (no memory peak) with a very small memory footprint. 40 | - `seq::radix_hash_map`: associative version of `seq::radix_hash_set`. 41 | - [seq::concurrent_map](docs/concurrent_map.md) and `seq::concurrent_set`: higly scalable concurrent hash tables with interfaces similar to `boost::concurrent_flat_set/map`. 42 | - Strings: 43 | - [seq::tiny_string](docs/tiny_string.md): relocatable string-like class with configurable Small String Optimization and tiny memory footprint. Makes most string containers faster. 44 | 45 | 46 | Content 47 | ------- 48 | 49 | The library is divided in 7 small modules: 50 | - [bits](docs/bits.md): low-level bits manipulation utilities 51 | - [hash](docs/hash.md): tiny hashing framework 52 | - [charconv](docs/charconv.md): fast arithmetic to/from string conversion 53 | - [format](docs/format.md): fast and type safe formatting tools 54 | - [containers](docs/containers.md): main module, collection of original containers: double ended vector, tiered-vector, ordered hash map, flat map based on tiered-vector, compressed vector... 55 | - [any](docs/any.md): type-erasing polymorphic object wrapper used to build heterogeneous containers, including hash tables and sorted containers. 56 | - [algorithm](docs/algorithm.md): a (small) collection of algorithm include the `net_sort` stable sorting algorithm. 57 | 58 | A cmake project is provided for installation and compilation of tests/benchmarks. 59 | 60 | *seq* library was tested with gcc 10.1.0 and 13.2.0 (Windows and Linux), msvc 19.43 (Windows), ClangCL 12.0.0 (Windows). 61 | 62 | Design 63 | ------ 64 | 65 | *seq* library is a small collection of self dependant components. There is no restriction on internal dependencies, and a seq component can use any number of other components. For instance, almost all modules rely on the [bits](docs/bits.md) one. 66 | 67 | All classes and functions are defined in the `seq` namespace, and names are lower case with underscore separators, much like the STL. 68 | Macro names are upper case and start with the `SEQ_` prefix. 69 | 70 | The directory structure is flat and use the "stuttering" scheme `seq/seq` used by many other libraries like boost. 71 | Including a file has the following syntax: `#include ` 72 | 73 | The `seq/tests` subdirectory includes tests for all components, usually named `test_modulename.cpp`, and rely on CTest (shipped with CMake). The tests try to cover as much features as possible, *but bugs might still be present*. Do not hesitate to contact me if you discover something unusual. 74 | The `seq/benchs` subdirectory includes benchmarks for some components, usually named `bench_modulename.cpp`, and rely on CTest. The benchmarks are performed against other libraries that are provided in the 'benchs' folder. 75 | 76 | Build 77 | ----- 78 | 79 | The *seq* library is header-only, but a cmake file is provided for installation and build of tests and benchmarks. 80 | 81 | Currently, the following options are provided: 82 | 83 | - SEQ_BUILD_TESTS(OFF): build all tests 84 | - SEQ_BUILD_BENCHS(OFF): build all benchmarks 85 | 86 | 87 | Acknowledgements 88 | ---------------- 89 | 90 | *seq* library uses a simplified version of the [komihash](https://github.com/avaneev/komihash) hash function for its hashing framework. 91 | 92 | The `net_sort` stable sorting algorithm uses several ideas originaly coming (I think) from the [quadsort](https://github.com/scandum/quadsort) algorithm from scandum (bidirectional merge and ping-pong merge). 93 | 94 | Benchmarks (in `seq/benchs`) compare the performances of the *seq* library with other great libraries that I use in personnal or professional projects: 95 | - plf: used for the plf::colony container, 96 | - gtl: used for its gtl::btree_set and gtl::parallel_flat_hash_map, 97 | - boost: used for boost::flat_set, boost::unordered_flat_set and boost::concurrent_flat_map, 98 | - unordered_dense: used for ankerl::unordered_dense::set, 99 | - TBB: used for tbb::concurrent_unordered_map and tbb::concurrent_hash_map. 100 | 101 | Some of these libraries are included in the `seq/benchs` folder. 102 | 103 | 104 | seq:: library and this page Copyright (c) 2025, Victor Moncada 105 | -------------------------------------------------------------------------------- /benchs/gtl/soa.hpp: -------------------------------------------------------------------------------- 1 | #ifndef gtl_soa_hpp_ 2 | #define gtl_soa_hpp_ 3 | 4 | // --------------------------------------------------------------------------- 5 | // Copyright (c) 2022, Gregory Popovitch - greg7mdp@gmail.com 6 | // modified from the Mark Liu's version - licenses below 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); 9 | // you may not use this file except in compliance with the License. 10 | // You may obtain a copy of the License at 11 | // 12 | // https://www.apache.org/licenses/LICENSE-2.0 13 | // --------------------------------------------------------------------------- 14 | // Copyright (c) 2018 Mark Liu 15 | // 16 | // Permission is hereby granted, free of charge, to any person obtaining a copy 17 | // of this software and associated documentation files (the "Software"), to deal 18 | // in the Software without restriction, including without limitation the rights 19 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 20 | // copies of the Software, and to permit persons to whom the Software is 21 | // furnished to do so, subject to the following conditions: 22 | // 23 | // The above copyright notice and this permission notice shall be included in all 24 | // copies or substantial portions of the Software. 25 | // 26 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 32 | // SOFTWARE. 33 | // --------------------------------------------------------------------------- 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | namespace gtl { 44 | 45 | template 46 | class soa { 47 | public: 48 | using storage_type = std::tuple...>; 49 | 50 | template 51 | using nth_col_type = typename std::tuple_element::type; 52 | 53 | template 54 | using col_type = typename nth_col_type::value_type; 55 | 56 | template 57 | const auto& get_column() const { 58 | return std::get(data_); 59 | } 60 | 61 | template 62 | nth_col_type& get_column() { 63 | return std::get(data_); 64 | } 65 | 66 | size_t size() const { return get_column<0>().size(); } 67 | 68 | bool empty() const { return get_column<0>().empty(); } 69 | 70 | template 71 | void insert(Xs... xs) { 72 | insert_impl(std::index_sequence_for{}, std::forward_as_tuple(xs...)); 73 | } 74 | 75 | auto operator[](size_t idx) const { 76 | return std::apply([=](auto&... x) { return std::tie(x[idx]...); }, data_); 77 | } 78 | 79 | auto operator[](size_t idx) { 80 | return std::apply([=](auto&... x) { return std::tie(x[idx]...); }, data_); 81 | } 82 | 83 | template 84 | auto view(size_t row) const { 85 | return get_row_impl(std::integer_sequence{}, row); 86 | } 87 | 88 | template 89 | auto view(size_t row) { 90 | return get_row_impl(std::integer_sequence{}, row); 91 | } 92 | 93 | void clear() { 94 | std::apply([](auto&... x) { (x.clear(), ...); }, data_); 95 | } 96 | 97 | void resize(size_t sz) { 98 | std::apply([=](auto&... x) { (x.resize(sz), ...); }, data_); 99 | } 100 | 101 | void reserve(size_t sz) { 102 | std::apply([=](auto&... x) { (x.reserve(sz), ...); }, data_); 103 | } 104 | 105 | template 106 | void sort_by_field(C&& comp) { 107 | size_t num_elems = size(); 108 | thread_local sort_data sort_tmp; // thread_local makes it static 109 | 110 | sort_tmp.resize(num_elems); 111 | for (size_t i = 0; i < num_elems; ++i) 112 | sort_tmp.o[i] = i; 113 | 114 | auto& col = get_column(); 115 | 116 | auto comp_wrapper = [&](size_t a, size_t b) { return comp(col[a], col[b]); }; 117 | 118 | std::stable_sort(sort_tmp.o.begin(), sort_tmp.o.end(), comp_wrapper); 119 | 120 | sort_by_reference_impl(sort_tmp, std::index_sequence_for{}); 121 | } 122 | 123 | template 124 | void sort_by_field() { 125 | sort_by_field([](auto&& a, auto&& b) { return a < b; }); 126 | } 127 | 128 | void print(std::basic_ostream& ss) const { 129 | size_t num_elems = size(); 130 | ss << "soa {\n"; 131 | for (size_t i = 0; i < num_elems; ++i) { 132 | const auto t = (*this)[i]; 133 | ss << "\t"; 134 | print(ss, t, std::make_index_sequence()); 135 | ss << '\n'; 136 | } 137 | ss << "}" << '\n'; 138 | } 139 | 140 | private: 141 | struct sort_data { 142 | std::vector o; // sort order 143 | bit_vector done; 144 | void resize(size_t sz) { 145 | o.resize(sz); 146 | done.resize(sz); 147 | } 148 | }; 149 | 150 | template 151 | static void print(std::basic_ostream& ss, const TupType& _tup, std::index_sequence) { 152 | // c++17 unary left fold, of the form `... op pack`, where `op` is the comma operator 153 | (..., (ss << (I == 0 ? "" : ", ") << std::get(_tup))); 154 | } 155 | 156 | template 157 | void insert_impl(std::integer_sequence, T t) { 158 | ((get_column().push_back(std::get(t))), ...); 159 | } 160 | 161 | template 162 | auto get_row_impl(std::integer_sequence, size_t row) const { 163 | return std::tie(get_column()[row]...); 164 | } 165 | 166 | template 167 | auto get_row_impl(std::integer_sequence, size_t row) { 168 | return std::tie(get_column()[row]...); 169 | } 170 | 171 | template 172 | void sort_by_reference_impl(sort_data& sort_tmp, std::integer_sequence) { 173 | ((sort_col_by_reference(sort_tmp, std::integral_constant{})), ...); 174 | } 175 | 176 | // o contains the index containing the value going at this position 177 | // so if o[0] = 5, it means that c[5] should go to c[0]. 178 | // c contains the values to be reordered according to o. 179 | template 180 | void reorder(C& c, const std::vector& o, bit_vector& done) { 181 | size_t num_elems = o.size(); 182 | 183 | done.reset(); 184 | 185 | for (size_t i = 0; i < num_elems; ++i) { 186 | if (!done[i]) { 187 | size_t curidx = i; 188 | typename C::value_type ci(std::move(c[i])); 189 | 190 | do { 191 | assert(!done[curidx]); 192 | done.set(curidx); 193 | if (o[curidx] == i) { 194 | c[curidx] = std::move(ci); 195 | break; 196 | } 197 | c[curidx] = std::move(c[o[curidx]]); 198 | curidx = o[curidx]; 199 | } while (o[curidx] != curidx); 200 | } 201 | } 202 | } 203 | 204 | template 205 | void sort_col_by_reference(sort_data& sort_tmp, std::integral_constant) { 206 | auto& col = std::get(data_); 207 | reorder(col, sort_tmp.o, sort_tmp.done); 208 | } 209 | 210 | storage_type data_; 211 | }; 212 | 213 | template 214 | std::ostream& operator<<(std::ostream& cout, const gtl::soa& soa) { 215 | soa.print(cout); 216 | return cout; 217 | } 218 | } 219 | 220 | #endif /* gtl_soa_hpp_ */ 221 | -------------------------------------------------------------------------------- /seq/lock.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2025 Victor Moncada 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #ifndef SEQ_LOCK_HPP 26 | #define SEQ_LOCK_HPP 27 | 28 | /** @file */ 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #include "bits.hpp" 37 | 38 | namespace seq 39 | { 40 | /// @brief Lightweight and fast spinlock implementation based on https://rigtorp.se/spinlock/ 41 | /// 42 | /// spinlock is a lightweight spinlock implementation following the TimedMutex requirements. 43 | /// 44 | class spinlock 45 | { 46 | std::atomic d_lock; 47 | 48 | public: 49 | constexpr spinlock() noexcept 50 | : d_lock(0) 51 | { 52 | } 53 | 54 | spinlock(spinlock const&) = delete; 55 | spinlock& operator=(spinlock const&) = delete; 56 | 57 | SEQ_ALWAYS_INLINE void lock() noexcept 58 | { 59 | for (;;) { 60 | // Optimistically assume the lock is free on the first try 61 | if (!d_lock.exchange(true, std::memory_order_acquire)) 62 | return; 63 | 64 | // Wait for lock to be released without generating cache misses 65 | while (d_lock.load(std::memory_order_relaxed)) 66 | // Issue X86 PAUSE or ARM YIELD instruction to reduce contention between 67 | // hyper-threads 68 | std::this_thread::yield(); 69 | } 70 | } 71 | 72 | SEQ_ALWAYS_INLINE bool is_locked() const noexcept { return d_lock.load(std::memory_order_relaxed); } 73 | SEQ_ALWAYS_INLINE bool try_lock() noexcept 74 | { 75 | // First do a relaxed load to check if lock is free in order to prevent 76 | // unnecessary cache misses if someone does while(!try_lock()) 77 | return !d_lock.load(std::memory_order_relaxed) && !d_lock.exchange(true, std::memory_order_acquire); 78 | } 79 | SEQ_ALWAYS_INLINE bool try_lock_shared() noexcept { return try_lock(); } 80 | 81 | SEQ_ALWAYS_INLINE void unlock() noexcept { d_lock.store(false, std::memory_order_release); } 82 | 83 | template 84 | SEQ_ALWAYS_INLINE bool try_lock_for(const std::chrono::duration& duration) noexcept 85 | { 86 | return try_lock_until(std::chrono::system_clock::now() + duration); 87 | } 88 | 89 | template 90 | SEQ_ALWAYS_INLINE bool try_lock_until(const std::chrono::time_point& timePoint) noexcept 91 | { 92 | for (;;) { 93 | if (!d_lock.exchange(true, std::memory_order_acquire)) 94 | return true; 95 | 96 | while (d_lock.load(std::memory_order_relaxed)) { 97 | if (std::chrono::system_clock::now() > timePoint) 98 | return false; 99 | std::this_thread::yield(); 100 | } 101 | } 102 | } 103 | 104 | SEQ_ALWAYS_INLINE void lock_shared() { lock(); } 105 | SEQ_ALWAYS_INLINE void unlock_shared() { unlock(); } 106 | }; 107 | 108 | /// @brief An unfaire read-write spinlock class that favors write operations 109 | /// 110 | template 111 | class shared_spinner 112 | { 113 | static_assert(std::is_unsigned_v, "shared_spinner only supports unsigned atomic types!"); 114 | using lock_type = LockType; 115 | static constexpr lock_type write = 1; 116 | static constexpr lock_type need_lock = 2; 117 | static constexpr lock_type read = 4; 118 | static constexpr lock_type max_read_mask = 1ull << (sizeof(lock_type) * 8u - 1u); 119 | 120 | bool failed_lock(lock_type& expect) 121 | { 122 | if (!(expect & (need_lock))) 123 | d_lock.fetch_or(need_lock, std::memory_order_release); 124 | expect = need_lock; 125 | return false; 126 | } 127 | SEQ_ALWAYS_INLINE bool try_lock(lock_type& expect) 128 | { 129 | if SEQ_UNLIKELY(!d_lock.compare_exchange_strong(expect, write, std::memory_order_acq_rel)) { 130 | return failed_lock(expect); 131 | } 132 | return true; 133 | } 134 | SEQ_ALWAYS_INLINE void yield() { std::this_thread::yield(); } 135 | 136 | std::atomic d_lock; 137 | 138 | public: 139 | constexpr shared_spinner() 140 | : d_lock(0) 141 | { 142 | } 143 | shared_spinner(shared_spinner const&) = delete; 144 | shared_spinner& operator=(shared_spinner const&) = delete; 145 | 146 | SEQ_ALWAYS_INLINE void lock() 147 | { 148 | lock_type expect = 0; 149 | while SEQ_UNLIKELY(!try_lock(expect)) 150 | yield(); 151 | } 152 | SEQ_ALWAYS_INLINE void unlock() 153 | { 154 | SEQ_ASSERT_DEBUG(d_lock & write, ""); 155 | d_lock.fetch_and(static_cast(~(write | need_lock)), std::memory_order_release); 156 | } 157 | SEQ_ALWAYS_INLINE void lock_shared() 158 | { 159 | while SEQ_UNLIKELY(!try_lock_shared()) 160 | yield(); 161 | } 162 | SEQ_ALWAYS_INLINE void unlock_shared() 163 | { 164 | SEQ_ASSERT_DEBUG(d_lock > 0, ""); 165 | d_lock.fetch_sub(read, std::memory_order_release); 166 | } 167 | // Attempt to acquire writer permission. Return false if we didn't get it. 168 | SEQ_ALWAYS_INLINE bool try_lock() 169 | { 170 | if (d_lock.load(std::memory_order_relaxed) & (need_lock | write)) 171 | return false; 172 | lock_type expect = 0; 173 | return d_lock.compare_exchange_strong(expect, write, std::memory_order_acq_rel); 174 | } 175 | SEQ_ALWAYS_INLINE bool try_lock_fast() { return try_lock();} 176 | SEQ_ALWAYS_INLINE bool try_lock_shared() 177 | { 178 | // This version might be slightly slower in some situations (low concurrency). 179 | // However it works for very small lock type (like uint8_t) by avoiding overflows. 180 | if constexpr (sizeof(d_lock) == 1) { 181 | lock_type content = d_lock.load(std::memory_order_relaxed); 182 | return (!(content & (need_lock | write | max_read_mask)) && d_lock.compare_exchange_strong(content, content + read)); 183 | } 184 | else { 185 | // Version based on fetch_add 186 | if (!(d_lock.load(std::memory_order_relaxed) & (need_lock | write))) { 187 | if SEQ_LIKELY(!(d_lock.fetch_add(read, std::memory_order_acquire) & (need_lock | write))) 188 | return true; 189 | d_lock.fetch_add(-read, std::memory_order_release); 190 | } 191 | return false; 192 | } 193 | } 194 | SEQ_ALWAYS_INLINE bool is_locked() const noexcept { return d_lock.load(std::memory_order_relaxed) != 0; } 195 | SEQ_ALWAYS_INLINE bool is_locked_shared() const noexcept { return d_lock.load(std::memory_order_relaxed) & write; } 196 | }; 197 | 198 | using shared_spinlock = shared_spinner<>; 199 | 200 | /// @brief Dumy lock class that basically does nothing 201 | /// 202 | struct null_lock 203 | { 204 | void lock() noexcept {} 205 | void unlock() noexcept {} 206 | bool try_lock() noexcept { return true; } 207 | template 208 | bool try_lock_for(const std::chrono::duration&) noexcept 209 | { 210 | return true; 211 | } 212 | template 213 | bool try_lock_until(const std::chrono::time_point&) noexcept 214 | { 215 | return true; 216 | } 217 | bool is_locked() const noexcept { return false; } 218 | void lock_shared() noexcept {} 219 | bool try_lock_shared() noexcept { return true; } 220 | void unlock_shared() noexcept {} 221 | }; 222 | 223 | } 224 | 225 | #endif 226 | -------------------------------------------------------------------------------- /benchs/gtl/debug_vis/gtl.natvis: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | sz=unset {_s} 7 | sz={_sz,d} {_s} 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | ... {_s._Mypair._Myval2._Myfirst+3,nax} {_s._Mypair._Myval2._Myfirst+2,nax} {_s._Mypair._Myval2._Myfirst+1,nax} {_s._Mypair._Myval2._Myfirst+0,nax} 16 | {_s._Mypair._Myval2._Myfirst+3,nax} {_s._Mypair._Myval2._Myfirst+2,nax} {_s._Mypair._Myval2._Myfirst+1,nax} {_s._Mypair._Myval2._Myfirst+0,nax} 17 | {_s._Mypair._Myval2._Myfirst+2,nax} {_s._Mypair._Myval2._Myfirst+1,nax} {_s._Mypair._Myval2._Myfirst+0,nax} 18 | {_s._Mypair._Myval2._Myfirst+1,nax} {_s._Mypair._Myval2._Myfirst+0,nax} 19 | {_s._Mypair._Myval2._Myfirst+0,nax} 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | {{size = {size_}}} 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | size_ 37 | 38 | 39 | 40 | *slot,na 41 | 42 | ++slot 43 | ++ctrl 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | {{size = {size_}}} 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | size_ 61 | 62 | 63 | 64 | **slot,na 65 | 66 | ++slot 67 | ++ctrl 68 | 69 | 70 | 71 | 72 | 73 | 74 | {value} 75 | 76 | 77 | 78 | 79 | unset 80 | end() 81 | {*slot_,na} 82 | 83 | 84 | 85 | 86 | unset 87 | end() 88 | {**slot_,na} 89 | 90 | 91 | 92 | 93 | 94 | {{size = ?}} 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | maxidx = 2 << maxidx 104 | 105 | 106 | ctrl = sets_._Elems[idx].set_.ctrl_ 107 | slot = sets_._Elems[idx].set_.slots_ 108 | ctrl_end = sets_._Elems[idx].set_.ctrl_ + sets_._Elems[idx].set_.capacity_ 109 | slot_end = sets_._Elems[idx].set_.slots_ + sets_._Elems[idx].set_.capacity_ 110 | 111 | 112 | 113 | *slot,na 114 | 115 | ++slot 116 | ++ctrl 117 | 118 | ++idx 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | {{size = ?}} 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | maxidx = 2 << maxidx 137 | 138 | 139 | ctrl = sets_._Elems[idx].set_.ctrl_ 140 | slot = sets_._Elems[idx].set_.slots_ 141 | ctrl_end = sets_._Elems[idx].set_.ctrl_ + sets_._Elems[idx].set_.capacity_ 142 | slot_end = sets_._Elems[idx].set_.slots_ + sets_._Elems[idx].set_.capacity_ 143 | 144 | 145 | 146 | *slot,na 147 | 148 | ++slot 149 | ++ctrl 150 | 151 | ++idx 152 | 153 | 154 | 155 | 156 | 157 | 158 | {it_,na} 159 | 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /docs/tiny_string.md: -------------------------------------------------------------------------------- 1 | 2 | # Tiny string 3 | 4 | `seq::tiny_string` is a string class similar to std::string but aiming at greater performances when used in containers. 5 | It provides a customizable Small String Optimization (SSO) much like boost::small_vector, where the maximum static size before an allocation is triggered is defined at compile time. 6 | 7 | `seq::tiny_string` contains some preallocated elements in-place, which can avoid the use of dynamic storage allocation when the actual number of elements is below that preallocated threshold (*MaxStaticSize* parameter). 8 | 9 | `seq::tiny_string` supports any type of character in a similar way as `std::basic_string`. 10 | 11 | 12 | ## Motivation 13 | 14 | Why another string class? I started writing tiny_string to provide a small and relocatable string class that can be used with a compressed vector class (now moved to [stenos](https://github.com/Thermadiag/stenos) library). 15 | Indeed, libstdc++ std::string implementation is not relocatable as it stores a pointer to its internal data for small strings. In addition, most std::string implementations have a size of 32 bytes (at least msvc and libstdc++ ones), which is unnecessary when considering a compressed container. Therefore, I started working on a string implementation with the following specificities: 16 | - Relocatable, 17 | - Small footprint (16 bytes on 64 bits machine if possible), 18 | - Customizable Small String Optimization (SSO), 19 | - Higly optimized for small strings (fast copy/move assignment and fast comparison operators). 20 | 21 | It turns out that such string implementation makes all flat containers (like `std::vector` or `std::deque`) faster (at least for small strings) as it greatly reduces cache misses, memory allocations, and allows optimization tricks for relocatable types. Within the seq library, all containers are aware of this property and are much faster with `seq::tstring` instead of `std::string`. 22 | The Customizable Small String Optimization is also very convenient to avoid unnecessary memory allocations for different workloads. 23 | 24 | For the rest of the documentation, only standard `char` strings are considered. 25 | 26 | ## Size and bookkeeping 27 | 28 | By default on 64 bits machines, a `tiny_string` contains enough room to store a 16 bytes string, therefore a length of 15 bytes for null terminated strings. 29 | Starting version 1.2 of *seq* library, tiny_string does not store any additional bytes for internal bookkeeping. The fact that a string MUST be null terminated allows some tricks in order to use the full string size for SSO. 30 | 31 | This means that the default `seq::tstring` has a size of 16 bytes and can hold a small string of... 16 bytes (including trailing 0). 32 | Likewise, `seq::tiny_string, std::allocator, 31>` has a size of 32 bytes and can hold a small string of 32 bytes. 33 | 34 | When the tiny_string grows beyong the preallocated threshold, memory is allocated on the heap based on provided allocator using a grow factor of 2. 35 | 36 | Starting version 1.2 of *seq* library, tiny_string behaves exactly like std::string and does not deallocate memory on shrinking, except on calls to `shrink_to_fit()`. 37 | 38 | Several typedefs are provided for convenience: 39 | 40 | - `seq::tstring`: equivalent to `seq::tiny_string, std::allocator, 0 >` 41 | - `seq::wtstring`: equivalent to `seq::tiny_string, std::allocator, 0 >` 42 | 43 | 44 | ## Static size 45 | 46 | The maximum preallocated space is specified as a template parameter (*MaxStaticSize*). 47 | By default, this value is set to 0, meaning that the tiny_string only uses 2 words of either 32 or 64 bits depending on the architecture (whatever the char type is). 48 | Therefore, the maximum in-place capacity is either 8 or 16 bytes for 1 byte char type, less for wchar_t string. 49 | 50 | The maximum preallocated space can be increased up to 120 elements (size of 119 since the string is null terminated) for char type. 51 | Use `seq::tiny_string::max_allowed_static_size` to retrieve the maximum static size for a given char type. 52 | 53 | ## Relocatable type 54 | 55 | `seq::tiny_string` is relocatable, meaning that it does not store pointer to internal data. 56 | Relocatable types can be used more efficiently in containers that are aware of this property. For instance, `seq::devector`, `seq::tiered_vector`, `seq::flat_map/set`, `seq::radix_map/set` and `seq::radix_hash_map/set` are faster when working with relocatable types, as the process to move one object from a memory layout about to be destroyed to a new one can be accomplished through a simple memcpy. 57 | 58 | Msvc implementation of std::string is also relocatable, while libstdc++ implementation is not as it stores a pointer to its internal data for small strings. 59 | 60 | Within the *seq* library, a relocatable type must statify `seq::is_relocatable::value == true`. 61 | 62 | Note that tiny_string is only relocatable if the allocator itself is relocatable (which is the case for the default std::allocator). 63 | 64 | The tables below gives performances results when inserting and successfully finding 500k random small strings (size = 13) in a `seq::flat_set`, `phmap::btree_set` and `std::set`. The first table is for std::string, the second for seq::tstring. 65 | The benchmark is available in `seq/benchs/bench_tiny_string.cpp` and was compiled with gcc 10.1.0 (-O3) for msys2 on Windows 10, using Intel(R) Core(TM) i7-10850H at 2.70GHz. 66 | 67 | std::string | seq::flat_set | phmap::btree_set | std::set | 68 | --------------------|--------------------|--------------------|--------------------| 69 | insert | 783 ms | 250 ms | 250 ms | 70 | find | 141 ms | 234 ms | 235 ms | 71 | 72 | 73 | seq::tstring | seq::flat_set | phmap::btree_set | std::set | 74 | --------------------|--------------------|--------------------|--------------------| 75 | insert | 265 ms | 109 ms | 219 ms | 76 | find | 94 ms | 94 ms | 203 ms | 77 | 78 | 79 | `seq::flat_set` internally uses a `seq::tiered_vector` which is very sensible to the value type size for inserting/erasing. Furtheremore, it is aware of the relocatable property of its value type, which is why there is a factor 3 speed-up when inserting tstring instead of std::string. Even its lookup performance benefits from the small size of tstring as it reduces cache misses for the last stages of the find operation. 80 | 81 | A pure stable node based container like `std::set` does not benefit greatly from using tstring as it only reduces the size of allocated nodes. Observed speedup for find operation is probably due to the comparison operator for tstring which is slightly faster than the std::string one for small strings. 82 | 83 | `phmap::btree_set` is also a node based container, but each node can contain several elements. Note that btree_set uses a fix sized node in bytes, and its arity is computed from this size. A similar strategy is used for std::deque in most implementation to get its bucket size. Using tstring instead of std::string doubles the btree arity, increasing its overall performances for both insertion and lookup. 84 | 85 | Within the `seq` library, the following containers provide optimizations for relocatable types: 86 | - random access containers: `seq::tiered_vector`, `seq::devector`, `seq::cvector` 87 | - sorted containers: `seq::flat_set`, `seq::flat_map`, `seq::flat_multiset`, `seq::flat_mutlimap`, `seq::radix_set`, `seq::radix_map` 88 | - hash tables: `seq::radix_hash_set`, `seq::radix_hash_map` 89 | 90 | 91 | ## Interface 92 | 93 | `seq::tiny_string` provides the same interface as std::string. 94 | Functions working on other strings like find(), compare()... are overloaded to also work on std::string. 95 | The comparison operators are also overloaded to work with std::string. 96 | 97 | `seq::tiny_string` also works with std::istream/std::ostream exactly like std::string. 98 | 99 | `seq::tiny_string` provides the same invalidation rules as std::string as well as the same exception guarantees. 100 | 101 | A specialization of `std::hash` and `seq::hasher` is provided for tiny_string types which relies on [komihash](https://github.com/avaneev/komihash). This specialization is transparent and supports hashing std::string, tiny_string and const char*. 102 | 103 | ## String view 104 | 105 | `seq::tiny_string` is specialized for seq::view_allocator in order to provide a `std::string_view` like class. 106 | It is provided for compilers that do not support (yet) std::string_view, and provides a similar interface. 107 | 108 | The global typedef `seq::tstring_view` is provided for convenience, and is equivalent to `seq::tiny_string,seq::view_allocator,0>`. 109 | tstring_view is also hashable and can be compared to other tiny_string types as well as std::string. 110 | 111 | -------------------------------------------------------------------------------- /benchs/gtl/utils.hpp: -------------------------------------------------------------------------------- 1 | #ifndef gtl_utils_hpp_guard 2 | #define gtl_utils_hpp_guard 3 | 4 | // --------------------------------------------------------------------------- 5 | // Copyright (c) 2022, Gregory Popovitch - greg7mdp@gmail.com 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // https://www.apache.org/licenses/LICENSE-2.0 12 | // --------------------------------------------------------------------------- 13 | 14 | #include 15 | #include 16 | 17 | namespace gtl { 18 | 19 | // --------------------------------------------------------------------------- 20 | // An object which calls a lambda in its constructor, and another one in 21 | // its destructor 22 | // 23 | // This object must be captured in a local variable, Otherwise, since it is a 24 | // temporary, it will be destroyed immediately, thus calling the unset function. 25 | // 26 | // scoped_set_unset rollback(...); // good 27 | // --------------------------------------------------------------------------- 28 | template 29 | class scoped_set_unset { 30 | public: 31 | template 32 | scoped_set_unset(Set&& set, Unset&& unset, bool do_it = true) 33 | : do_it_(do_it) 34 | , unset_(std::move(unset)) { 35 | if (do_it_) 36 | std::forward(set)(); 37 | } 38 | 39 | ~scoped_set_unset() { 40 | if (do_it_) 41 | unset_(); 42 | } 43 | 44 | void dismiss() noexcept { do_it_ = false; } 45 | 46 | scoped_set_unset(scoped_set_unset&&) = delete; 47 | scoped_set_unset(const scoped_set_unset&) = delete; 48 | scoped_set_unset& operator=(const scoped_set_unset&) = delete; 49 | void* operator new(std::size_t) = delete; 50 | 51 | private: 52 | bool do_it_; 53 | Unset unset_; 54 | }; 55 | 56 | // --------------------------------------------------------------------------- 57 | // An object which calls a lambda in its constructor, and another one in 58 | // its destructor 59 | // 60 | // This object must be captured in a local variable, Otherwise, since it is a 61 | // temporary, it will be destroyed immediately, thus calling the function. 62 | // 63 | // scoped_guard rollback(...); // good 64 | // --------------------------------------------------------------------------- 65 | template 66 | class scoped_guard { 67 | public: 68 | scoped_guard(F&& unset, bool do_it = true) noexcept(std::is_nothrow_move_constructible_v) 69 | : do_it_(do_it) 70 | , unset_(std::move(unset)) {} 71 | 72 | ~scoped_guard() { 73 | if (do_it_) 74 | unset_(); 75 | } 76 | 77 | void dismiss() noexcept { do_it_ = false; } 78 | 79 | scoped_guard(scoped_guard&&) = delete; 80 | scoped_guard(const scoped_guard&) = delete; 81 | scoped_guard& operator=(const scoped_guard&) = delete; 82 | void* operator new(std::size_t) = delete; 83 | 84 | private: 85 | bool do_it_; 86 | F unset_; 87 | }; 88 | 89 | // --------------------------------------------------------------------------- 90 | // An object which assigns a value to a variable in its constructor, and resets 91 | // the previous its destructor 92 | // 93 | // This object must be captured in a local variable, Otherwise, since it is a 94 | // temporary, it will be destroyed immediately, thus reverting to the old value 95 | // immediately 96 | // 97 | // scoped_set_value rollback(retries, 7); // good 98 | // --------------------------------------------------------------------------- 99 | template 100 | class scoped_set_value { 101 | public: 102 | template 103 | scoped_set_value(T& var, V&& val, bool do_it = true) noexcept(std::is_nothrow_copy_constructible_v && 104 | std::is_nothrow_move_assignable_v && 105 | std::is_nothrow_move_assignable_v && 106 | std::is_nothrow_copy_assignable_v) 107 | : v_(var) 108 | , do_it_(do_it) { 109 | if (do_it_) { 110 | old_value_ = std::move(v_); 111 | v_ = std::forward(val); 112 | } 113 | } 114 | 115 | ~scoped_set_value() { 116 | if (do_it_) 117 | v_ = std::move(old_value_); 118 | } 119 | 120 | void dismiss() noexcept { do_it_ = false; } 121 | 122 | scoped_set_value(const scoped_set_value&) = delete; 123 | scoped_set_value& operator=(const scoped_set_value&) = delete; 124 | void* operator new(std::size_t) = delete; 125 | 126 | T& v_; 127 | T old_value_; 128 | bool do_it_; 129 | }; 130 | 131 | // --------------------------------------------------------------------------- 132 | // assigns val to var, and returns true if the value changed 133 | // --------------------------------------------------------------------------- 134 | template 135 | bool change(T& var, V&& val) noexcept(std::is_nothrow_move_assignable_v && std::is_nothrow_copy_assignable_v) { 136 | if (var != val) { 137 | var = std::forward(val); 138 | return true; 139 | } 140 | return false; 141 | } 142 | 143 | // --------------------------------------------------------------------------- 144 | // assigns val to var, and returns the previous value 145 | // --------------------------------------------------------------------------- 146 | template 147 | T replace(T& var, V&& val) noexcept(std::is_nothrow_move_assignable_v && std::is_nothrow_copy_assignable_v) { 148 | T old = std::move(var); 149 | var = std::forward(val); 150 | return old; 151 | } 152 | 153 | // --------------------------------------------------------------------------- 154 | // --------------------------------------------------------------------------- 155 | template 156 | struct always_false : std::false_type {}; 157 | 158 | // --------------------------------------------------------------------------- 159 | // A baseclass to keep track of modifications. 160 | // Change member `x_` using `set_with_ts` 161 | // --------------------------------------------------------------------------- 162 | class timestamp { 163 | public: 164 | timestamp() noexcept { stamp_ = ++clock_; } 165 | 166 | timestamp(uint64_t stamp) noexcept 167 | : stamp_(stamp) {} 168 | 169 | void touch() noexcept { stamp_ = ++clock_; } 170 | void touch(const timestamp& o) noexcept { stamp_ = o.stamp_; } 171 | 172 | void reset() noexcept { stamp_ = 0; } 173 | bool is_set() const noexcept { return !!stamp_; } 174 | 175 | bool is_newer_than(const timestamp& o) const noexcept { return stamp_ > o.stamp_; } 176 | bool is_older_than(const timestamp& o) const noexcept { return stamp_ < o.stamp_; } 177 | 178 | bool operator==(const timestamp& o) const noexcept { return stamp_ == o.stamp_; } 179 | bool operator<(const timestamp& o) const noexcept { return stamp_ < o.stamp_; } 180 | bool operator>(const timestamp& o) const noexcept { return stamp_ > o.stamp_; } 181 | 182 | // returns most recent 183 | timestamp operator|(const timestamp& o) const noexcept { return stamp_ > o.stamp_ ? stamp_ : o.stamp_; } 184 | timestamp& operator|=(const timestamp& o) noexcept { 185 | *this = *this | o; 186 | return *this; 187 | } 188 | 189 | uint64_t get() const noexcept { return stamp_; } 190 | 191 | timestamp get_timestamp() const noexcept { return *this; } 192 | 193 | template 194 | bool set_with_ts(T& var, V&& val) { 195 | if (gtl::change(var, std::forward(val))) { 196 | this->touch(); 197 | return true; 198 | } 199 | return false; 200 | } 201 | 202 | private: 203 | uint64_t stamp_; 204 | static inline uint64_t clock_ = 0; 205 | }; 206 | 207 | // --------------------------------------------------------------------------- 208 | // A baseclass (using CRTP) for classes providing get_timestamp() 209 | // --------------------------------------------------------------------------- 210 | template 211 | class provides_timestamp { 212 | public: 213 | template 214 | bool is_newer_than(const TS& o) const { 215 | return static_cast(this)->get_timestamp() > o.get_timestamp(); 216 | } 217 | 218 | template 219 | bool is_older_than(const TS& o) const { 220 | return static_cast(this)->get_timestamp() < o.get_timestamp(); 221 | } 222 | 223 | // returns most recent 224 | template 225 | timestamp operator|(const TS& o) const { 226 | return static_cast(this)->get_timestamp() | o.get_timestamp(); 227 | } 228 | }; 229 | 230 | } // namespace gtl 231 | 232 | #endif // gtl_utils_hpp_guard 233 | -------------------------------------------------------------------------------- /benchs/zipfian_int_distribution.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2017 Lucas Lersch 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | /* Implementation derived from: 26 | * "Quickly Generating Billion-Record Synthetic Databases", Jim Gray et al, 27 | * SIGMOD 1994 28 | */ 29 | 30 | /* 31 | * The zipfian_int_distribution class is intended to be compatible with other 32 | * distributions introduced in #include by the C++11 standard. 33 | * 34 | * Usage example: 35 | * #include 36 | * #include "zipfian_int_distribution.h" 37 | * int main() 38 | * { 39 | * std::default_random_engine generator; 40 | * zipfian_int_distribution distribution(1, 10, 0.99); 41 | * int i = distribution(generator); 42 | * } 43 | */ 44 | 45 | /* 46 | * IMPORTANT: constructing the distribution object requires calculating the zeta 47 | * value which becomes prohibetively expensive for very large ranges. As an 48 | * alternative for such cases, the user can pass the pre-calculated values and 49 | * avoid the calculation every time. 50 | * 51 | * Usage example: 52 | * #include 53 | * #include "zipfian_int_distribution.h" 54 | * int main() 55 | * { 56 | * std::default_random_engine generator; 57 | * zipfian_int_distribution::param_type p(1, 1e6, 0.99, 27.000); 58 | * zipfian_int_distribution distribution(p); 59 | * int i = distribution(generator); 60 | * } 61 | */ 62 | 63 | /* 64 | * Joaquin M Lopez Munoz, May-Jul 2023: 65 | * - Trivial changes to get rid of GCC specific functions and some warnings. 66 | * - Cached values to speed up zipfian_int_distribution::operator(). 67 | * - Replaced std::generate_canonical with faster alternative (contributed 68 | * by Martin Leitner-Ankerl from https://prng.di.unimi.it/). 69 | */ 70 | 71 | #include 72 | #include 73 | #include 74 | #include 75 | #include 76 | 77 | double uniform01(uint64_t r) { 78 | auto i = (UINT64_C(0x3ff) << 52U) | (r >> 12U); 79 | // can't use union in c++ here for type puning, it's undefined behavior. 80 | // std::memcpy is optimized anyways. 81 | double d{}; 82 | std::memcpy(&d, &i, sizeof(double)); 83 | return d - 1.0; 84 | } 85 | 86 | template 87 | class zipfian_int_distribution 88 | { 89 | static_assert(std::is_integral<_IntType>::value, "Template argument not an integral type."); 90 | 91 | public: 92 | /** The type of the range of the distribution. */ 93 | typedef _IntType result_type; 94 | /** Parameter type. */ 95 | struct param_type 96 | { 97 | typedef zipfian_int_distribution<_IntType> distribution_type; 98 | 99 | explicit param_type(_IntType __a = 0, _IntType __b = std::numeric_limits<_IntType>::max(), double __theta = 0.99) 100 | : _M_a(__a), _M_b(__b), _M_theta(__theta), 101 | _M_zeta(zeta(_M_b - _M_a + 1, __theta)), _M_zeta2theta(zeta(2, __theta)), 102 | _M_alpha(alpha(__theta)), _M_eta(eta(__a, __b, __theta, _M_zeta, _M_zeta2theta)), 103 | _M_1_plus_05_to_theta(_1_plus_05_to_theta(__theta)) 104 | { 105 | assert(_M_a <= _M_b && _M_theta > 0.0 && _M_theta < 1.0); 106 | } 107 | 108 | explicit param_type(_IntType __a, _IntType __b, double __theta, double __zeta) 109 | : _M_a(__a), _M_b(__b), _M_theta(__theta), _M_zeta(__zeta), 110 | _M_zeta2theta(zeta(2, __theta)), 111 | _M_alpha(alpha(__theta)), _M_eta(eta(__a, __b, __theta, _M_zeta, _M_zeta2theta)), 112 | _M_1_plus_05_to_theta(_1_plus_05_to_theta(__theta)) 113 | { 114 | assert(_M_a <= _M_b && _M_theta > 0.0 && _M_theta < 1.0); 115 | } 116 | 117 | result_type a() const { return _M_a; } 118 | 119 | result_type b() const { return _M_b; } 120 | 121 | double theta() const { return _M_theta; } 122 | 123 | double zeta() const { return _M_zeta; } 124 | 125 | double zeta2theta() const { return _M_zeta2theta; } 126 | 127 | double alpha() const { return _M_alpha; } 128 | 129 | double eta() const { return _M_eta; } 130 | 131 | double _1_plus_05_to_theta() const { return _M_1_plus_05_to_theta; } 132 | 133 | friend bool operator==(const param_type& __p1, const param_type& __p2) 134 | { 135 | return __p1._M_a == __p2._M_a 136 | && __p1._M_b == __p2._M_b 137 | && __p1._M_theta == __p2._M_theta 138 | && __p1._M_zeta == __p2._M_zeta 139 | && __p1._M_zeta2theta == __p2._M_zeta2theta; 140 | } 141 | 142 | private: 143 | _IntType _M_a; 144 | _IntType _M_b; 145 | double _M_theta; 146 | double _M_zeta; 147 | double _M_zeta2theta; 148 | double _M_alpha; 149 | double _M_eta; 150 | double _M_1_plus_05_to_theta; 151 | 152 | /** 153 | * @brief Calculates zeta. 154 | * 155 | * @param __n [IN] The size of the domain. 156 | * @param __theta [IN] The skew factor of the distribution. 157 | */ 158 | double zeta(unsigned long __n, double __theta) 159 | { 160 | double ans = 0.0; 161 | for(unsigned long i=1; i<=__n; ++i) 162 | ans += std::pow(1.0/i, __theta); 163 | return ans; 164 | } 165 | 166 | double alpha(double __theta) 167 | { 168 | return 1 / (1 - __theta); 169 | }; 170 | 171 | double eta(_IntType __a, _IntType __b, double __theta, double __zeta, double __zeta2theta) 172 | { 173 | return (1 - std::pow(2.0 / (__b - __a + 1), 1 - __theta)) / (1 - __zeta2theta / __zeta); 174 | } 175 | 176 | double _1_plus_05_to_theta(double __theta) 177 | { 178 | return 1.0 + std::pow(0.5, __theta); 179 | } 180 | }; 181 | 182 | public: 183 | /** 184 | * @brief Constructs a zipfian_int_distribution object. 185 | * 186 | * @param __a [IN] The lower bound of the distribution. 187 | * @param __b [IN] The upper bound of the distribution. 188 | * @param __theta [IN] The skew factor of the distribution. 189 | */ 190 | explicit zipfian_int_distribution(_IntType __a = _IntType(0), _IntType __b = _IntType(1), double __theta = 0.99) 191 | : _M_param(__a, __b, __theta) 192 | { } 193 | 194 | explicit zipfian_int_distribution(const param_type& __p) : _M_param(__p) 195 | { } 196 | 197 | /** 198 | * @brief Resets the distribution state. 199 | * 200 | * Does nothing for the zipfian int distribution. 201 | */ 202 | void reset() { } 203 | 204 | result_type a() const { return _M_param.a(); } 205 | 206 | result_type b() const { return _M_param.b(); } 207 | 208 | double theta() const { return _M_param.theta(); } 209 | 210 | /** 211 | * @brief Returns the parameter set of the distribution. 212 | */ 213 | param_type param() const { return _M_param; } 214 | 215 | /** 216 | * @brief Sets the parameter set of the distribution. 217 | * @param __param The new parameter set of the distribution. 218 | */ 219 | void param(const param_type& __param) { _M_param = __param; } 220 | 221 | /** 222 | * @brief Returns the inclusive lower bound of the distribution range. 223 | */ 224 | result_type min() const { return this->a(); } 225 | 226 | /** 227 | * @brief Returns the inclusive upper bound of the distribution range. 228 | */ 229 | result_type max() const { return this->b(); } 230 | 231 | /** 232 | * @brief Generating functions. 233 | */ 234 | template 235 | result_type operator()(_UniformRandomNumberGenerator& __urng) 236 | { return this->operator()(__urng, _M_param); } 237 | 238 | template 239 | result_type operator()(_UniformRandomNumberGenerator& __urng, const param_type& __p) 240 | { 241 | double u = uniform01(__urng()); 242 | 243 | double uz = u * __p.zeta(); 244 | if(uz < 1.0) return __p.a(); 245 | if(uz < __p._1_plus_05_to_theta()) return __p.a() + 1; 246 | 247 | return (result_type)(__p.a() + ((__p.b() - __p.a() + 1) * std::pow(__p.eta()*u-__p.eta()+1, __p.alpha()))); 248 | } 249 | 250 | /** 251 | * @brief Return true if two zipfian int distributions have 252 | * the same parameters. 253 | */ 254 | friend bool operator==(const zipfian_int_distribution& __d1, const zipfian_int_distribution& __d2) 255 | { return __d1._M_param == __d2._M_param; } 256 | 257 | private: 258 | param_type _M_param; 259 | }; 260 | -------------------------------------------------------------------------------- /seq/tagged_pointer.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2025 Victor Moncada 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #ifndef SEQ_TAGGED_POINTER_HPP 26 | #define SEQ_TAGGED_POINTER_HPP 27 | 28 | /** @file */ 29 | 30 | /** \addtogroup memory 31 | * @{ 32 | */ 33 | 34 | #include 35 | 36 | #include "bits.hpp" 37 | 38 | namespace seq 39 | { 40 | enum TagPointerType 41 | { 42 | // If a tagged pointer points to a variable on the stack, no choice but to assume an alignment of alignof(T). 43 | // This might leave much less space for tag (0 for bytes types). 44 | StackPointer, 45 | // When pointer to a variable allocated on the heap, we can safely use alignof(std::max_align_t). 46 | HeapPointer, 47 | // User defined alignment, use at your own risks 48 | CustomAlignment 49 | }; 50 | 51 | namespace detail 52 | { 53 | // Get tagged_pointer actual alignment value 54 | template 55 | constexpr uintptr_t find_alignment() 56 | { 57 | if constexpr (Type == HeapPointer) 58 | return SEQ_DEFAULT_ALIGNMENT; 59 | else if constexpr (Type == CustomAlignment) 60 | return UserDefinedAlignment; 61 | else 62 | return alignof(T); 63 | } 64 | } 65 | 66 | /// @brief Tagged pointer class. 67 | /// 68 | /// seq::tagged_pointer uses the low bits of a pointer address to store metadata (or tag value). 69 | /// The number of bits used for the tag value depends on the TagPointerType flag: 70 | /// - StackPointer (default): the tagged_pointer is assumed to point on a stack value or on a value inside an array. 71 | /// In this case, the tag bits is equal to static_bit_scan_reverse::value. For instance, 2 bits for int32_t, 72 | /// 3 bits for int64_t and ... 0 bits for char. 73 | /// - HeapPointer: the tagged_pointer is assumed to point on a heap allocated value. Therefore, its alignment is (in theory) 74 | /// equal to alignof(std::max_align_t). On most platforms, the tag bits is either 3 or 4. 75 | /// - CustomAlignment : the tag bits is given by static_bit_scan_reverse::value. 76 | /// 77 | /// tagged_pointer is specialized to work with void pointer. 78 | /// 79 | template 80 | class tagged_pointer 81 | { 82 | static constexpr uintptr_t align = detail::find_alignment(); 83 | 84 | static_assert((((align & (align - 1)) == 0)), "alignment must be a non null power of 2"); 85 | static constexpr uintptr_t bits = static_bit_scan_reverse::value; 86 | 87 | uintptr_t d_ptr; 88 | 89 | public: 90 | /// @brief storage and tag type 91 | using tag_type = uintptr_t; 92 | using value_type = T; 93 | using reference = T&; 94 | using const_reference = const T&; 95 | using pointer = T*; 96 | using const_pointer = const T*; 97 | 98 | /// @brief tagged pointer type 99 | static constexpr TagPointerType type = Type; 100 | /// @brief number of bits for the tag 101 | static constexpr uintptr_t tag_bits = bits; 102 | /// @brief mask used to extract the pointer address 103 | static constexpr uintptr_t mask_high = static_cast(~((1ULL << tag_bits) - 1ULL)); 104 | /// @brief mask used to extract the tag 105 | static constexpr uintptr_t mask_low = static_cast(((1ULL << tag_bits) - 1ULL)); 106 | 107 | /// @brief Construct from pointer 108 | SEQ_ALWAYS_INLINE tagged_pointer(T* ptr = nullptr) noexcept 109 | : d_ptr(reinterpret_cast(ptr)) 110 | { 111 | } 112 | /// @brief Construct from pointer and tag 113 | SEQ_ALWAYS_INLINE tagged_pointer(T* ptr, tag_type t) noexcept 114 | : d_ptr(reinterpret_cast(ptr) | (t & mask_low)) 115 | { 116 | } 117 | /// @brief Returns the pointer 118 | SEQ_ALWAYS_INLINE auto ptr() noexcept -> pointer { return reinterpret_cast(d_ptr & mask_high); } 119 | /// @brief Returns the pointer 120 | SEQ_ALWAYS_INLINE auto ptr() const noexcept -> const_pointer { return reinterpret_cast(d_ptr & mask_high); } 121 | /// @brief Returns the tag 122 | SEQ_ALWAYS_INLINE auto tag() const noexcept -> tag_type { return d_ptr & mask_low; } 123 | 124 | /// @brief Set the pointer value 125 | SEQ_ALWAYS_INLINE void set_ptr(pointer ptr) noexcept { d_ptr = tag() | reinterpret_cast(ptr); } 126 | /// @brief Set the tag value 127 | SEQ_ALWAYS_INLINE auto set_tag(tag_type tag) noexcept -> tag_type 128 | { 129 | d_ptr = tag | (d_ptr & mask_high); 130 | return tag; 131 | } 132 | 133 | SEQ_ALWAYS_INLINE void set(pointer ptr, tag_type tag) noexcept { d_ptr = reinterpret_cast(ptr) | (tag & mask_low); } 134 | 135 | SEQ_ALWAYS_INLINE auto full() const noexcept -> uintptr_t { return d_ptr; } 136 | SEQ_ALWAYS_INLINE uintptr_t set_full(uintptr_t p) noexcept { return d_ptr = p; } 137 | 138 | SEQ_ALWAYS_INLINE auto split() const noexcept -> std::pair { return std::pair(ptr(), tag()); } 139 | SEQ_ALWAYS_INLINE auto split() noexcept -> std::pair { return std::pair(ptr(), tag()); } 140 | 141 | /// @brief cast operator to pointer 142 | SEQ_ALWAYS_INLINE operator pointer() noexcept { return ptr(); } 143 | /// @brief cast operator to pointer 144 | SEQ_ALWAYS_INLINE operator const_pointer() const noexcept { return ptr(); } 145 | 146 | /// @brief Returns the pointer 147 | SEQ_ALWAYS_INLINE auto operator->() noexcept -> pointer { return ptr(); } 148 | /// @brief Returns the pointer 149 | SEQ_ALWAYS_INLINE auto operator->() const noexcept -> const_pointer { return ptr(); } 150 | /// @brief Returns a reference to the pointed value 151 | SEQ_ALWAYS_INLINE auto operator*() noexcept -> reference { return *ptr(); } 152 | /// @brief Returns a reference to the pointed value 153 | SEQ_ALWAYS_INLINE auto operator*() const noexcept -> const_reference { return *ptr(); } 154 | }; 155 | 156 | template 157 | class tagged_pointer 158 | { 159 | // Specialization for void* , remove the reference type and related members 160 | 161 | static constexpr uintptr_t align = (Type != CustomAlignment ? SEQ_DEFAULT_ALIGNMENT : UserDefinedAlignment); 162 | static_assert(align > 0 && (((align & (align - 1)) == 0)), "alignment must be a non null power of 2"); 163 | static constexpr uintptr_t bits = static_bit_scan_reverse::value; 164 | 165 | uintptr_t d_ptr; 166 | 167 | public: 168 | using tag_type = uintptr_t; 169 | using value_type = void; 170 | using pointer = void*; 171 | using const_pointer = const void*; 172 | 173 | static constexpr TagPointerType type = Type; 174 | static constexpr uintptr_t tag_bits = bits; 175 | static constexpr uintptr_t mask_high = static_cast(~((1ULL << tag_bits) - 1ULL)); 176 | static constexpr uintptr_t mask_low = static_cast(((1ULL << tag_bits) - 1ULL)); 177 | 178 | SEQ_ALWAYS_INLINE tagged_pointer(void* ptr = nullptr) noexcept 179 | : d_ptr(reinterpret_cast(ptr)) 180 | { 181 | } 182 | SEQ_ALWAYS_INLINE tagged_pointer(void* ptr, tag_type t) noexcept 183 | : d_ptr(reinterpret_cast(ptr) | (t & mask_low)) 184 | { 185 | } 186 | SEQ_ALWAYS_INLINE auto ptr() noexcept -> pointer { return reinterpret_cast(d_ptr & mask_high); } 187 | SEQ_ALWAYS_INLINE auto ptr() const noexcept -> const_pointer { return reinterpret_cast(d_ptr & mask_high); } 188 | SEQ_ALWAYS_INLINE auto tag() const noexcept -> tag_type { return d_ptr & mask_low; } 189 | SEQ_ALWAYS_INLINE void set_ptr(pointer ptr) noexcept { d_ptr = tag() | reinterpret_cast(ptr); } 190 | SEQ_ALWAYS_INLINE auto set_tag(tag_type tag) noexcept -> tag_type 191 | { 192 | d_ptr = tag | (d_ptr & mask_high); 193 | return tag; 194 | } 195 | SEQ_ALWAYS_INLINE void set(pointer ptr, tag_type tag) noexcept { d_ptr = reinterpret_cast(ptr) | (tag & mask_low); } 196 | SEQ_ALWAYS_INLINE auto full() const noexcept -> uintptr_t { return d_ptr; } 197 | SEQ_ALWAYS_INLINE auto rfull() noexcept -> uintptr_t& { return d_ptr; } 198 | SEQ_ALWAYS_INLINE uintptr_t set_full(uintptr_t p) noexcept { return d_ptr = p; } 199 | 200 | SEQ_ALWAYS_INLINE auto split() const noexcept -> std::pair { return std::pair(ptr(), tag()); } 201 | SEQ_ALWAYS_INLINE auto split() noexcept -> std::pair { return std::pair(ptr(), tag()); } 202 | 203 | SEQ_ALWAYS_INLINE operator pointer() noexcept { return ptr(); } 204 | SEQ_ALWAYS_INLINE operator const_pointer() const noexcept { return ptr(); } 205 | SEQ_ALWAYS_INLINE auto operator->() noexcept -> pointer { return ptr(); } 206 | SEQ_ALWAYS_INLINE auto operator->() const noexcept -> const_pointer { return ptr(); } 207 | }; 208 | 209 | } 210 | 211 | /** @}*/ 212 | // end memory 213 | 214 | #endif 215 | --------------------------------------------------------------------------------