Add jdk27-perf: the same Spring Boot service measured on JDK 25 and JDK 27
Live heap, throughput, p99 and startup across four configurations, including opt-in and opt-out of compact object headers. Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01WzVQ6zFh9cuLPj9RSbRH8U
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
import java.net.URI;
|
||||
import java.net.http.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
|
||||
/** Closed-loop load: THREADS threads, WARMUP seconds discarded, then MEASURE seconds recorded. */
|
||||
public class Load {
|
||||
public static void main(String[] a) throws Exception {
|
||||
int port = Integer.parseInt(a[0]), warmup = Integer.parseInt(a[1]), measure = Integer.parseInt(a[2]), threads = Integer.parseInt(a[3]);
|
||||
HttpClient c = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build();
|
||||
HttpRequest rq = HttpRequest.newBuilder(URI.create("http://localhost:" + port + "/quote?n=200")).build();
|
||||
long start = System.nanoTime();
|
||||
long measureFrom = start + warmup * 1_000_000_000L, end = measureFrom + measure * 1_000_000_000L;
|
||||
List<long[]> perThread = new CopyOnWriteArrayList<>();
|
||||
ExecutorService ex = Executors.newFixedThreadPool(threads);
|
||||
List<Future<?>> fs = new ArrayList<>();
|
||||
for (int t = 0; t < threads; t++) fs.add(ex.submit(() -> {
|
||||
long[] buf = new long[2_000_000]; int n = 0;
|
||||
try {
|
||||
while (true) {
|
||||
long s = System.nanoTime();
|
||||
if (s >= end) break;
|
||||
c.send(rq, HttpResponse.BodyHandlers.discarding());
|
||||
long e = System.nanoTime();
|
||||
if (s >= measureFrom && n < buf.length) buf[n++] = e - s;
|
||||
}
|
||||
} catch (Exception e) { throw new RuntimeException(e); }
|
||||
perThread.add(Arrays.copyOf(buf, n));
|
||||
return null;
|
||||
}));
|
||||
for (Future<?> f : fs) f.get();
|
||||
ex.shutdown();
|
||||
long total = 0; for (long[] b : perThread) total += b.length;
|
||||
long[] all = new long[(int) total]; int p = 0;
|
||||
for (long[] b : perThread) { System.arraycopy(b, 0, all, p, b.length); p += b.length; }
|
||||
Arrays.sort(all);
|
||||
System.out.printf("requests=%d throughput=%.0f req/s p50=%.2f ms p99=%.2f ms p99.9=%.2f ms max=%.2f ms%n",
|
||||
total, total / (double) measure, all[(int) (total * 0.50)] / 1e6, all[(int) (total * 0.99)] / 1e6,
|
||||
all[(int) (total * 0.999)] / 1e6, all[(int) total - 1] / 1e6);
|
||||
}
|
||||
}
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
# Usage: live-set.sh <java-home> <label> [jvm args...]
|
||||
# Starts the server, forces a full GC through /live, and prints the heap still in use.
|
||||
# This is the resident data set: 3,000,000 two-int records plus the Spring Boot baseline.
|
||||
set -u
|
||||
JH=$1; LABEL=$2; shift 2
|
||||
JAR="$(dirname "$0")/../target/jdk27-perf-1.0.0.jar"
|
||||
PORT=${PORT:-18082}
|
||||
"$JH/bin/java" "$@" -jar "$JAR" --server.port=$PORT --spring.main.banner-mode=off \
|
||||
--logging.level.root=WARN > /dev/null 2>&1 &
|
||||
PID=$!
|
||||
until [ "$(curl -s -o /dev/null -m 2 -w '%{http_code}' "localhost:$PORT/stats")" = 200 ]; do sleep 0.05; done
|
||||
live=$(curl -s "localhost:$PORT/live" | sed -n 's/.*"liveHeapMiB":\([0-9]*\).*/\1/p')
|
||||
echo "$LABEL: live heap after full GC ${live} MiB"
|
||||
kill $PID 2>/dev/null; wait $PID 2>/dev/null
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env bash
|
||||
# Regenerates output/. Set J25 and J27. REPS defaults to 3. Configs are interleaved per repetition
|
||||
# so slow drift on the machine hits every configuration equally. Timings are indicative only.
|
||||
set -u
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
OUT="$HERE/../output"; mkdir -p "$OUT"
|
||||
: "${J25:?}" "${J27:?}"
|
||||
export JDK_CLIENT="$J25"
|
||||
REPS=${REPS:-3}
|
||||
{
|
||||
for p in "25|$J25" "27|$J27"; do
|
||||
v=${p%%|*}; h=${p#*|}
|
||||
echo "== JDK $v"
|
||||
"$h/bin/java" -version 2>&1 | grep -v Picked | sed -n 1p
|
||||
"$h/bin/java" -XX:+PrintFlagsFinal -version 2>&1 | grep -E ' (UseCompactObjectHeaders|UseG1GC|UseSerialGC|UseZGC|MaxHeapSize) ' | sed 's/ \{2,\}/ /g'
|
||||
echo
|
||||
done
|
||||
} > "$OUT/01-defaults.txt"
|
||||
: > "$OUT/02-runs.txt"
|
||||
for r in $(seq 1 "$REPS"); do
|
||||
"$HERE/run-one.sh" "$J25" "jdk25 defaults" >> "$OUT/02-runs.txt" 2>&1
|
||||
"$HERE/run-one.sh" "$J25" "jdk25 +CompactObjectHeaders" -XX:+UseCompactObjectHeaders >> "$OUT/02-runs.txt" 2>&1
|
||||
"$HERE/run-one.sh" "$J27" "jdk27 defaults" >> "$OUT/02-runs.txt" 2>&1
|
||||
"$HERE/run-one.sh" "$J27" "jdk27 -CompactObjectHeaders" -XX:-UseCompactObjectHeaders >> "$OUT/02-runs.txt" 2>&1
|
||||
done
|
||||
"$HERE/summarize.sh" "$OUT/02-runs.txt" > "$OUT/03-summary.txt"
|
||||
# 04 - resident data set after a forced full GC (this is where compact headers show up)
|
||||
{
|
||||
for r in 1 2 3; do
|
||||
"$HERE/live-set.sh" "$J25" "jdk25 defaults"
|
||||
"$HERE/live-set.sh" "$J25" "jdk25 +CompactObjectHeaders" -XX:+UseCompactObjectHeaders
|
||||
"$HERE/live-set.sh" "$J27" "jdk27 defaults"
|
||||
"$HERE/live-set.sh" "$J27" "jdk27 -CompactObjectHeaders" -XX:-UseCompactObjectHeaders
|
||||
done
|
||||
} > "$OUT/04-live-set.txt" 2>&1
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
# Usage: run-one.sh <java-home> <label> [jvm args...]
|
||||
# One cold start, then 15 s warmup + 30 s measured load, then a /stats snapshot. One result block.
|
||||
set -u
|
||||
JH=$1; LABEL=$2; shift 2
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
JAR="$HERE/../target/jdk27-perf-1.0.0.jar"
|
||||
PORT=${PORT:-18081}
|
||||
CLIENT=${JDK_CLIENT:-$JH}
|
||||
t0=$(date +%s%N)
|
||||
"$JH/bin/java" "$@" -jar "$JAR" --server.port=$PORT --spring.main.banner-mode=off \
|
||||
--logging.level.root=WARN > /dev/null 2>&1 &
|
||||
PID=$!
|
||||
until [ "$(curl -s -o /dev/null -m 2 -w '%{http_code}' "localhost:$PORT/stats")" = 200 ]; do sleep 0.02; done
|
||||
t1=$(date +%s%N)
|
||||
echo "== $LABEL"
|
||||
echo "startup_ms=$(( (t1 - t0) / 1000000 ))"
|
||||
"$CLIENT/bin/java" "$HERE/Load.java" $PORT 15 30 ${THREADS:-4} 2>&1 | grep -v Picked
|
||||
curl -s "localhost:$PORT/stats"; echo
|
||||
kill $PID 2>/dev/null; wait $PID 2>/dev/null
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
# Usage: summarize.sh output/02-runs.txt -> median of each metric per label
|
||||
awk '
|
||||
/^== /{label=substr($0,4); next}
|
||||
/^startup_ms=/{split($0,a,"="); st[label]=st[label] " " a[2]}
|
||||
/^requests=/{for(i=1;i<=NF;i++){split($i,kv,"="); if(kv[1]=="throughput")tp[label]=tp[label] " " kv[2]; if(kv[1]=="p99")p99[label]=p99[label] " " kv[2]}}
|
||||
/^\{"javaVersion"/{match($0,/"heapUsedMiB":[0-9]+/); h=substr($0,RSTART+14,RLENGTH-14); hu[label]=hu[label] " " h; match($0,/"rssMiB":[0-9]+/); r=substr($0,RSTART+9,RLENGTH-9); rss[label]=rss[label] " " r}
|
||||
function med(s, n,arr,i,j,t){n=split(s,arr," "); for(i=1;i<=n;i++)for(j=i+1;j<=n;j++)if(arr[j]+0<arr[i]+0){t=arr[i];arr[i]=arr[j];arr[j]=t}; return arr[int((n+1)/2)]}
|
||||
END{for(l in st) printf "%s | startup %s ms | throughput %s req/s | p99 %s ms | heap used %s MiB | RSS %s MiB\n", l, med(st[l]), med(tp[l]), med(p99[l]), med(hu[l]), med(rss[l])}' "$1" | sort
|
||||
Reference in New Issue
Block a user