From e5b892baed478513adcb6425773cae1eda033057 Mon Sep 17 00:00:00 2001 From: scivision <10931741+scivision@users.noreply.github.com> Date: Tue, 25 Jun 2024 19:58:33 -0400 Subject: [PATCH] Correct CMake script. Improve C++ language feature selection (#243) * cmake: correct minimum version the previous script would fail with CMake < 3.13 anyway. * cmake: canonical way to set C++ options * ci: use modern CMake syntax * doc: modern CMake syntax * most compilers have C++17 fallthrough * correct logic for [[noreturn]] --------- Co-authored-by: scivision --- .github/workflows/linux.yml | 26 ++++++++++---- .github/workflows/macos.yml | 26 ++++++++++---- .github/workflows/windows.yml | 26 +++++++++----- CMakeLists.txt | 42 +++++++++++------------ README.md | 60 +++++++++++++++++++-------------- parallel_hashmap/phmap_config.h | 25 ++++++++------ 6 files changed, 123 insertions(+), 82 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 22ef267..d9939bb 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -14,12 +14,24 @@ jobs: compiler: [g++, clang++] flags: [-std=c++11, -std=c++17] optimize: [-O2] + + env: + CXX: ${{ matrix.compiler }} + CXXFLAGS: ${{ matrix.flags }} ${{ matrix.optimize }} + CTEST_OUTPUT_ON_FAILURE: 1 + CTEST_NO_TESTS_ACTION: error + CTEST_PARALLEL_LEVEL: 0 + CMAKE_BUILD_PARALLEL_LEVEL: 4 + steps: - name: Checkout - uses: actions/checkout@v2.0.0 - - name: Build and test - env: - CXX: ${{ matrix.compiler }} - CXXFLAGS: ${{ matrix.flags }} ${{ matrix.optimize }} - run: | - mkdir build && cd build && cmake -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON -DCMAKE_BUILD_TYPE=Release .. && cmake --build . && make test + uses: actions/checkout@v4 + + - name: CMake configure + run: cmake -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON -DCMAKE_BUILD_TYPE=Release -B build + + - name: CMake build + run: cmake --build build + + - name: CMake test + run: ctest --test-dir build diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 7d9db26..536b67f 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -14,12 +14,24 @@ jobs: compiler: [g++, clang++] flags: [-std=c++11, -std=c++17] optimize: [-O2] + + env: + CXX: ${{ matrix.compiler }} + CXXFLAGS: ${{ matrix.flags }} ${{ matrix.optimize }} + CTEST_OUTPUT_ON_FAILURE: 1 + CTEST_NO_TESTS_ACTION: error + CTEST_PARALLEL_LEVEL: 0 + CMAKE_BUILD_PARALLEL_LEVEL: 4 + steps: - name: Checkout - uses: actions/checkout@v2.0.0 - - name: Build and test - env: - CXX: ${{ matrix.compiler }} - CXXFLAGS: ${{ matrix.flags }} ${{ matrix.optimize }} - run: | - mkdir build && cd build && cmake -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON -DCMAKE_BUILD_TYPE=Release .. && cmake --build . && make test + uses: actions/checkout@v4 + + - name: CMake configure + run: cmake -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON -DCMAKE_BUILD_TYPE=Release -B build + + - name: CMake build + run: cmake --build build + + - name: CMake test + run: ctest --test-dir build diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index f86ecc8..efab634 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -13,13 +13,23 @@ jobs: os: [windows-latest] flags: ["/std:c++11", "/std:c++latest"] optimize: [/O2] + + env: + CXXFLAGS: ${{ matrix.flags }} ${{ matrix.optimize }} + CTEST_OUTPUT_ON_FAILURE: 1 + CTEST_NO_TESTS_ACTION: error + CTEST_PARALLEL_LEVEL: 0 + CMAKE_BUILD_PARALLEL_LEVEL: 4 + steps: - name: Checkout - uses: actions/checkout@v2.0.0 - - name: Build and test - env: - CXX: ${{ matrix.compiler }} - CXXFLAGS: ${{ matrix.flags }} ${{ matrix.optimize }} - CTEST_OUTPUT_ON_FAILURE: 1 - run: | - cmake -Bbuild -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON -DCMAKE_BUILD_TYPE=Release && cmake --build build --target ALL_BUILD && cmake --build build --target RUN_TESTS + uses: actions/checkout@v4 + + - name: CMake configure + run: cmake -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON -B build + + - name: CMake build + run: cmake --build build --config Release + + - name: CMake test + run: ctest --test-dir build -C Release diff --git a/CMakeLists.txt b/CMakeLists.txt index c3e207d..de3b85b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,15 +17,12 @@ #]===================================================================] -cmake_minimum_required(VERSION 3.8) +cmake_minimum_required(VERSION 3.13) list (APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") include(DetectVersion) -cmake_policy(SET CMP0048 NEW) ## set VERSION as documented by the project() command. -cmake_policy(SET CMP0076 NEW) ## accept new policy - if(NOT CMAKE_CXX_STANDARD) set(CMAKE_CXX_STANDARD 11) ## compile with C++11 support endif() @@ -48,14 +45,14 @@ option(PHMAP_INSTALL "Enable installation" ${PHMAP_MASTER_PROJECT}) set(PHMAP_DIR parallel_hashmap) -set(PHMAP_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/${PHMAP_DIR}/phmap.h - ${CMAKE_CURRENT_SOURCE_DIR}/${PHMAP_DIR}/phmap_base.h - ${CMAKE_CURRENT_SOURCE_DIR}/${PHMAP_DIR}/phmap_bits.h +set(PHMAP_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/${PHMAP_DIR}/phmap.h + ${CMAKE_CURRENT_SOURCE_DIR}/${PHMAP_DIR}/phmap_base.h + ${CMAKE_CURRENT_SOURCE_DIR}/${PHMAP_DIR}/phmap_bits.h ${CMAKE_CURRENT_SOURCE_DIR}/${PHMAP_DIR}/phmap_config.h - ${CMAKE_CURRENT_SOURCE_DIR}/${PHMAP_DIR}/phmap_dump.h - ${CMAKE_CURRENT_SOURCE_DIR}/${PHMAP_DIR}/phmap_fwd_decl.h - ${CMAKE_CURRENT_SOURCE_DIR}/${PHMAP_DIR}/phmap_utils.h - ${CMAKE_CURRENT_SOURCE_DIR}/${PHMAP_DIR}/meminfo.h + ${CMAKE_CURRENT_SOURCE_DIR}/${PHMAP_DIR}/phmap_dump.h + ${CMAKE_CURRENT_SOURCE_DIR}/${PHMAP_DIR}/phmap_fwd_decl.h + ${CMAKE_CURRENT_SOURCE_DIR}/${PHMAP_DIR}/phmap_utils.h + ${CMAKE_CURRENT_SOURCE_DIR}/${PHMAP_DIR}/meminfo.h ${CMAKE_CURRENT_SOURCE_DIR}/${PHMAP_DIR}/btree.h) include(helpers) @@ -72,14 +69,14 @@ target_include_directories( if(PHMAP_INSTALL) include(GNUInstallDirs) include(CMakePackageConfigHelpers) - + install( DIRECTORY ${PROJECT_SOURCE_DIR}/${PHMAP_DIR}/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PHMAP_DIR}) - + install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME}-targets) - + export(EXPORT ${PROJECT_NAME}-targets FILE "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Targets.cmake") endif() @@ -89,7 +86,7 @@ option(PHMAP_BUILD_TESTS "Whether or not to build the tests" ${PHMAP_MASTE option(PHMAP_BUILD_EXAMPLES "Whether or not to build the examples" ${PHMAP_MASTER_PROJECT}) if(MSVC) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj") + add_compile_options("$<$:/bigobj>") endif() if (PHMAP_BUILD_TESTS OR PHMAP_BUILD_EXAMPLES) @@ -100,7 +97,7 @@ if (PHMAP_BUILD_TESTS) if (NOT PHMAP_GTEST_LIBS) include(cmake/DownloadGTest.cmake) - + check_target(gtest) check_target(gtest_main) check_target(gmock) @@ -169,12 +166,12 @@ endif() if (PHMAP_BUILD_EXAMPLES) if(NOT MSVC) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pedantic -Wall -Wextra -Wcast-align -Wcast-qual -Wdisabled-optimization -Winit-self -Wlogical-op -Wmissing-include-dirs -Woverloaded-virtual -Wredundant-decls -Wshadow -Wstrict-null-sentinel -Wswitch-default -Wno-unused") + add_compile_options("$<$:-pedantic;-Wall;-Wextra;-Wcast-align;-Wcast-qual;-Wdisabled-optimization;-Winit-self;-Wlogical-op;-Wmissing-include-dirs;-Woverloaded-virtual;-Wredundant-decls;-Wshadow;-Wstrict-null-sentinel;-Wswitch-default;-Wno-unused>") if (NOT CMAKE_COMPILER_IS_GNUCC OR CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 5.0) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unknown-warning-option -Wno-gnu-zero-variadic-macro-arguments") + add_compile_options("$<$:-Wno-unknown-warning-option;-Wno-gnu-zero-variadic-macro-arguments>") endif() else() - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4 /Zc:__cplusplus") + add_compile_options("$<$:/W4;/Zc:__cplusplus>") endif() set(THREADS_PREFER_PTHREAD_FLAG ON) @@ -203,8 +200,8 @@ if (PHMAP_BUILD_EXAMPLES) add_executable(ex_p_bench examples/p_bench.cc phmap.natvis) #set(Boost_INCLUDE_DIR /home/greg/dev/boost_1_82_0) # if boost installed in non-standard location - set(Boost_USE_STATIC_LIBS OFF) - set(Boost_USE_MULTITHREADED ON) + set(Boost_USE_STATIC_LIBS OFF) + set(Boost_USE_MULTITHREADED ON) set(Boost_USE_STATIC_RUNTIME OFF) # llil4map.cc - see https://www.perlmonks.com/?node_id=11149643 @@ -223,8 +220,7 @@ if (PHMAP_BUILD_EXAMPLES) target_compile_options(ex_llil4map PRIVATE "${OpenMP_CXX_FLAGS}") file(COPY examples/llil_utils DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") endif() - + target_link_libraries(ex_knucleotide Threads::Threads) target_link_libraries(ex_bench Threads::Threads) endif() - diff --git a/README.md b/README.md index d5adae4..3504d10 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ - + -# The Parallel Hashmap +# The Parallel Hashmap -[![License: Apache-2.0](https://img.shields.io/badge/License-Apache-yellow.svg)](https://opensource.org/licenses/Apache-2.0) [![Linux](https://github.com/greg7mdp/parallel-hashmap/actions/workflows/linux.yml/badge.svg)](https://github.com/greg7mdp/parallel-hashmap/actions/workflows/linux.yml) [![MacOS](https://github.com/greg7mdp/parallel-hashmap/actions/workflows/macos.yml/badge.svg)](https://github.com/greg7mdp/parallel-hashmap/actions/workflows/macos.yml) [![Windows](https://github.com/greg7mdp/parallel-hashmap/actions/workflows/windows.yml/badge.svg)](https://github.com/greg7mdp/parallel-hashmap/actions/workflows/windows.yml) +[![License: Apache-2.0](https://img.shields.io/badge/License-Apache-yellow.svg)](https://opensource.org/licenses/Apache-2.0) [![Linux](https://github.com/greg7mdp/parallel-hashmap/actions/workflows/linux.yml/badge.svg)](https://github.com/greg7mdp/parallel-hashmap/actions/workflows/linux.yml) [![MacOS](https://github.com/greg7mdp/parallel-hashmap/actions/workflows/macos.yml/badge.svg)](https://github.com/greg7mdp/parallel-hashmap/actions/workflows/macos.yml) [![Windows](https://github.com/greg7mdp/parallel-hashmap/actions/workflows/windows.yml/badge.svg)](https://github.com/greg7mdp/parallel-hashmap/actions/workflows/windows.yml) ## Overview -This repository aims to provide a set of excellent **hash map** implementations, as well as a **btree** alternative to std::map and std::set, with the following characteristics: +This repository aims to provide a set of excellent **hash map** implementations, as well as a **btree** alternative to std::map and std::set, with the following characteristics: - **Header only**: nothing to build, just copy the `parallel_hashmap` directory to your project and you are good to go. @@ -48,7 +48,15 @@ Copy the parallel_hashmap directory to your project. Update your include path. T If you are using Visual Studio, you probably want to add `phmap.natvis` to your projects. This will allow for a clear display of the hash table contents in the debugger. -> A cmake configuration files (CMakeLists.txt) is provided for building the tests and examples. Command for building and running the tests is: `mkdir build && cd build && cmake -DPHMAP_BUILD_TESTS=ON -DPHMAP_BUILD_EXAMPLES=ON .. && cmake --build . && make test` +> A cmake configuration files (CMakeLists.txt) is provided for building the tests and examples. Command for building and running the tests is: + +```sh +cmake -DPHMAP_BUILD_TESTS=ON -DPHMAP_BUILD_EXAMPLES=ON -B build + +cmake --build build + +ctest --test-dir build +``` ## Example @@ -58,27 +66,27 @@ If you are using Visual Studio, you probably want to add `phmap.natvis` to your #include using phmap::flat_hash_map; - + int main() { // Create an unordered_map of three strings (that map to strings) - flat_hash_map email = + flat_hash_map email = { { "tom", "tom@gmail.com"}, { "jeff", "jk@gmail.com"}, { "jim", "jimg@microsoft.com"} }; - - // Iterate and print keys and values - for (const auto& n : email) + + // Iterate and print keys and values + for (const auto& n : email) std::cout << n.first << "'s email is: " << n.second << "\n"; - + // Add a new entry email["bill"] = "bg@whatever.com"; - + // and print it std::cout << "bill's email is: " << email["bill"] << "\n"; - + return 0; } ``` @@ -115,8 +123,8 @@ The full types with template parameters can be found in the [parallel_hashmap/ph - The `parallel` hash maps are preferred when you have a few hash maps that will store a very large number of values. The `non-parallel` hash maps are preferred if you have a large number of hash maps, each storing a relatively small number of values. -- The benefits of the `parallel` hash maps are: - a. reduced peak memory usage (when resizing), and +- The benefits of the `parallel` hash maps are: + a. reduced peak memory usage (when resizing), and b. multithreading support (and inherent internal parallelism) **Key decision points for btree containers:** @@ -139,7 +147,7 @@ When an ordering is not needed, a hash container is typically a better choice th - The Abseil hash tables internally randomize a hash seed, so that the table iteration order is non-deterministic. This can be useful to prevent *Denial Of Service* attacks when a hash table is used for a customer facing web service, but it can make debugging more difficult. The *phmap* hashmaps by default do **not** implement this randomization, but it can be enabled by adding `#define PHMAP_NON_DETERMINISTIC 1` before including the header `phmap.h` (as is done in raw_hash_set_test.cc). -- Unlike the Abseil hash maps, we do an internal mixing of the hash value provided. This prevents serious degradation of the hash table performance when the hash function provided by the user has poor entropy distribution. The cost in performance is very minimal, and this helps provide reliable performance even with *imperfect* hash functions. +- Unlike the Abseil hash maps, we do an internal mixing of the hash value provided. This prevents serious degradation of the hash table performance when the hash function provided by the user has poor entropy distribution. The cost in performance is very minimal, and this helps provide reliable performance even with *imperfect* hash functions. ## Memory usage @@ -189,10 +197,10 @@ In order to use a flat_hash_set or flat_hash_map, a hash function should be prov - Provide a hash functor via the HashFcn template parameter -- As with boost, you may add a `hash_value()` friend function in your class. +- As with boost, you may add a `hash_value()` friend function in your class. For example: - + ```c++ #include // minimal header providing phmap::HashState() #include @@ -201,8 +209,8 @@ using std::string; struct Person { bool operator==(const Person &o) const - { - return _first == o._first && _last == o._last && _age == o._age; + { + return _first == o._first && _last == o._last && _age == o._age; } friend size_t hash_value(const Person &p) @@ -230,8 +238,8 @@ using std::string; struct Person { bool operator==(const Person &o) const - { - return _first == o._first && _last == o._last && _age == o._age; + { + return _first == o._first && _last == o._last && _age == o._age; } string _first; @@ -253,7 +261,7 @@ namespace std } ``` -The `std::hash` specialization for `Person` combines the hash values for both first and last name and age, using the convenient phmap::HashState() function, and returns the combined hash value. +The `std::hash` specialization for `Person` combines the hash values for both first and last name and age, using the convenient phmap::HashState() function, and returns the combined hash value. ### file "main.cpp" @@ -268,7 +276,7 @@ int main() // As we have defined a specialization of std::hash() for Person, // we can now create sparse_hash_set or sparse_hash_map of Persons // ---------------------------------------------------------------- - phmap::flat_hash_set persons = + phmap::flat_hash_set persons = { { "John", "Mitchell", 35 }, { "Jane", "Smith", 32 }, { "Jane", "Smith", 30 }, @@ -287,7 +295,7 @@ Parallel Hashmap containers follow the thread safety rules of the Standard C++ l - A single phmap hash table is thread safe for reading from multiple threads. For example, given a hash table A, it is safe to read A from thread 1 and from thread 2 simultaneously. -- If a single hash table is being written to by one thread, then all reads and writes to that hash table on the same or other threads must be protected. For example, given a hash table A, if thread 1 is writing to A, then thread 2 must be prevented from reading from or writing to A. +- If a single hash table is being written to by one thread, then all reads and writes to that hash table on the same or other threads must be protected. For example, given a hash table A, if thread 1 is writing to A, then thread 2 must be prevented from reading from or writing to A. - It is safe to read and write to one instance of a type even if another thread is reading or writing to a different instance of the same type. For example, given hash tables A and B of the same type, it is safe if A is being written in thread 1 and B is being read in thread 2. @@ -304,4 +312,4 @@ While C++ is the native language of the Parallel Hashmap, we welcome bindings ma ## Acknowledgements -Many thanks to the Abseil developers for implementing the swiss table and btree data structures (see [abseil-cpp](https://github.com/abseil/abseil-cpp)) upon which this work is based, and to Google for releasing it as open-source. +Many thanks to the Abseil developers for implementing the swiss table and btree data structures (see [abseil-cpp](https://github.com/abseil/abseil-cpp)) upon which this work is based, and to Google for releasing it as open-source. diff --git a/parallel_hashmap/phmap_config.h b/parallel_hashmap/phmap_config.h index a871ba0..77494d4 100644 --- a/parallel_hashmap/phmap_config.h +++ b/parallel_hashmap/phmap_config.h @@ -18,7 +18,7 @@ // // Includes work from abseil-cpp (https://github.com/abseil/abseil-cpp) // with modifications. -// +// // Copyright 2018 The Abseil Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -81,14 +81,14 @@ #error "phmap requires __apple_build_version__ of 4211165 or higher." #endif -// Enforce C++11 as the minimum. +// Enforce C++11 as the minimum. #if defined(__cplusplus) && !defined(_MSC_VER) #if __cplusplus < 201103L #error "C++ versions less than C++11 are not supported." #endif #endif -// We have chosen glibc 2.12 as the minimum +// We have chosen glibc 2.12 as the minimum #if defined(__GLIBC__) && defined(__GLIBC_PREREQ) #if !__GLIBC_PREREQ(2, 12) #error "Minimum required version of glibc is 2.12." @@ -103,7 +103,7 @@ #warning "phmap assumes CHAR_BIT == 8." #endif -// phmap currently assumes that an int is 4 bytes. +// phmap currently assumes that an int is 4 bytes. #if INT_MAX < 2147483647 #error "phmap assumes that int is at least 4 bytes. " #endif @@ -176,10 +176,10 @@ #undef PHMAP_HAVE_TLS #undef PHMAP_HAVE_THREAD_LOCAL #endif -#endif +#endif // ------------------------------------------------------------ -// Checks whether the __int128 compiler extension for a 128-bit +// Checks whether the __int128 compiler extension for a 128-bit // integral type is supported. // ------------------------------------------------------------ #ifdef PHMAP_HAVE_INTRINSIC_INT128 @@ -197,7 +197,7 @@ #endif // ------------------------------------------------------------------ -// Checks whether the compiler both supports and enables exceptions. +// Checks whether the compiler both supports and enables exceptions. // ------------------------------------------------------------------ #ifdef PHMAP_HAVE_EXCEPTIONS #error PHMAP_HAVE_EXCEPTIONS cannot be directly set. @@ -441,7 +441,9 @@ #define PHMAP_ATTRIBUTE_NONNULL(...) #endif -#if PHMAP_HAVE_ATTRIBUTE(noreturn) || (defined(__GNUC__) && !defined(__clang__)) +#if PHMAP_HAVE_ATTRIBUTE(noreturn) + #define PHMAP_ATTRIBUTE_NORETURN [[noreturn]] +#elif defined(__GNUC__) && !defined(__clang__) #define PHMAP_ATTRIBUTE_NORETURN __attribute__((noreturn)) #elif defined(_MSC_VER) #define PHMAP_ATTRIBUTE_NORETURN __declspec(noreturn) @@ -646,7 +648,7 @@ // ---------------------------------------------------------------------- #if PHMAP_HAVE_CC17 #define PHMAP_IF_CONSTEXPR(expr) if constexpr ((expr)) -#else +#else #define PHMAP_IF_CONSTEXPR(expr) if ((expr)) #endif @@ -680,8 +682,9 @@ namespace macros_internal { } // namespace macros_internal } // namespace phmap -// TODO(zhangxy): Use c++17 standard [[fallthrough]] macro, when supported. -#if defined(__clang__) && defined(__has_warning) +#if PHMAP_HAVE_CPP_ATTRIBUTE(fallthrough) + #define PHMAP_FALLTHROUGH [[fallthrough]] +#elif defined(__clang__) && defined(__has_warning) #if __has_feature(cxx_attributes) && __has_warning("-Wimplicit-fallthrough") #define PHMAP_FALLTHROUGH_INTENDED [[clang::fallthrough]] #endif