Reduce per command syscalls by reusing cached time when HW monotic clock is available (#14713)
Some checks are pending
CI / test-ubuntu-latest (push) Waiting to run
CI / test-sanitizer-address (push) Waiting to run
CI / build-debian-old (push) Waiting to run
CI / build-macos-latest (push) Waiting to run
CI / build-32bit (push) Waiting to run
CI / build-libc-malloc (push) Waiting to run
CI / build-centos-jemalloc (push) Waiting to run
CI / build-old-chain-jemalloc (push) Waiting to run
Codecov / code-coverage (push) Waiting to run
External Server Tests / test-external-standalone (push) Waiting to run
External Server Tests / test-external-cluster (push) Waiting to run
External Server Tests / test-external-nodebug (push) Waiting to run
Spellcheck / Spellcheck (push) Waiting to run

This PR reduces per-command `ustime()` syscalls in `call()` by reusing
cached time and batching wall-clock updates when HW monotonic time is
available.

### What changed
- Pass `server.ustime` to `enterExecutionUnit()` instead of calling
`ustime()`.
- Use HW monotonic clock to measure duration and accumulate it across
commands.
- Refresh cached time with `ustime()` only when accumulated duration >
**10µs** or after **25 commands**.
- Fallback to direct `ustime()` when HW monotonic clock isn’t available.

### Impact
- `ustime` CPU: **4.58% → 0.25%**, which leads to ~4% boost on max QPS

### Notes
- Time drift is bounded (≤10µs or 25 commands).
- No behavior change on non-HW-monotonic systems.

---------

Co-authored-by: Yuan Wang <yuan.wang@redis.com>
This commit is contained in:
Filipe Oliveira (Redis) 2026-01-22 09:45:45 +00:00 committed by GitHub
parent d099c10581
commit 3ff37ea815
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 30 additions and 6 deletions

View file

@ -3030,6 +3030,8 @@ void initServer(void) {
server.last_sig_received = 0;
memset(server.io_threads_clients_num, 0, sizeof(server.io_threads_clients_num));
atomicSetWithSync(server.running, 0);
server.accum_call_count_since_ustime = 0;
server.monotonic_us_when_ustime = 0;
/* Initiate acl info struct */
server.acl_info.invalid_cmd_accesses = 0;
@ -3856,7 +3858,31 @@ void call(client *c, int flags) {
long long old_master_repl_offset = server.master_repl_offset;
incrCommandStatsOnError(NULL, 0);
const long long call_timer = ustime();
/* Use monotonic clock if available, and update cached time if needed */
const int use_hw_clock = monotonicGetType() == MONOTONIC_CLOCK_HW;
monotime monotonic_start = 0;
if (use_hw_clock) {
monotonic_start = getMonotonicUs();
if (server.execution_nesting == 0) {
server.accum_call_count_since_ustime++;
/* Sync cached time when monotonic clock moves more than 10us
* or after 25 commands */
if (monotonic_start - server.monotonic_us_when_ustime > 10 ||
server.accum_call_count_since_ustime > 25)
{
updateCachedTime(0);
/* Recalculate monotonic_start after time update as ustime()
* in updateCachedTime() might have taken some time */
monotonic_start = getMonotonicUs();
server.monotonic_us_when_ustime = monotonic_start;
server.accum_call_count_since_ustime = 0;
}
}
}
/* Pass current server.ustime to avoid ustime() call if monotonic clock is used
* and time will be updated before command execution based on monotonic clock. */
const long long call_timer = use_hw_clock ? server.ustime : ustime();
enterExecutionUnit(1, call_timer);
/* setting the CLIENT_EXECUTING_COMMAND flag so we will avoid
@ -3865,10 +3891,6 @@ void call(client *c, int flags) {
* re-processing and unblock the client.*/
c->flags |= CLIENT_EXECUTING_COMMAND;
monotime monotonic_start = 0;
if (monotonicGetType() == MONOTONIC_CLOCK_HW)
monotonic_start = getMonotonicUs();
c->cmd->proc(c);
exitExecutionUnit();
@ -3880,7 +3902,7 @@ void call(client *c, int flags) {
/* In order to avoid performance implication due to querying the clock using a system call 3 times,
* we use a monotonic clock, when we are sure its cost is very low, and fall back to non-monotonic call otherwise. */
ustime_t duration;
if (monotonicGetType() == MONOTONIC_CLOCK_HW)
if (use_hw_clock)
duration = getMonotonicUs() - monotonic_start;
else
duration = ustime() - call_timer;

View file

@ -2391,6 +2391,8 @@ struct redisServer {
redisAtomic int daylight_active; /* Currently in daylight saving time. */
mstime_t mstime; /* 'unixtime' in milliseconds. */
ustime_t ustime; /* 'unixtime' in microseconds. */
int accum_call_count_since_ustime; /* Command count since last ustime update */
monotime monotonic_us_when_ustime; /* Monotonic time when last ustime update */
mstime_t cmd_time_snapshot; /* Time snapshot of the root execution nesting. */
size_t blocking_op_nesting; /* Nesting level of blocking operation, used to reset blocked_last_cron. */
long long blocked_last_cron; /* Indicate the mstime of the last time we did cron jobs from a blocking operation */