Skip to content

(draft) perf: fast-path execution for simple read-only commands (+24% throughput)#4018

Draft
rainsupreme wants to merge 1 commit into
valkey-io:unstablefrom
valkey-rainfall:perf/fast-path-readonly
Draft

(draft) perf: fast-path execution for simple read-only commands (+24% throughput)#4018
rainsupreme wants to merge 1 commit into
valkey-io:unstablefrom
valkey-rainfall:perf/fast-path-readonly

Conversation

@rainsupreme

@rainsupreme rainsupreme commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Add a fast execution path in processCommand() that bypasses call() for eligible read-only commands, delivering +24% throughput on ARM Graviton 3 (GET 16B, io-threads=9, P=10).

What it does

For commands that are CMD_READONLY | CMD_FAST (GET, HGET, MGET, EXISTS, TTL, etc.) when no features requiring the full call() ceremony are active, execute the command handler directly with minimal overhead:

  • 63/64 commands: cmd->proc(c) + calls++ + stat_numcommands++ + commandlog check. No timing, no zmalloc, no afterCommand.
  • 1/64 commands (sampled): Full timing pair, latency histogram, zmalloc peak check, afterCommand cleanup. Microseconds extrapolated ×64 for accurate usec_per_call in INFO.

Why it's ~24% faster

Analysis: Why ~24% faster

CPU flamegraph profiling on the main thread reveals that for a simple
GET, the per-command ceremony in call() costs nearly as much as the actual
work
(hash lookup + value serialization + reply building):

Component % of main-thread time Skipped on 63/64 cmds?
ustime() timing pair (14ns on ARM) 3.4%
zmalloc_used_memory() 4.7%
afterCommand() + pending flush 3-5%
call() flag save/restore + propagation checks 5-8%
moduleFireCommandResultEvent dispatch 2.5% ✓ (all commands)
updateCommandLatencyHistogram ~1%
Actual command execution (cmd->proc) ~45% ✗ always runs

For O(1) reads like GET, the per-request instruction budget is ~3,200 instructions. The fast path eliminates ~700-800 instructions of ceremony per non-sampled command, which at 2.5M rps and IPC 2.87 directly translates to the observed +24% throughput.

Eligibility conditions

All must be true (falls through to unchanged call() otherwise):

  • CMD_READONLY | CMD_FAST flags set
  • No module command-result listeners (commandResultSuccessListeners == 0)
  • No MONITOR clients connected
  • Client-side caching tracking not active
  • Top-level execution (execution_nesting == 0)

Covers ~80-95% of commands in typical caching workloads.

What's preserved

  • INFO commandstats: calls exact, usec_per_call statistically accurate (1/64 sampling, ~40K samples/sec at 2.5M rps)
  • Latency histogram: real duration samples at 1/64 rate
  • Command log: always checked (large-reply threshold works on every command; slow-execution threshold uses duration=0 on non-sampled commands — O(1) reads never trigger it anyway)
  • stat_numcommands: exact
  • Peak memory tracking: sampled 1/64

What's skipped

  • moduleFireCommandResultEvent (no-op when no listeners)
  • replicationFeedMonitors (no-op when no monitors)
  • trackingRememberKeys (disabled when not tracking)
  • valkey_commands_trace (LTTng, no-op in default builds)
  • Propagation logic (read-only commands never propagate)
  • Debug argv assertions
  • Per-command timing on 63/64 commands (14ns × 2 saved per command)
  • zmalloc_used_memory on 63/64 commands
  • afterCommand on 63/64 commands

Benchmark results (ARM Graviton 3, c7g.metal)

Build RPS Δ
unstable (baseline) 2,474,204
This PR 3,074,640 +24.3%

Config: io-threads=9, P=10, GET 16B real data (pre-populated), 5 reps × 300s, pinned cores 0-8 server / 9-17 client.

Testing

  • Unit tests pass with --config io-threads 9 (commandlog, io-threads, keyspace, multi, expire)
  • 20M mixed-op stress test (50% GET + 50% SET, io-threads=9, P=10): 0 corrupted values, 0 errors
  • Falls through to unchanged call() for any non-eligible command — zero risk to write paths, transactions, modules, or any non-default configuration

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Important

Review skipped

Ignore keyword(s) in the title.

⛔ Ignored keywords (1)
  • draft

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 19de7460-34d6-4294-9ac7-e7618b3689c8

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@rainsupreme rainsupreme force-pushed the perf/fast-path-readonly branch from 5f70f74 to 3d28e97 Compare June 22, 2026 21:43
Add a fast execution path in processCommand() that bypasses call() for
eligible read-only commands. This eliminates per-command overhead from
module event dispatch, propagation logic, monitor feeding, client-side
caching tracking, debug assertions, command tracing, and most timing —
while preserving statistically accurate telemetry via 1-in-64 sampling.

Eligibility conditions (all must be true):
- CMD_READONLY | CMD_FAST (O(1)/O(log N) read commands: GET, HGET, etc.)
- No module command-result listeners subscribed
- No MONITOR clients connected
- Client-side caching tracking not active on this client
- Top-level execution (not nested)

Sampling strategy (every 64th fast-path command):
- Full ustime() timing pair — duration feeds commandlog + histogram
- real_cmd->microseconds extrapolated ×64 (preserves usec_per_call avg)
- Latency histogram receives real (non-extrapolated) duration
- zmalloc peak memory check
- afterCommand() cleanup

Non-sampled commands (63/64):
- cmd->proc(c) executes directly with minimal setup
- cmd->calls++ and stat_numcommands++ always updated
- failed_calls tracked on deferred errors
- No timing, no zmalloc, no commandlog, no afterCommand

Measured: +18-21% throughput on ARM Graviton 3, GET 16B t9-p10.
Covers ~80-95% of commands in typical caching workloads.

Signed-off-by: Rain Valentine <rsg000@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant