Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/performance-ab.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ on:
- layout
- scheduler-reflection
- ray-proxy
- riichi-reaction
- sichuan-hu
- region-apply
- layout-cache
- infra
forks:
description: Trusted CI override for JMH forks (blank uses base config)
Expand Down Expand Up @@ -157,6 +161,10 @@ jobs:
"performance-layout": "layout",
"performance-scheduler-reflection": "scheduler-reflection",
"performance-ray-proxy": "ray-proxy",
"performance-riichi-reaction": "riichi-reaction",
"performance-sichuan-hu": "sichuan-hu",
"performance-region-apply": "region-apply",
"performance-layout-cache": "layout-cache",
}
selected = [profile for label, profile in choices.items() if label in labels]
if len(selected) != 1:
Expand Down
226 changes: 226 additions & 0 deletions perf/ab/gate-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,88 @@
"top/ellan/mahjong/runtime/ServerScheduler.class"
]
},
"riichi-reaction": {
"include": "^top\\.ellan\\.mahjong\\.riichi\\.RiichiReactionBenchmark\\.(?:structurallyImpossibleCall|structurallyImpossibleRonOnly|firstPossiblePonAnalysis)$",
"benchmark_ids": [
"riichi.impossible.time",
"riichi.impossible.alloc",
"riichi.ron-only.time",
"riichi.ron-only.alloc",
"riichi.pon.time",
"riichi.pon.alloc"
],
"minimum_passes": 2,
"must_pass": [
"riichi.impossible.time",
"riichi.ron-only.time"
],
"required_classes": [
"top/ellan/mahjong/riichi/RiichiPlayerAnalysisState.class",
"top/ellan/mahjong/riichi/RiichiPlayerState.class"
]
},
"sichuan-hu": {
"include": "^top\\.ellan\\.mahjong\\.table\\.core\\.round\\.SichuanHuEvaluatorBenchmark\\.(?:waitingTilesStandardHand|evaluateStandardWin|canWinWithFixedMeld)$",
"benchmark_ids": [
"sichuan.waiting.time",
"sichuan.waiting.alloc",
"sichuan.evaluate.time",
"sichuan.evaluate.alloc",
"sichuan.can-win.time",
"sichuan.can-win.alloc"
],
"minimum_passes": 3,
"must_pass": [
"sichuan.waiting.time",
"sichuan.evaluate.time",
"sichuan.can-win.time"
],
"required_classes": [
"top/ellan/mahjong/table/core/round/SichuanHuEvaluator.class"
]
},
"region-apply": {
"include": "^top\\.ellan\\.mahjong\\.table\\.render\\.RegionRenderApplyBenchmark\\.(?:planAndDeferRepresentativeTableRegions|applyStableStartedTable|precomputeCompleteStartedTableRegions)$",
"benchmark_ids": [
"region.apply-deferred.time",
"region.apply-deferred.alloc",
"region.apply-stable.time",
"region.apply-stable.alloc",
"region.complete-fingerprints.time",
"region.complete-fingerprints.alloc"
],
"minimum_passes": 3,
"must_pass": [
"region.apply-deferred.time",
"region.apply-stable.time",
"region.complete-fingerprints.time"
],
"required_classes": [
"top/ellan/mahjong/table/render/TableRegionDisplayCoordinator.class",
"top/ellan/mahjong/table/render/TableRegionFingerprintService.class"
]
},
"layout-cache": {
"include": "^top\\.ellan\\.mahjong\\.table\\.core\\.SessionRenderLayoutCacheBenchmark\\.(?:stableSessionCacheHit|representativeSeatComponentMiss|representativeWallComponentMiss)$",
"benchmark_ids": [
"layout-cache.hit.time",
"layout-cache.hit.alloc",
"layout-cache.seat-miss.time",
"layout-cache.seat-miss.alloc",
"layout-cache.wall-miss.time",
"layout-cache.wall-miss.alloc"
],
"minimum_passes": 3,
"must_pass": [
"layout-cache.hit.time",
"layout-cache.seat-miss.time",
"layout-cache.wall-miss.time"
],
"required_classes": [
"top/ellan/mahjong/render/layout/TableRenderLayout.class",
"top/ellan/mahjong/table/core/MahjongTableSession.class"
]
},
"ray-proxy": {
"include": "^top\\.ellan\\.mahjong\\.perf\\.RayProxyCoordinatorBenchmark\\.proxyPlan(?:1Viewer|4Viewers|32Viewers)$",
"benchmark_ids": [
Expand Down Expand Up @@ -414,6 +496,150 @@
"metric": "gc.alloc.rate.norm",
"direction": "lower"
},
{
"id": "riichi.impossible.time",
"benchmark": "top.ellan.mahjong.riichi.RiichiReactionBenchmark.structurallyImpossibleCall",
"metric": "primary",
"direction": "lower"
},
{
"id": "riichi.impossible.alloc",
"benchmark": "top.ellan.mahjong.riichi.RiichiReactionBenchmark.structurallyImpossibleCall",
"metric": "gc.alloc.rate.norm",
"direction": "lower"
},
{
"id": "riichi.ron-only.time",
"benchmark": "top.ellan.mahjong.riichi.RiichiReactionBenchmark.structurallyImpossibleRonOnly",
"metric": "primary",
"direction": "lower"
},
{
"id": "riichi.ron-only.alloc",
"benchmark": "top.ellan.mahjong.riichi.RiichiReactionBenchmark.structurallyImpossibleRonOnly",
"metric": "gc.alloc.rate.norm",
"direction": "lower"
},
{
"id": "riichi.pon.time",
"benchmark": "top.ellan.mahjong.riichi.RiichiReactionBenchmark.firstPossiblePonAnalysis",
"metric": "primary",
"direction": "lower"
},
{
"id": "riichi.pon.alloc",
"benchmark": "top.ellan.mahjong.riichi.RiichiReactionBenchmark.firstPossiblePonAnalysis",
"metric": "gc.alloc.rate.norm",
"direction": "lower"
},
{
"id": "sichuan.waiting.time",
"benchmark": "top.ellan.mahjong.table.core.round.SichuanHuEvaluatorBenchmark.waitingTilesStandardHand",
"metric": "primary",
"direction": "lower"
},
{
"id": "sichuan.waiting.alloc",
"benchmark": "top.ellan.mahjong.table.core.round.SichuanHuEvaluatorBenchmark.waitingTilesStandardHand",
"metric": "gc.alloc.rate.norm",
"direction": "lower"
},
{
"id": "sichuan.evaluate.time",
"benchmark": "top.ellan.mahjong.table.core.round.SichuanHuEvaluatorBenchmark.evaluateStandardWin",
"metric": "primary",
"direction": "lower"
},
{
"id": "sichuan.evaluate.alloc",
"benchmark": "top.ellan.mahjong.table.core.round.SichuanHuEvaluatorBenchmark.evaluateStandardWin",
"metric": "gc.alloc.rate.norm",
"direction": "lower"
},
{
"id": "sichuan.can-win.time",
"benchmark": "top.ellan.mahjong.table.core.round.SichuanHuEvaluatorBenchmark.canWinWithFixedMeld",
"metric": "primary",
"direction": "lower"
},
{
"id": "sichuan.can-win.alloc",
"benchmark": "top.ellan.mahjong.table.core.round.SichuanHuEvaluatorBenchmark.canWinWithFixedMeld",
"metric": "gc.alloc.rate.norm",
"direction": "lower"
},
{
"id": "region.apply-deferred.time",
"benchmark": "top.ellan.mahjong.table.render.RegionRenderApplyBenchmark.planAndDeferRepresentativeTableRegions",
"metric": "primary",
"direction": "lower"
},
{
"id": "region.apply-deferred.alloc",
"benchmark": "top.ellan.mahjong.table.render.RegionRenderApplyBenchmark.planAndDeferRepresentativeTableRegions",
"metric": "gc.alloc.rate.norm",
"direction": "lower"
},
{
"id": "region.apply-stable.time",
"benchmark": "top.ellan.mahjong.table.render.RegionRenderApplyBenchmark.applyStableStartedTable",
"metric": "primary",
"direction": "lower"
},
{
"id": "region.apply-stable.alloc",
"benchmark": "top.ellan.mahjong.table.render.RegionRenderApplyBenchmark.applyStableStartedTable",
"metric": "gc.alloc.rate.norm",
"direction": "lower"
},
{
"id": "region.complete-fingerprints.time",
"benchmark": "top.ellan.mahjong.table.render.RegionRenderApplyBenchmark.precomputeCompleteStartedTableRegions",
"metric": "primary",
"direction": "lower"
},
{
"id": "region.complete-fingerprints.alloc",
"benchmark": "top.ellan.mahjong.table.render.RegionRenderApplyBenchmark.precomputeCompleteStartedTableRegions",
"metric": "gc.alloc.rate.norm",
"direction": "lower"
},
{
"id": "layout-cache.hit.time",
"benchmark": "top.ellan.mahjong.table.core.SessionRenderLayoutCacheBenchmark.stableSessionCacheHit",
"metric": "primary",
"direction": "lower"
},
{
"id": "layout-cache.hit.alloc",
"benchmark": "top.ellan.mahjong.table.core.SessionRenderLayoutCacheBenchmark.stableSessionCacheHit",
"metric": "gc.alloc.rate.norm",
"direction": "lower"
},
{
"id": "layout-cache.seat-miss.time",
"benchmark": "top.ellan.mahjong.table.core.SessionRenderLayoutCacheBenchmark.representativeSeatComponentMiss",
"metric": "primary",
"direction": "lower"
},
{
"id": "layout-cache.seat-miss.alloc",
"benchmark": "top.ellan.mahjong.table.core.SessionRenderLayoutCacheBenchmark.representativeSeatComponentMiss",
"metric": "gc.alloc.rate.norm",
"direction": "lower"
},
{
"id": "layout-cache.wall-miss.time",
"benchmark": "top.ellan.mahjong.table.core.SessionRenderLayoutCacheBenchmark.representativeWallComponentMiss",
"metric": "primary",
"direction": "lower"
},
{
"id": "layout-cache.wall-miss.alloc",
"benchmark": "top.ellan.mahjong.table.core.SessionRenderLayoutCacheBenchmark.representativeWallComponentMiss",
"metric": "gc.alloc.rate.norm",
"direction": "lower"
},
{
"id": "ray.viewers1.time",
"benchmark": "top.ellan.mahjong.perf.RayProxyCoordinatorBenchmark.proxyPlan1Viewer",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package top.ellan.mahjong.riichi;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import top.ellan.mahjong.riichi.model.MahjongTile;
import top.ellan.mahjong.riichi.model.TileInstance;

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class RiichiReactionBenchmark {
@Benchmark
public ReactionOptions structurallyImpossibleCall(NoCallState state) {
return state.player.reactionOptionsFor(state.discard, false, false);
}

@Benchmark
public ReactionOptions structurallyImpossibleRonOnly(NoCallState state) {
return state.player.reactionOptionsFor(state.discard, false, true);
}

@Benchmark
public ReactionOptions firstPossiblePonAnalysis(PossibleCallState state) {
return state.player.reactionOptionsFor(state.discard, false, false);
}

@State(Scope.Thread)
public static class NoCallState {
private RiichiPlayerState player;
private TileInstance discard;
private Map<?, ?> furoReactions;

@Setup(Level.Trial)
public void setUp() throws ReflectiveOperationException {
this.player = player(List.of(
MahjongTile.M1, MahjongTile.M4, MahjongTile.M7,
MahjongTile.P1, MahjongTile.P4, MahjongTile.P7,
MahjongTile.S1, MahjongTile.S4, MahjongTile.S7,
MahjongTile.SOUTH, MahjongTile.WEST, MahjongTile.WHITE_DRAGON, MahjongTile.GREEN_DRAGON
));
this.discard = tile(100, MahjongTile.EAST);
if (this.player.reactionOptionsFor(this.discard, false, false) != null) {
throw new IllegalStateException("No-call reaction fixture changed");
}
Field cacheField = RiichiPlayerAnalysisState.class.getDeclaredField("cachedFuroReactions");
cacheField.setAccessible(true);
this.furoReactions = (Map<?, ?>) cacheField.get(this.player);
}

@Setup(Level.Invocation)
public void clearFuroReaction() {
this.furoReactions.clear();
}
}

@State(Scope.Thread)
public static class PossibleCallState {
private RiichiPlayerState player;
private TileInstance discard;

@Setup(Level.Invocation)
public void setUp() {
this.player = player(List.of(
MahjongTile.M5_RED, MahjongTile.M5, MahjongTile.M1, MahjongTile.M9,
MahjongTile.P1, MahjongTile.P9, MahjongTile.S1, MahjongTile.S9,
MahjongTile.EAST, MahjongTile.SOUTH, MahjongTile.WEST,
MahjongTile.WHITE_DRAGON, MahjongTile.GREEN_DRAGON
));
this.discard = tile(200, MahjongTile.M5);
}
}

private static RiichiPlayerState player(List<MahjongTile> hand) {
RiichiPlayerState player = new RiichiPlayerState("benchmark", "benchmark", false);
for (int index = 0; index < hand.size(); index++) {
player.getHands().add(tile(index, hand.get(index)));
}
return player;
}

private static TileInstance tile(int id, MahjongTile tile) {
return new TileInstance(new UUID(0L, id + 1L), tile);
}
}
Loading