Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 66 additions & 40 deletions .github/workflows/ccpp.yml
Original file line number Diff line number Diff line change
@@ -1,46 +1,72 @@
name: C/C++ CI

on: [push]
on:
push:
branches: [master]
pull_request:
workflow_dispatch:

jobs:
build-ubuntu:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v1
- name: Build & Test
run: |
cmake -E remove_directory build
cmake -B build -S . -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="-Werror -O2 -fsanitize=address,undefined"
cmake --build build
cd build
ctest --output-on-failure

build-windows:
permissions:
contents: read

runs-on: windows-latest

steps:
- uses: actions/checkout@v1
- name: Build & Test
run: |
cmake -E remove_directory build
cmake -B build -S .
cmake --build build --config Debug
cd build
ctest -C Debug --output-on-failure

build-macos:

runs-on: macOS-latest
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
build:
name: Build (${{ matrix.name }})
runs-on: ${{ matrix.os }}
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
include:
- name: Linux GCC
os: ubuntu-latest
compiler: g++
flags: -O1 -g -fsanitize=address,undefined -fno-omit-frame-pointer
- name: Linux Clang
os: ubuntu-latest
compiler: clang++
flags: -O1 -g -fsanitize=address,undefined -fno-omit-frame-pointer
- name: macOS Clang
os: macos-latest
compiler: clang++
flags: -O1 -g -fsanitize=address,undefined -fno-omit-frame-pointer
- name: Windows MSVC
os: windows-latest
compiler: cl
flags: ''
env:
CXX: ${{ matrix.compiler }}
HASHMAP_CXX_FLAGS: ${{ matrix.flags }}
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v1
- name: Build & Test
run: |
cmake -E remove_directory build
cmake -B build -S . -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="-Werror -O2 -fsanitize=address,undefined"
cmake --build build
cd build
ctest --output-on-failure
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Configure
run: |
cmake_args=()
if [[ "$RUNNER_OS" != Windows ]]; then
cmake_args+=("-DCMAKE_BUILD_TYPE=Debug")
fi
if [[ -n "$HASHMAP_CXX_FLAGS" ]]; then
cmake_args+=("-DCMAKE_CXX_FLAGS=$HASHMAP_CXX_FLAGS")
fi
cmake -S . -B build \
-DCMAKE_CXX_STANDARD=14 \
-DHASHMAP_WARNINGS_AS_ERRORS=ON \
"${cmake_args[@]}"
- name: Build
run: cmake --build build --config Debug --parallel 2
- name: Test
run: ctest --test-dir build -C Debug --output-on-failure
- name: Check CMake consumers and package relocation
run: >-
cmake -DHASHMAP_SOURCE_DIR="$GITHUB_WORKSPACE"
-DHASHMAP_CHECK_DIR="$RUNNER_TEMP/hashmap-package-check"
-P tests/cmake/check.cmake
161 changes: 93 additions & 68 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,86 +1,111 @@
cmake_minimum_required(VERSION 3.20)

project(HashMap VERSION 1.0 LANGUAGES CXX)
project(HashMap VERSION 1.0.0 LANGUAGES CXX)

add_library(${PROJECT_NAME} INTERFACE)
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})

target_compile_features(${PROJECT_NAME} INTERFACE cxx_std_14)

target_include_directories(${PROJECT_NAME} INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)

# Tests and examples
set(HASHMAP_TOP_LEVEL OFF)
if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
if(MSVC)
add_compile_options(/permissive- /W4)
else()
add_compile_options(-Wall -Wextra -Wpedantic)
endif()

if(NOT WIN32)
find_package(absl)
set(HASHMAP_TOP_LEVEL ON)
endif()

add_executable(HashMapBenchmark src/HashMapBenchmark.cpp)
target_link_libraries(HashMapBenchmark HashMap)
if(absl_FOUND)
target_link_libraries(HashMapBenchmark absl::flat_hash_map)
option(HASHMAP_BUILD_TESTS "Build HashMap tests" ${HASHMAP_TOP_LEVEL})
option(HASHMAP_BUILD_EXAMPLES "Build HashMap examples" ${HASHMAP_TOP_LEVEL})
set(HASHMAP_BENCHMARK_DEFAULT ${HASHMAP_TOP_LEVEL})
if(WIN32)
set(HASHMAP_BENCHMARK_DEFAULT OFF)
endif()
option(HASHMAP_BUILD_BENCHMARKS "Build the POSIX benchmark" ${HASHMAP_BENCHMARK_DEFAULT})
option(HASHMAP_INSTALL "Generate HashMap install rules" ${HASHMAP_TOP_LEVEL})
option(HASHMAP_WARNINGS_AS_ERRORS "Treat HashMap executable warnings as errors" OFF)

add_library(HashMap INTERFACE)
add_library(HashMap::HashMap ALIAS HashMap)
target_compile_features(HashMap INTERFACE cxx_std_14)
target_include_directories(HashMap INTERFACE
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>")

function(hashmap_executable target source)
add_executable(${target} ${source})
target_link_libraries(${target} PRIVATE HashMap::HashMap)
set_target_properties(${target} PROPERTIES CXX_EXTENSIONS OFF)
if(MSVC)
target_compile_options(${target} PRIVATE /permissive- /W4)
if(HASHMAP_WARNINGS_AS_ERRORS)
target_compile_options(${target} PRIVATE /WX)
endif()
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$")
target_compile_options(HashMapBenchmark PRIVATE -msse4.2)
elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(${target} PRIVATE -Wall -Wextra -Wpedantic)
if(HASHMAP_WARNINGS_AS_ERRORS)
target_compile_options(${target} PRIVATE -Werror)
endif()
target_compile_features(HashMapBenchmark PRIVATE cxx_std_17)
endif()
endfunction()

add_executable(HashMapExample src/HashMapExample.cpp)
target_link_libraries(HashMapExample HashMap)

add_executable(HashMapTest src/HashMapTest.cpp)
target_link_libraries(HashMapTest HashMap)

enable_testing()
add_test(NAME HashMapTest COMMAND HashMapTest)
if(TARGET HashMapBenchmark)
add_test(NAME HashMapBenchmarkSmoke COMMAND HashMapBenchmark -t 4 -c 1000 -i 1000)
if(HASHMAP_BUILD_TESTS)
include(CTest)
if(BUILD_TESTING)
hashmap_executable(HashMapTest src/HashMapTest.cpp)
add_test(NAME HashMapTest COMMAND HashMapTest)
endif()
endif()

# Install
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)

write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
COMPATIBILITY SameMajorVersion
)

export(
TARGETS ${PROJECT_NAME}
NAMESPACE ${PROJECT_NAME}::
FILE "${PROJECT_NAME}Config.cmake"
)
if(HASHMAP_BUILD_EXAMPLES)
hashmap_executable(HashMapExample src/HashMapExample.cpp)
endif()

if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
install(
DIRECTORY "include/"
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
if(HASHMAP_BUILD_BENCHMARKS)
if(WIN32)
message(FATAL_ERROR "HashMapBenchmark requires POSIX; disable HASHMAP_BUILD_BENCHMARKS on Windows")
endif()

install(
TARGETS ${PROJECT_NAME}
EXPORT "${PROJECT_NAME}Config"
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
option(HASHMAP_BENCHMARK_WITH_ABSL "Use installed Abseil when available" ON)
option(HASHMAP_BENCHMARK_WITH_SPARSEHASH "Use installed sparsehash when available" ON)
hashmap_executable(HashMapBenchmark src/HashMapBenchmark.cpp)
target_compile_features(HashMapBenchmark PRIVATE cxx_std_17)
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$")
target_compile_options(HashMapBenchmark PRIVATE -msse4.2)
endif()

install(
EXPORT "${PROJECT_NAME}Config"
NAMESPACE ${PROJECT_NAME}::
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}"
)
if(HASHMAP_BENCHMARK_WITH_ABSL)
find_package(absl CONFIG QUIET)
if(TARGET absl::flat_hash_map)
target_link_libraries(HashMapBenchmark PRIVATE absl::flat_hash_map)
target_compile_definitions(HashMapBenchmark PRIVATE HASHMAP_BENCHMARK_HAS_ABSL)
endif()
endif()
if(HASHMAP_BENCHMARK_WITH_SPARSEHASH)
find_path(HASHMAP_SPARSEHASH_INCLUDE_DIR NAMES google/dense_hash_map)
mark_as_advanced(HASHMAP_SPARSEHASH_INCLUDE_DIR)
if(HASHMAP_SPARSEHASH_INCLUDE_DIR)
target_include_directories(HashMapBenchmark SYSTEM PRIVATE "${HASHMAP_SPARSEHASH_INCLUDE_DIR}")
target_compile_definitions(HashMapBenchmark PRIVATE HASHMAP_BENCHMARK_HAS_SPARSEHASH)
endif()
endif()
if(HASHMAP_BUILD_TESTS AND BUILD_TESTING)
add_test(NAME HashMapBenchmarkSmoke COMMAND HashMapBenchmark -t 4 -c 1000 -i 1000)
endif()
endif()

install(
FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}"
)
if(HASHMAP_INSTALL)
set(HASHMAP_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/HashMap")
configure_package_config_file(cmake/HashMapConfig.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/HashMapConfig.cmake"
INSTALL_DESTINATION "${HASHMAP_INSTALL_CMAKEDIR}")
write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/HashMapConfigVersion.cmake"
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion
ARCH_INDEPENDENT)

install(TARGETS HashMap EXPORT HashMapTargets)
install(DIRECTORY include/ DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
install(EXPORT HashMapTargets FILE HashMapTargets.cmake
NAMESPACE HashMap:: DESTINATION "${HASHMAP_INSTALL_CMAKEDIR}")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/HashMapConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/HashMapConfigVersion.cmake"
DESTINATION "${HASHMAP_INSTALL_CMAKEDIR}")
export(EXPORT HashMapTargets FILE "${CMAKE_CURRENT_BINARY_DIR}/HashMapTargets.cmake"
NAMESPACE HashMap::)
endif()
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,52 @@ The rest of the member functions are implemented as for
hm.erase(1);
```

## Build and install

The header-only `HashMap::HashMap` CMake target requires C++14. Configure with
CMake 3.20 or newer:

```sh
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel
ctest --test-dir build --output-on-failure
cmake --install build --prefix /path/to/install
```

Standalone builds enable tests and examples, plus the benchmark on Linux and
macOS. These are disabled by default when included with `add_subdirectory`.

| CMake option | Standalone default | Purpose |
| --- | --- | --- |
| `HASHMAP_BUILD_TESTS` | `ON` | Build tests when `BUILD_TESTING` is also enabled |
| `HASHMAP_BUILD_EXAMPLES` | `ON` | Build the example |
| `HASHMAP_BUILD_BENCHMARKS` | `ON` on POSIX, `OFF` on Windows | Build the benchmark |
| `HASHMAP_INSTALL` | `ON` | Generate installation and package files |
| `HASHMAP_WARNINGS_AS_ERRORS` | `OFF` | Treat warnings in project executables as errors |

To consume an installed package:

```cmake
find_package(HashMap 1 CONFIG REQUIRED)
target_link_libraries(my_app PRIVATE HashMap::HashMap)
```

The same target is available after `add_subdirectory(path/to/HashMap)`.
Installation respects `CMAKE_INSTALL_INCLUDEDIR` and `CMAKE_INSTALL_LIBDIR`.

## Benchmark

A benchmark `src/HashMapBenchmark.cpp` is included with the sources. The
benchmark simulates a delete heavy workload where items are repeatedly inserted
and deleted.

The benchmark uses `std::chrono::steady_clock` and requires no benchmark
framework or vendored dependencies. CMake enables comparisons against installed
Abseil and sparsehash when found. Set `HASHMAP_BENCHMARK_WITH_ABSL=OFF` or
`HASHMAP_BENCHMARK_WITH_SPARSEHASH=OFF` to disable either comparison. Abseil must
provide its CMake package; sparsehash must provide `google/dense_hash_map`.
No dependencies are downloaded during configuration.

The benchmark is built on Linux and macOS; Windows builds the example and tests.
All containers in a run use the same hash: hardware CRC32 on x86-64 and
MurmurHash3's `fmix64` integer mixer on ARM and other architectures. Hash choices
Expand Down
4 changes: 4 additions & 0 deletions cmake/HashMapConfig.cmake.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@PACKAGE_INIT@

include("${CMAKE_CURRENT_LIST_DIR}/HashMapTargets.cmake")
check_required_components(HashMap)
8 changes: 4 additions & 4 deletions src/HashMapBenchmark.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
#include <unistd.h>
#include <unordered_map>

#if __has_include(<google/dense_hash_map>)
#if defined(HASHMAP_BENCHMARK_HAS_SPARSEHASH)
#include <google/dense_hash_map>
#endif

#if __has_include(<absl/container/flat_hash_map.h>)
#if defined(HASHMAP_BENCHMARK_HAS_ABSL)
#include <absl/container/flat_hash_map.h>
#endif

Expand Down Expand Up @@ -178,7 +178,7 @@ int main(int argc, char *argv[]) {
b("HashMap", hm);
}

#if __has_include(<google/dense_hash_map>)
#if defined(HASHMAP_BENCHMARK_HAS_SPARSEHASH)
if (type == -1 || type == 2) {
// Couldn't get it to work with the huge_page_allocator
google::dense_hash_map<key, value, hash> hm(count);
Expand All @@ -188,7 +188,7 @@ int main(int argc, char *argv[]) {
}
#endif

#if __has_include(<absl/container/flat_hash_map.h>)
#if defined(HASHMAP_BENCHMARK_HAS_ABSL)
if (type == -1 || type == 3) {
absl::flat_hash_map<key, value, hash, std::equal_to<>,
huge_page_allocator<std::pair<key, value>>>
Expand Down
Loading
Loading