From 928a81e8728f102adae3c270599537603ea19ff5 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sat, 10 Jan 2026 13:53:35 +0000 Subject: [PATCH] microbenchmarks: benchmark the oid code At present, the library's oid manipulation functions are slower when built in SHA256 mode than when not. Add some microbenchmarks around the oid compare and copy functions to understand this better. --- CMakeLists.txt | 7 +- benchmarks/CMakeLists.txt | 1 + benchmarks/libgit2/CMakeLists.txt | 78 +++++++++++++++ benchmarks/libgit2/main.c | 31 ++++++ benchmarks/libgit2/oid.c | 151 ++++++++++++++++++++++++++++++ benchmarks/libgit2/precompiled.c | 1 + benchmarks/libgit2/precompiled.h | 2 + tests/libgit2/CMakeLists.txt | 6 +- tests/util/CMakeLists.txt | 6 +- 9 files changed, 280 insertions(+), 3 deletions(-) create mode 100644 benchmarks/CMakeLists.txt create mode 100644 benchmarks/libgit2/CMakeLists.txt create mode 100644 benchmarks/libgit2/main.c create mode 100644 benchmarks/libgit2/oid.c create mode 100644 benchmarks/libgit2/precompiled.c create mode 100644 benchmarks/libgit2/precompiled.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 335901d1f..3e9747575 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,8 @@ option(EXPERIMENTAL_SHA256 "Enable experimental SHA256 support (for R&D/test # Optional subsystems option(BUILD_SHARED_LIBS "Build Shared Library (OFF for Static)" ON) -option(BUILD_TESTS "Build Tests using the Clar suite" ON) +option(BUILD_TESTS "Build the test suite" ON) +option(BUILD_BENCHMARKS "Build the benchmark suite" OFF) option(BUILD_CLI "Build the command-line interface" ON) option(BUILD_EXAMPLES "Build library usage example apps" OFF) option(BUILD_FUZZERS "Build the fuzz targets" OFF) @@ -112,6 +113,10 @@ if(BUILD_TESTS) add_subdirectory(tests) endif() +if(BUILD_BENCHMARKS) + add_subdirectory(benchmarks) +endif() + if(BUILD_EXAMPLES) add_subdirectory(examples) endif() diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt new file mode 100644 index 000000000..1b31354e5 --- /dev/null +++ b/benchmarks/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(libgit2) diff --git a/benchmarks/libgit2/CMakeLists.txt b/benchmarks/libgit2/CMakeLists.txt new file mode 100644 index 000000000..e2668b25e --- /dev/null +++ b/benchmarks/libgit2/CMakeLists.txt @@ -0,0 +1,78 @@ +# util: the unit tests for libgit2's utility library + +if(NOT "${CMAKE_VERSION}" VERSION_LESS 3.27) + cmake_policy(SET CMP0148 OLD) +endif() + +set(Python_ADDITIONAL_VERSIONS 3 2.7) +find_package(PythonInterp) + +if(NOT PYTHONINTERP_FOUND) + message(FATAL_ERROR "Could not find a python interpeter, which is needed to build the tests. " + "Make sure python is available, or pass -DBUILD_TESTS=OFF to skip building the tests") +endif() + +set(CLAR_PATH "${PROJECT_SOURCE_DIR}/deps/clar") +set(BENCHMARK_PATH "${CMAKE_CURRENT_SOURCE_DIR}") +add_definitions(-DCLAR_TMPDIR=\"libgit2_bench\") +add_definitions(-DCLAR_WIN32_LONGPATHS) +add_definitions(-DCLAR_HAS_REALPATH) +add_definitions(-D_FILE_OFFSET_BITS=64) + +file(GLOB BENCHMARK_SRC *.c *.h) +list(SORT BENCHMARK_SRC) + +set(CLAR_SRC + "${CLAR_PATH}/clar.c" + "${CLAR_PATH}/clar.h" + "${CLAR_PATH}/clar/fixtures.h" + "${CLAR_PATH}/clar/print.h" + "${CLAR_PATH}/clar/summary.h" + "${CLAR_PATH}/clar/sandbox.h" + "${CLAR_PATH}/clar/fs.h") + +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/clar.suite ${CMAKE_CURRENT_BINARY_DIR}/clar_suite.h + COMMAND ${PYTHON_EXECUTABLE} ${CLAR_PATH}/generate.py -p benchmark -o "${CMAKE_CURRENT_BINARY_DIR}" -f . + DEPENDS ${BENCHMARK_SRC} + WORKING_DIRECTORY ${BENCHMARK_PATH} +) + +set_source_files_properties( + ${CLAR_PATH}/clar.c + PROPERTIES OBJECT_DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/clar.suite) + +add_executable(libgit2_benchmarks ${CLAR_SRC} + ${BENCHMARK_SRC} + $ + ${LIBGIT2_DEPENDENCY_OBJECTS}) + +target_link_libraries(libgit2_benchmarks libgit2package ${LIBGIT2_SYSTEM_LIBS}) +if(NOT MSVC_IDE) + target_link_libraries(libgit2_benchmarks m) +endif() + +ide_split_sources(libgit2_benchmarks) + +target_include_directories(libgit2_benchmarks PRIVATE + "${CLAR_PATH}" + "${libgit2_BINARY_DIR}/src/util" + "${libgit2_BINARY_DIR}/include" + "${libgit2_SOURCE_DIR}/src/util" + "${libgit2_SOURCE_DIR}/include" + "${CMAKE_CURRENT_BINARY_DIR}" + "${LIBGIT2_DEPENDENCY_INCLUDES}" + "${LIBGIT2_SYSTEM_INCLUDES}") + +# +# Old versions of gcc require us to declare our test functions; don't do +# this on newer compilers to avoid unnecessary recompilation. +# +if(CMAKE_COMPILER_IS_GNUCC AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0) + target_compile_options(libgit2_benchmarks PRIVATE -include "clar_suite.h") +endif() + +if(MSVC_IDE) + set_target_properties(libgit2_benchmarks PROPERTIES COMPILE_FLAGS "/Yuprecompiled.h /FIprecompiled.h") + set_source_files_properties("precompiled.c" COMPILE_FLAGS "/Ycprecompiled.h") +endif() diff --git a/benchmarks/libgit2/main.c b/benchmarks/libgit2/main.c new file mode 100644 index 000000000..a8f680f6d --- /dev/null +++ b/benchmarks/libgit2/main.c @@ -0,0 +1,31 @@ +#include + +#include "clar.h" +#include + +#ifdef _WIN32 +int __cdecl main(int argc, char *argv[]) +#else +int main(int argc, char *argv[]) +#endif +{ + int res; + + clar_test_set_mode(CL_TEST_BENCHMARK); + clar_test_init(argc, argv); + + res = git_libgit2_init(); + if (res < 0) { + const git_error *err = git_error_last(); + const char *msg = err ? err->message : "unknown failure"; + fprintf(stderr, "failed to init libgit2: %s\n", msg); + return res; + } + + /* Run the test suite */ + res = clar_test_run(); + + clar_test_shutdown(); + + return res; +} diff --git a/benchmarks/libgit2/oid.c b/benchmarks/libgit2/oid.c new file mode 100644 index 000000000..8e5b157e6 --- /dev/null +++ b/benchmarks/libgit2/oid.c @@ -0,0 +1,151 @@ +#include "clar.h" + +#include +#include +#include + +#include + +#define BENCHMARK_OID_COUNT 256 + +static git_oid sha1_one[BENCHMARK_OID_COUNT]; +static git_oid *sha1_two; + +#ifdef GIT_EXPERIMENTAL_SHA256 +static git_oid sha256_one[BENCHMARK_OID_COUNT]; +static git_oid *sha256_two; +#endif + +static void update_data_to_val(git_oid *out, git_oid_t type, uint32_t val) +{ + unsigned char data[GIT_OID_MAX_SIZE] = {0}; + size_t size; + +#ifdef GIT_EXPERIMENTAL_SHA256 + size = (type == GIT_OID_SHA256) ? GIT_OID_SHA256_SIZE : GIT_OID_SHA1_SIZE; +#else + size = GIT_OID_SHA1_SIZE; + + ((void)(type)); +#endif + + memset(data, 0, GIT_OID_MAX_SIZE); + + data[size - 1] = (unsigned char)(val & 0x000000ff); + data[size - 2] = (unsigned char)((val & 0x0000ff00) >> 8); + data[size - 3] = (unsigned char)((val & 0x00ff0000) >> 16); + data[size - 4] = (unsigned char)((val & 0x00ff0000) >> 24); + +#ifdef GIT_EXPERIMENTAL_SHA256 + cl_assert(git_oid_from_raw(out, data, type) == 0); +#else + cl_assert(git_oid_fromraw(out, data) == 0); +#endif +} + +void benchmark_oid__initialize(void) +{ + uint32_t accum = 0; + size_t i; + + sha1_two = calloc(BENCHMARK_OID_COUNT, sizeof(git_oid)); + cl_assert(sha1_two != NULL); + +#ifdef GIT_EXPERIMENTAL_SHA256 + sha256_two = calloc(BENCHMARK_OID_COUNT, sizeof(git_oid)); + cl_assert(sha256_two != NULL); +#endif + + for (i = 0; i < BENCHMARK_OID_COUNT; i++) { + update_data_to_val(&sha1_one[i], GIT_OID_SHA1, accum++); + update_data_to_val(&sha1_two[i], GIT_OID_SHA1, accum++); + } + +#ifdef GIT_EXPERIMENTAL_SHA256 + for (i = 0; i < BENCHMARK_OID_COUNT; i++) { + update_data_to_val(&sha256_one[i], GIT_OID_SHA256, accum++); + update_data_to_val(&sha256_two[i], GIT_OID_SHA256, accum++); + } +#endif +} + +void benchmark_oid__reset(void) +{ +} + +void benchmark_oid__cleanup(void) +{ + free(sha1_two); + +#ifdef GIT_EXPERIMENTAL_SHA256 + free(sha256_two); +#endif +} + +void benchmark_oid__cmp_sha1(void) +{ + size_t i, j; + + for (i = 0; i < 1024 * 16; i++) + for (j = 0; j < BENCHMARK_OID_COUNT; j++) + git_oid_cmp(&sha1_one[j], &sha1_two[j]); +} + +void benchmark_oid__cmp_sha256(void) +{ +#ifdef GIT_EXPERIMENTAL_SHA256 + size_t i, j; + + for (i = 0; i < 1024 * 16; i++) + for (j = 0; j < BENCHMARK_OID_COUNT; j++) + git_oid_cmp(&sha256_one[j], &sha256_two[j]); +#else + clar__skip(); +#endif +} + +void benchmark_oid__cpy_sha1(void) +{ + git_oid dest; + size_t i, j; + + for (i = 0; i < 1024 * 16; i++) + for (j = 0; j < BENCHMARK_OID_COUNT; j++) + git_oid_cpy(&dest, &sha1_one[j]); +} + +void benchmark_oid__cpy_sha256(void) +{ +#ifdef GIT_EXPERIMENTAL_SHA256 + git_oid dest; + size_t i, j; + + for (i = 0; i < 1024 * 16; i++) + for (j = 0; j < BENCHMARK_OID_COUNT; j++) + git_oid_cpy(&dest, &sha256_one[j]); +#else + clar__skip(); +#endif +} + +void benchmark_oid__zero_sha1(void) +{ + size_t i, j; + + for (i = 0; i < 1024 * 16; i++) + for (j = 0; j < BENCHMARK_OID_COUNT; j++) + git_oid_is_zero(&sha1_one[j]); +} + +void benchmark_oid__zero_sha256(void) +{ +#ifdef GIT_EXPERIMENTAL_SHA256 + size_t i, j; + + for (i = 0; i < 1024 * 16; i++) + for (j = 0; j < BENCHMARK_OID_COUNT; j++) + git_oid_is_zero(&sha256_one[j]); +#else + clar__skip(); +#endif +} diff --git a/benchmarks/libgit2/precompiled.c b/benchmarks/libgit2/precompiled.c new file mode 100644 index 000000000..5f656a45d --- /dev/null +++ b/benchmarks/libgit2/precompiled.c @@ -0,0 +1 @@ +#include "precompiled.h" diff --git a/benchmarks/libgit2/precompiled.h b/benchmarks/libgit2/precompiled.h new file mode 100644 index 000000000..e6b34738d --- /dev/null +++ b/benchmarks/libgit2/precompiled.h @@ -0,0 +1,2 @@ +#include "git2.h" +#include "clar.h" diff --git a/tests/libgit2/CMakeLists.txt b/tests/libgit2/CMakeLists.txt index f8b80eb32..681635a35 100644 --- a/tests/libgit2/CMakeLists.txt +++ b/tests/libgit2/CMakeLists.txt @@ -10,7 +10,7 @@ find_package(PythonInterp) if(NOT PYTHONINTERP_FOUND) message(FATAL_ERROR "Could not find a python interpreter, which is needed to build the tests. " "Make sure python is available, or pass -DBUILD_TESTS=OFF to skip building the tests") -ENDIF() +endif() set(CLAR_PATH "${PROJECT_SOURCE_DIR}/deps/clar") set(CLAR_FIXTURES "${PROJECT_SOURCE_DIR}/tests/resources/") @@ -48,7 +48,11 @@ add_executable(libgit2_tests ${SRC_CLAR} ${SRC_TEST} ${LIBGIT2_OBJECTS}) set_target_properties(libgit2_tests PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) target_include_directories(libgit2_tests PRIVATE ${TEST_INCLUDES} ${LIBGIT2_INCLUDES} ${LIBGIT2_DEPENDENCY_INCLUDES}) target_include_directories(libgit2_tests SYSTEM PRIVATE ${LIBGIT2_SYSTEM_INCLUDES}) + target_link_libraries(libgit2_tests ${LIBGIT2_SYSTEM_LIBS}) +if(NOT MSVC_IDE) + target_link_libraries(libgit2_tests m) +endif() ide_split_sources(libgit2_tests) diff --git a/tests/util/CMakeLists.txt b/tests/util/CMakeLists.txt index 6b44b85fa..ba861aa1c 100644 --- a/tests/util/CMakeLists.txt +++ b/tests/util/CMakeLists.txt @@ -10,7 +10,7 @@ find_package(PythonInterp) if(NOT PYTHONINTERP_FOUND) message(FATAL_ERROR "Could not find a python interpeter, which is needed to build the tests. " "Make sure python is available, or pass -DBUILD_TESTS=OFF to skip building the tests") -ENDIF() +endif() set(CLAR_PATH "${libgit2_SOURCE_DIR}/deps/clar") set(CLAR_FIXTURES "${libgit2_SOURCE_DIR}/tests/resources/") @@ -48,7 +48,11 @@ set_target_properties(util_tests PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${libgit2_B target_include_directories(util_tests PRIVATE ${TEST_INCLUDES} ${LIBGIT2_INCLUDES} ${LIBGIT2_DEPENDENCY_INCLUDES}) target_include_directories(util_tests SYSTEM PRIVATE ${LIBGIT2_SYSTEM_INCLUDES}) + target_link_libraries(util_tests ${LIBGIT2_SYSTEM_LIBS}) +if(NOT MSVC_IDE) + target_link_libraries(util_tests m) +endif() ide_split_sources(util_tests)