From 2aff631ef09eb787345c71e5938801b103686ce3 Mon Sep 17 00:00:00 2001 From: Rory Graves Date: Mon, 15 Jun 2026 11:46:58 +0100 Subject: [PATCH] Scaffold termflow-bench JMH module Add modules/termflow-bench with the sbt-jmh plugin (0.4.7) and a trivial NoOpBench to prove the harness runs. The bench project is intentionally NOT added to root.aggregate(...), so it sits outside ciCheck and is built only by explicit sbt "bench/..." invocations and the bench CI workflow. Verified: sbt "bench/Jmh/run -i 1 -wi 1 -f1" compiles and runs. --- build.sbt | 15 +++++++++ modules/termflow-bench/README.md | 32 +++++++++++++++++++ .../main/scala/termflow/bench/NoOpBench.scala | 28 ++++++++++++++++ project/plugins.sbt | 1 + 4 files changed, 76 insertions(+) create mode 100644 modules/termflow-bench/README.md create mode 100644 modules/termflow-bench/src/main/scala/termflow/bench/NoOpBench.scala diff --git a/build.sbt b/build.sbt index 1936bad..ad226c9 100644 --- a/build.sbt +++ b/build.sbt @@ -254,6 +254,21 @@ lazy val termflowSample = (project in file("modules/termflow-sample")) publish / skip := true ) +// JMH microbenchmark module. Deliberately NOT added to root.aggregate(...) so +// it is excluded from `ciCheck` (scalafmtCheckAll/scalafixAll/test follow +// project aggregation). It is built only by explicit `sbt "bench/..."` +// invocations and the dedicated bench CI workflow. The val MUST stay named +// `bench` so `sbt "bench/Jmh/run"` works verbatim. +lazy val bench = (project in file("modules/termflow-bench")) + .enablePlugins(JmhPlugin) + .dependsOn(termflowScreen, termflowApp, termflowWidgets, termflowSample, termflowTestkit) + .settings( + name := "termflow-bench", + commonSettings, + publish / skip := true, + coverageEnabled := false + ) + addCommandAlias("ciCheck", ";scalafmtCheckAll;scalafixAll --check;test") // `coverage`/`coverageReport` are sbt-scoverage *commands* — they // can't be invoked with the `project/key` selector syntax. The pattern diff --git a/modules/termflow-bench/README.md b/modules/termflow-bench/README.md new file mode 100644 index 0000000..dd3671e --- /dev/null +++ b/modules/termflow-bench/README.md @@ -0,0 +1,32 @@ +# termflow-bench + +JMH microbenchmarks for TermFlow (Scala 3 only). + +## Not part of `ciCheck` + +This module is **deliberately excluded** from `root.aggregate(...)` in +`build.sbt`. Because sbt's `scalafmtCheckAll`, `scalafixAll --check`, and `test` +all follow project aggregation, that means `ciCheck` +(`;scalafmtCheckAll;scalafixAll --check;test`) does **not** compile, test, +format-check, or scalafix-lint anything here. + +The module is built only by: + +- explicit `sbt "bench/..."` invocations, and +- the dedicated bench CI workflow. + +Keep the code consistent with the repo's `.scalafmt.conf` anyway — run +`sbt "bench/scalafmt"` locally before committing. + +## Running + +```bash +# Quick smoke run (1 iteration, 1 warmup, 1 fork) +sbt "bench/Jmh/run -i 1 -wi 1 -f1" + +# A specific benchmark +sbt "bench/Jmh/run -i 1 -wi 1 -f1 NoOpBench" +``` + +JMH benchmarks are **main** sources (`src/main/scala/termflow/bench/`), not test +sources. diff --git a/modules/termflow-bench/src/main/scala/termflow/bench/NoOpBench.scala b/modules/termflow-bench/src/main/scala/termflow/bench/NoOpBench.scala new file mode 100644 index 0000000..5da9aa8 --- /dev/null +++ b/modules/termflow-bench/src/main/scala/termflow/bench/NoOpBench.scala @@ -0,0 +1,28 @@ +package termflow.bench + +import java.util.concurrent.TimeUnit + +import org.openjdk.jmh.annotations.* +import org.openjdk.jmh.infra.Blackhole + +/** + * Trivial smoke benchmark proving the JMH harness compiles and runs. + * + * It does no TermFlow work; it only sums a small range of ints into a + * [[Blackhole]] so the JIT cannot elide the loop. Real benchmarks land in + * sibling classes; this one exists so `sbt "bench/Jmh/run"` has something to + * execute from day one. + */ +@BenchmarkMode(Array(Mode.AverageTime)) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@State(Scope.Thread) +class NoOpBench: + + @Benchmark + def sumInts(bh: Blackhole): Unit = + var total = 0 + var i = 0 + while i < 1000 do + total += i + i += 1 + bh.consume(total) diff --git a/project/plugins.sbt b/project/plugins.sbt index cf1ff0d..e84532e 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -4,3 +4,4 @@ addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.11.0") addSbtPlugin("com.github.sbt" % "sbt-dynver" % "5.1.1") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.12") addSbtPlugin("com.github.sbt" % "sbt-unidoc" % "0.5.0") +addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.7")