From 3828ec1f69bb71ecc7bbfbb623c0deac81121a47 Mon Sep 17 00:00:00 2001 From: Bernie Innocenti Date: Fri, 2 Oct 2026 13:18:05 +0800 Subject: [PATCH 1/3] Run benchmarks in command-line order and report their geomean Named benchmarks now run in the order given rather than in table order, and a run of more than one ends with the geometric mean of the fastest iterations. The banner names the BenchWork version next to the compiler. Co-Authored-By: Claude Fable 5.1 --- README.md | 5 ++--- src/main.c | 56 ++++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 46 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 396f160..4252994 100644 --- a/README.md +++ b/README.md @@ -40,9 +40,8 @@ The FreeType rasterizer keeps its cell pool on the stack, so the program refuses to start on the shell's default 4 KiB stack: `stack 65536` first. Each benchmark reports its fastest and mean iteration in milliseconds, and a -checksum of its output that must be identical across compilers. Run it under -a cycle-paced emulator or on real hardware with nothing else running; the -timer is `ReadEClock()`. +checksum of its output that must be identical across compilers. The timer is +`ReadEClock()`. ## License diff --git a/src/main.c b/src/main.c index ad30585..d7f9d4a 100644 --- a/src/main.c +++ b/src/main.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -24,6 +25,9 @@ #ifndef BENCH_CFLAGS #define BENCH_CFLAGS "?" #endif +#ifndef BENCH_VERSION +#define BENCH_VERSION "?" +#endif // ftgrays keeps its 16 KiB cell pool on the stack and the Amiga shell's // default stack is 4 KiB. Swapping to a bigger one from C is not portable @@ -73,8 +77,8 @@ static void print_ms(ULONG us) { } // Set up, time and tear down one benchmark. Returns false on failure, after -// saying which step failed. -static bool run_bench(const struct bench *b, int iters) { +// saying which step failed; on success *BEST_US is the fastest iteration. +static bool run_bench(const struct bench *b, int iters, ULONG *best_us) { ULONG best = ~0UL, total = 0, check = 0, first = 0; bool ok = true; @@ -120,6 +124,7 @@ static bool run_bench(const struct bench *b, int iters) { print_ms(best); print_ms(total / (ULONG)iters); printf(" %08lx\n", (unsigned long)check); + *best_us = best; } if (b->teardown) b->teardown(); @@ -127,7 +132,10 @@ static bool run_bench(const struct bench *b, int iters) { } static int iters = 3; -static bool selected[NBENCHES], any; +// The benchmarks to run, in the order given on the command line, repeats +// included; all of them, in table order, when none is named. +static const struct bench **selected; +static size_t nselected; static bool fastmem; // Allocate a benchmark buffer, released with bench_free(). WHAT names it if @@ -167,15 +175,28 @@ void bench_zfree(void *opaque, void *address) { bench_free(address); } -// Run the selected benchmarks. Returns the failure count. +// Run the selected benchmarks, then the geometric mean of their fastest +// iterations when more than one ran: the one number that weighs a 10% +// change on a short workload the same as on a long one. Returns the failure +// count. static int run_all(void) { - int failures = 0; + int failures = 0, ran = 0; + double logsum = 0; - for (size_t j = 0; j < NBENCHES; j++) { - if (any && !selected[j]) - continue; - if (!run_bench(BENCHES[j], iters)) + for (size_t j = 0; j < nselected; j++) { + ULONG best; + + if (!run_bench(selected[j], iters, &best)) { failures++; + continue; + } + logsum += log((double)best); + ran++; + } + if (ran > 1) { + printf("%-12s %5s ", "geomean", ""); + print_ms((ULONG)(exp(logsum / ran) + 0.5)); + printf("\n"); } return failures; } @@ -192,6 +213,13 @@ int main(int argc, char **argv) { return 20; } + selected = malloc(((size_t)argc > NBENCHES ? (size_t)argc : NBENCHES) + * sizeof *selected); + if (!selected) { + printf("out of memory\n"); + return 20; + } + for (i = 1; i < argc; i++) { if (!strcmp(argv[i], "-n") && i + 1 < argc) { iters = atoi(argv[++i]); @@ -216,17 +244,21 @@ int main(int argc, char **argv) { printf("unknown benchmark %s\n", argv[i]); return 20; } - selected[j] = true; - any = true; + selected[nselected++] = BENCHES[j]; } } + if (!nselected) { + for (size_t j = 0; j < NBENCHES; j++) + selected[j] = BENCHES[j]; + nselected = NBENCHES; + } if (!timer_open()) { printf("cannot open timer.device\n"); return 20; } - printf("benchwork: %s\n", __VERSION__); + printf("benchwork %s, built with gcc %s\n", BENCH_VERSION, __VERSION__); printf("CFLAGS: %s\n\n", BENCH_CFLAGS); printf("%-12s %5s %10s %10s %s\n", "benchmark", "iters", "min ms", "mean ms", "check"); From 4aa8c08ada87565df5229a23f88b926029f079e1 Mon Sep 17 00:00:00 2001 From: Bernie Innocenti Date: Fri, 2 Oct 2026 13:18:05 +0800 Subject: [PATCH 2/3] Release binaries for 68000, 68020 and 68040 with FPU "make release" builds benchwork-000, -020 and -040; the Build workflow builds them on every push with a pinned m68k-amigaos-gcc release, and the Release workflow attaches the bare executables to a tag's release. Co-Authored-By: Claude Fable 5.1 --- .github/workflows/binaries.yml | 51 ++++++++++++++++++++++++++++++++++ .github/workflows/build.yml | 14 ++++++++++ .github/workflows/release.yml | 38 +++++++++++++++++++++++++ Makefile | 21 ++++++++++++-- 4 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/binaries.yml create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/binaries.yml b/.github/workflows/binaries.yml new file mode 100644 index 0000000..17c8a5b --- /dev/null +++ b/.github/workflows/binaries.yml @@ -0,0 +1,51 @@ +name: Binaries + +# Builds benchwork-000, benchwork-020 and benchwork-040 with a pinned +# m68k-amigaos-gcc release and uploads them as one artifact. The compiler +# version is part of what a benchmark binary measures, so it is fixed here +# rather than taken from whatever is current; bump it deliberately. +on: + workflow_call: + +env: + TOOLCHAIN: 16.2-rc13 + +jobs: + binaries: + runs-on: ubuntu-24.04 + + steps: + - name: Checkout code + uses: actions/checkout@v7 + with: + # git describe needs the tags for the version banner + fetch-depth: 0 + + - name: Cache the toolchain + id: cache + uses: actions/cache@v6 + with: + path: ~/m68k-amigaos-gcc-16.2 + key: m68k-amigaos-gcc-${{ env.TOOLCHAIN }}-x86_64-linux + + - name: Download the toolchain + if: steps.cache.outputs.cache-hit != 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release download "v$TOOLCHAIN" -R AmigaPorts/m68k-amigaos-gcc \ + -p "m68k-amigaos-gcc-$TOOLCHAIN-x86_64-linux.tar.xz" + tar -xJf m68k-amigaos-gcc-*.tar.xz -C "$HOME" + rm m68k-amigaos-gcc-*.tar.xz + + - name: Build the release binaries + run: | + export PATH="$HOME/m68k-amigaos-gcc-16.2/bin:$PATH" + m68k-amigaos-gcc --version | head -1 + make -j "$(nproc)" release + + - name: Upload the binaries + uses: actions/upload-artifact@v7 + with: + name: benchwork + path: benchwork-0* diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..f6e7105 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,14 @@ +name: Build + +# The release binaries on every push and pull request, so a tag push is never +# the first build of them; release.yml publishes the same binaries for a tag. +on: + push: + branches: + - '**' + pull_request: + workflow_dispatch: + +jobs: + build: + uses: ./.github/workflows/binaries.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..c932c17 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,38 @@ +name: Release + +on: + push: + tags: + - 'v*' + +jobs: + build: + uses: ./.github/workflows/binaries.yml + + release: + needs: build + runs-on: ubuntu-24.04 + permissions: + contents: write + + steps: + - name: Checkout code + uses: actions/checkout@v7 + with: + fetch-depth: 0 + + - name: Download the binaries + uses: actions/download-artifact@v8 + with: + name: benchwork + + # Bare executables, not archives: one download, copied to the Amiga + # as is. The tag message, subject and body, is the release text; + # write it with "git tag -a" or "git tag -s". + - name: Create release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ github.ref_name }} + run: | + gh release create "$TAG" benchwork-0* \ + --title "BenchWork ${TAG#v}" --notes-from-tag diff --git a/Makefile b/Makefile index d65f897..a76feb5 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,7 @@ CFLAGS ?= $(OPT) $(CPUFLAGS) # What the binary reports it was built with: taken now, before the # per-directory additions below. REPORTED_CFLAGS := $(CFLAGS) +VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo unknown) # The harness is held to a stricter standard than the code it measures, # which is compiled the way its own build systems compile it. @@ -19,7 +20,8 @@ WARNINGS = -Wall -Wextra -Wshadow -Wpointer-arith -Wwrite-strings \ INCLUDES = -Isrc -Ithird_party/zlib -Ithird_party/libpng -ALL_CFLAGS = $(CFLAGS) -noixemul $(INCLUDES) -DBENCH_CFLAGS='"$(REPORTED_CFLAGS)"' +ALL_CFLAGS = $(CFLAGS) -noixemul $(INCLUDES) -DBENCH_CFLAGS='"$(REPORTED_CFLAGS)"' \ + -DBENCH_VERSION='"$(VERSION)"' HARNESS_SRCS = \ src/main.c \ @@ -105,9 +107,22 @@ FONT ?= /usr/share/fonts/dejavu-sans-fonts/DejaVuSans.ttf glyphs: tools/dumpglyphs tools/dumpglyphs $(FONT) > src/glyphs.c +# The release binaries: one per CPU generation, named after it. The 68040 one +# uses the FPU; the others are soft float, as a program shipped for those +# machines would be. +RELEASE_CPUS = 000 020 040 +CPUFLAGS_000 = -m68000 +CPUFLAGS_020 = -m68020 +CPUFLAGS_040 = -m68040 -mhard-float + +release: $(RELEASE_CPUS:%=release-%) + +$(RELEASE_CPUS:%=release-%): release-%: + $(MAKE) BUILD=build-$* TARGET=benchwork-$* CPUFLAGS="$(CPUFLAGS_$*)" + clean: - rm -rf $(BUILD) $(TARGET) + rm -rf $(BUILD) $(TARGET) $(RELEASE_CPUS:%=build-%) $(RELEASE_CPUS:%=benchwork-%) -include $(OBJS:.o=.d) -.PHONY: all clean glyphs +.PHONY: all clean glyphs release $(RELEASE_CPUS:%=release-%) From 722dfda2edc04705e23c474731dd600f2c7258d1 Mon Sep 17 00:00:00 2001 From: Bernie Innocenti Date: Fri, 2 Oct 2026 13:23:21 +0800 Subject: [PATCH 3/3] Makefile: default CC to m68k-amigaos-gcc over make's built-in cc "CC ?=" never applied because make predefines CC; only a CC passed explicitly worked, which the README did not say. Co-Authored-By: Claude Fable 5.1 --- Makefile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a76feb5..2fdcca1 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,11 @@ # SPDX-License-Identifier: 0BSD -CC ?= m68k-amigaos-gcc +# make predefines CC as "cc", so "?=" would never apply; override only that +# built-in default and keep a CC given on the command line or in the +# environment. +ifeq ($(origin CC),default) +CC = m68k-amigaos-gcc +endif BUILD ?= build TARGET = benchwork