#!/usr/bin/env bash # User-space CPU instructions that ONE backend executes per ReadyForQuery. # # For each variant and case, the backend is measured with `perf stat -e # instructions:u -p ` while it runs N1 and then N2 'SELECT 1' statements # (each one ends with a 'Z'). (I(N2) - I(N1)) / (N2 - N1) removes the fixed # cost (setup, markers, perf attach). Variants are interleaved per repetition. # # cases: clean = session with no state # cursors100 = 100 cursors WITHOUT HOLD open in the transaction # cursors1000 = 1000 cursors WITHOUT HOLD open in the transaction # variants: base (master, BASE_PORT), pplain and prich (patched, PATCHED_PORT) # # PATCHED_BIN=/path/to/patched/bin bash rfq_instructions.sh [reps] # # Needs perf with kernel.perf_event_paranoid <= 2 (no root needed for :u). # Both servers must already be running (see the README). set -euo pipefail : "${PATCHED_BIN:?set PATCHED_BIN to the bin/ directory of the patched install}" SOCK=${SOCK:-/tmp/rfq-sock} BASE_PORT=${BASE_PORT:-54501} PATCHED_PORT=${PATCHED_PORT:-54502} REPS=${1:-5}; N1=10000; N2=30000 OUT=${OUT:-./rfq-instructions-out}; mkdir -p "$OUT"; : > "$OUT/raw.tsv" measure() { # -> instructions:u of the backend local port=$1 opts=$2 setup=$3 n=$4 dir pid psql_pid perf_pid dir=$(mktemp -d); mkfifo "$dir/in" PGOPTIONS="$opts" "$PATCHED_BIN/psql" -X -q -At -h "$SOCK" -p "$port" -U postgres -d postgres \ < "$dir/in" > "$dir/out" 2>&1 & psql_pid=$!; exec 7>"$dir/in" printf '%s\nSELECT %s, pg_backend_pid();\n' "$setup" "'PID'" >&7 for _ in $(seq 1 400); do pid=$(grep -m1 '^PID|' "$dir/out" | cut -d'|' -f2 || true); [ -n "$pid" ] && break; sleep 0.05 done if [ -z "$pid" ]; then echo "no backend pid: $(head -3 "$dir/out")" >&2 exec 7>&-; kill "$psql_pid"; rm -rf "$dir"; echo; return 0 fi perf stat -x, -e instructions:u -p "$pid" -o "$dir/perf" 2>/dev/null & perf_pid=$! sleep 0.3 { for _ in $(seq 1 "$n"); do echo 'SELECT 1;'; done; echo "SELECT 'DONE';"; } >&7 for _ in $(seq 1 6000); do grep -q '^DONE' "$dir/out" && break; sleep 0.02; done kill -INT "$perf_pid"; wait "$perf_pid" 2>/dev/null || true exec 7>&-; wait "$psql_pid" 2>/dev/null || true grep -m1 instructions "$dir/perf" | cut -d, -f1 rm -rf "$dir" } cursors() { # -> BEGIN plus DECLARE ... CURSOR (without HOLD) printf 'BEGIN; ' for i in $(seq 1 "$1"); do printf 'DECLARE c%d CURSOR FOR SELECT 1; ' "$i"; done } declare -A PORT=([base]=$BASE_PORT [pplain]=$PATCHED_PORT [prich]=$PATCHED_PORT) declare -A OPTS=([base]="" [pplain]="" [prich]="-c ready_for_query_message=rich") declare -A SETUP=([clean]="" [cursors100]="$(cursors 100)" [cursors1000]="$(cursors 1000)") for rep in $(seq 1 "$REPS"); do for case_ in clean cursors100 cursors1000; do for v in base pplain prich; do for n in $N1 $N2; do printf '%s\t%s\t%s\t%s\n' "$v" "$case_" "$n" \ "$(measure "${PORT[$v]}" "${OPTS[$v]}" "${SETUP[$case_]}" "$n")" >> "$OUT/raw.tsv" done done done echo "repetition $rep/$REPS done" >&2 done python3 - "$OUT/raw.tsv" "$N1" "$N2" <<'PY' import collections import statistics as st import sys raw, n1, n2 = sys.argv[1], int(sys.argv[2]), int(sys.argv[3]) d = collections.defaultdict(list) for line in open(raw): v, case, n, x = line.rstrip("\n").split("\t") d[(v, case, int(n))].append(int(x) if x.strip().isdigit() else None) for case in ("clean", "cursors100", "cursors1000"): print(f"== {case}: instructions:u per ReadyForQuery = (I({n2}) - I({n1})) / {n2 - n1}") per = {} for v in ("base", "pplain", "prich"): xs = [(y - x) / (n2 - n1) for x, y in zip(d[(v, case, n1)], d[(v, case, n2)]) if x and y] per[v] = xs print(f" {v:7s} n={len(xs)} median={st.median(xs):9.1f} min={min(xs):9.1f} max={max(xs):9.1f}") mb, mp, mr = (st.median(per[k]) for k in ("base", "pplain", "prich")) print(f" pplain - base = {mp - mb:+8.1f} instr/RFQ ({(mp / mb - 1) * 100:+.2f}%)") print(f" prich - pplain = {mr - mp:+8.1f} instr/RFQ ({(mr / mp - 1) * 100:+.2f}%)") PY