From 7da009204b4e0ce52840616381260b4d9498d1f8 Mon Sep 17 00:00:00 2001 From: Raimo33 Date: Sat, 6 Sep 2025 23:45:57 +0200 Subject: [PATCH 1/4] bench: replace wall-clock timers with cpu-timers where possible This commit improves the reliability of benchmarks by removing some of the influence of other background running processes. This is achieved by using CPU bound clocks that aren't influenced by interrupts, sleeps, blocked I/O, etc. --- src/bench.c | 2 ++ src/bench.h | 54 ++++++++++++++++++++++++++++---------------- src/bench_ecmult.c | 4 +++- src/bench_internal.c | 3 +++ 4 files changed, 42 insertions(+), 21 deletions(-) diff --git a/src/bench.c b/src/bench.c index 8ba7623a0e..43848676fc 100644 --- a/src/bench.c +++ b/src/bench.c @@ -4,6 +4,8 @@ * file COPYING or https://www.opensource.org/licenses/mit-license.php.* ***********************************************************************/ +#define _POSIX_C_SOURCE 199309L /* for clock_gettime() */ + #include #include #include diff --git a/src/bench.h b/src/bench.h index 232fb35fc0..0100c5da8e 100644 --- a/src/bench.h +++ b/src/bench.h @@ -7,30 +7,44 @@ #ifndef SECP256K1_BENCH_H #define SECP256K1_BENCH_H +#if defined(_WIN32) +# include +#else /* POSIX */ +# include +#endif + #include #include #include #include -#if (defined(_MSC_VER) && _MSC_VER >= 1900) -# include -#else -# include -#endif +static int64_t gettime_us(void) { +#if defined(_WIN32) + + LARGE_INTEGER freq, counter; + QueryPerformanceFrequency(&freq); + QueryPerformanceCounter(&counter); + return (int64_t)(counter.QuadPart * 1000000 / freq.QuadPart); + +#else /* POSIX */ + +# if defined(CLOCK_PROCESS_CPUTIME_ID) + /* In theory, CLOCK_PROCESS_CPUTIME_ID is only useful if the process is locked to a core, + * see `man clock_gettime` on Linux. In practice, modern CPUs have synchronized TSCs which + * address this issue, see https://docs.amd.com/r/en-US/ug1586-onload-user/Timer-TSC-Stability . */ + const clockid_t clock_type = CLOCK_PROCESS_CPUTIME_ID; +# elif defined(CLOCK_MONOTONIC) + /* fallback to global timer */ + const clockid_t clock_type = CLOCK_MONOTONIC; +# else + /* fallback to wall-clock timer */ + const clockid_t clock_type = CLOCK_REALTIME; +# endif + + struct timespec ts; + clock_gettime(clock_type, &ts); + return (int64_t)ts.tv_sec * 1000000 + ts.tv_nsec / 1000; -static int64_t gettime_i64(void) { -#if (defined(_MSC_VER) && _MSC_VER >= 1900) - /* C11 way to get wallclock time */ - struct timespec tv; - if (!timespec_get(&tv, TIME_UTC)) { - fputs("timespec_get failed!", stderr); - exit(EXIT_FAILURE); - } - return (int64_t)tv.tv_nsec / 1000 + (int64_t)tv.tv_sec * 1000000LL; -#else - struct timeval tv; - gettimeofday(&tv, NULL); - return (int64_t)tv.tv_usec + (int64_t)tv.tv_sec * 1000000LL; #endif } @@ -105,9 +119,9 @@ static void run_benchmark(char *name, void (*benchmark)(void*, int), void (*setu if (setup != NULL) { setup(data); } - begin = gettime_i64(); + begin = gettime_us(); benchmark(data, iter); - total = gettime_i64() - begin; + total = gettime_us() - begin; if (teardown != NULL) { teardown(data, iter); } diff --git a/src/bench_ecmult.c b/src/bench_ecmult.c index b2bab65d26..23812ab233 100644 --- a/src/bench_ecmult.c +++ b/src/bench_ecmult.c @@ -3,6 +3,9 @@ * Distributed under the MIT software license, see the accompanying * * file COPYING or https://www.opensource.org/licenses/mit-license.php.* ***********************************************************************/ + +#define _POSIX_C_SOURCE 199309L /* for clock_gettime() */ + #include #include @@ -362,7 +365,6 @@ int main(int argc, char **argv) { } secp256k1_ge_set_all_gej_var(data.pubkeys, data.pubkeys_gej, POINTS); - print_output_table_header_row(); /* Initialize offset1 and offset2 */ hash_into_offset(&data, 0); diff --git a/src/bench_internal.c b/src/bench_internal.c index 8688a4dc77..9f47998eba 100644 --- a/src/bench_internal.c +++ b/src/bench_internal.c @@ -3,6 +3,9 @@ * Distributed under the MIT software license, see the accompanying * * file COPYING or https://www.opensource.org/licenses/mit-license.php.* ***********************************************************************/ + +#define _POSIX_C_SOURCE 199309L /* for clock_gettime() */ + #include #include From ce6d39cba519ef3d9817e3562ee37c3c1dc0e8c2 Mon Sep 17 00:00:00 2001 From: Raimo33 Date: Wed, 10 Sep 2025 11:56:03 +0200 Subject: [PATCH 2/4] bench: print clock info --- src/bench.c | 1 + src/bench.h | 8 ++++++++ src/bench_ecmult.c | 1 + src/bench_internal.c | 1 + 4 files changed, 11 insertions(+) diff --git a/src/bench.c b/src/bench.c index 43848676fc..983d2f2f6b 100644 --- a/src/bench.c +++ b/src/bench.c @@ -254,6 +254,7 @@ int main(int argc, char** argv) { data.pubkeylen = 33; CHECK(secp256k1_ec_pubkey_serialize(data.ctx, data.pubkey, &data.pubkeylen, &pubkey, SECP256K1_EC_COMPRESSED) == 1); + print_clock_info(); print_output_table_header_row(); if (d || have_flag(argc, argv, "ecdsa") || have_flag(argc, argv, "verify") || have_flag(argc, argv, "ecdsa_verify")) run_benchmark("ecdsa_verify", bench_verify, NULL, NULL, &data, 10, iters); diff --git a/src/bench.h b/src/bench.h index 0100c5da8e..5553d282f1 100644 --- a/src/bench.h +++ b/src/bench.h @@ -190,6 +190,14 @@ static int get_iters(int default_iters) { } } +static void print_clock_info(void) { +#if defined(CLOCK_PROCESS_CPUTIME_ID) + printf("INFO: Using per-process CPU timer\n\n"); +#else + printf("WARN: Using global timer instead of per-process CPU timer.\n\n"); +#endif +} + static void print_output_table_header_row(void) { char* bench_str = "Benchmark"; /* left justified */ char* min_str = " Min(us) "; /* center alignment */ diff --git a/src/bench_ecmult.c b/src/bench_ecmult.c index 23812ab233..79f414d834 100644 --- a/src/bench_ecmult.c +++ b/src/bench_ecmult.c @@ -365,6 +365,7 @@ int main(int argc, char **argv) { } secp256k1_ge_set_all_gej_var(data.pubkeys, data.pubkeys_gej, POINTS); + print_clock_info(); print_output_table_header_row(); /* Initialize offset1 and offset2 */ hash_into_offset(&data, 0); diff --git a/src/bench_internal.c b/src/bench_internal.c index 9f47998eba..407e45d05c 100644 --- a/src/bench_internal.c +++ b/src/bench_internal.c @@ -401,6 +401,7 @@ int main(int argc, char **argv) { } } + print_clock_info(); print_output_table_header_row(); if (d || have_flag(argc, argv, "scalar") || have_flag(argc, argv, "half")) run_benchmark("scalar_half", bench_scalar_half, bench_setup, NULL, &data, 10, iters*100); From cce014725032a58416237e38aedd2033ba2202f5 Mon Sep 17 00:00:00 2001 From: Raimo33 Date: Wed, 10 Sep 2025 11:57:29 +0200 Subject: [PATCH 3/4] docs: add benchmarking best practices --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 3d3118adf9..24a4132f79 100644 --- a/README.md +++ b/README.md @@ -159,6 +159,8 @@ Benchmark ------------ If configured with `--enable-benchmark` (which is the default), binaries for benchmarking the libsecp256k1 functions will be present in the root directory after the build. +For reliable benchmarks, it is strongly recommended to pin the process to a single CPU core and to disable CPU frequency scaling. + To print the benchmark result to the command line: $ ./bench_name From 9128541044e8f19bdf130070ebafc2de240dc51f Mon Sep 17 00:00:00 2001 From: Raimo33 Date: Sun, 28 Sep 2025 15:49:51 +0200 Subject: [PATCH 4/4] build: add benchmark compatibility with older glibc versions --- src/CMakeLists.txt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fa3b2903eb..3f3ea48bb4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -125,12 +125,17 @@ endif() unset(${PROJECT_NAME}_soversion) if(SECP256K1_BUILD_BENCHMARK) + find_library(RT_LIBRARY rt) + add_library(optional_rt INTERFACE) + if(RT_LIBRARY) + target_link_libraries(optional_rt INTERFACE rt) + endif() add_executable(bench bench.c) - target_link_libraries(bench secp256k1) add_executable(bench_internal bench_internal.c) - target_link_libraries(bench_internal secp256k1_precomputed secp256k1_asm) add_executable(bench_ecmult bench_ecmult.c) - target_link_libraries(bench_ecmult secp256k1_precomputed secp256k1_asm) + target_link_libraries(bench secp256k1 optional_rt) + target_link_libraries(bench_internal secp256k1_precomputed secp256k1_asm optional_rt) + target_link_libraries(bench_ecmult secp256k1_precomputed secp256k1_asm optional_rt) endif() if(SECP256K1_BUILD_TESTS)