diff --git a/CMakeLists.txt b/CMakeLists.txt index a725b3c..3589bcd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,13 +1,13 @@ cmake_minimum_required(VERSION 3.20.0) # RHEL 8 project(benchmarks - VERSION 0.0.0.1 # PROJECT_VERSION_MAJOR, PROJECT_VERSION_MINOR, PROJECT_VERSION_PATCH, and PROJECT_VERSION_TWEAK. + VERSION 0.0.1.0 # PROJECT_VERSION_MAJOR, PROJECT_VERSION_MINOR, PROJECT_VERSION_PATCH, and PROJECT_VERSION_TWEAK. DESCRIPTION "Various benchmarks" HOMEPAGE_URL "https://github.com/gunchev/bench/" LANGUAGES C CXX) set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED TRUE) -set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED TRUE) # set(CMAKE_VERBOSE_MAKEFILE TRUE) # or "cd build; make VERBOSE=1" @@ -18,6 +18,9 @@ if(NOT CMAKE_BUILD_TYPE) "Choose the type of build, options are: Debug DebugCov RelWithDebInfo Release MinSizeRel." FORCE) endif() +include(FetchContent) + + # SOVERSION is ignored if NO_SONAME property is set. # "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}.${PROJECT_VERSION_TWEAK}" set(${PROJECT_NAME}_SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}") @@ -47,19 +50,14 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) # Compiler/linker flags -if(EXISTS "/etc/fedora-release") -set(CMAKE_C_FLAGS "-Wall -Wextra") -set(CMAKE_CXX_FLAGS "-Wall -Wextra") -else() set(CMAKE_C_FLAGS "-Wall -Wextra -Werror") set(CMAKE_CXX_FLAGS "-Wall -Wextra -Werror") -endif() + +# https://gcc.gnu.org/onlinedocs/gcc/Template-Instantiation.html#Template-Instantiation include(CheckCCompilerFlag) include(CheckCXXCompilerFlag) -# https://gcc.gnu.org/onlinedocs/gcc/Template-Instantiation.html#Template-Instantiation - set(CMAKE_REQUIRED_QUIET_OLD ${CMAKE_REQUIRED_QUIET}) set(CMAKE_REQUIRED_QUIET ON) macro(CheckAndAddFlag flag) @@ -120,20 +118,24 @@ set(CMAKE_C_FLAGS_MINSIZEREL "-Os -ggdb3 -D_FORTIFY_SOURCE=2 -DN set(CMAKE_CXX_FLAGS_MINSIZEREL "-Os -ggdb3 -D_FORTIFY_SOURCE=2 -DNDEBUG") +# Google benchmark +find_package(benchmark 1.9.4) +if (NOT benchmark_FOUND) + message(STATUS "Google Benchmark not found, fetching it...") -# Enable testing? -include(FindPkgConfig) -pkg_check_modules(CPPUNIT cppunit>=1.14.0) # REQUIRED -if(CPPUNIT_FOUND) - enable_testing() - include(CTest) - find_program(CTEST_COVERAGE_COMMAND NAMES gcov) - find_program(CTEST_MEMORYCHECK_COMMAND NAMES valgrind) - add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND}) + FetchContent_Declare( + benchmark + GIT_REPOSITORY https://github.com/google/benchmark.git + GIT_TAG v1.9.4 + ) + + # Disable benchmark's own tests + set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "" FORCE) + set(BENCHMARK_ENABLE_GTEST_TESTS OFF CACHE BOOL "" FORCE) + + FetchContent_MakeAvailable(benchmark) endif() - - -find_package(benchmark REQUIRED) +message(STATUS "Found Google benchmark version: ${benchmark_VERSION}") # Threads library # set(THREADS_PREFER_PTHREAD_FLAG TRUE) @@ -145,8 +147,29 @@ find_package(benchmark REQUIRED) # pkg_check_modules(HIREDIS REQUIRED hiredis>=1.0.2) +# Enable testing? +#include(FindPkgConfig) +#pkg_check_modules(CPPUNIT cppunit>=1.14.0) # REQUIRED +# set(GTEST_MIN_VERSION 1.13.0) +# find_package(GTest ${GTEST_MIN_VERSION} QUIET) +# if (NOT GTest_FOUND) +# message(STATUS "GoogleTest not found, fetching it...") +# FetchContent_Declare( +# googletest +# GIT_REPOSITORY https://github.com/google/googletest.git +# GIT_TAG v1.14.0 +# ) +# # Don't install or build gtest's own tests +# set(INSTALL_GTEST OFF CACHE BOOL "" FORCE) +# set(BUILD_GMOCK OFF CACHE BOOL "" FORCE) + +# FetchContent_MakeAvailable(googletest) +# endif() + + # include_directories(src) add_executable(transform src/transform.cpp) target_link_libraries(transform PRIVATE benchmark::benchmark) # fmt::fmt Threads::Threads) +target_compile_features(transform PRIVATE cxx_std_17) install(TARGETS transform DESTINATION bin) diff --git a/README.md b/README.md index 5c4d00d..4108928 100644 --- a/README.md +++ b/README.md @@ -4,4 +4,59 @@ Various C++ benchmarks: - transform.cpp - benchmark std::transform vs loop and custom lowercase and tolower -![](bench_lowercase.png) + +--- +```shell + $ make releasec # clang++ 21.1.8 + $ ./build/releasec/transform +``` + + 2026-02-07T15:43:00+02:00 + Running ./build/releasec/transform + Run on (32 X 5187.5 MHz CPU s) + CPU Caches: + L1 Data 48 KiB (x16) + L1 Instruction 32 KiB (x16) + L2 Unified 1024 KiB (x16) + L3 Unified 32768 KiB (x2) + Load Average: 0.58, 0.53, 0.60 + ***WARNING*** CPU scaling is enabled, the benchmark real time measurements may be noisy and will incur extra overhead. + ***WARNING*** ASLR is enabled, the results may have unreproducible noise in them. + ---------------------------------------------------------------------- + Benchmark Time CPU Iterations + ---------------------------------------------------------------------- + algorithm_lowercase_my 17.8 ns 17.7 ns 39664595 + loop_lowercase_my 31.9 ns 31.9 ns 22028280 + algorithm_lowercase_toupper 79.3 ns 79.2 ns 8838048 + loop_lowercase_toupper 69.7 ns 69.5 ns 9977740 +--- +```shell + $ make release # g++ 15.2.1 + $ ./build/release/transform +``` + + 2026-02-07T15:43:03+02:00 + Running ./build/release/transform + Run on (32 X 5187.5 MHz CPU s) + CPU Caches: + L1 Data 48 KiB (x16) + L1 Instruction 32 KiB (x16) + L2 Unified 1024 KiB (x16) + L3 Unified 32768 KiB (x2) + Load Average: 0.61, 0.54, 0.60 + ***WARNING*** CPU scaling is enabled, the benchmark real time measurements may be noisy and will incur extra overhead. + ***WARNING*** ASLR is enabled, the results may have unreproducible noise in them. + ---------------------------------------------------------------------- + Benchmark Time CPU Iterations + ---------------------------------------------------------------------- + algorithm_lowercase_my 17.7 ns 17.7 ns 33607332 + loop_lowercase_my 28.2 ns 28.2 ns 24781013 + algorithm_lowercase_toupper 77.1 ns 77.0 ns 9116098 + loop_lowercase_toupper 65.5 ns 65.4 ns 10687636 +--- + +gcc13 + +clang17 + +--- diff --git a/bench_lowercase.png b/bench_lowercase.png deleted file mode 100644 index 1ef7ba2..0000000 Binary files a/bench_lowercase.png and /dev/null differ diff --git a/bench_lowercase_clang17_cpp23.png b/bench_lowercase_clang17_cpp23.png new file mode 100644 index 0000000..6f85297 Binary files /dev/null and b/bench_lowercase_clang17_cpp23.png differ diff --git a/bench_lowercase_gcc13_cpp23.png b/bench_lowercase_gcc13_cpp23.png new file mode 100644 index 0000000..323a282 Binary files /dev/null and b/bench_lowercase_gcc13_cpp23.png differ diff --git a/src/transform.cpp b/src/transform.cpp index bbe8159..6699b6c 100755 --- a/src/transform.cpp +++ b/src/transform.cpp @@ -3,13 +3,16 @@ #include #include #include +#include const char* data = "Hello world, to be or not to be a long string, that is the question!"; static void algorithm_lowercase_my(benchmark::State& state) { + std::string name; + name.reserve(strlen(data)); for (auto _ : state) { // Code inside this loop is measured repeatedly - std::string name = data; + name = data; std::transform(name.cbegin(), name.cend(), name.begin(), [](char c) noexcept { return (c >= 'a' && c <= 'z') ? c = c - 'a' + 'A' : c; }); @@ -19,8 +22,10 @@ static void algorithm_lowercase_my(benchmark::State& state) { BENCHMARK(algorithm_lowercase_my); // Register the function as a benchmark static void loop_lowercase_my(benchmark::State& state) { + std::string name; + name.reserve(strlen(data)); for (auto _ : state) { // Code before the loop is not measured - std::string name = data; + name = data; for (char& c : name) { if (c >= 'a' && c <= 'z') { c = c - 'a' + 'A'; @@ -33,8 +38,10 @@ BENCHMARK(loop_lowercase_my); // Register the function as a benchmark static void algorithm_lowercase_toupper(benchmark::State& state) { + std::string name; + name.reserve(strlen(data)); for (auto _ : state) { // Code inside this loop is measured repeatedly - std::string name = data; + name = data; std::transform(name.cbegin(), name.cend(), name.begin(), [](char c) noexcept { return std::toupper(static_cast(c)); }); @@ -44,6 +51,21 @@ static void algorithm_lowercase_toupper(benchmark::State& state) { BENCHMARK(algorithm_lowercase_toupper); // Register the function as a benchmark +static void loop_lowercase_toupper(benchmark::State& state) { + std::string name; + name.reserve(strlen(data)); + for (auto _ : state) { // Code inside this loop is measured repeatedly + name = data; + for (char& c : name) { + if (c >= 'a' && c <= 'z') { + c = static_cast(std::toupper(c)); + } + } + benchmark::DoNotOptimize(name); // Make sure the variable is not optimized away by compiler + } +} +BENCHMARK(loop_lowercase_toupper); // Register the function as a benchmark + int main(int argc, char** argv) { benchmark::Initialize(&argc, argv); if (benchmark::ReportUnrecognizedArguments(argc, argv)) { @@ -53,16 +75,3 @@ int main(int argc, char** argv) { benchmark::RunSpecifiedBenchmarks(); return EX_OK; } - -static void loop_lowercase_toupper(benchmark::State& state) { - for (auto _ : state) { // Code inside this loop is measured repeatedly - std::string name = data; - for (char& c : name) { - if (c >= 'a' && c <= 'z') { - c = static_cast(std::toupper(c)); - } - } - benchmark::DoNotOptimize(name); // Make sure the variable is not optimized away by compiler - } -} -BENCHMARK(loop_lowercase_toupper); // Register the function as a benchmark