From e39c92dbbd493c6a189d1233d92d0e5a865725f3 Mon Sep 17 00:00:00 2001 From: Viktar Patotski Date: Sat, 6 Jun 2026 22:54:28 +0200 Subject: [PATCH] Add CLAUDE.md guidance file Co-Authored-By: Claude Opus 4.8 (1M context) --- CLAUDE.md | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..0b75581 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,55 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## What this is + +JMH microbenchmark suite (Java 21) comparing performance of common Java idioms — logging styles, +collections, strings, BigInteger/BigDecimal, exceptions vs null-checks, streams vs loops, etc. +No production/application code: every class is a benchmark. The original focus (per README) was +showing the value of `Supplier`-based log4j2 logging; the repo has since grown many unrelated +benchmark families. + +## Commands + +```bash +./gradlew jmh # run ALL benchmarks (uses the me.champeau.jmh plugin) +./gradlew jmhJar # build the runnable JMH uber-jar +``` + +Run a SINGLE benchmark — two ways: + +```bash +# 1. Each benchmark class has a main() that calls BenchmarkUtils.runBenchmark(...). +# Run that main from the IDE, or via the jmh jar: +./gradlew jmhJar +java -jar build/libs/*-jmh.jar LoggerBenchmarks # regex include, matches class simple name + +# 2. Gradle plugin include filter (regex on fully-qualified benchmark name): +./gradlew jmh -Pjmh.includes='.*LoggerBenchmarks.*' +``` + +There are no unit tests, no lint step. `org.gradle.daemon=false` is set in `gradle.properties`. + +## Architecture / conventions + +- All sources live under `src/jmh/java/com/patotski/performance/`, NOT `src/main`. The JMH plugin + compiles the `jmh` source set; `jmhAnnotationProcessor` generates the `BenchmarkList` — without it + you get a "missing /META-INF/BenchmarkList" error (see comment in `build.gradle`). +- One package per topic (`logging`, `collections`, `string`, `bigint`, `bigdec`, `array`, `enums`, + `exception`, `random`, `lombok`, `map`, `streams`). Add a new benchmark by creating a class in the + matching (or new) package. +- Each benchmark class is self-contained and carries its own JMH annotations at class level + (`@BenchmarkMode`, `@Warmup`, `@Measurement`, `@OutputTimeUnit(NANOSECONDS)`, `@Fork(1)`). + These per-class annotations override the global defaults in the `jmh { }` block of `build.gradle`. +- `@State(Scope.Benchmark)` inner classes hold the inputs; a `@Setup` method does any one-time init. +- Every class ends with a `main()` calling `BenchmarkUtils.runBenchmark(ThisClass.class)`. + `BenchmarkUtils` writes JSON to `reports/.json`, enables GC between iterations, + and forces 1 fork. Running via `main()` bypasses the `jmh { }` block entirely. +- Lombok is available (`io.freefair.lombok` plugin) — see `lombok/BigDecimalTuple.java` (`@Builder`). + +## Gotchas + +- `reports/` is the output dir (created by `BenchmarkUtils`); it is untracked working output. +- Logging benchmarks set the log level to `ERROR` in `@Setup` so `debug(...)` calls measure the + guarded/no-op path, which is the whole point of the supplier vs concat/format comparison.