Skip to content

Commit 1eae5ba

Browse files
committed
Harden production visibility A-B methodology
1 parent 62287ac commit 1eae5ba

2 files changed

Lines changed: 87 additions & 21 deletions

File tree

.github/workflows/benchmark.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ jobs:
7373
run: |
7474
java -Xms2G -Xmx2G -XX:+UseG1GC -jar server.jar --nogui | tee benchmark-server.log
7575
test -f plugins/InteractionVisualizerBenchmark/benchmark-results.jsonl
76+
test -f plugins/InteractionVisualizerBenchmark/benchmark-complete.txt
77+
EXPECTED_RESULTS=$(cat plugins/InteractionVisualizerBenchmark/benchmark-complete.txt)
78+
ACTUAL_RESULTS=$(wc -l < plugins/InteractionVisualizerBenchmark/benchmark-results.jsonl)
79+
test "$ACTUAL_RESULTS" -eq "$EXPECTED_RESULTS"
7680
7781
- name: Publish benchmark results
7882
if: always()
@@ -81,6 +85,7 @@ jobs:
8185
name: interactionvisualizer-ab-${{ github.sha }}
8286
path: |
8387
benchmark-server/plugins/InteractionVisualizerBenchmark/benchmark-results.jsonl
88+
benchmark-server/plugins/InteractionVisualizerBenchmark/benchmark-complete.txt
8489
benchmark-server/benchmark-server.log
8590
if-no-files-found: warn
8691
retention-days: 30

benchmark/src/main/java/com/loohp/interactionvisualizer/entities/DroppedItemBenchmarkPlugin.java

Lines changed: 82 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@
2222
import org.bukkit.util.Vector;
2323

2424
import java.io.IOException;
25+
import java.io.UncheckedIOException;
2526
import java.nio.file.Files;
2627
import java.nio.file.Path;
28+
import java.nio.file.StandardOpenOption;
2729
import java.util.ArrayList;
2830
import java.util.Arrays;
2931
import java.util.HashMap;
@@ -50,6 +52,8 @@ public final class DroppedItemBenchmarkPlugin extends JavaPlugin {
5052
private static volatile long blackhole;
5153

5254
private final List<String> results = new ArrayList<>();
55+
private Path resultsOutput;
56+
private Path completionOutput;
5357

5458
@Override
5559
public void onEnable() {
@@ -69,6 +73,11 @@ private void runBenchmarks() throws IOException {
6973
World world = Bukkit.getWorlds().getFirst();
7074
world.setAutoSave(false);
7175
world.getEntitiesByClass(Item.class).forEach(Item::remove);
76+
Files.createDirectories(getDataFolder().toPath());
77+
resultsOutput = getDataFolder().toPath().resolve("benchmark-results.jsonl");
78+
completionOutput = getDataFolder().toPath().resolve("benchmark-complete.txt");
79+
Files.writeString(resultsOutput, "");
80+
Files.deleteIfExists(completionOutput);
7281

7382
for (int itemCount : List.of(250, 1000, 2500)) {
7483
benchmarkCramping(world, itemCount, false);
@@ -87,10 +96,8 @@ private void runBenchmarks() throws IOException {
8796
reportTokenBucket(pending, 128, 32);
8897
}
8998

90-
Files.createDirectories(getDataFolder().toPath());
91-
Path output = getDataFolder().toPath().resolve("benchmark-results.jsonl");
92-
Files.write(output, results);
93-
getLogger().info("Wrote " + results.size() + " A/B results to " + output.toAbsolutePath());
99+
Files.writeString(completionOutput, Integer.toString(results.size()));
100+
getLogger().info("Wrote " + results.size() + " A/B results to " + resultsOutput.toAbsolutePath());
94101
}
95102

96103
private void benchmarkCramping(World world, int itemCount, boolean clustered) {
@@ -198,20 +205,11 @@ private void benchmarkVisibility(int itemCount, int viewerCount, String distribu
198205
throw new IllegalStateException("Visibility A/B mismatch: linear=" + expectedLabels
199206
+ ", legacy=" + legacyLabels + ", candidate=" + candidateLabels);
200207
}
201-
Comparison productionComparison;
202-
Comparison referenceComparison;
203-
if (((itemCount + viewerCount + seed) & 1) == 0) {
204-
productionComparison = compare(legacyProduction, candidate);
205-
referenceComparison = compare(linearReference, candidate);
206-
} else {
207-
referenceComparison = compare(linearReference, candidate);
208-
productionComparison = compare(legacyProduction, candidate);
209-
}
208+
boolean baselineStarts = ((itemCount * 31L + viewerCount * 17L + seed) & 1L) == 0L;
209+
Comparison productionComparison = compare(legacyProduction, candidate, baselineStarts);
210210
double reduction = itemCount == 0 ? 0.0D : 100.0D * (itemCount - expectedLabels) / itemCount;
211211
reportVisibilityComparison("visibility-production-ab", "legacy-production-viewer-grid", true,
212212
distribution, seed, itemCount, viewerCount, productionComparison, expectedLabels, reduction);
213-
reportVisibilityComparison("visibility-linear-reference", "linear-viewer-snapshot-any", false,
214-
distribution, seed, itemCount, viewerCount, referenceComparison, expectedLabels, reduction);
215213
}
216214

217215
private void reportVisibilityComparison(String benchmark, String baseline, boolean baselineUsesGrid,
@@ -224,16 +222,35 @@ private void reportVisibilityComparison(String benchmark, String baseline, boole
224222
"\"candidate\":\"production-primitive-viewer-index\"," +
225223
"\"candidateStorage\":\"primitive-soa\"," +
226224
"\"indexRebuiltPerOperation\":true,\"candidateUsesGrid\":false," +
227-
"\"sampleTargetNs\":%d,\"measurementRounds\":%d," +
225+
"\"sampleTargetNs\":%d,\"warmupRounds\":%d,\"measurementRounds\":%d," +
226+
"\"roundOrder\":\"%s\"," +
228227
"\"baselineMedianNs\":%d,\"baselineP95Ns\":%d," +
229228
"\"candidateMedianNs\":%d,\"candidateP95Ns\":%d,\"speedup\":%.3f," +
229+
"\"baselineFirstBaselineMedianNs\":%d," +
230+
"\"baselineFirstCandidateMedianNs\":%d," +
231+
"\"baselineFirstBaselineP95Ns\":%d," +
232+
"\"baselineFirstCandidateP95Ns\":%d," +
233+
"\"baselineFirstSpeedup\":%.3f," +
234+
"\"candidateFirstBaselineMedianNs\":%d," +
235+
"\"candidateFirstCandidateMedianNs\":%d," +
236+
"\"candidateFirstBaselineP95Ns\":%d," +
237+
"\"candidateFirstCandidateP95Ns\":%d," +
238+
"\"candidateFirstSpeedup\":%.3f," +
230239
"\"baselineRoundsNs\":%s,\"candidateRoundsNs\":%s," +
231240
"\"allLabels\":%d,\"activeLabels\":%d," +
232241
"\"labelReductionPct\":%.3f}",
233242
benchmark, distribution, seed, itemCount, viewerCount, VIEW_DISTANCE, baseline, baselineUsesGrid,
234-
SAMPLE_TARGET_NANOS, MEASUREMENT_ROUNDS,
243+
SAMPLE_TARGET_NANOS, WARMUP_ROUNDS, MEASUREMENT_ROUNDS, comparison.roundOrder(),
235244
comparison.baseline().median(), comparison.baseline().p95(),
236245
comparison.candidate().median(), comparison.candidate().p95(), comparison.speedup(),
246+
comparison.baselineFirst().baseline().median(),
247+
comparison.baselineFirst().candidate().median(),
248+
comparison.baselineFirst().baseline().p95(), comparison.baselineFirst().candidate().p95(),
249+
comparison.baselineFirst().speedup(),
250+
comparison.candidateFirst().baseline().median(),
251+
comparison.candidateFirst().candidate().median(),
252+
comparison.candidateFirst().baseline().p95(), comparison.candidateFirst().candidate().p95(),
253+
comparison.candidateFirst().speedup(),
237254
Arrays.toString(comparison.baselineRoundsNs()), Arrays.toString(comparison.candidateRoundsNs()),
238255
itemCount, activeLabels, reduction);
239256
report(result);
@@ -315,8 +332,13 @@ private void reportTokenBucket(int pending, int capacity, int refill) {
315332
}
316333

317334
private static Comparison compare(LongSupplier baseline, LongSupplier candidate) {
335+
return compare(baseline, candidate, true);
336+
}
337+
338+
private static Comparison compare(LongSupplier baseline, LongSupplier candidate, boolean baselineStarts) {
318339
for (int round = 0; round < WARMUP_ROUNDS; round++) {
319-
if ((round & 1) == 0) {
340+
boolean baselineFirst = ((round & 1) == 0) == baselineStarts;
341+
if (baselineFirst) {
320342
time(baseline);
321343
time(candidate);
322344
} else {
@@ -326,8 +348,11 @@ private static Comparison compare(LongSupplier baseline, LongSupplier candidate)
326348
}
327349
long[] baselineTimes = new long[MEASUREMENT_ROUNDS];
328350
long[] candidateTimes = new long[MEASUREMENT_ROUNDS];
351+
boolean[] baselineFirstRounds = new boolean[MEASUREMENT_ROUNDS];
329352
for (int round = 0; round < MEASUREMENT_ROUNDS; round++) {
330-
if ((round & 1) == 0) {
353+
boolean baselineFirst = ((round & 1) == 0) == baselineStarts;
354+
baselineFirstRounds[round] = baselineFirst;
355+
if (baselineFirst) {
331356
baselineTimes[round] = time(baseline);
332357
candidateTimes[round] = time(candidate);
333358
} else {
@@ -337,7 +362,10 @@ private static Comparison compare(LongSupplier baseline, LongSupplier candidate)
337362
}
338363
Samples baselineSamples = Samples.of(baselineTimes);
339364
Samples candidateSamples = Samples.of(candidateTimes);
340-
return new Comparison(baselineSamples, candidateSamples, baselineTimes, candidateTimes,
365+
return new Comparison(baselineSamples, candidateSamples,
366+
OrderStratum.of(baselineTimes, candidateTimes, baselineFirstRounds, true),
367+
OrderStratum.of(baselineTimes, candidateTimes, baselineFirstRounds, false),
368+
baselineStarts ? "ABBA" : "BAAB", baselineTimes, candidateTimes,
341369
(double) baselineSamples.median() / Math.max(1L, candidateSamples.median()));
342370
}
343371

@@ -355,6 +383,11 @@ private static long time(LongSupplier operation) {
355383

356384
private void report(String result) {
357385
results.add(result);
386+
try {
387+
Files.writeString(resultsOutput, result + System.lineSeparator(), StandardOpenOption.APPEND);
388+
} catch (IOException exception) {
389+
throw new UncheckedIOException("Unable to append benchmark result", exception);
390+
}
358391
getLogger().info("IV_BENCH_RESULT " + result);
359392
}
360393

@@ -367,7 +400,35 @@ private static Samples of(long[] values) {
367400
}
368401
}
369402

370-
private record Comparison(Samples baseline, Samples candidate, long[] baselineRoundsNs,
403+
private record OrderStratum(Samples baseline, Samples candidate, double speedup) {
404+
405+
private static OrderStratum of(long[] baselineTimes, long[] candidateTimes,
406+
boolean[] baselineFirstRounds, boolean baselineFirst) {
407+
int size = 0;
408+
for (boolean value : baselineFirstRounds) {
409+
if (value == baselineFirst) {
410+
size++;
411+
}
412+
}
413+
long[] baseline = new long[size];
414+
long[] candidate = new long[size];
415+
int output = 0;
416+
for (int index = 0; index < baselineFirstRounds.length; index++) {
417+
if (baselineFirstRounds[index] == baselineFirst) {
418+
baseline[output] = baselineTimes[index];
419+
candidate[output] = candidateTimes[index];
420+
output++;
421+
}
422+
}
423+
Samples baselineSamples = Samples.of(baseline);
424+
Samples candidateSamples = Samples.of(candidate);
425+
return new OrderStratum(baselineSamples, candidateSamples,
426+
(double) baselineSamples.median() / Math.max(1L, candidateSamples.median()));
427+
}
428+
}
429+
430+
private record Comparison(Samples baseline, Samples candidate, OrderStratum baselineFirst,
431+
OrderStratum candidateFirst, String roundOrder, long[] baselineRoundsNs,
371432
long[] candidateRoundsNs, double speedup) {
372433
}
373434

0 commit comments

Comments
 (0)