[PATCH v5] add high-performance clock
Robin Jarry
rjarry at redhat.com
Wed Aug 26 23:28:42 CEST 2026
Morten Brørup, Aug 25, 2026 at 21:06:
> Reading the common clock, gr_clock_ns(), is implemented as
> clock_gettime(CLOCK_MONOTONIC_RAW), which is quite slow, even though the
> kernel exposes it as a vDSO.
>
> For improved performance, especially in the dataplane, introduce a high-
> performance clock, based on snapshotting the common clock
> (clock_gettime(CLOCK_MONOTONIC_RAW)), for use where a snapshot is
> sufficiently accurate.
>
> Immediately before walking the graph in the dataplane worker thread's main
> loop, mark the clock as trusted and update it, so it is safe to use in the
> "process" functions of the graph nodes.
>
> Signed-off-by: Morten Brørup <mb at smartsharesystems.com>
> ---
> v5:
> * More header inclusion cleaning.
> * Rebased.
> v4:
> * Clean up header inclusion order.
> clock.h should only be included by gr_clock.h, and thus
> clock.c should include gr_clock.h instead of clock.h.
> v3:
> * Change gr_clock_ns(), adding a Grout specific implementation using
> the high-performance clock. This significantly reduces the amount of
> required changes inside Grout. (Robin Jarry)
> * Revert upgrade some graph node "process" functions to use
> this clock; they can simply continue using gr_clock_ns().
> * Add an explicit per-thread state variable to mark the clock as trusted,
> effectively enabling/disabling use of the high-performance clock.
> * Remove unused gr_clock_raw() API.
> * Add definition of Grout clock source.
> v2:
> * Eliminate the concept of a client API message required to read Grout's
> clock. (Robin Jarry)
> * Keep gr_clock_ns() as is.
> * For clock source, use gr_clock_ns() instead of rte_rdtsc().
> * Add instrumentation for debug builds.
> * Use __thread instead of RTE_PER_LCORE.
> This aligns with Grout's existing code style.
> ---
> api/gr_clock.h | 26 ++++++-----
> main/clock.c | 52 +++++++++++++++++++++
> main/clock.h | 73 ++++++++++++++++++++++++++++++
> main/meson.build | 1 +
> modules/infra/datapath/main_loop.c | 6 +++
> 5 files changed, 147 insertions(+), 11 deletions(-)
> create mode 100644 main/clock.c
> create mode 100644 main/clock.h
>
> diff --git a/api/gr_clock.h b/api/gr_clock.h
> index d2d98fba..85ab4838 100644
> --- a/api/gr_clock.h
> +++ b/api/gr_clock.h
> @@ -7,27 +7,31 @@
> #include <stdint.h>
> #include <time.h>
>
> +// Clock source.
> +// Must be a high-resolution clock, i.e. not a _COARSE variant.
> +#define GR_CLOCK_SOURCE CLOCK_MONOTONIC_RAW
> +
> // High-resolution clock [nanoseconds].
> -// Used with CLOCK_MONOTONIC_RAW, unless otherwise specified.
> +// Used with GR_CLOCK_SOURCE, unless otherwise specified.
> // Note: Does not have Y2038 problems. Not even with CLOCK_REALTIME.
> // Note: Using signed, to avoid need for casting to signed
> // in calculations where race conditions may cause negative differences.
> typedef int64_t gr_clock_ns_t;
>
> -// Get powered-on (non-suspended, non-hibernated) time since last boot,
> -// using a common clock across all processes.
> -static inline struct timespec gr_clock_raw(void) {
> - struct timespec tp = {0};
> - clock_gettime(CLOCK_MONOTONIC_RAW, &tp);
> - return tp;
> -}
> -
> -#define GR_NS_PER_S (gr_clock_ns_t)1000000000LL
> +#define GR_NS_PER_S (gr_clock_ns_t)1000000000LL
Please use `make format`.
> +#define GR_NS_PER_MS (gr_clock_ns_t)1000000LL
> +#define GR_NS_PER_US (gr_clock_ns_t)1000LL
>
> // Get powered-on (non-suspended, non-hibernated) time since last boot [nanoseconds],
> // using a common clock across all processes.
> // Does not return negative values.
> +#ifndef __GROUT_MAIN__
> static inline gr_clock_ns_t gr_clock_ns(void) {
> - struct timespec tp = gr_clock_raw();
> + struct timespec tp = {0};
> + clock_gettime(GR_CLOCK_SOURCE, &tp);
> return tp.tv_sec * GR_NS_PER_S + tp.tv_nsec;
> }
> +#else
> +// Grout implementation is in main/clock.h
> +#include "clock.h"
> +#endif
> diff --git a/main/clock.c b/main/clock.c
> new file mode 100644
> index 00000000..8e69ff63
> --- /dev/null
> +++ b/main/clock.c
> @@ -0,0 +1,52 @@
> +// SPDX-License-Identifier: BSD-3-Clause
> +// Copyright (c) 2026 SmartShare Systems
> +
> +#include "log.h"
> +#include "module.h"
> +
> +#include <gr_clock.h>
> +
> +LOG_TYPE("clock");
> +
> +__thread bool clock_trusted = false;
> +
> +__thread gr_clock_ns_t clock_snapshot_ns = INT64_C(-1);
> +__thread int32_t clock_snapshot_s = INT32_C(-1);
> +
> +void _clock_log_update(const char *file, int line, const char *func) {
> + LOG(DEBUG, "%s:%d %s: clock update",
> + file, line, func);
> +}
> +
> +void _clock_update(void) {
> + struct timespec tp;
> + clock_gettime(GR_CLOCK_SOURCE, &tp);
> + clock_snapshot_ns = tp.tv_sec * GR_NS_PER_S + tp.tv_nsec;
> + clock_snapshot_s = tp.tv_sec;
> +}
> +
> +static __rte_cold void clock_init(struct event_base *) {
> + // Update the clock for the main thread.
> + clock_update();
> +}
> +
> +static struct module clock_module = {
> + .name = "clock",
> + .init = clock_init,
> +};
> +
> +RTE_INIT(clock_constructor) {
> + module_register(&clock_module);
> +}
> +
> +// For review of compiled inline functions
> +
> +gr_clock_ns_t clock_ns_review(void);
> +gr_clock_ns_t clock_ns_review(void) {
> + return clock_ns();
> +}
> +
> +gr_clock_ns_t gr_clock_ns_review(void);
> +gr_clock_ns_t gr_clock_ns_review(void) {
> + return gr_clock_ns();
> +}
These functions are unused. Please remove them.
> diff --git a/main/clock.h b/main/clock.h
> new file mode 100644
> index 00000000..28e22c80
> --- /dev/null
> +++ b/main/clock.h
> @@ -0,0 +1,73 @@
> +// SPDX-License-Identifier: BSD-3-Clause
> +// Copyright (c) 2026 SmartShare Systems
> +
> +#pragma once
> +
> +#ifndef GR_CLOCK_SOURCE
> +#error "clock.h must not be included directly, include <gr_clock.h> instead"
> +#endif
> +
> +#include <rte_common.h>
> +#include <rte_debug.h>
> +
> +// Assume that the clock is updated for the current thread,
> +// i.e. the clock can be trusted.
> +extern __thread bool clock_trusted;
> +
> +// (Internal)
> +// The clock.
> +// Implemented as per-thread snapshot of the common clock.
> +// Note: Per thread to avoid certain race conditions.
> +extern __thread gr_clock_ns_t clock_snapshot_ns;
> +extern __thread int32_t clock_snapshot_s;
> +
> +// Get the clock for the current thread [nanoseconds].
> +// Does not return negative values.
> +static __rte_always_inline gr_clock_ns_t clock_ns(void) {
> + RTE_ASSERT(clock_snapshot_ns != INT64_C(-1));
> + __rte_assume(clock_snapshot_ns >= 0);
> + return clock_snapshot_ns;
> +}
> +
> +// Get the clock for the current thread [seconds].
> +// Does not return negative values.
> +static __rte_always_inline int32_t clock_s(void) {
> + RTE_ASSERT(clock_snapshot_s != INT32_C(-1));
> + __rte_assume(clock_snapshot_s >= 0);
> + return clock_snapshot_s;
> +}
Please remove clock_s() and clock_snapshot_s. It is unused and does not
bring much value since we have constants that allow converting a NS
value into seconds.
> +
> +// (Internal)
> +// Log that the clock was updated.
> +// Do not call from dataplane worker thread.
> +void _clock_log_update(const char *file, int line, const char *func);
> +
> +// (Internal)
> +void _clock_update(void);
> +
> +// Update the clock for the current thread.
> +#ifndef NDEBUG
> +#define clock_update() do { \
> + if (unlikely(!clock_trusted)) \
> + _clock_log_update(__FILE__, __LINE__, __func__); \
> + _clock_update(); \
> +} while (0)
This is unnecessary. Please remove it.
> +#else
> +#define clock_update() _clock_update()
> +#endif
> +
> +// Get the common clock [nanoseconds].
> +// Like the gr_clock_ns() Grout Client API.
> +static inline gr_clock_ns_t clock_common_ns(void) {
> + struct timespec tp = {0};
> + clock_gettime(GR_CLOCK_SOURCE, &tp);
> + gr_clock_ns_t ret = tp.tv_sec * GR_NS_PER_S + tp.tv_nsec;
> + __rte_assume(ret >= 0);
> + return ret;
> +}
> +
> +static __rte_always_inline gr_clock_ns_t gr_clock_ns(void) {
> + gr_clock_ns_t ret = likely(clock_trusted) ? clock_ns() : clock_common_ns();
> + __rte_assume(ret >= 0);
> + return ret;
> +}
Could we simplify all this code with no __rte_* macros?
extern __thread bool clock_trusted;
static inline void clock_set_trusted(bool trusted) {
clock_trusted = trusted;
}
static inline gr_clock_ns_t clock_common_ns(void) {
struct timespec tp = {0};
clock_gettime(GR_CLOCK_SOURCE, &tp);
return tp.tv_sec * GR_NS_PER_S + tp.tv_nsec;
}
extern __thread gr_clock_ns_t clock_snapshot_ns;
static inline void clock_update() {
clock_snapshot_ns = clock_common_ns();
}
static inline gr_clock_ns_t gr_clock_ns(void) {
return likely(clock_trusted) ? clock_snapshot_ns : clock_common_ns();
}
And then, in any C file, just store the two __thread variables. I don't
think it warrants a clock.c but I wouldn't mind.
> diff --git a/main/meson.build b/main/meson.build
> index 1abc8389..f089a9d2 100644
> --- a/main/meson.build
> +++ b/main/meson.build
> @@ -3,6 +3,7 @@
>
> src += files(
> 'api.c',
> + 'clock.c',
> 'config.c',
> 'control_queue.c',
> 'dpdk.c',
> diff --git a/modules/infra/datapath/main_loop.c b/modules/infra/datapath/main_loop.c
> index 2aae4030..a28dff57 100644
> --- a/modules/infra/datapath/main_loop.c
> +++ b/modules/infra/datapath/main_loop.c
> @@ -1,5 +1,6 @@
> // SPDX-License-Identifier: BSD-3-Clause
> // Copyright (c) 2023 Robin Jarry
> +// Copyright (c) 2026 SmartShare Systems
>
> #include "config.h"
> #include "control_input.h"
> @@ -20,6 +21,8 @@
> #include <rte_lcore.h>
> #include <rte_malloc.h>
>
> +#include <gr_clock.h>
> +
Please use `make format`.
> #include <pthread.h>
> #include <sched.h>
> #include <stdatomic.h>
> @@ -456,7 +459,10 @@ reconfig:
> worker_active_inc();
>
> for (;;) {
> + clock_trusted = true;
> + clock_update();
> rte_graph_walk(graph);
> + clock_trusted = false;
Could you use functions to update the trusted bool? It would be more
readable:
clock_set_trusted(true);
clock_update();
rte_graph_walk(graph);
clock_set_trusted(false);
>
> if (++loop == HOUSEKEEPING_INTERVAL) {
> // When RCU reclamation will be done in datapath workers,
--
Robin
# Made with real ingredients.
More information about the grout
mailing list