diff --git a/README.md b/README.md index 3d3118adf9..57bc4cffdd 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 dedicated CPU core and to disable CPU frequency scaling. + To print the benchmark result to the command line: $ ./bench_name diff --git a/src/bench.c b/src/bench.c index 8ba7623a0e..e0f74c788f 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 200112L /* for clock_gettime() */ + #include #include #include @@ -252,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 232fb35fc0..c0573eaf3e 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 wall-clock timer */ + const clockid_t clock_type = CLOCK_MONOTONIC; +# else + /* fallback to less precise 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); } @@ -176,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 Wall-Clock timer, results are highly influenced by other running processes\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 b2bab65d26..2350fd537e 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 200112L /* for clock_gettime() */ + #include #include @@ -362,7 +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 8688a4dc77..cd1efa8bbe 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 200112L /* for clock_gettime() */ + #include #include @@ -398,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);