Skip to content

Commit a1ea09a

Browse files
committed
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.
1 parent 28f273d commit a1ea09a

File tree

4 files changed

+51
-28
lines changed

4 files changed

+51
-28
lines changed

src/CMakeLists.txt

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -124,13 +124,23 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
124124
endif()
125125
unset(${PROJECT_NAME}_soversion)
126126

127+
if(SECP256K1_BUILD_BENCHMARK OR SECP256K1_BUILD_TESTS)
128+
if(WIN32)
129+
add_library(clock_interface INTERFACE) # It is empty on Windows.
130+
else()
131+
find_package(ClockGettime REQUIRED)
132+
add_library(clock_interface ALIAS POSIX::clock_gettime)
133+
endif()
134+
endif()
135+
136+
127137
if(SECP256K1_BUILD_BENCHMARK)
128138
add_executable(bench bench.c)
129-
target_link_libraries(bench secp256k1)
139+
target_link_libraries(bench secp256k1 clock_interface)
130140
add_executable(bench_internal bench_internal.c)
131-
target_link_libraries(bench_internal secp256k1_precomputed secp256k1_asm)
141+
target_link_libraries(bench_internal secp256k1_precomputed secp256k1_asm clock_interface)
132142
add_executable(bench_ecmult bench_ecmult.c)
133-
target_link_libraries(bench_ecmult secp256k1_precomputed secp256k1_asm)
143+
target_link_libraries(bench_ecmult secp256k1_precomputed secp256k1_asm clock_interface)
134144
endif()
135145

136146
if(SECP256K1_BUILD_TESTS)
@@ -145,13 +155,13 @@ if(SECP256K1_BUILD_TESTS)
145155
endif()
146156

147157
add_executable(noverify_tests tests.c)
148-
target_link_libraries(noverify_tests secp256k1_precomputed secp256k1_asm)
158+
target_link_libraries(noverify_tests secp256k1_precomputed secp256k1_asm clock_interface)
149159
target_compile_definitions(noverify_tests PRIVATE ${TEST_DEFINITIONS})
150160
add_test(NAME secp256k1_noverify_tests COMMAND noverify_tests)
151161
if(NOT CMAKE_BUILD_TYPE STREQUAL "Coverage")
152162
add_executable(tests tests.c)
153163
target_compile_definitions(tests PRIVATE VERIFY ${TEST_DEFINITIONS})
154-
target_link_libraries(tests secp256k1_precomputed secp256k1_asm)
164+
target_link_libraries(tests secp256k1_precomputed secp256k1_asm clock_interface)
155165
add_test(NAME secp256k1_tests COMMAND tests)
156166
endif()
157167
unset(TEST_DEFINITIONS)

src/bench.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,9 @@ static void run_benchmark(char *name, void (*benchmark)(void*, int), void (*setu
8585
if (setup != NULL) {
8686
setup(data);
8787
}
88-
begin = gettime_i64();
88+
begin = gettime_us();
8989
benchmark(data, iter);
90-
total = gettime_i64() - begin;
90+
total = gettime_us() - begin;
9191
if (teardown != NULL) {
9292
teardown(data, iter);
9393
}

src/tests_common.h

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,38 @@
1717

1818
#include <stdint.h>
1919

20-
#if (defined(_MSC_VER) && _MSC_VER >= 1900)
21-
# include <time.h>
22-
#else
23-
# include <sys/time.h>
20+
#if defined(_WIN32)
21+
# include <windows.h>
22+
#else /* POSIX */
23+
# include <time.h>
2424
#endif
2525

26-
static int64_t gettime_i64(void) {
27-
#if (defined(_MSC_VER) && _MSC_VER >= 1900)
28-
/* C11 way to get wallclock time */
29-
struct timespec tv;
30-
if (!timespec_get(&tv, TIME_UTC)) {
31-
fputs("timespec_get failed!", stderr);
32-
exit(EXIT_FAILURE);
33-
}
34-
return (int64_t)tv.tv_nsec / 1000 + (int64_t)tv.tv_sec * 1000000LL;
35-
#else
36-
struct timeval tv;
37-
gettimeofday(&tv, NULL);
38-
return (int64_t)tv.tv_usec + (int64_t)tv.tv_sec * 1000000LL;
26+
static int64_t gettime_us(void) {
27+
#if defined(_WIN32)
28+
29+
LARGE_INTEGER freq, counter;
30+
QueryPerformanceFrequency(&freq);
31+
QueryPerformanceCounter(&counter);
32+
return (int64_t)(counter.QuadPart * 1000000 / freq.QuadPart);
33+
34+
#else /* POSIX */
35+
36+
# if defined(CLOCK_PROCESS_CPUTIME_ID)
37+
/* In theory, CLOCK_PROCESS_CPUTIME_ID is only useful if the process is locked to a core,
38+
* see `man clock_gettime` on Linux. In practice, modern CPUs have synchronized TSCs which
39+
* address this issue, see https://docs.amd.com/r/en-US/ug1586-onload-user/Timer-TSC-Stability . */
40+
const clockid_t clock_type = CLOCK_PROCESS_CPUTIME_ID;
41+
# elif defined(CLOCK_MONOTONIC)
42+
/* fallback to global timer */
43+
const clockid_t clock_type = CLOCK_MONOTONIC;
44+
# else
45+
/* fallback to wall-clock timer */
46+
const clockid_t clock_type = CLOCK_REALTIME;
47+
# endif
48+
49+
struct timespec ts;
50+
clock_gettime(clock_type, &ts);
51+
return (int64_t)ts.tv_sec * 1000000 + ts.tv_nsec / 1000;
3952
#endif
4053
}
4154

src/unit_test.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -260,10 +260,10 @@ static int read_args(int argc, char** argv, int start, struct tf_framework* tf)
260260
}
261261

262262
static void run_test_log(const struct tf_test_entry* t) {
263-
int64_t start_time = gettime_i64();
263+
int64_t start_time = gettime_us();
264264
printf("Running %s..\n", t->name);
265265
t->func();
266-
printf("Test %s PASSED (%.3f sec)\n", t->name, (double)(gettime_i64() - start_time) / 1000000);
266+
printf("Test %s PASSED (%.3f sec)\n", t->name, (double)(gettime_us() - start_time) / 1000000);
267267
}
268268

269269
static void run_test(const struct tf_test_entry* t) { t->func(); }
@@ -416,7 +416,7 @@ static int tf_run(struct tf_framework* tf) {
416416
/* Loop iterator */
417417
int it;
418418
/* Initial test time */
419-
int64_t start_time = gettime_i64();
419+
int64_t start_time = gettime_us();
420420
/* Verify 'tf_init' has been called */
421421
if (!tf->fn_run_test) {
422422
fprintf(stderr, "Error: No test runner set. You must call 'tf_init' first to initialize the framework "
@@ -472,7 +472,7 @@ static int tf_run(struct tf_framework* tf) {
472472
}
473473

474474
/* Print accumulated time */
475-
printf("Total execution time: %.3f seconds\n", (double)(gettime_i64() - start_time) / 1000000);
475+
printf("Total execution time: %.3f seconds\n", (double)(gettime_us() - start_time) / 1000000);
476476
if (tf->fn_teardown && tf->fn_teardown() != 0) return EXIT_FAILURE;
477477

478478
return status;

0 commit comments

Comments
 (0)