diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..0110fa5 --- /dev/null +++ b/.clang-format @@ -0,0 +1,92 @@ +Language: Cpp +# BasedOnStyle: Google +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: None +AlignConsecutiveDeclarations: None +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 150 +CommentPragmas: '^ IWYU pragma:' +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: true +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] +IncludeCategories: + - Regex: '^<.*\.h>' + Priority: 1 + - Regex: '^<.*' + Priority: 2 + - Regex: '.*' + Priority: 3 +IncludeIsMainRegex: '([-_](test|unittest))?$' +IndentCaseLabels: true +IndentWidth: 2 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: false +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +ReflowComments: true +SortIncludes: Never +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto +TabWidth: 8 +UseTab: Never diff --git a/.github/workflows/action-cpp.yml b/.github/workflows/action-cpp.yml new file mode 100644 index 0000000..cdf65d7 --- /dev/null +++ b/.github/workflows/action-cpp.yml @@ -0,0 +1,78 @@ +name: build and cpack +on: [ push, pull_request ] +jobs: + ubuntu-22-04: + runs-on: ubuntu-latest + name: Build on ${{ matrix.container }} x86_64 + strategy: + # + # matrix for containers + # + matrix: + container: + - ubuntu:latest + - debian:latest + - fedora:latest + - alt:sisyphus + + container: + image: ${{ matrix.container }} + + steps: + - name: Get current date + id: date + run: echo "date=$(date +'%Y-%m-%d %H:%M:%S')" >> $GITHUB_OUTPUT + - name: Get commit sha + id: git_sha + run: echo "git_sha=$(echo $GITHUB_SHA)" >> $GITHUB_OUTPUT + - uses: actions/checkout@v3 + # install dependencies + - name: devel-pkgs + run: | + case "${{ matrix.container }}" in + ubuntu*|debian*) + apt-get update -y && apt-get install -yq binutils git make cmake catch2 gcc g++ lsb-release + ;; + fedora*) + yum update -y && yum install -yq binutils git make cmake catch2-devel rpm-build redhat-lsb + ;; + alt*) + apt-get update -y && apt-get install -yq binutils git make cmake ctest catch2-devel gcc gcc-c++ rpm-build lsb-release + ;; + esac + # build project + - name: mkdir + run: mkdir cmake-build-release + - name: cmake cmake-build-release + run: cmake -DCOMMITTER_DATE="${{ steps.date.outputs.date }}" -DCOMMITTER_FULLSHA="${{ steps.git_sha.outputs.git_sha }}" -DCOMMITTER_SHORTSHA="$(echo ${{ steps.git_sha.outputs.git_sha }} | cut -c1-7)" -DCMAKE_BUILD_TYPE=Release -Bcmake-build-release -H. + - name: cmake make + run: cmake --build cmake-build-release/ --target all --parallel + - name: get-version + id: get-version + run: echo "prj_ver=$(cat ./VERSION.txt)" >> $GITHUB_OUTPUT + - name: ctest + run: cd cmake-build-release && ctest && cd .. + - name: cpack + run: | + case "${{ matrix.container }}" in + ubuntu*|debian*) + cd cmake-build-release && cpack -G DEB && cd .. + ;; + fedora*) + cd cmake-build-release && cpack -G RPM && cd .. + ;; + alt*) + cd cmake-build-release && echo "%_allow_root_build 1" > /etc/rpm/macros.d/02-enable-build-root && cpack -G RPM && cd .. + ;; + esac + - uses: mad9000/actions-find-and-replace-string@3 + id: container + with: + source: ${{ matrix.container }} + find: ':' # we want to remove : from container name + replace: '-' # and replace it with - + - name: Upload BTree binary + uses: actions/upload-artifact@v3 + with: + name: ${{ format('BTree-{0}.{1}', steps.get-version.outputs.prj_ver, steps.container.outputs.value) }} + path: cmake-build-release/${{ format('BTree-{0}-noarch.???', steps.get-version.outputs.prj_ver) }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..533a8e2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +/cmake-build-*/ +/.idea/ +/.vscode/ +/vcpkg/ +CMakeLists.txt.user +vcpkg +VERSION.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 0df9317..fb9dc31 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,21 +1,198 @@ +set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${CMAKE_SOURCE_DIR}/cmake/modules") cmake_minimum_required(VERSION 3.16) -project(demo) + +set(MAJOR "0") +set(MINOR "0") +set(PATCH "1") +cmake_policy(SET CMP0048 NEW) + +project(BTree VERSION ${MAJOR}.${MINOR}.${PATCH}) + +option(FORCE_USE_SIMD "force define FC_USE_SIMD (apllicable only for x86_64)" OFF) +option(FORCE_PREFER_BINARY_SEARCH "force define FC_PREFER_BINARY_SEARCH (recommended for clang only)" OFF) set(CMAKE_CXX_STANDARD 20) -add_executable(demo test/perftest.cpp fc_comp.h fc_disk_fixed_alloc.h fc_mmfile.h fc_btree.h fc_disk_btree.h) +find_package(VersionHeader) +set(PROJECT_VERSION "${MAJOR}.${MINOR}.${PATCH}.${COMMITTER_SHORTSHA}") +file(WRITE ${CMAKE_SOURCE_DIR}/VERSION.txt "${MAJOR}.${MINOR}.${PATCH}") + +find_package(Catch2 3 QUIET) +set(Catch_VER 3) +if (NOT Catch2_FOUND) + find_package(Catch2 REQUIRED) + set(Catch_VER 2) +endif() +set(CATCH_LIBS_ALIASES Catch2::Catch2 Catch2::Catch2WithMain) + +function(get_linux_lsb_release_information) + find_program(LSB_RELEASE_EXEC lsb_release) + if(NOT LSB_RELEASE_EXEC) + message(FATAL_ERROR "Could not detect lsb_release executable, can not gather required information") + endif() + + execute_process(COMMAND "${LSB_RELEASE_EXEC}" --short --id OUTPUT_VARIABLE LSB_RELEASE_ID_SHORT OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND "${LSB_RELEASE_EXEC}" --short --release OUTPUT_VARIABLE LSB_RELEASE_VERSION_SHORT OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND "${LSB_RELEASE_EXEC}" --short --codename OUTPUT_VARIABLE LSB_RELEASE_CODENAME_SHORT OUTPUT_STRIP_TRAILING_WHITESPACE) + + set(LSB_RELEASE_ID_SHORT "${LSB_RELEASE_ID_SHORT}" PARENT_SCOPE) + set(LSB_RELEASE_VERSION_SHORT "${LSB_RELEASE_VERSION_SHORT}" PARENT_SCOPE) + set(LSB_RELEASE_CODENAME_SHORT "${LSB_RELEASE_CODENAME_SHORT}" PARENT_SCOPE) +endfunction() + +message(STATUS "COMMITTER_FULLSHA ${COMMITTER_FULLSHA}") +message(STATUS "COMMITTER_SHORTSHA ${COMMITTER_SHORTSHA}") +message(STATUS "COMMITTER_DATE ${COMMITTER_DATE}") +message(STATUS "PROJECT_VERSION ${PROJECT_VERSION}") + +if(CMAKE_SYSTEM_NAME MATCHES "Linux") + get_linux_lsb_release_information() + message(STATUS "OS Linux ${LSB_RELEASE_ID_SHORT} ${LSB_RELEASE_VERSION_SHORT} ${LSB_RELEASE_CODENAME_SHORT}") +else() + message(STATUS "OS ${CMAKE_SYSTEM_NAME}") + message(STATUS "OS VERSION ${CMAKE_SYSTEM_VERSION}") +endif() + + +add_library(BTree INTERFACE + include/fc/comp.h + include/fc/disk_fixed_alloc.h + include/fc/mmfile.h + include/fc/btree.h + include/fc/disk_btree.h + include/fc/mmfile_win.h + include/fc/mmfile_nix.h + include/fc/details.h) + +target_include_directories( + BTree + INTERFACE + "$" + "$" ) + +add_library(BTree::BTree ALIAS BTree) + +if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR FORCE_PREFER_BINARY_SEARCH) + add_definitions(-DFC_PREFER_BINARY_SEARCH=1) +endif() + +if (FORCE_USE_SIMD) + add_definitions(-DFC_USE_SIMD=1) +endif () + +add_executable(fc_tests + test/test_statistics.h + test/test_statistics.cpp + test/fc_catch2.h + test/unittest.cpp + test/rwtest.cpp + test/perftest.cpp + test/perftest_no_simd.cpp + test/perftest_string.cpp) + +if (${Catch_VER} EQUAL 2) + message(STATUS "Used old target for catch2") + if (NOT TARGET Catch2::Catch2WithMain) + set(CATCH_LIBS_ALIASES Catch2::Catch2) + endif() + target_compile_definitions(fc_tests PRIVATE CATCH2_OLD) +endif() + +target_include_directories(fc_tests PRIVATE ${CMAKE_CURRENT_LIST_DIR}) +target_link_libraries(fc_tests PRIVATE ${CATCH_LIBS_ALIASES} BTree::BTree) if(MSVC) - target_compile_options(demo PRIVATE /W4 /WX /nologo /MDd /EHsc /std:c++latest /experimental:module) - if (CMAKE_BUILD_TYPE STREQUAL "Debug") - elseif (CMAKE_BUILD_TYPE STREQUAL "Release") + target_compile_options(fc_tests PRIVATE /W4 /WX /nologo /MDd /EHsc /std:c++latest /experimental:module) + if (CMAKE_BUILD_TYPE STREQUAL "Release") add_compile_options(/Ox) endif() else() - target_compile_options(demo PRIVATE -Wall -Wextra -Wpedantic -Werror -march=native) - if (CMAKE_BUILD_TYPE STREQUAL "Debug") - add_compile_options(-g) - elseif (CMAKE_BUILD_TYPE STREQUAL "Release") - add_compile_options(-O3) - endif() + target_compile_options(fc_tests PRIVATE -Wall -Wextra -Wpedantic -Werror -march=native) endif() + +include(CTest) +include(Catch) +catch_discover_tests(fc_tests + EXTRA_ARGS --benchmark-samples=1) + +set(CPACK_SOURCE_IGNORE_FILES + #git files + "\\\\.git/" + "\\\\.github/" + # temporary files + "\\\\.swp$" + # backup files + "~$" + # eclipse files + "\\\\.cdtproject$" + "\\\\.cproject$" + "\\\\.project$" + "\\\\.settings/" + # others + "\\\\.#" + "/#" + "/build/" + "/_build/" + "/\\\\.git/" + "Makefile\\\\.in$" +) + +include(GNUInstallDirs) + +install( + TARGETS BTree + EXPORT BTree_Targets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +include(CMakePackageConfigHelpers) +write_basic_package_version_file( + "BTreeConfigVersion.cmake" + VERSION ${MAJOR}.${MINOR}.${PATCH} + COMPATIBILITY SameMajorVersion) + +configure_package_config_file( + "${PROJECT_SOURCE_DIR}/cmake/BTreeConfig.cmake.in" + "${PROJECT_BINARY_DIR}/BTreeConfig.cmake" + INSTALL_DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/BTree/cmake) + +install( + EXPORT BTree_Targets + FILE BTreeTargets.cmake + NAMESPACE BTree:: + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/BTree/cmake) + +install(FILES "${PROJECT_BINARY_DIR}/BTreeConfig.cmake" + "${PROJECT_BINARY_DIR}/BTreeConfigVersion.cmake" + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/BTree/cmake) + +install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/fc + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + +set(CPACK_PACKAGE_NAME "BTree") +set(CPACK_PACKAGE_VERSION "${MAJOR}.${MINOR}.${PATCH}") +set(CPACK_PACKAGE_VENDOR "community") +set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-noarch") +set(CMAKE_PROJECT_HOMEPAGE_URL "https://github.com/bas524/BTree") +set(CPACK_PACKAGE_CONTACT "bas524") +set(CPACK_PACKAGE_MAINTAINER "bas524") +set(CPACK_PACKAGE_DESCRIPTION "A general-purpose high-performance lightweight STL-like modern C++ B-Tree") + +set(CPACK_DEBIAN_PACKAGE_NAME ${CPACK_PACKAGE_NAME}) +set(CPACK_DEBIAN_PACKAGE_VERSION ${CPACK_PACKAGE_VERSION}) +set(CPACK_DEBIAN_PACKAGE_MAINTAINER ${CPACK_PACKAGE_MAINTAINER}) +set(CPACK_DEBIAN_PACKAGE_DESCRIPTION ${CPACK_PACKAGE_DESCRIPTION}) +set(CPACK_DEBIAN_PACKAGE_HOMEPAGE ${CMAKE_PROJECT_HOMEPAGE_URL}) +set(CPACK_DEBIAN_PACKAGE_LICENSE "Apache-2.0") +set(CPACK_DEBIAN_PACKAGE_DEPENDS "") + +set(CPACK_RPM_PACKAGE_NAME ${CPACK_PACKAGE_NAME}) +set(CPACK_RPM_PACKAGE_VERSION ${CPACK_PACKAGE_VERSION}) +set(CPACK_RPM_PACKAGE_MAINTAINER ${CPACK_PACKAGE_MAINTAINER}) +set(CPACK_RPM_PACKAGE_DESCRIPTION ${CPACK_PACKAGE_DESCRIPTION}) +set(CPACK_RPM_PACKAGE_URL ${CMAKE_PROJECT_HOMEPAGE_URL}) +set(CPACK_RPM_PACKAGE_LICENSE "Apache-2.0") +set(CPACK_RPM_PACKAGE_REQUIRES "") + +include(CPack) diff --git a/README.md b/README.md index 900c2cc..8146288 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,19 @@ There are four specialized B-Tree classes: ```frozenca::BTreeSet```, ```frozenca This library is header-only, so no additional setup process is required beyond including the headers. +Or + +For cmake projects: + +Install one of package BTree..rpm or BTree..deb or include this project into yours and then + + ```cmake + find_package(BTree) + #... + target_link_libraries(${your_target} PRIVATE BTree::BTree) + ``` + + ## Target OS/Compiler version This library aggressively uses C++20 features, and verified to work in gcc 11.2 and MSVC 19.32. @@ -25,7 +38,7 @@ There are currently no plans to support C++17 and earlier. Usage is very similar to the C++ standard library ordered associative containers (i.e. ```std::set``` and its friends) ```cpp -#include "fc_btree.h" +#include "fc/btree.h" #include #include @@ -208,7 +221,7 @@ Time to erase 1000000 elements: Average : 1639.79ms, Stdev : 82.7256ms, 95% ## Sanity check and unit test -If you want to contribute and test the code, please uncomment these lines, which will do full sanity checks on the entire tree: +If you want to contribute and test the code, pay attention and use macro _CONTROL_IN_TEST, which will do full sanity checks on the entire tree: https://github.com/frozenca/BTree/blob/adf3c3309f45a65010d767df674c232c12f5c00a/fc_btree.h#L350 https://github.com/frozenca/BTree/blob/adf3c3309f45a65010d767df674c232c12f5c00a/fc_btree.h#L531-#L532 diff --git a/cmake/BTreeConfig.cmake.in b/cmake/BTreeConfig.cmake.in new file mode 100644 index 0000000..ff0fa67 --- /dev/null +++ b/cmake/BTreeConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") \ No newline at end of file diff --git a/cmake/modules/FindVersionHeader.cmake b/cmake/modules/FindVersionHeader.cmake new file mode 100644 index 0000000..677f3c6 --- /dev/null +++ b/cmake/modules/FindVersionHeader.cmake @@ -0,0 +1,29 @@ +find_package(Git) + +if (NOT DEFINED COMMITTER_FULLSHA) + execute_process( + COMMAND ${GIT_EXECUTABLE} log -n 1 --pretty=format:%H + OUTPUT_VARIABLE COMMITTER_FULLSHA + OUTPUT_STRIP_TRAILING_WHITESPACE + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + ) +endif () + +if (NOT DEFINED COMMITTER_SHORTSHA) + execute_process( + COMMAND ${GIT_EXECUTABLE} log -n 1 --pretty=format:%h + OUTPUT_VARIABLE COMMITTER_SHORTSHA + OUTPUT_STRIP_TRAILING_WHITESPACE + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + ) +endif () + +if (NOT DEFINED COMMITTER_DATE) + execute_process( + COMMAND ${GIT_EXECUTABLE} log -n 1 --pretty=format:%ci + OUTPUT_VARIABLE COMMITTER_DATE + OUTPUT_STRIP_TRAILING_WHITESPACE + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + ) +endif () +configure_file(${CMAKE_SOURCE_DIR}/version.h.in ${CMAKE_BINARY_DIR}/version.h @ONLY) diff --git a/fc_btree.h b/include/fc/btree.h similarity index 90% rename from fc_btree.h rename to include/fc/btree.h index 1e08747..f00c844 100644 --- a/fc_btree.h +++ b/include/fc/btree.h @@ -1,19 +1,25 @@ #ifndef __FC_BTREE_H__ #define __FC_BTREE_H__ +#ifndef FC_USE_SIMD #define FC_USE_SIMD 0 +#endif // FC_USE_SIMD + +#ifndef FC_PREFER_BINARY_SEARCH #define FC_PREFER_BINARY_SEARCH 0 +#endif //FC_PREFER_BINARY_SEARCH #if FC_USE_SIMD -#include "fc_comp.h" +#include "fc/comp.h" #ifdef _MSC_VER #pragma warning(disable : 4324) #endif // MSC_VER #endif // FC_USE_SIMD + +#include "fc/details.h" #include #include #include -#include #include #include #include @@ -31,19 +37,18 @@ namespace frozenca { -template -concept Containable = std::is_same_v, T>; - -template -concept DiskAllocable = std::is_same_v, T> && - std::is_trivially_copyable_v &&(sizeof(T) % alignof(T) == 0); - -using attr_t = std::int32_t; - template struct BTreePair { K first; V second; + BTreePair(K &&k, V &&v): first(std::forward(k)), second(std::forward(v)) {} + + BTreePair() = default; + + BTreePair(K &&k): first(std::forward(k)), second() {} + + BTreePair(V &&v): first(), second(std::forward(v)) {} + operator std::pair() noexcept { return {first, second}; } friend bool operator==(const BTreePair &lhs, const BTreePair &rhs) noexcept { @@ -105,32 +110,11 @@ requires(Fanout >= 2) class BTreeBase; template class AllocTemplate> -BTreeBase -join(BTreeBase &&tree_left, - BTreeBase &&tree_right); - -template class AllocTemplate, typename T> -BTreeBase join( - BTreeBase &&tree_left, - T &&raw_value, - BTreeBase && - tree_right) requires std::is_constructible_v>; +struct join_helper; template class AllocTemplate, typename T> -std::pair, - BTreeBase> -split(BTreeBase &&tree, - T &&raw_key) requires std::is_constructible_v>; - -template class AllocTemplate, typename T> -std::pair, - BTreeBase> -split(BTreeBase &&tree, - T &&raw_key1, - T &&raw_key2) requires std::is_constructible_v>; +struct split_helper; template class AllocTemplate> @@ -553,9 +537,10 @@ requires(Fanout >= 2) class BTreeBase { [[nodiscard]] bool verify() const { // Uncomment these lines for testing - - // assert(begin_ == const_iterator_type(leftmost_leaf(root_.get()), 0)); - // assert(verify(root_.get())); +#ifdef _CONTROL_IN_TEST + assert(begin_ == const_iterator_type(leftmost_leaf(root_.get()), 0)); + assert(verify(root_.get())); +#endif return true; } @@ -1930,20 +1915,7 @@ requires(Fanout >= 2) class BTreeBase { public: template class AllocTemplate_> - friend BTreeBase - join(BTreeBase &&tree_left, - BTreeBase - &&tree_right); - - template class AllocTemplate_, - typename T_> - friend BTreeBase - join(BTreeBase &&tree_left, - T_ &&raw_value, - BTreeBase - &&tree_right) requires - std::is_constructible_v>; + friend struct join_helper; protected: std::pair @@ -2012,8 +1984,7 @@ requires(Fanout >= 2) class BTreeBase { supertree_left.set_begin(); BTreeBase new_tree_left = - join(std::move(supertree_left), std::move(xl->keys_[il - 1]), - std::move(tree_left)); + join(std::move(supertree_left), std::move(xl->keys_[il - 1]), std::move(tree_left)); tree_left = std::move(new_tree_left); } @@ -2097,55 +2068,43 @@ requires(Fanout >= 2) class BTreeBase { } public: - template class AllocTemplate_, - typename T> - friend std::pair, - BTreeBase> - split( - BTreeBase &&tree, - T &&raw_key) requires std::is_constructible_v>; - - template class AllocTemplate_, - typename T> - friend std::pair, - BTreeBase> - split(BTreeBase &&tree, - T &&raw_key1, T &&raw_key2) requires - std::is_constructible_v>; + template class AllocTemplate_, typename T> + friend struct split_helper; }; template class AllocTemplate> -BTreeBase -join(BTreeBase &&tree_left, - BTreeBase &&tree_right) { - if (tree_left.empty()) { - return std::move(tree_right); - } else if (tree_right.empty()) { - return std::move(tree_left); - } else { - auto it = tree_right.begin(); - V mid_value = *it; - tree_right.erase(it); - return join(std::move(tree_left), std::move(mid_value), - std::move(tree_right)); +struct join_helper { +private: + BTreeBase result_; + + using Tree = BTreeBase; + using Node = typename Tree::node_type; + using Proj = typename Tree::Proj; + static constexpr bool is_disk_ = Tree::is_disk_; + +public: + join_helper(BTreeBase &&tree_left, + BTreeBase &&tree_right) { + if (tree_left.empty()) { + result_ = std::move(tree_right); + } else if (tree_right.empty()) { + result_ = std::move(tree_left); + } else { + auto it = tree_right.begin(); + V mid_value = *it; + tree_right.erase(it); + result_ = join(std::move(tree_left), std::move(mid_value), + std::move(tree_right)); } } -template class AllocTemplate, typename T_> -BTreeBase -join(BTreeBase &&tree_left, - T_ &&raw_value, - BTreeBase - &&tree_right) requires - std::is_constructible_v> { - using Tree = BTreeBase; - using Node = typename Tree::node_type; - using Proj = typename Tree::Proj; - constexpr bool is_disk_ = Tree::is_disk_; + template + requires std::is_constructible_v> + join_helper(BTreeBase &&tree_left, + T_ &&raw_value, + BTreeBase &&tree_right) { V mid_value{std::forward(raw_value)}; if ((!tree_left.empty() && @@ -2242,7 +2201,7 @@ join(BTreeBase &&tree_left, } assert(new_tree.root_->size_ == size_left + size_right + 1); assert(new_tree.verify()); - return new_tree; + result_ = std::move(new_tree); } else { Tree new_tree = std::move(tree_right); attr_t curr_height = height_right; @@ -2301,52 +2260,56 @@ join(BTreeBase &&tree_left, new_tree.begin_ = new_begin; assert(new_tree.root_->size_ == size_left + size_right + 1); assert(new_tree.verify()); - return new_tree; + result_ = std::move(new_tree); } } - -template class AllocTemplate, typename T> -std::pair, - BTreeBase> -split( - BTreeBase &&tree, - T &&raw_key) requires(std::is_constructible_v>) { - using Tree = BTreeBase; - if (tree.empty()) { - Tree tree_left(tree.alloc_); - Tree tree_right(tree.alloc_); - return {std::move(tree_left), std::move(tree_right)}; - } - - K mid_key{std::forward(raw_key)}; - return tree.split_to_two_trees(tree.find_lower_bound(mid_key, false), - tree.find_upper_bound(mid_key, false)); -} - + BTreeBase&& result() { return std::move(result_); } +}; template class AllocTemplate, typename T> -std::pair, - BTreeBase> -split( - BTreeBase &&tree, T &&raw_key1, - T &&raw_key2) requires(std::is_constructible_v>) { +struct split_helper { +private: + std::pair, + BTreeBase> result_; +public: using Tree = BTreeBase; - if (tree.empty()) { - Tree tree_left(tree.alloc_); - Tree tree_right(tree.alloc_); - return {std::move(tree_left), std::move(tree_right)}; - } - K key1{std::forward(raw_key1)}; - K key2{std::forward(raw_key2)}; - if (Comp{}(key2, key1)) { - throw std::invalid_argument("split() key order is invalid\n"); + split_helper(BTreeBase &&tree, + T &&raw_key) + requires(std::is_constructible_v>) { + if (tree.empty()) { + Tree tree_left(tree.alloc_); + Tree tree_right(tree.alloc_); + result_ = {std::move(tree_left), std::move(tree_right)}; + } else { + K mid_key{std::forward(raw_key)}; + result_ = tree.split_to_two_trees(tree.find_lower_bound(mid_key, false), + tree.find_upper_bound(mid_key, false)); + } + } + split_helper(BTreeBase &&tree, + T &&raw_key1, + T &&raw_key2) + requires(std::is_constructible_v>) { + if (tree.empty()) { + Tree tree_left(tree.alloc_); + Tree tree_right(tree.alloc_); + result_ = {std::move(tree_left), std::move(tree_right)}; + } else { + K key1{std::forward(raw_key1)}; + K key2{std::forward(raw_key2)}; + if (Comp{}(key2, key1)) { + throw std::invalid_argument("split() key order is invalid\n"); + } + result_ = tree.split_to_two_trees(tree.find_lower_bound(key1, false), + tree.find_upper_bound(key2, false)); + } } - return tree.split_to_two_trees(tree.find_lower_bound(key1, false), - tree.find_upper_bound(key2, false)); -} + std::pair, + BTreeBase> && + result() { return std::move(result_); } +}; template class AllocTemplate = std::allocator> using BTreeSet = BTreeBase; @@ -2366,6 +2329,39 @@ template , t, Comp, true, AllocTemplate>; +template class AllocTemplate> +BTreeBase join(BTreeBase &&tree_left, + BTreeBase &&tree_right) { + return join_helper(std::move(tree_left), std::move(tree_right)).result(); +} + +template class AllocTemplate, typename T_> +BTreeBase join(BTreeBase &&tree_left, + T_ &&raw_value, + BTreeBase &&tree_right) { + return join_helper(std::move(tree_left), std::move(raw_value), std::move(tree_right)).result(); +} + +template class AllocTemplate, typename T> +std::pair, + BTreeBase> +split(BTreeBase &&tree, + T &&raw_key) { + return split_helper(std::move(tree), std::move(raw_key)).result(); +} + +template class AllocTemplate, typename T> +std::pair, + BTreeBase> +split(BTreeBase &&tree, + T &&raw_key1, + T &&raw_key2) { + return split_helper(std::move(tree), std::move(raw_key1), std::move(raw_key2)).result(); +} } // namespace frozenca #endif //__FC_BTREE_H__ diff --git a/fc_comp.h b/include/fc/comp.h similarity index 100% rename from fc_comp.h rename to include/fc/comp.h diff --git a/include/fc/details.h b/include/fc/details.h new file mode 100644 index 0000000..4d23a1c --- /dev/null +++ b/include/fc/details.h @@ -0,0 +1,19 @@ +#ifndef FC_DETAILS_H +#define FC_DETAILS_H + +#include + +namespace frozenca { + +template +concept Containable = std::is_same_v, T>; + +template +concept DiskAllocable = + std::is_same_v, T> && + std::is_trivially_copyable_v && (sizeof(T) % alignof(T) == 0); + +using attr_t = std::int32_t; + +} // namespace frozenca +#endif // FC_DETAILS_H diff --git a/fc_disk_btree.h b/include/fc/disk_btree.h similarity index 94% rename from fc_disk_btree.h rename to include/fc/disk_btree.h index 200744d..51433ba 100644 --- a/fc_disk_btree.h +++ b/include/fc/disk_btree.h @@ -1,8 +1,8 @@ #ifndef __FC_DISK_BTREE_H__ #define __FC_DISK_BTREE_H__ -#include "fc_btree.h" -#include "fc_disk_fixed_alloc.h" +#include "fc/btree.h" +#include "fc/disk_fixed_alloc.h" namespace frozenca { diff --git a/fc_disk_fixed_alloc.h b/include/fc/disk_fixed_alloc.h similarity index 95% rename from fc_disk_fixed_alloc.h rename to include/fc/disk_fixed_alloc.h index 2ea7ed5..e1e5e45 100644 --- a/fc_disk_fixed_alloc.h +++ b/include/fc/disk_fixed_alloc.h @@ -1,7 +1,8 @@ #ifndef __FC_DISK_FIXED_ALLOC_H__ #define __FC_DISK_FIXED_ALLOC_H__ -#include "fc_mmfile.h" +#include "fc/mmfile.h" +#include "fc/details.h" #include #include #include diff --git a/include/fc/mmfile.h b/include/fc/mmfile.h new file mode 100644 index 0000000..766ff29 --- /dev/null +++ b/include/fc/mmfile.h @@ -0,0 +1,56 @@ +#ifndef __FC_MMFILE_H__ +#define __FC_MMFILE_H__ + +#include +#include +#include + +#if _WIN32 || _WIN64 +#include "fc/mmfile_win.h" +#else +#include "fc/mmfile_nix.h" +#endif + +namespace frozenca { + +class MemoryMappedFile { + public: + static inline constexpr std::size_t new_file_size_ = + MemoryMappedFileImpl::new_file_size_; + using handle_type = MemoryMappedFileImpl::handle_type; + using path_type = MemoryMappedFileImpl::path_type; + + private: + MemoryMappedFileImpl impl_; + + public: + MemoryMappedFile(const std::filesystem::path &path, + std::size_t init_file_size = new_file_size_, + bool trunc = false) + : impl_{path, init_file_size, trunc} {} + + ~MemoryMappedFile() noexcept = default; + + public: + void resize(std::size_t new_size) { impl_.resize(new_size); } + + [[nodiscard]] std::size_t size() const noexcept { return impl_.size(); } + + [[nodiscard]] void *data() noexcept { return impl_.data(); } + + [[nodiscard]] const void *data() const noexcept { return impl_.data(); } + + friend bool operator==(const MemoryMappedFile &mmfile1, + const MemoryMappedFile &mmfile2) { + return mmfile1.impl_ == mmfile2.impl_; + } + + friend bool operator!=(const MemoryMappedFile &mmfile1, + const MemoryMappedFile &mmfile2) { + return !(mmfile1 == mmfile2); + } +}; + +} // namespace frozenca + +#endif //__FC_MMFILE_H__ diff --git a/include/fc/mmfile_nix.h b/include/fc/mmfile_nix.h new file mode 100644 index 0000000..4ecb65f --- /dev/null +++ b/include/fc/mmfile_nix.h @@ -0,0 +1,139 @@ +#ifndef FC_MMFILE_NIX_H +#define FC_MMFILE_NIX_H + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace frozenca { + +class MemoryMappedFileImpl { + public: + static inline constexpr std::size_t new_file_size_ = (1UL << 20UL); + using handle_type = int; + using path_type = std::filesystem::path::value_type; + + private: + const std::filesystem::path path_; + void *data_ = nullptr; + std::size_t size_ = 0; + + handle_type handle_ = 0; + int flags_ = 0; + + public: + MemoryMappedFileImpl(const std::filesystem::path &path, + std::size_t init_file_size = new_file_size_, + bool trunc = false) + : path_{path} { + bool exists = std::filesystem::exists(path); + if (exists && trunc) { + std::filesystem::remove(path); + exists = false; + } + open_file(path.c_str(), exists, init_file_size); + map_file(); + } + + ~MemoryMappedFileImpl() noexcept { + if (!data_) { + return; + } + bool error = false; + error = !unmap_file() || error; + error = !close_file() || error; + } + + private: + void open_file(const path_type *path, bool exists, + std::size_t init_file_size) { + flags_ = O_RDWR; + if (!exists) { + flags_ |= (O_CREAT | O_TRUNC); + } +#ifdef _LARGEFILE64_SOURCE + flags_ |= O_LARGEFILE; +#endif + errno = 0; + handle_ = open(path, flags_, S_IRWXU); + if (errno != 0) { + throw std::runtime_error("file open failed\n"); + } + + if (!exists) { + if (ftruncate(handle_, init_file_size) == -1) { + throw std::runtime_error("failed setting file size\n"); + } + } + + struct stat info {}; + bool success = (fstat(handle_, &info) != -1); + size_ = info.st_size; + if (!success) { + throw std::runtime_error("failed querying file size\n"); + } + } + + void map_file() { + void *data = + mmap(nullptr, size_, PROT_READ | PROT_WRITE, MAP_SHARED, handle_, 0); + if (data == reinterpret_cast(-1)) { + throw std::runtime_error("failed mapping file"); + } + data_ = data; + } + + bool close_file() noexcept { + return close(handle_) == 0; + } + + bool unmap_file() noexcept { + return (munmap(data_, size_) == 0); + } + + public: + void resize(std::size_t new_size) { + if (!data_) { + throw std::runtime_error("file is closed\n"); + } + if (!unmap_file()) { + throw std::runtime_error("failed unmappping file\n"); + } + if (ftruncate(handle_, new_size) == -1) { + throw std::runtime_error("failed resizing mapped file\n"); + } + size_ = static_cast(new_size); + map_file(); + } + + [[nodiscard]] std::size_t size() const noexcept { return size_; } + + [[nodiscard]] void *data() noexcept { return data_; } + + [[nodiscard]] const void *data() const noexcept { return data_; } + + friend bool operator==(const MemoryMappedFileImpl &mmfile1, + const MemoryMappedFileImpl &mmfile2) { + auto res = + (mmfile1.path_ == mmfile2.path_ && mmfile1.data_ == mmfile2.data_ && + mmfile1.size_ == mmfile2.size_ && mmfile1.handle_ == mmfile2.handle_ && + mmfile1.flags_ == mmfile2.flags_); + return res; + } + + friend bool operator!=(const MemoryMappedFileImpl &mmfile1, + const MemoryMappedFileImpl &mmfile2) { + return !(mmfile1 == mmfile2); + } +}; + +} // namespace frozenca + +#endif // FC_MMFILE_NIX_H diff --git a/fc_mmfile.h b/include/fc/mmfile_win.h similarity index 66% rename from fc_mmfile.h rename to include/fc/mmfile_win.h index f04ec88..d5bef1b 100644 --- a/fc_mmfile.h +++ b/include/fc/mmfile_win.h @@ -1,243 +1,177 @@ -#ifndef __FC_MMFILE_H__ -#define __FC_MMFILE_H__ - -#include -#include -#include - -#if _WIN32 || _WIN64 -#include -#else -#include -#include -#include -#include -#include -#include -#endif - -namespace frozenca { - -class MemoryMappedFile { -public: - static inline constexpr std::size_t new_file_size_ = (1UL << 20UL); -#if _WIN32 || _WIN64 - using handle_type = HANDLE; -#else - using handle_type = int; -#endif - using path_type = std::filesystem::path::value_type; - -private: - const std::filesystem::path path_; - void *data_ = nullptr; - std::size_t size_ = 0; - - handle_type handle_ = 0; - int flags_ = 0; -#if _WIN32 || _WIN64 - handle_type mapped_handle_ = 0; -#endif // Windows - -public: - MemoryMappedFile(const std::filesystem::path &path, - std::size_t init_file_size = new_file_size_, - bool trunc = false) - : path_{path} { - bool exists = std::filesystem::exists(path); - if (exists && trunc) { - std::filesystem::remove(path); - exists = false; - } - open_file(path.c_str(), exists, init_file_size); - map_file(); - } - - ~MemoryMappedFile() noexcept { - if (!data_) { - return; - } - bool error = false; - error = !unmap_file() || error; - error = !close_file() || error; - } - -private: - void open_file(const path_type *path, bool exists, - std::size_t init_file_size) { -#if _WIN32 || _WIN64 - DWORD dwDesiredAccess = GENERIC_READ | GENERIC_WRITE; - DWORD dwCreationDisposition = exists ? OPEN_EXISTING : CREATE_ALWAYS; - DWORD dwFlagsandAttributes = FILE_ATTRIBUTE_TEMPORARY; - handle_ = CreateFileW(path, dwDesiredAccess, FILE_SHARE_READ, 0, - dwCreationDisposition, dwFlagsandAttributes, 0); - if (handle_ == INVALID_HANDLE_VALUE) { - throw std::runtime_error("file open failed\n"); - } - - if (!exists) { - LONG sizehigh = (init_file_size >> (sizeof(LONG) * 8)); - LONG sizelow = (init_file_size & 0xffffffff); - DWORD result = SetFilePointer(handle_, sizelow, &sizehigh, FILE_BEGIN); - if ((result == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) || - !SetEndOfFile(handle_)) { - throw std::runtime_error("failed setting file size\n"); - } - } - - typedef BOOL(WINAPI * func)(HANDLE, PLARGE_INTEGER); - HMODULE hmod = GetModuleHandleA("kernel32.dll"); - func get_size = - reinterpret_cast(GetProcAddress(hmod, "GetFileSizeEx")); - if (get_size) { - LARGE_INTEGER info; - if (get_size(handle_, &info)) { - std::int64_t size = - ((static_cast(info.HighPart) << 32) | info.LowPart); - size_ = static_cast(size); - } else { - throw std::runtime_error("failed querying file size"); - } - } else { - DWORD hi = 0; - DWORD low = 0; - if ((low = GetFileSize(handle_, &hi)) != INVALID_FILE_SIZE) { - std::int64_t size = (static_cast(hi) << 32) | low; - size_ = static_cast(size); - } else { - throw std::runtime_error("failed querying file size"); - return; - } - } -#else - flags_ = O_RDWR; - if (!exists) { - flags_ |= (O_CREAT | O_TRUNC); - } -#ifdef _LARGEFILE64_SOURCE - flags_ |= O_LARGEFILE; -#endif - errno = 0; - handle_ = open(path, flags_, S_IRWXU); - if (errno != 0) { - throw std::runtime_error("file open failed\n"); - } - - if (!exists) { - if (ftruncate(handle_, init_file_size) == -1) { - throw std::runtime_error("failed setting file size\n"); - } - } - - struct stat info; - bool success = (fstat(handle_, &info) != -1); - size_ = info.st_size; - if (!success) { - throw std::runtime_error("failed querying file size\n"); - } -#endif - } - - void map_file() { -#if _WIN32 || _WIN64 - DWORD protect = PAGE_READWRITE; - mapped_handle_ = CreateFileMappingA(handle_, 0, protect, 0, 0, 0); - if (!mapped_handle_) { - throw std::runtime_error("failed mapping file"); - } - - DWORD access = FILE_MAP_WRITE; - void *data = MapViewOfFileEx(mapped_handle_, access, 0, 0, size_, 0); - if (!data) { - throw std::runtime_error("failed mapping file"); - } - data_ = data; -#else - void *data = mmap(0, size_, PROT_READ | PROT_WRITE, MAP_SHARED, handle_, 0); - if (data == reinterpret_cast(-1)) { - throw std::runtime_error("failed mapping file"); - } - data_ = data; -#endif - } - - bool close_file() noexcept { -#if _WIN32 || _WIN64 - return CloseHandle(handle_); -#else - return close(handle_) == 0; -#endif - } - - bool unmap_file() noexcept { -#if _WIN32 || _WIN64 - bool error = false; - error = !UnmapViewOfFile(data_) || error; - error = !CloseHandle(mapped_handle_) || error; - mapped_handle_ = NULL; - return !error; -#else - return (munmap(data_, size_) == 0); -#endif - } - -public: - void resize(std::size_t new_size) { - if (!data_) { - throw std::runtime_error("file is closed\n"); - } - if (!unmap_file()) { - throw std::runtime_error("failed unmappping file\n"); - } - -#if _WIN32 || _WIN64 - std::int64_t offset = SetFilePointer(handle_, 0, 0, FILE_CURRENT); - if (offset == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) { - throw std::runtime_error("failed querying file pointer"); - } - LONG sizehigh = (new_size >> (sizeof(LONG) * 8)); - LONG sizelow = (new_size & 0xffffffff); - DWORD result = SetFilePointer(handle_, sizelow, &sizehigh, FILE_BEGIN); - if ((result == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) || - !SetEndOfFile(handle_)) { - throw std::runtime_error("failed resizing mapped file"); - } - sizehigh = (offset >> (sizeof(LONG) * 8)); - sizelow = (offset & 0xffffffff); - SetFilePointer(handle_, sizelow, &sizehigh, FILE_BEGIN); -#else - if (ftruncate(handle_, new_size) == -1) { - throw std::runtime_error("failed resizing mapped file\n"); - } -#endif - size_ = static_cast(new_size); - map_file(); - } - - [[nodiscard]] std::size_t size() const noexcept { return size_; } - - [[nodiscard]] void *data() noexcept { return data_; } - - [[nodiscard]] const void *data() const noexcept { return data_; } - - friend bool operator==(const MemoryMappedFile &mmfile1, - const MemoryMappedFile &mmfile2) { - auto res = - (mmfile1.path_ == mmfile2.path_ && mmfile1.data_ == mmfile2.data_ && - mmfile1.size_ == mmfile2.size_ && mmfile1.handle_ == mmfile2.handle_ && - mmfile1.flags_ == mmfile2.flags_); -#if _WIN32 || _WIN64 - res = res && (mmfile1.mapped_handle_ == mmfile2.mapped_handle_); -#endif // Windows - return res; - } - - friend bool operator!=(const MemoryMappedFile &mmfile1, - const MemoryMappedFile &mmfile2) { - return !(mmfile1 == mmfile2); - } -}; - -} // namespace frozenca - -#endif //__FC_MMFILE_H__ +#ifndef FC_MMFILE_WIN_H +#define FC_MMFILE_WIN_H + +#include + +#include +#include +#include + +namespace frozenca { + +class MemoryMappedFileImpl { + public: + static inline constexpr std::size_t new_file_size_ = (1UL << 20UL); + using handle_type = HANDLE; + using path_type = std::filesystem::path::value_type; + + private: + const std::filesystem::path path_; + void *data_ = nullptr; + std::size_t size_ = 0; + + handle_type handle_ = 0; + int flags_ = 0; + handle_type mapped_handle_ = 0; + + public: + MemoryMappedFileImpl(const std::filesystem::path &path, + std::size_t init_file_size = new_file_size_, + bool trunc = false) + : path_{path} { + bool exists = std::filesystem::exists(path); + if (exists && trunc) { + std::filesystem::remove(path); + exists = false; + } + open_file(path.c_str(), exists, init_file_size); + map_file(); + } + + ~MemoryMappedFileImpl() noexcept { + if (!data_) { + return; + } + bool error = false; + error = !unmap_file() || error; + error = !close_file() || error; + } + + private: + void open_file(const path_type *path, bool exists, + std::size_t init_file_size) { + DWORD dwDesiredAccess = GENERIC_READ | GENERIC_WRITE; + DWORD dwCreationDisposition = exists ? OPEN_EXISTING : CREATE_ALWAYS; + DWORD dwFlagsandAttributes = FILE_ATTRIBUTE_TEMPORARY; + handle_ = CreateFileW(path, dwDesiredAccess, FILE_SHARE_READ, 0, + dwCreationDisposition, dwFlagsandAttributes, 0); + if (handle_ == INVALID_HANDLE_VALUE) { + throw std::runtime_error("file open failed\n"); + } + + if (!exists) { + LONG sizehigh = (init_file_size >> (sizeof(LONG) * 8)); + LONG sizelow = (init_file_size & 0xffffffff); + DWORD result = SetFilePointer(handle_, sizelow, &sizehigh, FILE_BEGIN); + if ((result == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) || + !SetEndOfFile(handle_)) { + throw std::runtime_error("failed setting file size\n"); + } + } + + typedef BOOL(WINAPI * func)(HANDLE, PLARGE_INTEGER); + HMODULE hmod = GetModuleHandleA("kernel32.dll"); + func get_size = + reinterpret_cast(GetProcAddress(hmod, "GetFileSizeEx")); + if (get_size) { + LARGE_INTEGER info; + if (get_size(handle_, &info)) { + std::int64_t size = + ((static_cast(info.HighPart) << 32) | info.LowPart); + size_ = static_cast(size); + } else { + throw std::runtime_error("failed querying file size"); + } + } else { + DWORD hi = 0; + DWORD low = 0; + if ((low = GetFileSize(handle_, &hi)) != INVALID_FILE_SIZE) { + std::int64_t size = (static_cast(hi) << 32) | low; + size_ = static_cast(size); + } else { + throw std::runtime_error("failed querying file size"); + return; + } + } + } + + void map_file() { + DWORD protect = PAGE_READWRITE; + mapped_handle_ = CreateFileMappingA(handle_, 0, protect, 0, 0, 0); + if (!mapped_handle_) { + throw std::runtime_error("failed mapping file"); + } + + DWORD access = FILE_MAP_WRITE; + void *data = MapViewOfFileEx(mapped_handle_, access, 0, 0, size_, 0); + if (!data) { + throw std::runtime_error("failed mapping file"); + } + data_ = data; + } + + bool close_file() noexcept { return CloseHandle(handle_); } + + bool unmap_file() noexcept { + bool error = false; + error = !UnmapViewOfFile(data_) || error; + error = !CloseHandle(mapped_handle_) || error; + mapped_handle_ = NULL; + return !error; + } + + public: + void resize(std::size_t new_size) { + if (!data_) { + throw std::runtime_error("file is closed\n"); + } + if (!unmap_file()) { + throw std::runtime_error("failed unmappping file\n"); + } + + std::int64_t offset = SetFilePointer(handle_, 0, 0, FILE_CURRENT); + if (offset == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) { + throw std::runtime_error("failed querying file pointer"); + } + LONG sizehigh = (new_size >> (sizeof(LONG) * 8)); + LONG sizelow = (new_size & 0xffffffff); + DWORD result = SetFilePointer(handle_, sizelow, &sizehigh, FILE_BEGIN); + if ((result == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) || + !SetEndOfFile(handle_)) { + throw std::runtime_error("failed resizing mapped file"); + } + sizehigh = (offset >> (sizeof(LONG) * 8)); + sizelow = (offset & 0xffffffff); + SetFilePointer(handle_, sizelow, &sizehigh, FILE_BEGIN); + + size_ = static_cast(new_size); + map_file(); + } + + [[nodiscard]] std::size_t size() const noexcept { return size_; } + + [[nodiscard]] void *data() noexcept { return data_; } + + [[nodiscard]] const void *data() const noexcept { return data_; } + + friend bool operator==(const MemoryMappedFileImpl &mmfile1, + const MemoryMappedFileImpl &mmfile2) { + auto res = + (mmfile1.path_ == mmfile2.path_ && mmfile1.data_ == mmfile2.data_ && + mmfile1.size_ == mmfile2.size_ && mmfile1.handle_ == mmfile2.handle_ && + mmfile1.flags_ == mmfile2.flags_); + + res = res && (mmfile1.mapped_handle_ == mmfile2.mapped_handle_); + + return res; + } + + friend bool operator!=(const MemoryMappedFileImpl &mmfile1, + const MemoryMappedFileImpl &mmfile2) { + return !(mmfile1 == mmfile2); + } +}; + +} // namespace frozenca + +#endif // FC_MMFILE_WIN_H diff --git a/test/fc_catch2.h b/test/fc_catch2.h new file mode 100644 index 0000000..f4240a8 --- /dev/null +++ b/test/fc_catch2.h @@ -0,0 +1,15 @@ +#ifndef FC_CATCH2_H +#define FC_CATCH2_H + +#ifndef CATCH_CONFIG_ENABLE_BENCHMARKING +#define CATCH_CONFIG_ENABLE_BENCHMARKING +#endif // CATCH_CONFIG_ENABLE_BENCHMARKING + +#ifdef CATCH2_OLD +#include +#else +#include +#include +#endif + +#endif // FC_CATCH2_H diff --git a/test/perftest.cpp b/test/perftest.cpp index 1815a3d..9ff641b 100644 --- a/test/perftest.cpp +++ b/test/perftest.cpp @@ -1,168 +1,58 @@ -#include "../fc_disk_btree.h" -#include -#include -#include -#include -#include -#include +#if defined(__x86_64__) || defined(_M_X64) +#if defined(FC_USE_SIMD) +#undef FC_USE_SIMD +#define FC_USE_SIMD 1 +#endif // FC_USE_SIMD +#endif + +#include "fc_catch2.h" +#include "fc/disk_btree.h" #include #include +#include +#include +#include "test_statistics.h" -struct stats { - float average = 0.0f; - float stdev = 0.0f; - float percentile_95 = 0.0f; - float percentile_99 = 0.0f; - float percentile_999 = 0.0f; -}; - -stats get_statistics(std::vector &v) { - auto n = std::ssize(v); - if (n == 0) { - return {}; - } - stats s; - s.average = std::accumulate(v.begin(), v.end(), 0.0f) / n; - float variance = 0.0f; - for (auto value : v) { - variance += std::pow(value - s.average, 2.0f); - } - variance /= n; - s.stdev = std::sqrt(variance); - std::ranges::sort(v); - s.percentile_95 = *(v.begin() + (19 * n / 20)); - s.percentile_99 = *(v.begin() + (99 * n / 100)); - s.percentile_999 = *(v.begin() + (999 * n / 1000)); - return s; -} - -template -void tree_perf_test(TreeType &tree, bool warmup = false) { - constexpr int max_n = 1'000'000; - constexpr int max_trials = 1; - - std::mt19937 gen(std::random_device{}()); - std::vector durations_insert; - std::vector durations_find; - std::vector durations_erase; - std::vector v(max_n); - std::iota(v.begin(), v.end(), 0); - - for (int t = 0; t < max_trials; ++t) { - float duration = 0.0f; - std::ranges::shuffle(v, gen); - for (auto num : v) { - auto start = std::chrono::steady_clock::now(); - tree.insert(num); - auto end = std::chrono::steady_clock::now(); - duration += - std::chrono::duration_cast>( - end - start) - .count(); - } - durations_insert.push_back(duration); - - duration = 0.0f; - std::ranges::shuffle(v, gen); - for (auto num : v) { - auto start = std::chrono::steady_clock::now(); - if (!tree.contains(num)) { - std::cerr << "Lookup verification fail!\n"; - } - auto end = std::chrono::steady_clock::now(); - duration += - std::chrono::duration_cast>( - end - start) - .count(); - } - durations_find.push_back(duration); - - duration = 0.0f; - std::ranges::shuffle(v, gen); - for (auto num : v) { - auto start = std::chrono::steady_clock::now(); - if (!tree.erase(num)) { - std::cerr << "Erase verification fail!\n"; - } - auto end = std::chrono::steady_clock::now(); - duration += - std::chrono::duration_cast>( - end - start) - .count(); - } - durations_erase.push_back(duration); - } - if (!warmup) { - { - auto stat = get_statistics(durations_insert); - std::cout << "Time to insert " << max_n << " elements:\n" - << "Average : " << stat.average << "ms,\n" - << "Stdev : " << stat.stdev << "ms,\n" - << "95% : " << stat.percentile_95 << "ms,\n" - << "99% : " << stat.percentile_99 << "ms,\n" - << "99.9% : " << stat.percentile_999 << "ms,\n"; - } - { - auto stat = get_statistics(durations_find); - std::cout << "Time to lookup " << max_n << " elements:\n" - << "Average : " << stat.average << "ms,\n" - << "Stdev : " << stat.stdev << "ms,\n" - << "95% : " << stat.percentile_95 << "ms,\n" - << "99% : " << stat.percentile_99 << "ms,\n" - << "99.9% : " << stat.percentile_999 << "ms,\n"; - } - { - auto stat = get_statistics(durations_erase); - std::cout << "Time to erase " << max_n << " elements:\n" - << "Average : " << stat.average << "ms,\n" - << "Stdev : " << stat.stdev << "ms,\n" - << "95% : " << stat.percentile_95 << "ms,\n" - << "99% : " << stat.percentile_99 << "ms,\n" - << "99.9% : " << stat.percentile_999 << "ms,\n"; - } - } -} - -int main() { +TEST_CASE("perftest") { namespace fc = frozenca; + std::unordered_map result; - std::cout << "Balanced tree test\n"; - { - fc::BTreeSet btree; - // warm up for benchmark - tree_perf_test(btree, true); - } - std::cout << "Warming up complete...\n"; + auto generate_values = []() { + std::vector::value_type> v(1'000'000); + std::iota(v.begin(), v.end(), 0); + return v; + }; - { - std::cout << "frozenca::BTreeSet test (fanout 64 - default)\n"; + BENCHMARK("Balanced tree test - warmap") { fc::BTreeSet btree; - tree_perf_test(btree); - } - { - std::cout << "frozenca::BTreeSet test (fanout 96)\n"; + tree_perf_test(btree, generate_values()); + }; + BENCHMARK("frozenca::BTreeSet test (fanout 64)") { + fc::BTreeSet btree; + result.emplace("BTreeSet(64)", tree_perf_test(btree, generate_values())); + }; + BENCHMARK("frozenca::BTreeSet test (fanout 96)") { fc::BTreeSet btree; - tree_perf_test(btree); - } - { - std::cout << "frozenca::DiskBTreeSet test (fanout 128)\n"; - fc::DiskBTreeSet btree("database.bin", 1UL << 25UL, - true); - tree_perf_test(btree); - } - { - std::cout << "frozenca::BTreeSet test (fanout 128)\n"; + result.emplace("BTreeSet(96)", tree_perf_test(btree, generate_values())); + }; + BENCHMARK("frozenca::DiskBTreeSet test (fanout 128)") { + fc::DiskBTreeSet btree("database.bin", 1UL << 25UL, true); + result.emplace("DiskBTreeSet(128)", tree_perf_test(btree, generate_values())); + }; + BENCHMARK("frozenca::BTreeSet test (fanout 128)") { fc::BTreeSet btree; - tree_perf_test(btree); - } - { - std::cout << "frozenca::BTreeSet test (don't use SIMD) \n"; - fc::BTreeSet btree; - tree_perf_test(btree); - } - { - std::cout << "std::set test\n"; + result.emplace("BTreeSet(128)", tree_perf_test(btree, generate_values())); + }; + BENCHMARK("std::set test") { std::set rbtree; - tree_perf_test(rbtree); + result.emplace("std::set", tree_perf_test(rbtree, generate_values())); + }; + + for (const auto &[key, value] : result) { + INFO("----------------"); + INFO(key); + std::stringstream ss; + value.print_stats(ss); + INFO(ss.str()); } -} \ No newline at end of file +} diff --git a/test/perftest_no_simd.cpp b/test/perftest_no_simd.cpp new file mode 100644 index 0000000..6f919f3 --- /dev/null +++ b/test/perftest_no_simd.cpp @@ -0,0 +1,36 @@ +#if defined(FC_USE_SIMD) +#undef FC_USE_SIMD +#define FC_USE_SIMD 0 +#endif // FC_USE_SIMD + +#include "fc_catch2.h" +#include "fc/disk_btree.h" +#include +#include +#include +#include +#include "test_statistics.h" + +TEST_CASE("perftest-no-simd") { + namespace fc = frozenca; + std::unordered_map result; + + auto generate_values = []() { + std::vector::value_type> v(1'000'000); + std::iota(v.begin(), v.end(), 0); + return v; + }; + + BENCHMARK("frozenca::BTreeSet test (don't use SIMD)") { + fc::BTreeSet btree; + result.emplace("BTreeSet(no-simd)", tree_perf_test(btree, generate_values())); + }; + + for (const auto &[key, value] : result) { + INFO("----------------"); + INFO(key); + std::stringstream ss; + value.print_stats(ss); + INFO(ss.str()); + } +} diff --git a/test/perftest_string.cpp b/test/perftest_string.cpp index 24f6a8c..1be0bc6 100644 --- a/test/perftest_string.cpp +++ b/test/perftest_string.cpp @@ -1,181 +1,48 @@ -#include "../fc_disk_btree.h" -#include -#include -#include +#if defined(FC_USE_SIMD) +#undef FC_USE_SIMD +#define FC_USE_SIMD 0 +#endif // FC_USE_SIMD + +#include "fc_catch2.h" +#include "fc/btree.h" #include -#include -#include #include #include - -struct stats { - float average = 0.0f; - float stdev = 0.0f; - float percentile_95 = 0.0f; - float percentile_99 = 0.0f; - float percentile_999 = 0.0f; -}; - -stats get_statistics(std::vector &v) { - auto n = std::ssize(v); - if (n == 0) { - return {}; - } - stats s; - s.average = std::accumulate(v.begin(), v.end(), 0.0f) / n; - float variance = 0.0f; - for (auto value : v) { - variance += std::pow(value - s.average, 2.0f); - } - variance /= n; - s.stdev = std::sqrt(variance); - std::ranges::sort(v); - s.percentile_95 = *(v.begin() + (19 * n / 20)); - s.percentile_99 = *(v.begin() + (99 * n / 100)); - s.percentile_999 = *(v.begin() + (999 * n / 1000)); - return s; +#include +#include "test_statistics.h" + + +TEST_CASE("perftest-string") { + namespace fc = frozenca; + constexpr int max_n = 1'000'000; + constexpr int max_length = 50; + + std::unordered_map result; + auto str_vec = generate_random_strings(max_n, max_length, false); + + BENCHMARK("Balanced tree test - warmap") { + fc::BTreeSet btree; + tree_perf_test(btree, str_vec); + }; + + BENCHMARK("frozenca::BTreeSet string (fanout 64)") { + fc::BTreeSet btree; + result.emplace("BTreeSet(64)", tree_perf_test(btree, str_vec)); + }; + BENCHMARK("frozenca::BTreeSet string (fanout 128)") { + fc::BTreeSet btree; + result.emplace("BTreeSet(128)", tree_perf_test(btree, str_vec)); + }; + BENCHMARK("std::set string") { + std::set rbtree; + result.emplace("std::set", tree_perf_test(rbtree, str_vec)); + }; + + for (const auto &[key, value] : result) { + INFO("----------------"); + INFO(key); + std::stringstream ss; + value.print_stats(ss); + INFO(ss.str()); + } } - -std::vector generate_random_strings(int max_n, int max_length, bool allow_duplicates) { - std::vector res; - - std::mt19937 gen(std::random_device{}()); - std::uniform_int_distribution length_dist (1, max_length); - std::uniform_int_distribution ch_dist (32, 126); - - for (int i = 0; i < max_n; ++i) { - int len = length_dist(gen); - std::string s; - for (int l = 0; l < len; ++l) { - s += static_cast(ch_dist(gen)); - } - res.push_back(std::move(s)); - } - - if (!allow_duplicates) { - std::ranges::sort(res); - auto ret = std::ranges::unique(res); - res.erase(ret.begin(), ret.end()); - } - - return res; -} - -constexpr int max_n = 1'000'000; -constexpr int max_length = 50; - -auto str_vec = generate_random_strings(max_n, max_length, false); - -template -void tree_perf_test(TreeType &tree, bool warmup = false) { - constexpr int max_trials = 10; - - std::mt19937 gen(std::random_device{}()); - std::vector durations_insert; - std::vector durations_find; - std::vector durations_erase; - std::vector v(max_n); - - - for (int t = 0; t < max_trials; ++t) { - float duration = 0.0f; - std::ranges::shuffle(str_vec, gen); - for (const auto& str : str_vec) { - auto start = std::chrono::steady_clock::now(); - tree.insert(str); - auto end = std::chrono::steady_clock::now(); - duration += - std::chrono::duration_cast>( - end - start) - .count(); - } - durations_insert.push_back(duration); - - duration = 0.0f; - std::ranges::shuffle(str_vec, gen); - for (const auto& str : str_vec) { - auto start = std::chrono::steady_clock::now(); - if (!tree.contains(str)) { - std::cerr << "Lookup verification fail!\n"; - } - auto end = std::chrono::steady_clock::now(); - duration += - std::chrono::duration_cast>( - end - start) - .count(); - } - durations_find.push_back(duration); - - duration = 0.0f; - std::ranges::shuffle(str_vec, gen); - for (const auto& str : str_vec) { - auto start = std::chrono::steady_clock::now(); - if (!tree.erase(str)) { - std::cerr << "Erase verification fail!\n"; - } - auto end = std::chrono::steady_clock::now(); - duration += - std::chrono::duration_cast>( - end - start) - .count(); - } - durations_erase.push_back(duration); - } - if (!warmup) { - { - auto stat = get_statistics(durations_insert); - std::cout << "Time to insert " << max_n << " elements:\n" - << "Average : " << stat.average << "ms,\n" - << "Stdev : " << stat.stdev << "ms,\n" - << "95% : " << stat.percentile_95 << "ms,\n" - << "99% : " << stat.percentile_99 << "ms,\n" - << "99.9% : " << stat.percentile_999 << "ms,\n"; - } - { - auto stat = get_statistics(durations_find); - std::cout << "Time to lookup " << max_n << " elements:\n" - << "Average : " << stat.average << "ms,\n" - << "Stdev : " << stat.stdev << "ms,\n" - << "95% : " << stat.percentile_95 << "ms,\n" - << "99% : " << stat.percentile_99 << "ms,\n" - << "99.9% : " << stat.percentile_999 << "ms,\n"; - } - { - auto stat = get_statistics(durations_erase); - std::cout << "Time to erase " << max_n << " elements:\n" - << "Average : " << stat.average << "ms,\n" - << "Stdev : " << stat.stdev << "ms,\n" - << "95% : " << stat.percentile_95 << "ms,\n" - << "99% : " << stat.percentile_99 << "ms,\n" - << "99.9% : " << stat.percentile_999 << "ms,\n"; - } - } -} - -int main() { - namespace fc = frozenca; - - std::cout << "Balanced tree test\n"; - { - fc::BTreeSet btree; - // warm up for benchmark - tree_perf_test(btree, true); - } - std::cout << "Warming up complete...\n"; - - { - std::cout << "frozenca::BTreeSet test (fanout 64 - default)\n"; - fc::BTreeSet btree; - tree_perf_test(btree); - } - { - std::cout << "frozenca::BTreeSet test (fanout 128)\n"; - fc::BTreeSet btree; - tree_perf_test(btree); - } - { - std::cout << "std::set test\n"; - std::set rbtree; - tree_perf_test(rbtree); - } -} \ No newline at end of file diff --git a/test/rwtest.cpp b/test/rwtest.cpp index 3adb965..55898d8 100644 --- a/test/rwtest.cpp +++ b/test/rwtest.cpp @@ -1,15 +1,19 @@ -#include "../fc_btree.h" +#define _CONTROL_IN_TEST + +#include "fc_catch2.h" #include #include -int main() { +#include "fc/btree.h" + +TEST_CASE("rw-test") { namespace fc = frozenca; fc::BTreeSet btree_out; constexpr int n = 100; for (int i = 0; i < n; ++i) { - btree_out.insert(i); + REQUIRE_NOTHROW(btree_out.insert(i)); } { std::ofstream ofs{"btree.bin", std::ios_base::out | std::ios_base::binary | @@ -24,9 +28,6 @@ int main() { } for (int i = 0; i < n; ++i) { - if (!btree_in.contains(i)) { - std::cout << "deserialized tree key lookup failed\n"; - } + REQUIRE(btree_in.contains(i)); } - std::cout << "OK\n"; } diff --git a/test/test_statistics.cpp b/test/test_statistics.cpp new file mode 100644 index 0000000..6f974f4 --- /dev/null +++ b/test/test_statistics.cpp @@ -0,0 +1,63 @@ +#include "test_statistics.h" +#include +#include +#include + +stats get_statistics(std::vector &v) { + auto n = std::ssize(v); + if (n == 0) { + return {}; + } + stats s; + s.average = std::accumulate(v.begin(), v.end(), 0.0f) / n; + float variance = 0.0f; + for (auto value : v) { + variance += std::pow(value - s.average, 2.0f); + } + variance /= n; + s.stdev = std::sqrt(variance); + std::ranges::sort(v); + s.percentile_95 = *(v.begin() + (19 * n / 20)); + s.percentile_99 = *(v.begin() + (99 * n / 100)); + s.percentile_999 = *(v.begin() + (999 * n / 1000)); + return s; +} + +void perf_result::print_stats(std::ostream &os) const { + auto print = [this, &os](const std::string &stat_name, const stats &stat) { + os << "\tTime to " << stat_name << " " << values_cnt << " elements:\n" + << "\tAverage : " << stat.average << "ms,\n" + << "\tStdev : " << stat.stdev << "ms,\n" + << "\t95% : " << stat.percentile_95 << "ms,\n" + << "\t99% : " << stat.percentile_99 << "ms,\n" + << "\t99.9% : " << stat.percentile_999 << "ms,\n"; + }; + print("insert", insert); + print("find", find); + print("erase", erase); +} + +std::vector generate_random_strings(int max_n, int max_length, bool allow_duplicates) { + std::vector res; + + std::mt19937 gen(std::random_device{}()); + std::uniform_int_distribution length_dist(1, max_length); + std::uniform_int_distribution ch_dist(32, 126); + + for (int i = 0; i < max_n; ++i) { + int len = length_dist(gen); + std::string s; + for (int l = 0; l < len; ++l) { + s += static_cast(ch_dist(gen)); + } + res.push_back(std::move(s)); + } + + if (!allow_duplicates) { + std::ranges::sort(res); + auto ret = std::ranges::unique(res); + res.erase(ret.begin(), ret.end()); + } + + return res; +} diff --git a/test/test_statistics.h b/test/test_statistics.h new file mode 100644 index 0000000..b022f43 --- /dev/null +++ b/test/test_statistics.h @@ -0,0 +1,86 @@ +#ifndef FC_TEST_STATISTICS_H +#define FC_TEST_STATISTICS_H + +#include "fc_catch2.h" +#include +#include +#include +#include +#include +#include +#include + +struct stats { + float average = 0.0f; + float stdev = 0.0f; + float percentile_95 = 0.0f; + float percentile_99 = 0.0f; + float percentile_999 = 0.0f; +}; + +struct perf_result { + size_t values_cnt{0}; + stats insert; + stats find; + stats erase; + void print_stats(std::ostream &os) const; +}; + +stats get_statistics(std::vector &v); + +std::vector generate_random_strings(int max_n, int max_length, bool allow_duplicates); + +template +[[maybe_unused]] perf_result tree_perf_test(TreeType &tree, std::vector v, size_t trials = 1) { + const size_t max_n = v.size(); + const size_t max_trials = trials; + + std::mt19937 gen(std::random_device{}()); + std::vector durations_insert; + std::vector durations_find; + std::vector durations_erase; + + for (size_t t = 0; t < max_trials; ++t) { + float duration = 0.0f; + std::ranges::shuffle(v, gen); + for (auto num : v) { + auto start = std::chrono::steady_clock::now(); + tree.insert(num); + auto end = std::chrono::steady_clock::now(); + duration += std::chrono::duration_cast>(end - start).count(); + } + durations_insert.push_back(duration); + + duration = 0.0f; + std::ranges::shuffle(v, gen); + for (auto num : v) { + auto start = std::chrono::steady_clock::now(); + if (!tree.contains(num)) { + FAIL("Lookup verification fail!"); + } + auto end = std::chrono::steady_clock::now(); + duration += std::chrono::duration_cast>(end - start).count(); + } + durations_find.push_back(duration); + + duration = 0.0f; + std::ranges::shuffle(v, gen); + for (auto num : v) { + auto start = std::chrono::steady_clock::now(); + if (!tree.erase(num)) { + FAIL("Erase verification fail!"); + } + auto end = std::chrono::steady_clock::now(); + duration += std::chrono::duration_cast>(end - start).count(); + } + durations_erase.push_back(duration); + } + perf_result result; + result.values_cnt = max_n; + result.insert = get_statistics(durations_insert); + result.find = get_statistics(durations_find); + result.erase = get_statistics(durations_erase); + return result; +} + +#endif // FC_TEST_STATISTICS_H diff --git a/test/unittest.cpp b/test/unittest.cpp index 5a734b9..5ce261a 100644 --- a/test/unittest.cpp +++ b/test/unittest.cpp @@ -1,4 +1,11 @@ -#include "../fc_btree.h" +#define _CONTROL_IN_TEST + +#ifndef CATCH_CONFIG_MAIN +#define CATCH_CONFIG_MAIN +#endif // CATCH_CONFIG_MAIN + +#include "fc_catch2.h" + #include #include #include @@ -7,279 +14,216 @@ #include #include -int main() { - namespace fc = frozenca; +#include "fc/btree.h" - // recommend to use debug build for these unit tests +namespace fc = frozenca; - std::cout << "B-Tree unit tests\n"; +TEST_CASE("BTree insert-lookup-erase") { + fc::BTreeSet btree; + constexpr int n = 100; - { - fc::BTreeSet btree; - constexpr int n = 100; + std::mt19937 gen(std::random_device{}()); - std::mt19937 gen(std::random_device{}()); - - std::vector v(n); - std::iota(v.begin(), v.end(), 0); - - std::cout << "Random insert\n"; + std::vector v(n); + std::iota(v.begin(), v.end(), 0); + SECTION("Random insert") { std::ranges::shuffle(v, gen); for (auto num : v) { btree.insert(num); } - std::cout << "OK\n"; - - std::cout << "Random lookup\n"; + } + SECTION("Random lookup") { std::ranges::shuffle(v, gen); for (auto num : v) { - if (!btree.contains(num)) { - std::cerr << "Lookup failed!\n"; - } + btree.insert(num); } - std::cout << "OK\n"; - - std::cout << "Random erase\n"; std::ranges::shuffle(v, gen); for (auto num : v) { - if (!btree.erase(num)) { - std::cerr << "Erase failed!\n"; - } + REQUIRE(btree.contains(num)); } - std::cout << "OK\n"; } - - { - std::cout << "std::initializer_list test\n"; - - fc::BTreeSet btree{1, 4, 3, 2, 3, 3, 6, 5, 8}; - if (btree.size() == 7) { - std::cout << "OK\n"; - } else { - std::cout << "std::initializer_list test failed\n"; - } - } - { - std::cout << "Multiset test\n"; - - fc::BTreeMultiSet btree{1, 4, 3, 2, 3, 3, 6, 5, 8}; - if (btree.size() == 9) { - std::cout << "OK\n"; - } else { - std::cout << "Multiset test failed\n"; + SECTION("Random erase") { + std::ranges::shuffle(v, gen); + for (auto num : v) { + btree.insert(num); } - btree.erase(3); - if (btree.size() == 6) { - std::cout << "OK\n"; - } else { - std::cout << "Multiset test failed\n"; + std::ranges::shuffle(v, gen); + for (auto num : v) { + REQUIRE(btree.erase(num)); } } - { - std::cout << "Order statistic test\n"; +} - fc::BTreeSet btree; - constexpr int n = 100; +TEST_CASE("BTree std::initializer_list-test") { + fc::BTreeSet btree{1, 4, 3, 2, 3, 3, 6, 5, 8}; + REQUIRE(btree.size() == 7); +} - for (int i = 0; i < n; ++i) { - btree.insert(i); - } +TEST_CASE("Multiset test") { + fc::BTreeMultiSet btree{1, 4, 3, 2, 3, 3, 6, 5, 8}; + REQUIRE(btree.size() == 9); + REQUIRE_NOTHROW(btree.erase(3)); + REQUIRE(btree.size() == 6); +} - for (int i = 0; i < n; ++i) { - if (btree.kth(i) != i) { - std::cout << "Kth failed\n"; - } - } - std::cout << "OK\n"; +TEST_CASE("Order statistic test") { + fc::BTreeSet btree; + constexpr int n = 100; - for (int i = 0; i < n; ++i) { - if (btree.order(btree.find(i)) != i) { - std::cout << "Order failed\n"; - } - } - std::cout << "OK\n"; + for (int i = 0; i < n; ++i) { + REQUIRE_NOTHROW(btree.insert(i)); } - { - std::cout << "Enumerate test\n"; - fc::BTreeSet btree; - constexpr int n = 100; - for (int i = 0; i < n; ++i) { - btree.insert(i); - } - auto rg = btree.enumerate(20, 30); - if (std::ranges::distance(rg.begin(), rg.end()) == 11) { - std::cout << "OK\n"; - } else { - std::cout << "Enumerate failed\n"; - } + for (int i = 0; i < n; ++i) { + REQUIRE(btree.kth(i) == i); + } - std::cout << "erase_if test\n"; - btree.erase_if([](auto n) { return n >= 20 && n <= 90; }); - if (btree.size() == 29) { - std::cout << "OK\n"; - } else { - std::cout << "erase_if failed\n"; - } + for (int i = 0; i < n; ++i) { + REQUIRE(btree.order(btree.find(i)) == i); } - { - std::cout << "BTreeMap test\n"; - fc::BTreeMap btree; - - btree["asd"] = 3; - btree["a"] = 6; - btree["bbb"] = 9; - btree["asdf"] = 8; - btree["asdf"] = 333; - if (btree["asdf"] == 333) { - std::cout << "OK\n"; - } else { - std::cout << "operator[] failed\n"; - } +} - btree.emplace("asdfgh", 200); - if (btree["asdfgh"] == 200) { - std::cout << "OK\n"; - } else { - std::cout << "emplace() failed\n"; - } +TEST_CASE("Enumerate") { + fc::BTreeSet btree; + constexpr int n = 100; + + for (int i = 0; i < n; ++i) { + REQUIRE_NOTHROW(btree.insert(i)); } - { - std::cout << "join/split test\n"; - fc::BTreeSet btree1; - for (int i = 0; i < 100; ++i) { - btree1.insert(i); - } + auto rg = btree.enumerate(20, 30); + REQUIRE(std::ranges::distance(rg.begin(), rg.end()) == 11); - fc::BTreeSet btree2; - for (int i = 101; i < 300; ++i) { - btree2.insert(i); - } + SECTION("erase_if test") { + REQUIRE_NOTHROW(btree.erase_if([](auto n) { return n >= 20 && n <= 90; })); + REQUIRE(btree.size() == 29); + } +} - auto btree3 = fc::join(std::move(btree1), 100, std::move(btree2)); +TEST_CASE("BTreeMap") { + fc::BTreeMap btree; - for (int i = 0; i < 300; ++i) { - if (!btree3.contains(i)) { - std::cout << "Join failed\n"; - } - } - std::cout << "OK\n"; + REQUIRE_NOTHROW(btree["asd"] = 3); + REQUIRE_NOTHROW(btree["a"] = 6); + REQUIRE_NOTHROW(btree["bbb"] = 9); + REQUIRE_NOTHROW(btree["asdf"] = 8); + REQUIRE_NOTHROW(btree["asdf"] = 333); + REQUIRE(btree["asdf"] == 333); - auto [btree4, btree5] = fc::split(std::move(btree3), 200); - for (int i = 0; i < 200; ++i) { - if (!btree4.contains(i)) { - std::cout << "Split failed\n"; - } - } - if (btree5.contains(200)) { - std::cout << "Split failed\n"; - } - for (int i = 201; i < 300; ++i) { - if (!btree5.contains(i)) { - std::cout << "Split failed\n"; - } - } - std::cout << "Multiset split test\n"; + REQUIRE_NOTHROW(btree.emplace("asdfgh", 200)); + REQUIRE(btree["asdfgh"] == 200); +} - fc::BTreeMultiSet btree6; - btree6.insert(0); - btree6.insert(2); - for (int i = 0; i < 100; ++i) { - btree6.insert(1); - } - auto [btree7, btree8] = fc::split(std::move(btree6), 1); - if (btree7.size() != 1 || btree8.size() != 1) { - std::cout << "Split failed: " << btree7.size() << ' ' << btree8.size() - << '\n'; - } +TEST_CASE("Join/Split") { + fc::BTreeSet btree1; + for (int i = 0; i < 100; ++i) { + REQUIRE_NOTHROW(btree1.insert(i)); + } - std::cout << "OK\n"; + fc::BTreeSet btree2; + for (int i = 101; i < 300; ++i) { + REQUIRE_NOTHROW(btree2.insert(i)); } - { - std::cout << "Two arguments join test\n"; - fc::BTreeSet tree1; - for (int i = 0; i < 100; ++i) { - tree1.insert(i); - } - fc::BTreeSet tree2; - for (int i = 100; i < 200; ++i) { - tree2.insert(i); - } - auto tree3 = fc::join(std::move(tree1), std::move(tree2)); - for (int i = 0; i < 200; ++i) { - if (!tree3.contains(i)) { - std::cout << "Join fail\n"; - } - } - std::cout << "OK\n"; + fc::BTreeSet btree3; + + REQUIRE_NOTHROW( + btree3 = fc::join(std::move(btree1), 100, std::move(btree2))); + + for (int i = 0; i < 300; ++i) { + REQUIRE(btree3.contains(i)); } - { - std::cout << "Three arguments split test\n"; - fc::BTreeSet tree1; - for (int i = 0; i < 100; ++i) { - tree1.insert(i); - } - auto [tree2, tree3] = fc::split(std::move(tree1), 10, 80); - if (tree2.size() != 10 || tree3.size() != 19) { - std::cout << "Split fail\n"; - } - std::cout << "OK\n"; + + auto [btree4, btree5] = fc::split(std::move(btree3), 200); + for (int i = 0; i < 200; ++i) { + REQUIRE(btree4.contains(i)); } - { - std::cout << "Multiset erase test\n"; - fc::BTreeMultiSet tree1; - tree1.insert(0); - for (int i = 0; i < 100; ++i) { - tree1.insert(1); - } - tree1.insert(2); + REQUIRE_FALSE(btree5.contains(200)); - tree1.erase(1); + for (int i = 201; i < 300; ++i) { + REQUIRE(btree5.contains(i)); + } +} - if (tree1.size() != 2) { - std::cout << "Mutliset erase failed: " << tree1.size() << '\n'; - } - std::cout << "OK\n"; +TEST_CASE("Multiset split") { + fc::BTreeMultiSet btree6; + REQUIRE_NOTHROW(btree6.insert(0)); + REQUIRE_NOTHROW(btree6.insert(2)); + for (int i = 0; i < 100; ++i) { + REQUIRE_NOTHROW(btree6.insert(1)); } - { - std::cout << "Range insert test\n"; - fc::BTreeSet btree; - btree.insert(1); - btree.insert(10); - - std::vector v{2, 5, 4, 3, 7, 6, 6, 6, 2, 8, 8, 9}; - btree.insert_range(std::move(v)); - - for (int i = 1; i < 10; ++i) { - if (!btree.contains(i)) { - std::cout << "Range insert failed\n"; - } - } - std::cout << "OK\n"; + auto [btree7, btree8] = fc::split(std::move(btree6), 1); + REQUIRE(btree7.size() == 1); + REQUIRE(btree8.size() == 1); +} + +TEST_CASE("Two arguments join") { + fc::BTreeSet tree1; + for (int i = 0; i < 100; ++i) { + REQUIRE_NOTHROW(tree1.insert(i)); } - { - fc::BTreeSet btree; - btree.insert(1); - btree.insert(10); - - std::vector v{2, 5, 4, 3, 7, 6, 6, 6, 2, 8, 8, 9, 10}; - btree.insert_range(std::move(v)); - - for (int i = 1; i < 10; ++i) { - if (!btree.contains(i)) { - std::cout << "Range insert failed\n"; - } - } - std::cout << "OK\n"; + fc::BTreeSet tree2; + for (int i = 100; i < 200; ++i) { + REQUIRE_NOTHROW(tree2.insert(i)); } - { - std::cout << "count() test\n"; - fc::BTreeMultiSet btree2; - btree2.insert(1); - btree2.insert(1); - if (btree2.count(1) != 2 || btree2.count(0) != 0 || btree2.count(2) != 0) { - std::cout << "count() failed\n"; - } - std::cout << "OK\n"; + auto tree3 = fc::join(std::move(tree1), std::move(tree2)); + for (int i = 0; i < 200; ++i) { + REQUIRE(tree3.contains(i)); } } + +TEST_CASE("Three arguments split") { + fc::BTreeSet tree1; + for (int i = 0; i < 100; ++i) { + tree1.insert(i); + } + auto [tree2, tree3] = fc::split(std::move(tree1), 10, 80); + REQUIRE(tree2.size() == 10); + REQUIRE(tree3.size() == 19); +} + +TEST_CASE("Multiset erase") { + fc::BTreeMultiSet tree1; + REQUIRE_NOTHROW(tree1.insert(0)); + for (int i = 0; i < 100; ++i) { + REQUIRE_NOTHROW(tree1.insert(1)); + } + REQUIRE_NOTHROW(tree1.insert(2)); + + REQUIRE_NOTHROW(tree1.erase(1)); + + REQUIRE(tree1.size() == 2); +} + +TEST_CASE("Range insert-1") { + fc::BTreeSet btree; + REQUIRE_NOTHROW(btree.insert(1)); + REQUIRE_NOTHROW(btree.insert(10)); + + std::vector v{2, 5, 4, 3, 7, 6, 6, 6, 2, 8, 8, 9}; + REQUIRE_NOTHROW(btree.insert_range(std::move(v))); + + for (int i = 1; i < 10; ++i) { + REQUIRE(btree.contains(i)); + } +} + +TEST_CASE("Range insert-2") { + fc::BTreeSet btree; + REQUIRE_NOTHROW(btree.insert(1)); + REQUIRE_NOTHROW(btree.insert(10)); + + std::vector v{2, 5, 4, 3, 7, 6, 6, 6, 2, 8, 8, 9, 10}; + REQUIRE_NOTHROW(btree.insert_range(std::move(v))); + + for (int i = 1; i < 10; ++i) { + REQUIRE(btree.contains(i)); + } +} + +TEST_CASE("count()") { + fc::BTreeMultiSet btree2; + REQUIRE_NOTHROW(btree2.insert(1)); + REQUIRE_NOTHROW(btree2.insert(1)); + REQUIRE(btree2.count(1) == 2); + REQUIRE(btree2.count(0) == 0); + REQUIRE(btree2.count(2) == 0); +} diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 0000000..2194253 --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,8 @@ +{ + "name": "btree", + "version-string": "1.0.0", + "license": "Apache-2.0", + "dependencies": [ + "catch2" + ] +} diff --git a/version.h.in b/version.h.in new file mode 100644 index 0000000..724de55 --- /dev/null +++ b/version.h.in @@ -0,0 +1,11 @@ +#ifndef VERSION_H_IN +#define VERSION_H_IN + +#cmakedefine MAJOR "@MAJOR@" +#cmakedefine MINOR "@MINOR@" +#cmakedefine PATCH "@PATCH@" +#cmakedefine COMMITTER_FULLSHA "@COMMITTER_FULLSHA@" +#cmakedefine COMMITTER_SHORTSHA "@COMMITTER_SHORTSHA@" +#cmakedefine COMMITTER_DATE "@COMMITTER_DATE@" + +#endif // VERSION_H_IN