> From: Mattias Rönnblom [mailto:mattias.ronnb...@ericsson.com] > Sent: Wednesday, 15 March 2023 18.04 > > The htimer library attempts at providing a timer facility with roughly > the same functionality, but less overhead and better scalability than > DPDK timer library. > > The htimer library employs per-lcore hierarchical timer wheels and a > message-based synchronization/MT-safety scheme. > > RFC v2: > * Fix spelling. > * Fix signed/unsigned comparisons and discontinue the use of name-less > function parameters, both of which may result in compiler warnings. > * Undo the accidental removal of the bitset tests from the 'fast_tests'. > * Add a number of missing include files, causing build failures > (e.g., on AArch64 builds). > * Add perf test attempting to compare rte_timer, rte_htimer and rte_htw. > * Use nanoseconds (instead of TSC) as the default time unit. > * add() and manage() has flags which allows the caller to specify the > time unit (nanoseconds, TSC, or ticks) for the times provided. > > Signed-off-by: Mattias Rönnblom <mattias.ronnb...@ericsson.com> > ---
Two more series of comments, see inline below: 1. Arguing for using "tick" as the default unit of time. 2. Some bugs in the time conversion functions. [...] > diff --git a/lib/htimer/meson.build b/lib/htimer/meson.build > new file mode 100644 > index 0000000000..2dd5d6a24b > --- /dev/null > +++ b/lib/htimer/meson.build > @@ -0,0 +1,7 @@ > +# SPDX-License-Identifier: BSD-3-Clause > +# Copyright(c) 2023 Ericsson AB > + > +sources = files('rte_htw.c', 'rte_htimer_msg_ring.c', 'rte_htimer_mgr.c') > +headers = files('rte_htimer_mgr.h', 'rte_htimer.h') > + > +deps += ['ring'] > diff --git a/lib/htimer/rte_htimer.h b/lib/htimer/rte_htimer.h > new file mode 100644 > index 0000000000..6ac86292b5 > --- /dev/null > +++ b/lib/htimer/rte_htimer.h > @@ -0,0 +1,68 @@ > +/* SPDX-License-Identifier: BSD-3-Clause > + * Copyright(c) 2023 Ericsson AB > + */ > + > +#ifndef _RTE_HTIMER_H_ > +#define _RTE_HTIMER_H_ > + > +#include <stdbool.h> > +#include <stdint.h> > +#include <sys/queue.h> > + > +#include <rte_bitops.h> > + > +struct rte_htimer; > + > +typedef void (*rte_htimer_cb_t)(struct rte_htimer *, void *); > + > +struct rte_htimer { > + /** > + * Absolute timer expiration time (in ticks). > + */ > + uint64_t expiration_time; > + /** > + * Time between expirations (in ticks). Zero for one-shot timers. > + */ > + uint64_t period; > + /** > + * Owning lcore. May safely be read from any thread. > + */ > + uint32_t owner_lcore_id; > + /** > + * The current state of the timer. > + */ > + uint32_t state:4; > + /** > + * Flags set on this timer. > + */ > + uint32_t flags:28; > + /** > + * User-specified callback function pointer. > + */ > + rte_htimer_cb_t cb; > + /** > + * Argument for user callback. > + */ > + void *cb_arg; > + /** > + * Pointers used to add timer to various internal lists. > + */ > + LIST_ENTRY(rte_htimer) entry; > +}; > + > +#define RTE_HTIMER_FLAG_ABSOLUTE_TIME RTE_BIT32(0) > +#define RTE_HTIMER_FLAG_PERIODICAL RTE_BIT32(1) > +#define RTE_HTIMER_FLAG_TIME_TICK RTE_BIT32(2) > +#define RTE_HTIMER_FLAG_TIME_TSC RTE_BIT32(3) After further consideration, and taking the time conversion functions into account, I think the default unit of time should be "tick", not nanoseconds. It seems more natural, and might offer more flexibility in the future. So instead of: +#define RTE_HTIMER_FLAG_TIME_TICK RTE_BIT32(2) +#define RTE_HTIMER_FLAG_TIME_TSC RTE_BIT32(3) then: +#define RTE_HTIMER_FLAG_TIME_TSC RTE_BIT32(2) +#define RTE_HTIMER_FLAG_TIME_NS RTE_BIT32(3) and perhaps in the future: +#define RTE_HTIMER_FLAG_TIME_US RTE_BIT32(4) > + > +#define RTE_HTIMER_STATE_PENDING 1 > +#define RTE_HTIMER_STATE_EXPIRED 2 > +#define RTE_HTIMER_STATE_CANCELED 3 > + > +LIST_HEAD(rte_htimer_list, rte_htimer); > + > +#ifdef __cplusplus > +} > +#endif > + > +#endif /* _RTE_HTIMER_H_ */ > diff --git a/lib/htimer/rte_htimer_mgr.c b/lib/htimer/rte_htimer_mgr.c > new file mode 100644 > index 0000000000..efdfcf0985 > --- /dev/null > +++ b/lib/htimer/rte_htimer_mgr.c > @@ -0,0 +1,547 @@ > +/* SPDX-License-Identifier: BSD-3-Clause > + * Copyright(c) 2023 Ericsson AB > + */ > + > +#include <inttypes.h> > +#include <math.h> > +#include <stdbool.h> > +#include <sys/queue.h> > +#include <unistd.h> > + > +#include <rte_branch_prediction.h> > +#include <rte_common.h> > +#include <rte_cycles.h> > +#include <rte_errno.h> > +#include <rte_htw.h> > +#include <rte_prefetch.h> > +#include <rte_ring_elem.h> > + > +#include "rte_htimer_mgr.h" > +#include "rte_htimer_msg.h" > +#include "rte_htimer_msg_ring.h" > + > +#define MAX_MSG_BATCH_SIZE 16 > + > +struct htimer_mgr { > + struct rte_htimer_msg_ring *msg_ring; > + struct rte_htw *htw; > + > + unsigned int async_msgs_idx __rte_cache_aligned; > + unsigned int num_async_msgs; > + struct rte_htimer_msg async_msgs[MAX_MSG_BATCH_SIZE]; > +} __rte_cache_aligned; > + > +static uint64_t ns_per_tick; > +static double tsc_per_tick; > + > +static struct htimer_mgr mgrs[RTE_MAX_LCORE + 1]; > + > +#define MAX_ASYNC_TRANSACTIONS 1024 > +#define MSG_RING_SIZE MAX_ASYNC_TRANSACTIONS > + > +static inline uint64_t > +tsc_to_tick(uint64_t tsc) > +{ > + return tsc / tsc_per_tick; > +} > + > +static inline uint64_t > +tsc_to_tick_round_up(uint64_t tsc) > +{ > + uint64_t tick; > + > + tick = (tsc + tsc_per_tick / 2) / tsc_per_tick; This does not round up, it rounds off. E.g. tsc_per_tick=10.0, tsc=1 becomes (1 + 5.0) / 10.0 = 0.6, which becomes 0 (when converted to integer). E.g. tsc_per_tick=10.0, tsc=5 becomes (5 + 5.0) / 10.0 = 1.0, which becomes 1. > + > + return tick; > +} > + > +static inline uint64_t > +ns_to_tick(uint64_t ns) > +{ > + return ns / ns_per_tick; > +} > + > +static inline uint64_t > +ns_to_tick_round_up(uint64_t ns) > +{ > + uint64_t tick; > + > + tick = ceil(ns / ns_per_tick); ns_per_tick is integer, not floating point, so the division is performed as integer division, and ceil() has no effect; i.e. the above is the same as: tick = ns / ns_per_tick; Which also means that it does not round up. > + > + return tick; > +} > + > +static inline uint64_t > +tick_to_ns(uint64_t tick) > +{ > + return tick * ns_per_tick; > +} > + > +static struct htimer_mgr * > +mgr_get(unsigned int lcore_id) > +{ > + return &mgrs[lcore_id]; > +} > + > +static int > +mgr_init(unsigned int lcore_id) > +{ > + char ring_name[RTE_RING_NAMESIZE]; > + unsigned int socket_id; > + struct htimer_mgr *mgr = &mgrs[lcore_id]; > + > + socket_id = rte_lcore_to_socket_id(lcore_id); > + > + snprintf(ring_name, sizeof(ring_name), "htimer_%d", lcore_id); > + > + mgr->msg_ring = > + rte_htimer_msg_ring_create(ring_name, MSG_RING_SIZE, socket_id, > + RING_F_SC_DEQ); > + > + if (mgr->msg_ring == NULL) > + goto err; > + > + mgr->htw = rte_htw_create(); > + > + if (mgr->htw == NULL) > + goto err_free_ring; > + > + mgr->async_msgs_idx = 0; > + mgr->num_async_msgs = 0; > + > + return 0; > + > +err_free_ring: > + rte_htimer_msg_ring_free(mgr->msg_ring); > +err: > + return -ENOMEM; > +} > + > +static void > +mgr_deinit(unsigned int lcore_id) > +{ > + struct htimer_mgr *mgr = &mgrs[lcore_id]; > + > + rte_htw_destroy(mgr->htw); > + > + rte_htimer_msg_ring_free(mgr->msg_ring); > +} > + > +static volatile bool initialized; > + > +static void > +assure_initialized(void) > +{ > + RTE_ASSERT(initialized); > +} > + > +int > +rte_htimer_mgr_init(uint64_t _ns_per_tick) > +{ > + unsigned int lcore_id; > + > + RTE_VERIFY(!initialized); > + > + ns_per_tick = _ns_per_tick; > + > + tsc_per_tick = (ns_per_tick / 1e9) * rte_get_tsc_hz(); > + > + for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) { > + int rc; > + > + rc = mgr_init(lcore_id); > + > + if (rc < 0) { > + unsigned int deinit_lcore_id; > + > + for (deinit_lcore_id = 0; deinit_lcore_id < lcore_id; > + deinit_lcore_id++) > + mgr_deinit(deinit_lcore_id); > + > + return rc; > + } > + } > + > + initialized = true; > + > + return 0; > +} > + > +void > +rte_htimer_mgr_deinit(void) > +{ > + unsigned int lcore_id; > + > + assure_initialized(); > + > + for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) > + mgr_deinit(lcore_id); > + > + initialized = false; > +} > + > +static void > +assure_valid_time_conversion_flags(uint32_t flags __rte_unused) > +{ > + RTE_ASSERT(!((flags & RTE_HTIMER_FLAG_TIME_TSC) && > + (flags & RTE_HTIMER_FLAG_TIME_TICK))); With my above suggestion of using tick as default time unit, this would be changed to: + RTE_ASSERT(!((flags & RTE_HTIMER_FLAG_TIME_TSC) && + (flags & RTE_HTIMER_FLAG_TIME_NS) && + (flags & RTE_HTIMER_FLAG_TIME_US))); > +} > + > +static void > +assure_valid_add_flags(uint32_t flags) > +{ > + assure_valid_time_conversion_flags(flags); > + > + RTE_ASSERT(!(flags & ~(RTE_HTIMER_FLAG_PERIODICAL | > + RTE_HTIMER_FLAG_ABSOLUTE_TIME | > + RTE_HTIMER_FLAG_TIME_TSC | > + RTE_HTIMER_FLAG_TIME_TICK))); With my above suggestion of using tick as default time unit, this would be changed to: + RTE_ASSERT(!(flags & ~(RTE_HTIMER_FLAG_PERIODICAL | + RTE_HTIMER_FLAG_ABSOLUTE_TIME | + RTE_HTIMER_FLAG_TIME_TSC | + RTE_HTIMER_FLAG_TIME_NS | + RTE_HTIMER_FLAG_TIME_US))); > +} > + > +static uint64_t > +convert_time(uint64_t t, uint32_t flags) > +{ > + if (flags & RTE_HTIMER_FLAG_TIME_TSC) > + return tsc_to_tick(t); > + else if (flags & RTE_HTIMER_FLAG_TIME_TICK) > + return t; > + else > + return ns_to_tick(t); With my above suggestion of using tick as default time unit, this would be changed to: + if (flags & RTE_HTIMER_FLAG_TIME_TSC) + return tsc_to_tick(t); + else if (flags & RTE_HTIMER_FLAG_TIME_NS) + return ns_to_tick(t); + else if (flags & RTE_HTIMER_FLAG_TIME_US) + return us_to_tick(t); + else + return t; > +} > + > +void > +rte_htimer_mgr_add(struct rte_htimer *timer, uint64_t expiration_time, > + uint64_t period, rte_htimer_cb_t timer_cb, > + void *timer_cb_arg, uint32_t flags) > +{ > + unsigned int lcore_id = rte_lcore_id(); > + struct htimer_mgr *mgr = mgr_get(lcore_id); > + uint64_t expiration_time_tick; > + uint64_t period_tick; > + > + assure_initialized(); > + > + assure_valid_add_flags(flags); > + > + expiration_time_tick = convert_time(expiration_time, flags); > + > + period_tick = convert_time(period, flags); > + > + rte_htw_add(mgr->htw, timer, expiration_time_tick, period_tick, > + timer_cb, timer_cb_arg, flags); > + > + timer->owner_lcore_id = lcore_id; > +}