bench: retune spin A/B sweep for saturation (4/6 cores, 16 loadgen)
The old 1/2/4/8 sweep with an 8-core loadgen plateaued ~102k rps at 4 and 8 server cores -- loadgen-bound, ~85% server CPU, so the 4-core (gate-active) point was never actually saturated. Retune: - CORES 1 2 4 8 -> 1 2 4 6. The gate is active at 2-4, inert at 1 and >=5, so 6 is now the saturating inert control and 4-vs-6 brackets the gate boundary under real load (8 couldn't be saturated). - LOADGEN_CPUS 12-19 (8c) -> 8-23 (16c); WRK_THREADS 8 -> 16; WRK_CONNS 256 -> 512. Gives >=2x loadgen-cores-per-server-core at every sweep point (16:1/8:1/4:1/2.7:1) and leaves cores 6-7 for the OS. - analyzer: gate label now follows the real 2..=4 rule (6 reads inert), and the reading guide notes to confirm the 6-core control is CPU- not loadgen-bound before trusting it. Note: at 22/24 logical CPUs in use, loadgen will share SMT siblings with the server; sibling-disjoint isn't achievable at 16 loadgen cores. Override LOADGEN_CPUS after checking lscpu -e if that matters.
This commit is contained in:
+10
-5
@@ -18,7 +18,10 @@ import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
GATED = {2, 4} # core counts where the RFC 004 spin policy is active
|
||||
def gate_active(cores):
|
||||
"""RFC 004 spinner cap is non-zero only at 2..=4 schedulers (1 and >=5
|
||||
allow 0 spinners), so the policy is active exactly there."""
|
||||
return 2 <= cores <= 4
|
||||
|
||||
|
||||
def parse_latency_ms(s):
|
||||
@@ -88,7 +91,7 @@ def main():
|
||||
b = fnum(before.get(k, {}).get("sustained_rps"))
|
||||
a = fnum(after.get(k, {}).get("sustained_rps"))
|
||||
spd = f"{a / b:.2f}x" if (a and b) else "—"
|
||||
gate = "ACTIVE" if cores in GATED else "inert"
|
||||
gate = "ACTIVE" if gate_active(cores) else "inert"
|
||||
bs = f"{b:,.0f}" if b is not None else "—"
|
||||
as_ = f"{a:,.0f}" if a is not None else "—"
|
||||
print(f"{srv:<10} {scn:<4} {cores:>5} {bs:>12} {as_:>12} {spd:>8} {gate}")
|
||||
@@ -107,7 +110,7 @@ def main():
|
||||
delta = f"{(a - b):+.2f}"
|
||||
else:
|
||||
delta = "—"
|
||||
gate = "ACTIVE" if cores in GATED else "inert"
|
||||
gate = "ACTIVE" if gate_active(cores) else "inert"
|
||||
bs = f"{b:.2f}" if b is not None else "—"
|
||||
as_ = f"{a:.2f}" if a is not None else "—"
|
||||
print(f"{srv:<10} {scn:<4} {cores:>5} {bs:>10} {as_:>10} {delta:>9} {gate}")
|
||||
@@ -126,8 +129,10 @@ def main():
|
||||
|
||||
print(
|
||||
"\nReading it: spin is 'worth it' if s3 (and/or s2) RPS rises and/or "
|
||||
"tail latency drops at cores 2/4 (gate ACTIVE), with cores 1/8 (inert) "
|
||||
"staying flat. Flat-or-worse at 2/4 = not worth it on real traffic."
|
||||
"tail latency drops at the gate-ACTIVE cores (2-4), with the inert "
|
||||
"controls (1 and >=5, e.g. 6) staying flat. Flat-or-worse at 2/4 = "
|
||||
"not worth it on real traffic. Cross-check that the saturating control "
|
||||
"(6 cores) is actually CPU-bound, not loadgen-bound, before trusting it."
|
||||
)
|
||||
if "axum-mem" in {k[0] for k in keys}:
|
||||
print(
|
||||
|
||||
+21
-9
@@ -20,10 +20,13 @@
|
||||
# Scenarios (per RECON §6.4): s2 (router+auth, no store) and s3 (memory store,
|
||||
# the headline). Server is urus-mem for both. We do NOT touch s4/sqlite.
|
||||
#
|
||||
# Core sweep (per RECON §6.3): the spin gate is active at 2-4 schedulers and
|
||||
# inert at 1 and >=5, so 2 and 4 should show the effect and 1 and 8 bracket it
|
||||
# as controls. Server is pinned to cores 0..N-1; the load generator gets its
|
||||
# own disjoint cores (LOADGEN_CPUS).
|
||||
# Core sweep: the spin gate is active at 2-4 schedulers and inert at 1 and
|
||||
# >=5. We sweep 1/2/4/6 server cores: 2 and 4 are gate-ACTIVE, 1 and 6 are
|
||||
# inert controls -- 6 also being a *saturating* control, since the old 8-core
|
||||
# point couldn't be saturated by an 8-core loadgen (it plateaued ~102k rps,
|
||||
# loadgen-bound). Server is pinned to cores 0..N-1; the load generator gets
|
||||
# its own disjoint cores (LOADGEN_CPUS), sized ~>=2x the server cores per the
|
||||
# saturation goal while leaving cores free for the OS.
|
||||
#
|
||||
# Usage:
|
||||
# ./spin_ab_urus.sh # full A/B, defaults
|
||||
@@ -37,8 +40,10 @@
|
||||
# Env knobs (all overridable):
|
||||
# BEFORE / AFTER smarm commit-ishes (default b0c9685 / HEAD)
|
||||
# SCENARIOS space-separated (default "s2 s3")
|
||||
# CORES server core counts to sweep (default "1 2 4 8")
|
||||
# LOADGEN_CPUS loadgen taskset range (default "12-19")
|
||||
# CORES server core counts to sweep (default "1 2 4 6")
|
||||
# LOADGEN_CPUS loadgen taskset range (default "8-23" = 16 cores)
|
||||
# WRK_THREADS loadgen wrk/wrk2 threads (default 16)
|
||||
# WRK_CONNS loadgen open connections (default 512)
|
||||
# SERVER memory server backend (default urus-mem)
|
||||
# PROBE_SEC/WARMUP_SEC/MEASURE_SEC/SAT_RATIO/PREPOP/WRK_CONNS/WRK_THREADS
|
||||
# passed straight through to runner.sh
|
||||
@@ -74,9 +79,15 @@ AFTER_DEFAULT="$(git -C "$SMARM_DIR" rev-parse --short HEAD)"
|
||||
AFTER="${AFTER:-$AFTER_DEFAULT}"
|
||||
|
||||
SCENARIOS="${SCENARIOS:-s2 s3}"
|
||||
CORES="${CORES:-1 2 4 8}"
|
||||
CORES="${CORES:-1 2 4 6}"
|
||||
SERVER="${SERVER:-urus-mem}"
|
||||
LOADGEN_CPUS="${LOADGEN_CPUS:-12-19}"
|
||||
LOADGEN_CPUS="${LOADGEN_CPUS:-8-23}"
|
||||
|
||||
# Loadgen sizing: ~2x loadgen cores per server core. 16 cores covers the
|
||||
# 6-core server at ~2.7x and the 4-core at 4x. Threads track the core count;
|
||||
# connections bumped so the probe can actually find the server's peak.
|
||||
export WRK_THREADS="${WRK_THREADS:-16}"
|
||||
export WRK_CONNS="${WRK_CONNS:-512}"
|
||||
|
||||
QUICK=0
|
||||
DRY=0
|
||||
@@ -111,7 +122,8 @@ echo ">>> smarm: $SMARM_DIR"
|
||||
echo ">>> BEFORE=$BEFORE AFTER=$AFTER"
|
||||
echo ">>> servers: ${SERVERS_TO_RUN[*]}"
|
||||
echo ">>> scenarios: $SCENARIOS"
|
||||
echo ">>> cores: $CORES (loadgen on $LOADGEN_CPUS)"
|
||||
echo ">>> cores: $CORES"
|
||||
echo ">>> loadgen: cpus=$LOADGEN_CPUS threads=$WRK_THREADS conns=$WRK_CONNS"
|
||||
echo ">>> machine: $NPROC cores"
|
||||
echo ">>> outdir: $OUTDIR"
|
||||
[[ "$QUICK" == "1" ]] && echo ">>> --quick: probe/warmup/measure = ${PROBE_SEC}/${WARMUP_SEC}/${MEASURE_SEC}s"
|
||||
|
||||
Reference in New Issue
Block a user