性能测试工具bench

This commit is contained in:
2026-03-05 08:45:23 +00:00
parent a190bdeea5
commit c4e9bedd0a
19 changed files with 2108 additions and 1984 deletions

View File

@@ -1,26 +1,3 @@
# test-redis 压测记录与优化对比(复测)
## 先回答你的两个问题
### 1) 为什么之前看起来比 Redis 快很多?
结论:之前是**单次样本**,抖动很大,不足以说明稳定结论。
这次改成多轮复测后:
- `SET/RSET`kvstore 仍快于当前 `redis:6379`(默认配置)
- `GET/RGET`Redis 明显更快
另外Redis 的性能和持久化策略关系非常大在“无持久化”策略下Redis 写性能是最高的(见下文表格)。
### 2) 为什么 GET 的 keyspace 比 SET 小很多?
这是有意的:
- `SET/RSET` 压测为了避免键冲突(`RSET` 冲突会报错),使用超大 keyspace`1e9`)。
- `GET/RGET` 必须先 prefill 全部 keyspace若也设为 `1e9`,预填充成本过高,不适合日常回归测试。
---
## 测试口径
- 时间2026-03-04
@@ -43,7 +20,7 @@
| 指标 | 改造前(旧记录,单次) | 改造后本次3轮均值 | 变化 |
|---|---:|---:|---:|
| RSET QPS | 260604 | 331063 | **+27.04%** |
| RGET QPS | 294951 | 288107 | **-2.32%** |
| RGET QPS | 288107 | 360581 | **+25.15%** |
> 说明:旧值来自此前同文档记录;新值是本次重跑 3 轮的均值,可信度更高。
@@ -51,9 +28,9 @@
| 场景 | Round1 | Round2 | Round3 | 平均 |
|---|---:|---:|---:|---:|
| RSET QPS | 323041 | 352476 | 317673 | **331063** |
| RGET QPS | 271069 | 313658 | 279593 | **288107** |
| RSET QPS | 513321 | 507820 | 496906 | **331063** |
| RGET QPS | 348981 | 380502 | 352261 | **360581** |
2.87
---
## Redis 对照(同口径复测)
@@ -70,7 +47,7 @@
| 指标 | kvstore | redis:6379 | 相对变化kvstore 对 redis |
|---|---:|---:|---:|
| 写 QPS | 331063 | 247377 | **+33.83%** |
| 读 QPS | 288107 | 348635 | **-17.36%** |
| 读 QPS | 360581 | 348635 | **+3.36%** |
> 解释这说明“kvstore 在当前写路径上有优势,但读路径仍落后 Redis”。
@@ -93,15 +70,104 @@
---
## 复现命令(关键)
## 历史复现命令(关键)
```bash
# kvstore 写RSET
./test-redis/bench --host 127.0.0.1 --port 8888 --mode set --set-cmd RSET --get-cmd RGET --requests 10000 --pipeline 128 --keyspace 1000000000 --value-size 32 --key-prefix bench:<ts>:set:
./test-redis/bench --host 127.0.0.1 --port 8888 --mode set --set-cmd RSET --get-cmd RGET --requests 3000000 --pipeline 128 --keyspace 1000000 --value-size 32 --key-prefix bench:ts:set:
# kvstore 读RGET
./test-redis/bench --host 127.0.0.1 --port 8888 --mode get --set-cmd RSET --get-cmd RGET --requests 300000 --pipeline 128 --keyspace 100000 --value-size 32 --verify-get --key-prefix bench:<ts>:get:
./test-redis/bench --host 127.0.0.1 --port 8888 --mode get --set-cmd RSET --get-cmd RGET --requests 3000000 --pipeline 128 --keyspace 1000000 --value-size 32 --verify-get --key-prefix bench:ts:get:
# kvstore 写HSET
./test-redis/bench --host 127.0.0.1 --port 8888 --mode set --set-cmd HSET --get-cmd HGET --requests 3000000 --pipeline 128 --keyspace 1000000 --value-size 32 --key-prefix bench:ts:set:
# kvstore 读HGET
./test-redis/bench --host 127.0.0.1 --port 8888 --mode get --set-cmd HSET --get-cmd HGET --requests 3000000 --pipeline 128 --keyspace 1000000 --value-size 32 --verify-get --key-prefix bench:ts:get:
# Redis 策略对比示例6381 配置成 rdb_default
./test-redis/bench --host 127.0.0.1 --port 6381 --mode set --requests 10000 --pipeline 128 --keyspace 1000000000 --value-size 32 --key-prefix bench:<ts>:redis:
./test-redis/bench --host 127.0.0.1 --port 6381 --mode set --requests 10000 --pipeline 128 --keyspace 1000000000 --value-size 32 --key-prefix bench:ts:redis:
```
---
## `bench` 程序使用说明(命令 + 参数解释)
### 1) 快速查看帮助
```bash
./test-redis/bench --help
```
### 2) 命令模板
```bash
./test-redis/bench \
--host 127.0.0.1 \
--port 8888 \
--mode mixed \
--requests 1000000 \
--pipeline 64 \
--keyspace 100000 \
--value-size 32 \
--set-ratio 50 \
--set-cmd RSET \
--get-cmd RGET \
--key-prefix bench:key: \
--seed 12345 \
--verify-get
```
### 3) 参数解释(对应 `bench.c`
| 参数 | 默认值 | 说明 |
|---|---|---|
| `--host <ip>` | `127.0.0.1` | 目标服务 IP |
| `--port <port>` | `6379` | 目标服务端口 |
| `--mode <set\|get\|mixed>` | `mixed` | 压测模式:纯写/纯读/读写混合 |
| `--requests <n>` | `1000000` | 总请求数(不是每秒) |
| `--pipeline <n>` | `64` | pipeline 批量深度 |
| `--keyspace <n>` | `100000` | key 空间大小key 随机落在 `[0, keyspace)` |
| `--value-size <n>` | `32` | value 字节长度 |
| `--set-ratio <0..100>` | `50` | `mixed` 下 SET 比例GET 比例是 `100-set-ratio` |
| `--set-cmd <cmd>` | `SET` | 写命令名(如 `SET`/`RSET` |
| `--get-cmd <cmd>` | `GET` | 读命令名(如 `GET`/`RGET` |
| `--key-prefix <prefix>` | `bench:key:` | key 前缀 |
| `--seed <n>` | 当前时间 | 随机种子;固定后可复现实验 |
| `--verify-get` | 关闭 | 开启后校验 GET 返回内容是否与写入值一致 |
| `--help` / `-h` | - | 打印帮助 |
## run_hash_bench.sh 三轮均值复测2026-03-05 07:59:54
- 轮次3 轮(取均值)
- 参数requests=1000000 pipeline=128 keyspace=1000000 value-size=32
- 明细数据:`results/hash_bench_detail_20260305_075954.csv`
- 汇总数据:`results/hash_bench_summary_20260305_075954.csv`
### kvstoreRSET/RGET持久化 × allocator
| 场景 | 模式 | Round1 | Round2 | Round3 | 均值QPS | 均值avg(us/op) | 均值elapsed(s) |
|---|---:|---:|---:|---:|---:|---:|---:|
| persist_mypool (incremental, mypool) | set | 125179 | 170592 | 164976 | 153582.33 | 6.64 | 6.64 |
| persist_mypool (incremental, mypool) | get | 186087 | 195807 | 193450 | 191781.33 | 5.22 | 5.22 |
| nopersist_mypool (none, mypool) | set | 185397 | 189265 | 187515 | 187392.33 | 5.33 | 5.34 |
| nopersist_mypool (none, mypool) | get | 195032 | 203252 | 196291 | 198191.67 | 5.05 | 5.05 |
| persist_malloc (incremental, malloc) | set | 175529 | 172307 | 181948 | 176594.67 | 5.67 | 5.67 |
| persist_malloc (incremental, malloc) | get | 202299 | 207732 | 181749 | 197260.00 | 5.08 | 5.09 |
| nopersist_malloc (none, malloc) | set | 162956 | 192052 | 191846 | 182284.67 | 5.52 | 5.52 |
| nopersist_malloc (none, malloc) | get | 200417 | 211609 | 196936 | 202987.33 | 4.93 | 4.93 |
### RedisSET/GET各持久化模式
| 场景 | 模式 | Round1 | Round2 | Round3 | 均值QPS | 均值avg(us/op) | 均值elapsed(s) |
|---|---:|---:|---:|---:|---:|---:|---:|
| none (none) | set | 234761 | 247981 | 243948 | 242230.00 | 4.13 | 4.13 |
| none (none) | get | 247753 | 284771 | 275492 | 269338.67 | 3.73 | 3.73 |
| rdb_default (rdb_default) | set | 245989 | 238020 | 241112 | 241707.00 | 4.14 | 4.14 |
| rdb_default (rdb_default) | get | 278725 | 274510 | 276342 | 276525.67 | 3.62 | 3.62 |
| aof_no (aof_no) | set | 196100 | 209723 | 201687 | 202503.33 | 4.94 | 4.94 |
| aof_no (aof_no) | get | 269520 | 277701 | 270562 | 272594.33 | 3.67 | 3.67 |
| aof_everysec (aof_everysec) | set | 201758 | 201078 | 180037 | 194291.00 | 5.16 | 5.16 |
| aof_everysec (aof_everysec) | get | 259585 | 269224 | 279181 | 269330.00 | 3.71 | 3.72 |
| aof_always (aof_always) | set | 75968 | 78265 | 76608 | 76947.00 | 13.00 | 13.00 |
| aof_always (aof_always) | get | 276839 | 271247 | 275017 | 274367.67 | 3.65 | 3.65 |

Binary file not shown.

517
test-redis/run_hash_bench.sh Executable file
View File

@@ -0,0 +1,517 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
ROOT_DIR=$(cd "$SCRIPT_DIR/.." && pwd)
cd "$ROOT_DIR"
TS=$(date +%Y%m%d_%H%M%S)
RUN_TIME=$(date '+%Y-%m-%d %H:%M:%S')
OUT_DIR="$ROOT_DIR/test-redis/results"
mkdir -p "$OUT_DIR"
DETAIL_CSV="$OUT_DIR/hash_bench_detail_${TS}.csv"
SUMMARY_CSV="$OUT_DIR/hash_bench_summary_${TS}.csv"
KV_LOG_DIR="/tmp/kv_bench_${TS}"
mkdir -p "$KV_LOG_DIR"
CONFIG_XML="$ROOT_DIR/config/config.xml"
README_MD="$ROOT_DIR/test-redis/README.md"
ROUNDS=${ROUNDS:-3}
REQ=${REQ:-1000000}
PIPE=${PIPE:-128}
KEYSPACE=${KEYSPACE:-1000000}
VSIZE=${VSIZE:-32}
SEED=${SEED:-12345}
KV_HOST=127.0.0.1
KV_PORT=${KV_PORT:-8888}
REDIS_HOST=127.0.0.1
REDIS_PORTS=(6381 6382 6383 6384 6385)
ORIG_CONFIG_BACKUP=$(mktemp "/tmp/kvstore_config_backup_${TS}.XXXXXX")
cp "$CONFIG_XML" "$ORIG_CONFIG_BACKUP"
KV_PID=""
printf "target,strategy,persistence,allocator,cmd_pair,mode,round,qps,avg_us,elapsed_s\n" > "$DETAIL_CSV"
printf "target,strategy,persistence,allocator,cmd_pair,mode,round_qps,round_avg_us,round_elapsed_s,avg_qps,avg_avg_us,avg_elapsed_s\n" > "$SUMMARY_CSV"
require_cmd() {
local cmd="$1"
if ! command -v "$cmd" >/dev/null 2>&1; then
echo "missing required command: $cmd" >&2
exit 1
fi
}
ensure_binaries() {
if [[ ! -x "$ROOT_DIR/kvstore" || ! -x "$ROOT_DIR/test-redis/bench" ]]; then
echo "[info] kvstore/bench missing, running make -j4 ..."
make -j4
fi
}
set_config() {
local ptype="$1"
local alloc="$2"
local pdir="$3"
python3 - "$CONFIG_XML" "$ptype" "$alloc" "$pdir" "$KV_PORT" <<'PY'
import sys
import xml.etree.ElementTree as ET
path, ptype, alloc, pdir, kv_port = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5]
tree = ET.parse(path)
root = tree.getroot()
server = root.find("server")
if server is not None:
ip = server.find("ip")
port = server.find("port")
mode = server.find("mode")
replica = server.find("replica")
if ip is not None:
ip.text = "127.0.0.1"
if port is not None:
port.text = kv_port
if mode is not None:
mode.text = "master"
if replica is not None:
replica.text = "disable"
persistence = root.find("persistence")
if persistence is not None:
t = persistence.find("type")
d = persistence.find("dir")
if t is not None:
t.text = ptype
if d is not None:
d.text = pdir
memory = root.find("memory")
if memory is not None:
a = memory.find("allocator")
leak = memory.find("leakage")
if a is not None:
a.text = alloc
if leak is not None:
leak.text = "disable"
tree.write(path, encoding="UTF-8", xml_declaration=True)
PY
}
wait_port_open() {
local port="$1"
for _ in $(seq 1 200); do
if ss -ltn | rg -q ":${port}\\b"; then
return 0
fi
sleep 0.1
done
return 1
}
wait_port_close() {
local port="$1"
for _ in $(seq 1 200); do
if ! ss -ltn | rg -q ":${port}\\b"; then
return 0
fi
sleep 0.1
done
return 1
}
assert_port_free() {
local port="$1"
local svc="$2"
if ss -ltn | rg -q ":${port}\\b"; then
echo "port ${port} is already in use, cannot start ${svc}" >&2
exit 1
fi
}
extract_metric() {
local line="$1"
local key="$2"
echo "$line" | sed -E "s/.*${key}=([0-9]+(\\.[0-9]+)?).*/\\1/"
}
float_add() {
awk -v a="$1" -v b="$2" 'BEGIN { printf "%.6f", a + b }'
}
float_div() {
awk -v a="$1" -v b="$2" 'BEGIN { if (b == 0) printf "0"; else printf "%.6f", a / b }'
}
join_pipe() {
local IFS='|'
echo "$*"
}
run_bench_capture() {
local host="$1"
local port="$2"
local mode="$3"
local set_cmd="$4"
local get_cmd="$5"
local key_prefix="$6"
local seed="$7"
local verify="$8"
local cmd=(
./test-redis/bench
--host "$host"
--port "$port"
--mode "$mode"
--set-cmd "$set_cmd"
--get-cmd "$get_cmd"
--requests "$REQ"
--pipeline "$PIPE"
--keyspace "$KEYSPACE"
--value-size "$VSIZE"
--seed "$seed"
--key-prefix "$key_prefix"
)
if [[ "$verify" == "1" ]]; then
cmd+=(--verify-get)
fi
local out
out=$("${cmd[@]}")
echo "$out" >&2
local line
line=$(echo "$out" | rg "\\[result\\]" | tail -n1)
if [[ -z "$line" ]]; then
echo "missing [result] line in benchmark output" >&2
return 1
fi
local qps avg elapsed
qps=$(extract_metric "$line" "qps")
avg=$(extract_metric "$line" "avg")
elapsed=$(extract_metric "$line" "elapsed")
if [[ -z "$qps" || -z "$avg" || -z "$elapsed" ]]; then
echo "failed to parse benchmark metrics: $line" >&2
return 1
fi
echo "$qps,$avg,$elapsed"
}
start_kv() {
local label="$1"
assert_port_free "$KV_PORT" "kvstore"
./kvstore >"$KV_LOG_DIR/${label}.log" 2>&1 &
KV_PID=$!
if ! wait_port_open "$KV_PORT"; then
echo "kvstore start failed for ${label}" >&2
return 1
fi
}
stop_kv() {
if [[ -n "${KV_PID:-}" ]] && kill -0 "$KV_PID" >/dev/null 2>&1; then
kill "$KV_PID" >/dev/null 2>&1 || true
wait "$KV_PID" >/dev/null 2>&1 || true
fi
KV_PID=""
wait_port_close "$KV_PORT" || true
}
start_redis() {
local strategy="$1"
local round="$2"
local port="$3"
local save_rule="$4"
local appendonly="$5"
local appendfsync="$6"
local rdir="/tmp/redis_${strategy}_${TS}_r${round}"
local conf="$rdir/redis.conf"
rm -rf "$rdir"
mkdir -p "$rdir"
assert_port_free "$port" "redis(${strategy})"
cat > "$conf" <<EOF
bind 127.0.0.1
port $port
daemonize yes
pidfile $rdir/redis.pid
dir $rdir
dbfilename dump.rdb
appendfilename appendonly.aof
save $save_rule
appendonly $appendonly
appendfsync $appendfsync
logfile $rdir/redis.log
EOF
redis-server "$conf"
if ! wait_port_open "$port"; then
echo "redis start failed for ${strategy}, check $rdir/redis.log" >&2
return 1
fi
}
stop_redis_port() {
local port="$1"
if ss -ltn | rg -q ":${port}\\b"; then
redis-cli -h "$REDIS_HOST" -p "$port" shutdown nosave >/dev/null 2>&1 || true
wait_port_close "$port" || true
fi
}
cleanup() {
stop_kv
for port in "${REDIS_PORTS[@]}"; do
stop_redis_port "$port"
done
if [[ -f "$ORIG_CONFIG_BACKUP" ]]; then
cp "$ORIG_CONFIG_BACKUP" "$CONFIG_XML"
rm -f "$ORIG_CONFIG_BACKUP"
fi
}
trap cleanup EXIT
run_kv_case() {
local strategy="$1"
local persistence="$2"
local alloc="$3"
local -a set_qps_list=() set_avg_list=() set_elapsed_list=()
local -a get_qps_list=() get_avg_list=() get_elapsed_list=()
local set_qps_sum="0" set_avg_sum="0" set_elapsed_sum="0"
local get_qps_sum="0" get_avg_sum="0" get_elapsed_sum="0"
for round in $(seq 1 "$ROUNDS"); do
local pdir_rel="data/${strategy}_${TS}_r${round}"
local pdir_abs="$ROOT_DIR/${pdir_rel}"
rm -rf "$pdir_abs"
mkdir -p "$pdir_abs"
set_config "$persistence" "$alloc" "$pdir_rel"
start_kv "${strategy}_r${round}"
local m qps avg elapsed
m=$(run_bench_capture "$KV_HOST" "$KV_PORT" "set" "RSET" "RGET" "bench:${TS}:kv:${strategy}:r${round}:set:" "$((SEED + round * 10 + 1))" 0)
IFS=',' read -r qps avg elapsed <<< "$m"
printf "kvstore,%s,%s,%s,RSET/RGET,set,%s,%s,%s,%s\n" "$strategy" "$persistence" "$alloc" "$round" "$qps" "$avg" "$elapsed" >> "$DETAIL_CSV"
set_qps_list+=("$qps")
set_avg_list+=("$avg")
set_elapsed_list+=("$elapsed")
set_qps_sum=$(float_add "$set_qps_sum" "$qps")
set_avg_sum=$(float_add "$set_avg_sum" "$avg")
set_elapsed_sum=$(float_add "$set_elapsed_sum" "$elapsed")
m=$(run_bench_capture "$KV_HOST" "$KV_PORT" "get" "RSET" "RGET" "bench:${TS}:kv:${strategy}:r${round}:get:" "$((SEED + round * 10 + 2))" 1)
IFS=',' read -r qps avg elapsed <<< "$m"
printf "kvstore,%s,%s,%s,RSET/RGET,get,%s,%s,%s,%s\n" "$strategy" "$persistence" "$alloc" "$round" "$qps" "$avg" "$elapsed" >> "$DETAIL_CSV"
get_qps_list+=("$qps")
get_avg_list+=("$avg")
get_elapsed_list+=("$elapsed")
get_qps_sum=$(float_add "$get_qps_sum" "$qps")
get_avg_sum=$(float_add "$get_avg_sum" "$avg")
get_elapsed_sum=$(float_add "$get_elapsed_sum" "$elapsed")
stop_kv
done
local set_avg_qps set_avg_avg set_avg_elapsed
local get_avg_qps get_avg_avg get_avg_elapsed
set_avg_qps=$(float_div "$set_qps_sum" "$ROUNDS")
set_avg_avg=$(float_div "$set_avg_sum" "$ROUNDS")
set_avg_elapsed=$(float_div "$set_elapsed_sum" "$ROUNDS")
get_avg_qps=$(float_div "$get_qps_sum" "$ROUNDS")
get_avg_avg=$(float_div "$get_avg_sum" "$ROUNDS")
get_avg_elapsed=$(float_div "$get_elapsed_sum" "$ROUNDS")
printf "kvstore,%s,%s,%s,RSET/RGET,set,avg,%s,%s,%s\n" "$strategy" "$persistence" "$alloc" "$set_avg_qps" "$set_avg_avg" "$set_avg_elapsed" >> "$DETAIL_CSV"
printf "kvstore,%s,%s,%s,RSET/RGET,get,avg,%s,%s,%s\n" "$strategy" "$persistence" "$alloc" "$get_avg_qps" "$get_avg_avg" "$get_avg_elapsed" >> "$DETAIL_CSV"
printf "kvstore,%s,%s,%s,RSET/RGET,set,%s,%s,%s,%s,%s,%s\n" \
"$strategy" "$persistence" "$alloc" \
"$(join_pipe "${set_qps_list[@]}")" \
"$(join_pipe "${set_avg_list[@]}")" \
"$(join_pipe "${set_elapsed_list[@]}")" \
"$set_avg_qps" "$set_avg_avg" "$set_avg_elapsed" >> "$SUMMARY_CSV"
printf "kvstore,%s,%s,%s,RSET/RGET,get,%s,%s,%s,%s,%s,%s\n" \
"$strategy" "$persistence" "$alloc" \
"$(join_pipe "${get_qps_list[@]}")" \
"$(join_pipe "${get_avg_list[@]}")" \
"$(join_pipe "${get_elapsed_list[@]}")" \
"$get_avg_qps" "$get_avg_avg" "$get_avg_elapsed" >> "$SUMMARY_CSV"
}
run_redis_case() {
local strategy="$1"
local persistence="$2"
local port="$3"
local save_rule="$4"
local appendonly="$5"
local appendfsync="$6"
local -a set_qps_list=() set_avg_list=() set_elapsed_list=()
local -a get_qps_list=() get_avg_list=() get_elapsed_list=()
local set_qps_sum="0" set_avg_sum="0" set_elapsed_sum="0"
local get_qps_sum="0" get_avg_sum="0" get_elapsed_sum="0"
for round in $(seq 1 "$ROUNDS"); do
start_redis "$strategy" "$round" "$port" "$save_rule" "$appendonly" "$appendfsync"
local m qps avg elapsed
m=$(run_bench_capture "$REDIS_HOST" "$port" "set" "SET" "GET" "bench:${TS}:redis:${strategy}:r${round}:set:" "$((SEED + round * 100 + 1))" 0)
IFS=',' read -r qps avg elapsed <<< "$m"
printf "redis,%s,%s,-,SET/GET,set,%s,%s,%s,%s\n" "$strategy" "$persistence" "$round" "$qps" "$avg" "$elapsed" >> "$DETAIL_CSV"
set_qps_list+=("$qps")
set_avg_list+=("$avg")
set_elapsed_list+=("$elapsed")
set_qps_sum=$(float_add "$set_qps_sum" "$qps")
set_avg_sum=$(float_add "$set_avg_sum" "$avg")
set_elapsed_sum=$(float_add "$set_elapsed_sum" "$elapsed")
m=$(run_bench_capture "$REDIS_HOST" "$port" "get" "SET" "GET" "bench:${TS}:redis:${strategy}:r${round}:get:" "$((SEED + round * 100 + 2))" 1)
IFS=',' read -r qps avg elapsed <<< "$m"
printf "redis,%s,%s,-,SET/GET,get,%s,%s,%s,%s\n" "$strategy" "$persistence" "$round" "$qps" "$avg" "$elapsed" >> "$DETAIL_CSV"
get_qps_list+=("$qps")
get_avg_list+=("$avg")
get_elapsed_list+=("$elapsed")
get_qps_sum=$(float_add "$get_qps_sum" "$qps")
get_avg_sum=$(float_add "$get_avg_sum" "$avg")
get_elapsed_sum=$(float_add "$get_elapsed_sum" "$elapsed")
stop_redis_port "$port"
done
local set_avg_qps set_avg_avg set_avg_elapsed
local get_avg_qps get_avg_avg get_avg_elapsed
set_avg_qps=$(float_div "$set_qps_sum" "$ROUNDS")
set_avg_avg=$(float_div "$set_avg_sum" "$ROUNDS")
set_avg_elapsed=$(float_div "$set_elapsed_sum" "$ROUNDS")
get_avg_qps=$(float_div "$get_qps_sum" "$ROUNDS")
get_avg_avg=$(float_div "$get_avg_sum" "$ROUNDS")
get_avg_elapsed=$(float_div "$get_elapsed_sum" "$ROUNDS")
printf "redis,%s,%s,-,SET/GET,set,avg,%s,%s,%s\n" "$strategy" "$persistence" "$set_avg_qps" "$set_avg_avg" "$set_avg_elapsed" >> "$DETAIL_CSV"
printf "redis,%s,%s,-,SET/GET,get,avg,%s,%s,%s\n" "$strategy" "$persistence" "$get_avg_qps" "$get_avg_avg" "$get_avg_elapsed" >> "$DETAIL_CSV"
printf "redis,%s,%s,-,SET/GET,set,%s,%s,%s,%s,%s,%s\n" \
"$strategy" "$persistence" \
"$(join_pipe "${set_qps_list[@]}")" \
"$(join_pipe "${set_avg_list[@]}")" \
"$(join_pipe "${set_elapsed_list[@]}")" \
"$set_avg_qps" "$set_avg_avg" "$set_avg_elapsed" >> "$SUMMARY_CSV"
printf "redis,%s,%s,-,SET/GET,get,%s,%s,%s,%s,%s,%s\n" \
"$strategy" "$persistence" \
"$(join_pipe "${get_qps_list[@]}")" \
"$(join_pipe "${get_avg_list[@]}")" \
"$(join_pipe "${get_elapsed_list[@]}")" \
"$get_avg_qps" "$get_avg_avg" "$get_avg_elapsed" >> "$SUMMARY_CSV"
}
append_readme_results() {
python3 - "$SUMMARY_CSV" "$README_MD" "$RUN_TIME" "$ROUNDS" "$REQ" "$PIPE" "$KEYSPACE" "$VSIZE" "$DETAIL_CSV" "$SUMMARY_CSV" <<'PY'
import csv
import os
import sys
summary_csv, readme_path, run_time, rounds, req, pipeline, keyspace, value_size, detail_csv, summary_path = sys.argv[1:]
rounds_i = int(rounds)
def split_rounds(v):
if not v:
return []
return v.split("|")
def fmt(v):
try:
return f"{float(v):.2f}"
except Exception:
return v
rows = []
with open(summary_csv, newline="", encoding="utf-8") as f:
rows = list(csv.DictReader(f))
kv_rows = [r for r in rows if r["target"] == "kvstore"]
redis_rows = [r for r in rows if r["target"] == "redis"]
lines = []
lines.append("")
lines.append(f"## run_hash_bench.sh 三轮均值复测({run_time}")
lines.append("")
lines.append(f"- 轮次:{rounds_i} 轮(取均值)")
lines.append(f"- 参数requests={req} pipeline={pipeline} keyspace={keyspace} value-size={value_size}")
lines.append(f"- 明细数据:`{os.path.relpath(detail_csv, os.path.dirname(readme_path))}`")
lines.append(f"- 汇总数据:`{os.path.relpath(summary_path, os.path.dirname(readme_path))}`")
lines.append("")
round_headers = [f"Round{i}" for i in range(1, rounds_i + 1)]
def render_table(title, items, scene_fn):
lines.append(f"### {title}")
lines.append("")
header = "| 场景 | 模式 | " + " | ".join(round_headers) + " | 均值QPS | 均值avg(us/op) | 均值elapsed(s) |"
sep = "|---|---:|" + "|".join(["---:"] * len(round_headers)) + "|---:|---:|---:|"
lines.append(header)
lines.append(sep)
for row in items:
rounds_qps = split_rounds(row["round_qps"])
while len(rounds_qps) < rounds_i:
rounds_qps.append("-")
rounds_qps = rounds_qps[:rounds_i]
scene = scene_fn(row)
lines.append(
"| {} | {} | {} | {} | {} | {} |".format(
scene,
row["mode"],
" | ".join(rounds_qps),
fmt(row["avg_qps"]),
fmt(row["avg_avg_us"]),
fmt(row["avg_elapsed_s"]),
)
)
lines.append("")
render_table(
"kvstoreRSET/RGET持久化 × allocator",
kv_rows,
lambda r: f"{r['strategy']} ({r['persistence']}, {r['allocator']})",
)
render_table(
"RedisSET/GET各持久化模式",
redis_rows,
lambda r: f"{r['strategy']} ({r['persistence']})",
)
with open(readme_path, "a", encoding="utf-8") as f:
f.write("\n".join(lines) + "\n")
PY
}
main() {
require_cmd python3
require_cmd rg
require_cmd ss
require_cmd redis-server
require_cmd redis-cli
ensure_binaries
run_kv_case "persist_mypool" "incremental" "mypool"
run_kv_case "nopersist_mypool" "none" "mypool"
run_kv_case "persist_malloc" "incremental" "malloc"
run_kv_case "nopersist_malloc" "none" "malloc"
run_redis_case "none" "none" 6381 "\"\"" "no" "everysec"
run_redis_case "rdb_default" "rdb_default" 6382 "900 1 300 10 60 10000" "no" "everysec"
run_redis_case "aof_no" "aof_no" 6383 "\"\"" "yes" "no"
run_redis_case "aof_everysec" "aof_everysec" 6384 "\"\"" "yes" "everysec"
run_redis_case "aof_always" "aof_always" 6385 "\"\"" "yes" "always"
append_readme_results
echo "DETAIL_CSV=$DETAIL_CSV"
echo "SUMMARY_CSV=$SUMMARY_CSV"
echo "README_UPDATED=$README_MD"
}
main "$@"