diff --git a/buftable_bench/.gitignore b/buftable_bench/.gitignore new file mode 100644 index 00000000000..2988e8c3b49 --- /dev/null +++ b/buftable_bench/.gitignore @@ -0,0 +1,2 @@ +_work/ +.builds/ diff --git a/buftable_bench/README.md b/buftable_bench/README.md new file mode 100644 index 00000000000..b3b92a38a35 --- /dev/null +++ b/buftable_bench/README.md @@ -0,0 +1,57 @@ +# buftable_bench + +A micro-benchmark of PostgreSQL's shared **buffer mapping table** ops. The SQL function +`buftable_bench_probe(n, rounds, random)` calls `BufTableLookup` / `BufTableInsert` / +`BufTableDelete` **directly** on the live shared table and **bulk-times** each op (one rdtsc pair ÷ +N) — no `ReadBuffer`, no page copy, no per-op rdtsc. It operates on free buffer slots + synthetic +tags and restores the table afterward. + +The whole thing is one self-contained test-module extension — the **only change vs stock +PostgreSQL**. x86_64 only (uses `rdtsc`). + +## Files + +``` +buftable_bench/ +├── run.sh # build the current branch (release) + run + print numbers +├── instrumentation/buftable_bench_module/ # the extension (drop into src/test/modules/) +│ ├── buftable_bench.c # buftable_bench_probe() — the probe +│ ├── buftable_bench--1.0.sql # CREATE FUNCTION buftable_bench_probe(...) +│ ├── buftable_bench.control +│ ├── Makefile +│ └── meson.build +├── scripts/ +│ ├── bench_probe.sh # run the probe against one explicit install +│ └── compare_probe.sh [sizes...] # flat-vs-dynahash A/B (needs two builds) +└── results/ + ├── probe_summary.txt # recorded A/B numbers + └── compare_probe.results.txt +``` +(`.builds/` and `_work/` are local build/scratch caches — gitignored.) + +## How to run + +**The current branch** (builds this repo's `HEAD` as a release, caches it per commit, runs, and +prints the per-op numbers): + +```sh +buftable_bench/run.sh # default shared_buffers = 1GB +buftable_bench/run.sh 256MB 4GB 16GB # any sizes +``` +First run for a commit builds (~30 s–few min); later runs are instant. Output (to stdout), labeled +`branch@commit`: +``` +-- shared_buffers=1GB (n=104857 keys) -- + op | avg_ns | count + insert | 18.555 | 1048570 + lookup_hit | 23.064 | 1048570 + lookup_miss | 21.902 | 1048570 + delete | 24.569 | 1048570 +``` +Knobs: `BUFTABLE_PROBE_ROUNDS` (default 10); `PG_CONFIG=/path/bin/pg_config` to skip the build and +use an existing install. (`run.sh` is **single-arm** — it measures the current branch only.) + +**Flat-vs-dynahash A/B** (optional; needs two installs at `~/pg-bench/{flat,dyna}`): +```sh +buftable_bench/scripts/compare_probe.sh 256MB 4GB 16GB +``` diff --git a/buftable_bench/instrumentation/buftable_bench_module/Makefile b/buftable_bench/instrumentation/buftable_bench_module/Makefile new file mode 100644 index 00000000000..75ca30c79fe --- /dev/null +++ b/buftable_bench/instrumentation/buftable_bench_module/Makefile @@ -0,0 +1,22 @@ +# src/test/modules/buftable_bench/Makefile + +PGFILEDESC = "buftable_bench - rdtsc micro-benchmark for the buffer mapping table" + +MODULE_big = buftable_bench +OBJS = \ + $(WIN32RES) \ + buftable_bench.o + +EXTENSION = buftable_bench +DATA = buftable_bench--1.0.sql + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = src/test/modules/buftable_bench +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/buftable_bench/instrumentation/buftable_bench_module/buftable_bench--1.0.sql b/buftable_bench/instrumentation/buftable_bench_module/buftable_bench--1.0.sql new file mode 100644 index 00000000000..f81c0e60d5d --- /dev/null +++ b/buftable_bench/instrumentation/buftable_bench_module/buftable_bench--1.0.sql @@ -0,0 +1,17 @@ +/* src/test/modules/buftable_bench/buftable_bench--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION buftable_bench" to load this file. \quit + +CREATE FUNCTION buftable_bench_probe( + IN n int8, + IN rounds int8 DEFAULT 1, + IN random bool DEFAULT true, + OUT op text, + OUT avg_ns float8, + OUT count int8) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'buftable_bench_probe' +LANGUAGE C; + +REVOKE ALL ON FUNCTION buftable_bench_probe(int8, int8, bool) FROM PUBLIC; diff --git a/buftable_bench/instrumentation/buftable_bench_module/buftable_bench.c b/buftable_bench/instrumentation/buftable_bench_module/buftable_bench.c new file mode 100644 index 00000000000..67d09b3579c --- /dev/null +++ b/buftable_bench/instrumentation/buftable_bench_module/buftable_bench.c @@ -0,0 +1,255 @@ +/*------------------------------------------------------------------------- + * + * buftable_bench.c + * Pollution-free in-place benchmark of the shared buffer mapping table. + * + * Throwaway micro-benchmark module (NOT for upstream). One SQL function, + * buftable_bench_probe(n, rounds), times lookup (hit+miss), insert, and delete + * by calling BufTable{Insert,Lookup,Delete} DIRECTLY on the real shared table + * -- no ReadBuffer, no 8 KB page copy, no per-op rdtsc. Each op's loop is + * bulk-timed with a single rdtsc pair, so the measurement isn't polluted by + * page-copy cache traffic or per-op timer overhead. + * + * It works against STOCK PostgreSQL: it only calls the existing public + * BufTable* / BufTableHashCode functions, so no core changes are needed -- the + * two arms being compared are just two stock builds (flat table vs dynahash). + * + * Insert/delete mutate the live table, so we only use FREE buffer slots (their + * mapping entry is guaranteed empty) and restore the table afterward. + * + * x86_64 only (rdtsc). + * + * IDENTIFICATION + * src/test/modules/buftable_bench/buftable_bench.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "fmgr.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "portability/instr_time.h" +#include "storage/buf_internals.h" +#include "storage/bufmgr.h" +#include "utils/builtins.h" +#include "utils/tuplestore.h" + +PG_MODULE_MAGIC; + +PG_FUNCTION_INFO_V1(buftable_bench_probe); + +#define BUFTABLE_BENCH_PROBE_COLS 3 + +/* synthetic relfilenodes for bench tags — unlikely to collide with anything real */ +#define BENCH_SPC_OID 0xB0B0 +#define BENCH_DB_OID 0xB1B1 +#define BENCH_REL_PRESENT ((RelFileNumber) 0x7E570001) +#define BENCH_REL_ABSENT ((RelFileNumber) 0x7E570002) + +static inline uint64 +bench_rdtsc(void) +{ + uint32 lo, + hi; + + __asm__ __volatile__("rdtsc" : "=a"(lo), "=d"(hi)::"memory"); + return ((uint64) hi << 32) | lo; +} + +/* cycles per nanosecond, measured over a ~2 ms wall-clock window */ +static double +probe_calibrate(void) +{ + instr_time w0, + w1, + d; + uint64 c0, + c1; + double ns; + + INSTR_TIME_SET_CURRENT(w0); + c0 = bench_rdtsc(); + do + { + INSTR_TIME_SET_CURRENT(w1); + d = w1; + INSTR_TIME_SUBTRACT(d, w0); + } while (INSTR_TIME_GET_DOUBLE(d) < 0.002); + c1 = bench_rdtsc(); + ns = INSTR_TIME_GET_DOUBLE(d) * 1e9; + return (ns > 0.0) ? (double) (c1 - c0) / ns : 0.0; +} + +/* + * buftable_bench_probe(n, rounds) -> SETOF (op text, avg_ns float8, count int8) + * + * Rows: insert, lookup_hit, lookup_miss, delete. See file header. + */ +Datum +buftable_bench_probe(PG_FUNCTION_ARGS) +{ + int64 n = PG_GETARG_INT64(0); + int64 rounds = PG_ARGISNULL(1) ? 1 : PG_GETARG_INT64(1); + bool randomize = PG_ARGISNULL(2) ? true : PG_GETARG_BOOL(2); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + int *bufids; + int64 *ord; + BufferTag *ptag, + *atag; + uint32 *phash, + *ahash; + int64 nfree = 0; + double cyc_per_ns, + denom; + uint64 ins = 0, + lkh = 0, + lkm = 0, + del = 0; + volatile int64 sink = 0; + RelFileLocator rp = {.spcOid = BENCH_SPC_OID,.dbOid = BENCH_DB_OID,.relNumber = BENCH_REL_PRESENT}; + RelFileLocator ra = {.spcOid = BENCH_SPC_OID,.dbOid = BENCH_DB_OID,.relNumber = BENCH_REL_ABSENT}; + const char *names[4] = {"insert", "lookup_hit", "lookup_miss", "delete"}; + double avg[4]; + + if (n <= 0 || rounds <= 0) + ereport(ERROR, (errmsg("n and rounds must be positive"))); + + InitMaterializedSRF(fcinfo, 0); + + /* collect up to n FREE buffer slots (mapping entry guaranteed empty) */ + bufids = palloc(sizeof(int) * n); + for (int i = 0; i < NBuffers && nfree < n; i++) + { + BufferDesc *desc = GetBufferDescriptor(i); + uint64 state = pg_atomic_read_u64(&desc->state); + + if (!(state & BM_TAG_VALID)) + bufids[nfree++] = i; + } + n = nfree; + if (n == 0) + ereport(ERROR, (errmsg("no free buffers to probe with"))); + + /* build present + absent tags and their hashes */ + ptag = palloc(sizeof(BufferTag) * n); + atag = palloc(sizeof(BufferTag) * n); + phash = palloc(sizeof(uint32) * n); + ahash = palloc(sizeof(uint32) * n); + for (int64 j = 0; j < n; j++) + { + InitBufferTag(&ptag[j], &rp, MAIN_FORKNUM, (BlockNumber) j); + InitBufferTag(&atag[j], &ra, MAIN_FORKNUM, (BlockNumber) j); + phash[j] = BufTableHashCode(&ptag[j]); + ahash[j] = BufTableHashCode(&atag[j]); + } + + /* + * Iteration order over the keys: identity (sequential) or a Fisher-Yates + * shuffle (random). A shuffled order makes the timed loops visit keys in + * an order uncorrelated with where their entries/elements live, so BOTH + * arms' entry/element access is random (not just the bucket access, which + * the hash already scatters). Done once in setup (untimed). + */ + ord = palloc(sizeof(int64) * n); + for (int64 i = 0; i < n; i++) + ord[i] = i; + if (randomize) + { + uint64 rng = 0x9E3779B97F4A7C15ULL; /* fixed seed -> reproducible */ + + for (int64 i = n - 1; i > 0; i--) + { + int64 k, + tmp; + + rng ^= rng << 13; + rng ^= rng >> 7; + rng ^= rng << 17; + k = (int64) (rng % (uint64) (i + 1)); + tmp = ord[i]; + ord[i] = ord[k]; + ord[k] = tmp; + } + } + + cyc_per_ns = probe_calibrate(); + + PG_TRY(); + { + for (int64 r = 0; r < rounds; r++) + { + uint64 t0, + t1; + + t0 = bench_rdtsc(); + for (int64 i = 0; i < n; i++) + { + int64 j = ord[i]; + + BufTableInsert(&ptag[j], phash[j], bufids[j]); + } + t1 = bench_rdtsc(); + ins += t1 - t0; + + t0 = bench_rdtsc(); + for (int64 i = 0; i < n; i++) + { + int64 j = ord[i]; + + sink += BufTableLookup(&ptag[j], phash[j]); + } + t1 = bench_rdtsc(); + lkh += t1 - t0; + + t0 = bench_rdtsc(); + for (int64 i = 0; i < n; i++) + { + int64 j = ord[i]; + + sink += BufTableLookup(&atag[j], ahash[j]); + } + t1 = bench_rdtsc(); + lkm += t1 - t0; + + t0 = bench_rdtsc(); + for (int64 i = 0; i < n; i++) + { + int64 j = ord[i]; + + BufTableDelete(&ptag[j], phash[j]); + } + t1 = bench_rdtsc(); + del += t1 - t0; + } + } + PG_CATCH(); + { + /* best-effort restore: remove any present tag still mapped */ + for (int64 j = 0; j < n; j++) + if (BufTableLookup(&ptag[j], phash[j]) >= 0) + BufTableDelete(&ptag[j], phash[j]); + PG_RE_THROW(); + } + PG_END_TRY(); + + denom = (double) n * (double) rounds * cyc_per_ns; + avg[0] = ins / denom; + avg[1] = lkh / denom; + avg[2] = lkm / denom; + avg[3] = del / denom; + + for (int i = 0; i < 4; i++) + { + Datum values[BUFTABLE_BENCH_PROBE_COLS]; + bool nulls[BUFTABLE_BENCH_PROBE_COLS] = {0}; + + values[0] = CStringGetTextDatum(names[i]); + values[1] = Float8GetDatum(avg[i]); + values[2] = Int64GetDatum(n * rounds); + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + } + + (void) sink; + return (Datum) 0; +} diff --git a/buftable_bench/instrumentation/buftable_bench_module/buftable_bench.control b/buftable_bench/instrumentation/buftable_bench_module/buftable_bench.control new file mode 100644 index 00000000000..aae6cb1810f --- /dev/null +++ b/buftable_bench/instrumentation/buftable_bench_module/buftable_bench.control @@ -0,0 +1,4 @@ +comment = 'rdtsc micro-benchmark for the shared buffer mapping table' +default_version = '1.0' +module_pathname = '$libdir/buftable_bench' +relocatable = true diff --git a/buftable_bench/instrumentation/buftable_bench_module/meson.build b/buftable_bench/instrumentation/buftable_bench_module/meson.build new file mode 100644 index 00000000000..752773841b2 --- /dev/null +++ b/buftable_bench/instrumentation/buftable_bench_module/meson.build @@ -0,0 +1,22 @@ +# Copyright (c) 2024-2026, PostgreSQL Global Development Group + +buftable_bench_sources = files( + 'buftable_bench.c', +) + +if host_system == 'windows' + buftable_bench_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'buftable_bench', + '--FILEDESC', 'buftable_bench - rdtsc micro-benchmark for the buffer mapping table',]) +endif + +buftable_bench = shared_module('buftable_bench', + buftable_bench_sources, + kwargs: pg_test_mod_args, +) +test_install_libs += buftable_bench + +test_install_data += files( + 'buftable_bench.control', + 'buftable_bench--1.0.sql', +) diff --git a/buftable_bench/results/compare_probe.results.txt b/buftable_bench/results/compare_probe.results.txt new file mode 100644 index 00000000000..c55903132b8 --- /dev/null +++ b/buftable_bench/results/compare_probe.results.txt @@ -0,0 +1,24 @@ +RESULT dyna 256MB insert 28.995 262140 +RESULT dyna 256MB lookup_hit 21.134 262140 +RESULT dyna 256MB lookup_miss 21.257 262140 +RESULT dyna 256MB delete 21.798 262140 +RESULT flat 256MB insert 13.245 262140 +RESULT flat 256MB lookup_hit 16.222 262140 +RESULT flat 256MB lookup_miss 17.498 262140 +RESULT flat 256MB delete 17.805 262140 +RESULT dyna 4GB insert 66.845 4194300 +RESULT dyna 4GB lookup_hit 49.673 4194300 +RESULT dyna 4GB lookup_miss 39.825 4194300 +RESULT dyna 4GB delete 56.704 4194300 +RESULT flat 4GB insert 30.506 4194300 +RESULT flat 4GB lookup_hit 31.717 4194300 +RESULT flat 4GB lookup_miss 28.786 4194300 +RESULT flat 4GB delete 34.971 4194300 +RESULT dyna 16GB insert 104.300 16777210 +RESULT dyna 16GB lookup_hit 82.074 16777210 +RESULT dyna 16GB lookup_miss 62.069 16777210 +RESULT dyna 16GB delete 88.639 16777210 +RESULT flat 16GB insert 55.068 16777210 +RESULT flat 16GB lookup_hit 62.942 16777210 +RESULT flat 16GB lookup_miss 47.260 16777210 +RESULT flat 16GB delete 69.308 16777210 diff --git a/buftable_bench/results/probe_summary.txt b/buftable_bench/results/probe_summary.txt new file mode 100644 index 00000000000..a6ac9ba855d --- /dev/null +++ b/buftable_bench/results/probe_summary.txt @@ -0,0 +1,29 @@ +# Pollution-free in-place A/B of the REAL shared buffer mapping table. +# buftable_bench_probe(n, rounds, random): direct BufTable{Insert,Lookup,Delete} +# calls, bulk-timed (one rdtsc pair per phase), NO ReadBuffer/page-copy, NO +# per-op rdtsc. random=true (DEFAULT): keys visited in a shuffled order so the +# bucket AND entry/element accesses are random for both arms. +# speedup = dynahash/flat (>1 = flat faster). n ~ 0.8*NBuffers, rounds=10. +# +# === RANDOM access (default) === +# sb op dynahash_ns flat_ns speedup +# 256MB insert 28.99 13.25 2.19x +# 256MB lookup_hit 21.13 16.22 1.30x +# 256MB lookup_miss 21.26 17.50 1.21x +# 256MB delete 21.80 17.81 1.22x +# 4GB insert 66.85 30.51 2.19x +# 4GB lookup_hit 49.67 31.72 1.57x +# 4GB lookup_miss 39.83 28.79 1.38x +# 4GB delete 56.70 34.97 1.62x +# 16GB insert 104.30 55.07 1.89x +# 16GB lookup_hit 82.07 62.94 1.30x +# 16GB lookup_miss 62.07 47.26 1.31x +# 16GB delete 88.64 69.31 1.28x +# +# Flat is FASTER on EVERY op at EVERY size (1.2-2.2x). Random access raises +# absolute ns and compresses the ratio vs sequential (a common-mode key-fetch +# cost, equal for both arms) -- the more conservative, realistic number. +# +# For reference, the earlier SEQUENTIAL order (random=false) gave larger ratios: +# insert 2.4/2.8/3.5x, lookup_hit 1.6/1.7/2.3x, lookup_miss 1.3/1.3/1.7x, +# delete 1.4/1.6/2.2x @256MB/4GB/16GB (entry/element access was prefetch-friendly). diff --git a/buftable_bench/run.sh b/buftable_bench/run.sh new file mode 100755 index 00000000000..5ac38801863 --- /dev/null +++ b/buftable_bench/run.sh @@ -0,0 +1,97 @@ +#!/usr/bin/env bash +# +# run.sh [size ...] +# +# Build the CURRENTLY CHECKED-OUT BRANCH of this repo (its HEAD) as a release, +# then run the buffer-mapping-table probe and print the per-op numbers for it. +# Single arm (just this branch's buf_table.c) — for the flat-vs-dynahash A/B +# use scripts/compare_probe.sh. +# +# The build is cached per commit under buftable_bench/.builds/, so the +# first run for a commit takes a few minutes and later runs are instant. +# It exports the committed tree (git archive) into a temp dir to build, so your +# working checkout is never touched. +# +# Env: PG_CONFIG (use this prebuilt install instead of building), +# BUFTABLE_PROBE_ROUNDS (default 10), BUFTABLE_BENCH_WORK. +set -euo pipefail + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +MODSRC="$HERE/instrumentation/buftable_bench_module" +WORK="${BUFTABLE_BENCH_WORK:-$HERE/_work}"; mkdir -p "$WORK" +ROUNDS="${BUFTABLE_PROBE_ROUNDS:-10}" +SIZES="${*:-1GB}" + +repo="$(git -C "$HERE" rev-parse --show-toplevel)" +commit="$(git -C "$repo" rev-parse --short HEAD)" +branch="$(git -C "$repo" rev-parse --abbrev-ref HEAD)" +label="$branch@$commit" + +# ---- locate or build a release install of the current HEAD ------------------ +if [[ -n "${PG_CONFIG:-}" ]]; then + BIN="$("$PG_CONFIG" --bindir)" + echo "==> using PG_CONFIG build: $("$PG_CONFIG" --version) [$BIN]" >&2 +else + prefix="$HERE/.builds/$commit" + if [[ -x "$prefix/bin/pg_config" ]] && \ + [[ -e "$("$prefix/bin/pg_config" --pkglibdir)/buftable_bench.so" ]]; then + echo "==> reusing cached release build of $label [$prefix]" >&2 + else + echo "==> building $label as release (first run for this commit, ~2-4 min)..." >&2 + src="$(mktemp -d "$WORK/src.$commit.XXXX")" + git -C "$repo" archive HEAD | tar -x -C "$src" + mkdir -p "$src/src/test/modules/buftable_bench" + cp "$MODSRC"/* "$src/src/test/modules/buftable_bench"/ + ( + cd "$src" + ./configure --prefix="$prefix" --without-icu --without-zlib --without-readline >/dev/null + make -s -j"$(nproc)" install >/dev/null + make -s -C src/test/modules/buftable_bench install >/dev/null + ) || { echo "build failed; see $src" >&2; exit 1; } + rm -rf "$src" + echo "==> built $label" >&2 + fi + BIN="$prefix/bin" +fi + +# ---- run the probe per size ------------------------------------------------- +size_to_bytes() { + local s="${1^^}" + case "$s" in + *GB) echo $(( ${s%GB} * 1024 * 1024 * 1024 ));; + *MB) echo $(( ${s%MB} * 1024 * 1024 ));; + *KB) echo $(( ${s%KB} * 1024 ));; + *) echo "$s";; + esac +} + +echo +echo "===== buftable_bench: $label (random access, rounds=$ROUNDS) =====" +for SIZE in $SIZES; do + DATADIR="$(mktemp -d "$WORK/pgdata.${SIZE}.XXXX")" + SOCKDIR="$(mktemp -d /tmp/pgb.XXXXXX)" + N="$(awk -v b="$(size_to_bytes "$SIZE")" 'BEGIN{printf "%d", 0.8*b/8192}')" + + "$BIN/initdb" -D "$DATADIR" --no-sync -A trust >/dev/null 2>&1 + cat >> "$DATADIR/postgresql.conf" </dev/null + + echo "-- shared_buffers=$SIZE (n=$N keys) --" + "$BIN/psql" -h "$SOCKDIR" -d postgres -q -P pager=off \ + -c "CREATE EXTENSION buftable_bench;" \ + -c "SELECT op, round(avg_ns::numeric,3) AS avg_ns, count + FROM buftable_bench_probe($N, $ROUNDS) + ORDER BY array_position(ARRAY['insert','lookup_hit','lookup_miss','delete'], op);" + + "$BIN/pg_ctl" -D "$DATADIR" -m immediate stop >/dev/null 2>&1 || true + rm -rf "$DATADIR" "$SOCKDIR" +done +echo "================================================================" diff --git a/buftable_bench/scripts/bench_probe.sh b/buftable_bench/scripts/bench_probe.sh new file mode 100755 index 00000000000..fde619911b2 --- /dev/null +++ b/buftable_bench/scripts/bench_probe.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# +# bench_probe.sh +# +# Pollution-free, in-place benchmark of the REAL shared buffer mapping table for +# lookup (hit+miss), insert, and delete, via buftable_bench_probe() — which calls +# BufTable{Insert,Lookup,Delete} directly (no ReadBuffer, no 8 KB page copy, no +# per-op rdtsc; each op's loop is bulk-timed). No table/prewarm needed: it uses +# free buffer slots + synthetic tags and restores the table afterward. +# +# Emits "RESULT ". +# Env: BUFTABLE_PROBE_ROUNDS (default 10). +set -euo pipefail + +PREFIX="${1:?usage: bench_probe.sh }" +SIZE="${2:?usage: bench_probe.sh }" +ROUNDS="${BUFTABLE_PROBE_ROUNDS:-10}" +ARM="$(basename "$PREFIX")" +BIN="$PREFIX/bin" + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SCRATCH="${BUFTABLE_BENCH_WORK:-$HERE/_work}"; mkdir -p "$SCRATCH" +DATADIR="$(mktemp -d "$SCRATCH/pgdata.${ARM}.${SIZE}.pr.XXXX")" +SOCKDIR="$(mktemp -d /tmp/pgb.XXXXXX)" +LOG="$DATADIR/server.log" + +log() { echo "[$ARM $SIZE probe] $*" >&2; } +cleanup() { "$BIN/pg_ctl" -D "$DATADIR" -m immediate stop >/dev/null 2>&1 || true; rm -rf "$DATADIR" "$SOCKDIR"; } +trap cleanup EXIT + +size_to_bytes() { + local s="${1^^}" + case "$s" in + *GB) echo $(( ${s%GB} * 1024 * 1024 * 1024 ));; + *MB) echo $(( ${s%MB} * 1024 * 1024 ));; + *) echo "$s";; + esac +} +SB="$(size_to_bytes "$SIZE")" +N="$(awk -v b="$SB" 'BEGIN{printf "%d", 0.8*b/8192}')" # ~0.8x NBuffers -> load factor ~1 + +"$BIN/initdb" -D "$DATADIR" --no-sync -A trust >/dev/null 2>&1 +cat >> "$DATADIR/postgresql.conf" </dev/null + +OUT="$("$BIN/psql" -h "$SOCKDIR" -d postgres -q -X -At -F' ' -v ON_ERROR_STOP=1 \ + -c "CREATE EXTENSION buftable_bench;" \ + -c "SELECT 'R', op, round(avg_ns::numeric,3), count FROM buftable_bench_probe($N, $ROUNDS);" 2>&1)" || { + log "psql failed:"; echo "$OUT" >&2; exit 1; +} + +echo "$OUT" | awk -v arm="$ARM" -v sz="$SIZE" '$1=="R"{printf "RESULT %s %s %s %s %s\n", arm, sz, $2, $3, $4}' +log "done" diff --git a/buftable_bench/scripts/compare_probe.sh b/buftable_bench/scripts/compare_probe.sh new file mode 100755 index 00000000000..fe98a8ab149 --- /dev/null +++ b/buftable_bench/scripts/compare_probe.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# +# compare_probe.sh [size ...] +# +# Pollution-free in-place A/B of the shared buffer mapping table for all three +# ops (insert / lookup_hit / lookup_miss / delete), via buftable_bench_probe(). +# Prints, per size, dynahash vs flat avg_ns and speedup = dynahash/flat (>1 = +# flat faster). Sizes default "256MB 4GB 16GB" or $BUFTABLE_CAPI_SIZES or args. +set -euo pipefail + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BENCH="$HERE/bench_probe.sh" +FLAT=/home/dhruv.aron/pg-bench/flat +DYNA=/home/dhruv.aron/pg-bench/dyna +SCRATCH="${BUFTABLE_BENCH_WORK:-$HERE/_work}"; mkdir -p "$SCRATCH" +RESULTS="$SCRATCH/compare_probe.results.txt"; : > "$RESULTS" + +if [[ $# -gt 0 ]]; then SIZES="$*"; else SIZES="${BUFTABLE_CAPI_SIZES:-256MB 4GB 16GB}"; fi + +for size in $SIZES; do + for prefix in "$DYNA" "$FLAT"; do + echo ">>> $(basename "$prefix") @ $size" >&2 + bash "$BENCH" "$prefix" "$size" | grep '^RESULT ' >> "$RESULTS" + done +done + +echo +echo "===== pollution-free direct-probe A/B (real shared table) =====" +awk ' +{ arm=$2; size=$3; op=$4; v[size,op,arm]=$5; cnt[size,op,arm]=$6; + if(!(size in seen)){seen[size]=1; order[++ni]=size} } +END{ + split("insert lookup_hit lookup_miss delete", ops, " "); + for(i=1;i<=ni;i++){ s=order[i]; + printf "\n=== shared_buffers = %s (samples/op %s) ===\n", s, cnt[s,"insert","flat"]; + printf "%-12s %12s %12s %14s\n","op","dynahash ns","flat ns","speedup(dh/ft)"; + for(j=1;j<=4;j++){ o=ops[j]; + dh=v[s,o,"dyna"]; ft=v[s,o,"flat"]; + if(dh==""||ft==""){ printf "%-12s %12s %12s %14s\n",o,(dh==""?"-":dh),(ft==""?"-":ft),"n/a"; continue } + printf "%-12s %12.3f %12.3f %13.2fx\n", o, dh, ft, (ft>0?dh/ft:0); + } + } +}' "$RESULTS" +echo "==============================================================="