Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
e2a1992
build the perf benchmark
triceo Aug 24, 2026
282e10d
introduce the CI
triceo Aug 24, 2026
3384c8f
temporary
triceo Aug 24, 2026
387e0ec
another temporary
triceo Aug 24, 2026
66b69c5
fix baseline
triceo Aug 24, 2026
60bf031
throw earlier
triceo Aug 24, 2026
a345c3f
fix assign/unassign
triceo Aug 24, 2026
c360eb7
sort table
triceo Aug 24, 2026
08d9aa1
Increase warmup and measurements
triceo Aug 24, 2026
129f70c
Make sure we only test moves, not score
triceo Aug 24, 2026
92277b3
Moar memory
triceo Aug 24, 2026
d6d4913
Fix memory use
triceo Aug 24, 2026
fc167c1
Bump forks to 4
triceo Aug 24, 2026
e602378
Fix another harness bug
triceo Aug 24, 2026
2ef67f1
Improve the table
triceo Aug 24, 2026
7b1576d
Switch to 8 forks
triceo Aug 24, 2026
b379036
Switch to 16 forks
triceo Aug 24, 2026
7acdd52
Finally go to 20 forks
triceo Aug 25, 2026
08d304b
improve the table
triceo Aug 31, 2026
36b2e69
feat(moveprovider): split move generation from the commit
triceo Aug 31, 2026
c8f8d57
feat(moveprovider): list the score margins below the table
triceo Aug 31, 2026
385ec49
docs(moveprovider): explain the undetermined verdict in the legend
triceo Aug 31, 2026
fe9a9fc
Refactor
triceo Sep 1, 2026
019e7b5
Tweak
triceo Sep 1, 2026
0d0c152
Fix the measurements
triceo Sep 1, 2026
b683650
chore: TEMP raise inline budget to test fork bimodality
triceo Sep 2, 2026
a70a025
fix(moveprovider): restore the wider drawOnly tolerance
triceo Sep 2, 2026
dc860a1
chore: TEMP test -Xbatch against fork bimodality
triceo Sep 2, 2026
363beea
fix(moveprovider): compile synchronously to stop the fork split
triceo Sep 2, 2026
9ca8277
Bump to 2.6.0
triceo Sep 3, 2026
92af4d9
Hide moves
triceo Sep 4, 2026
8b49d62
Prepare for merging
triceo Sep 4, 2026
4bcccb1
Formatting
triceo Sep 5, 2026
383c58d
Bech to benchmarking again
triceo Sep 6, 2026
4cabe9f
Eliminate VDSO
triceo Sep 6, 2026
6129281
feat(ci): improve scoredirector benchmark reporting
triceo Sep 8, 2026
0e2efad
Final changes
triceo Sep 8, 2026
ea81652
Slightly increase tolerance
triceo Sep 8, 2026
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
113 changes: 79 additions & 34 deletions .github/workflows/performance_move_provider.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,11 @@
# Both baseline and SUT (Software Under Test) are built from source first,
# with their binaries uploaded as artifacts.
# This is done on GitHub infrastructure, to achieve maximum parallelization.
#
# Unlike the ScoreDirector Perf Regression Test, a benchmark subjob produces no report of its own -
# it only uploads its raw JMH results as an artifact. After every subjob finishes, one summarizing
# job downloads all of them and aggregates them into a single table, which is the only report this
# workflow produces.
#
# The benchmark subjobs run on GitHub-hosted ARM runners, to maximize parallelization. This trades
# away the stable, dedicated machine the ScoreDirector workflow uses; if the resulting numbers turn
# out too noisy, this can be revisited.
#
# Every job in this workflow checks out this very repository at the ref selected in the "Use
# workflow from" dropdown - NOT a hardcoded branch. This is what lets the workflow be dispatched
# from an unmerged feature branch and actually run that branch's code.
# This is done on GitHub ARM-based infrastructure, to achieve maximum parallelization.
#
# A benchmark subjob produces no report of its own -
# it only uploads its raw JMH results as an artifact.
# After every subjob finishes, one summarizing job downloads all of them and aggregates them into a single table,
# which is the only report this workflow produces.
name: MoveProvider Perf Regression Test
permissions:
contents: read
Expand All @@ -28,7 +19,7 @@ on:
required: true
baseline:
description: 'Baseline branch or tag (branches need to use 999-SNAPSHOT)'
default: 'v2.5.0'
default: 'v2.6.0'
required: true
jdk_branch:
description: 'JDK version'
Expand Down Expand Up @@ -84,7 +75,7 @@ jobs:

build_baseline:
needs: decisions
runs-on: ubuntu-latest # Leverage massive parallelization of Github-hosted runners.
runs-on: ubuntu-latest
steps:
- name: Checkout timefold-solver-benchmarks
uses: actions/checkout@v7
Expand Down Expand Up @@ -116,9 +107,24 @@ jobs:
uses: actions/checkout@v7
with:
repository: TimefoldAI/timefold-solver-enterprise
ref: ${{ github.event.inputs.baseline }}
ref: main
token: ${{ secrets.BENCHMARK_PUBLISH_TOKEN }}
path: ./timefold-solver-enterprise
- name: Switch to correct Enterprise branch if it exists
if: needs.decisions.outputs.needs_snapshot_built == 'true'
working-directory: ./timefold-solver-enterprise
shell: bash
env:
TARGET_BRANCH: ${{ github.event.inputs.baseline }}
run: |
# Match a branch OR a tag named $TARGET_BRANCH (no --heads): the baseline solver branch may
# not exist in enterprise at all (e.g. it needs no enterprise changes). Falls back to the
# already-checked-out `main` if no match exists.
if git ls-remote --exit-code origin "$TARGET_BRANCH" > /dev/null; then
git fetch --depth=1 origin "$TARGET_BRANCH"
git checkout -B "$TARGET_BRANCH" FETCH_HEAD
fi
git status
- name: Quickly build timefold-solver-enterprise
if: needs.decisions.outputs.needs_snapshot_built == 'true'
working-directory: ./timefold-solver-enterprise
Expand Down Expand Up @@ -157,7 +163,7 @@ jobs:
if-no-files-found: error

build_sut:
runs-on: ubuntu-latest # Leverage massive parallelization of Github-hosted runners.
runs-on: ubuntu-latest
steps:
- name: Checkout timefold-solver-benchmarks
uses: actions/checkout@v7
Expand Down Expand Up @@ -273,9 +279,20 @@ jobs:
with:
path: ./timefold-solver-benchmarks

# Fine-tuned for stability, same as the ScoreDirector benchmark. Selects exactly the one
# provider family/name this subjob is responsible for; the other family is left blank so that
# AbstractConfiguration#parseExamples resolves it to "run nothing" rather than "run everything".
# - Fine-tuned for stability, same as the ScoreDirector benchmark.
# Selects exactly the one provider family/name this subjob is responsible for;
# the other family is left blank so that AbstractConfiguration#parseExamples resolves it to "run nothing"
# rather than "run everything".
# - One measurement iteration for each fork, on purpose.
# A JVM compiles the move-generation loop into one of a few shapes,
# keeps that shape for its whole life,
# and the shapes differ by up to 40 % -
# so the five iterations inside a fork were five copies of one sample.
# JMH divides the spread of all of them by the square root of their count,
# which made the reported error about 2.6x too small.
# With one iteration for each fork, every sample is its own JVM,
# and JMH's own number is right with no post-processing.
# - Four warmup iterations; established experimentally.
- name: Configure the benchmark
working-directory: ./timefold-solver-benchmarks
shell: bash
Expand All @@ -286,8 +303,8 @@ jobs:
NAME="${PROVIDER#*:}"
{
echo "forks=20"
echo "warmup_iterations=5"
echo "measurement_iterations=5"
echo "warmup_iterations=4"
echo "measurement_iterations=1"
echo "relative_score_error_threshold=0.02"
if [ "$FAMILY" = basic ]; then
echo "basic_move_provider=$NAME"
Expand Down Expand Up @@ -329,6 +346,13 @@ jobs:
cp benchmarks-baseline.jar target/benchmarks.jar
./run-moveprovider.sh
cp results/moveprovider/$RUN_ID/results.json baseline-results.json
# JMH does not fail the process when one benchmark trial errors out - it just omits it
# from the results, so a crashed run can still exit 0. Both scenarios must be present.
COUNT=$(jq 'length' baseline-results.json)
if [ "$COUNT" -ne 2 ]; then
echo "::error::Baseline run produced $COUNT/2 benchmark results - it did not produce metrics."
exit 1
fi

- name: (SUT) Setup JDK and Maven
uses: actions/setup-java@v6.0.0
Expand All @@ -348,18 +372,21 @@ jobs:
cp benchmarks-sut.jar target/benchmarks.jar
./run-moveprovider.sh
cp results/moveprovider/$RUN_ID/results.json sut-results.json
# JMH does not fail the process when one benchmark trial errors out - it just omits it
# from the results, so a crashed run can still exit 0. Both scenarios must be present.
COUNT=$(jq 'length' sut-results.json)
if [ "$COUNT" -ne 2 ]; then
echo "::error::SUT run produced $COUNT/2 benchmark results - it did not produce metrics."
exit 1
fi

- name: Archive benchmark data
uses: actions/upload-artifact@v7
with:
name: data-${{ env.PROVIDER_ID }}
path: |
./timefold-solver-benchmarks/moveprovider-benchmark.properties
./timefold-solver-benchmarks/baseline-results.json
./timefold-solver-benchmarks/sut-results.json
if-no-files-found: error

# Uploaded before "Archive benchmark data" (and given an id)
# so its artifact-url output can be recorded into a file
# and ride along inside the data-* artifact -
# that's the only artifact the summarize job downloads,
# so this is how a row in its table can link straight to these assets.
- name: Archive benchmark assets
id: archive_assets
uses: actions/upload-artifact@v7
with:
name: assets-${{ env.PROVIDER_ID }}
Expand All @@ -370,7 +397,25 @@ jobs:
./timefold-solver-benchmarks/results/moveprovider/${{ env.SUT_DIR }}/**/*.html
if-no-files-found: warn

summarize:
# Empty when the step above found no assets (if-no-files-found: warn uploads nothing, so
# artifact-url is unset); summarize-moveprovider.py treats an empty/missing file as "no link".
- name: Record the assets artifact URL
shell: bash
run: |
echo "${{ steps.archive_assets.outputs.artifact-url }}" > ./timefold-solver-benchmarks/assets-url.txt

- name: Archive benchmark data
uses: actions/upload-artifact@v7
with:
name: data-${{ env.PROVIDER_ID }}
path: |
./timefold-solver-benchmarks/moveprovider-benchmark.properties
./timefold-solver-benchmarks/baseline-results.json
./timefold-solver-benchmarks/sut-results.json
./timefold-solver-benchmarks/assets-url.txt
if-no-files-found: error

report:
needs: [ decisions, benchmark ]
if: always() && needs.decisions.result == 'success'
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/performance_quickstarts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ on:
required: true
baseline:
description: 'Baseline branch or tag'
default: 'v2.5.0'
default: 'v2.6.0'
required: true
jdk_branch:
description: 'JDK version'
Expand Down
Loading
Loading