--- title: "Benchmark with a reproducible receipt" name: benchmarks description: Run a scoped micro-comparison and report enough context to reproduce it. output: rmarkdown::html_vignette: toc: yes toc_depth: 2 css: albers.css includes: in_header: albers-header.html params: family: red base_size: 13 content_width: 72 preset: homage resource_files: - albers.css - albers.js - albers-header.html - fonts vignette: | %\VignetteIndexEntry{Benchmark with a reproducible receipt} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include=FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", message = FALSE, warning = FALSE, fig.align = "center", fig.retina = 2, out.width = "100%", fig.width = 7, fig.asp = 0.56 ) if (requireNamespace("albersdown", quietly = TRUE) && requireNamespace("ggplot2", quietly = TRUE)) { ggplot2::theme_set(albersdown::theme_albers(family = params$family, preset = params$preset)) } library(neurocluster) ``` ```{r albers-classes, echo=FALSE, results='asis'} cat(sprintf( paste0(''), params$family, params$preset, params$content_width )) ``` A benchmark is evidence only for its measured workload. This article does not read a checked-in `results.csv` or present old machine timings as current. It runs a deliberately small comparison during rendering and shows the receipt needed to reproduce it. Use the pattern—not the tiny timings—to design a study for your data. ## What exactly is measured here? The estimand is elapsed wall-clock seconds for a complete `cluster4d()` call on one generated 400-voxel, 18-time-point volume, after one warm-up call. We keep three repetitions for each method and require every fit to be valid and to produce the requested K. These are smoke-scale timings, not whole-brain performance claims. ```{r benchmark-data, include=FALSE} syn <- generate_synthetic_volume( scenario = "gaussian_blobs", dims = c(10, 10, 4), n_clusters = 4, n_time = 18, noise_sd = 0.06, seed = 713 ) fit_one <- function(method) { if (method == "snic") { cluster4d(syn$vec, syn$mask, 4, method = "snic", spatial_weight = 0.4) } else { cluster4d( syn$vec, syn$mask, 4, method = "slic", spatial_weight = 0.4, max_iterations = 6, preserve_k = TRUE, parallel = FALSE ) } } invisible(fit_one("snic")) invisible(fit_one("slic")) ``` ```{r benchmark-run} methods <- c("snic", "slic") benchmark_order <- c("snic", "slic", "slic", "snic", "snic", "slic") repetitions <- as.integer(ave( seq_along(benchmark_order), benchmark_order, FUN = seq_along )) data.frame(run = seq_along(benchmark_order), method = benchmark_order, repetition = repetitions) timings <- do.call(rbind, lapply(seq_along(benchmark_order), function(run) { method <- benchmark_order[run] elapsed <- system.time(fit <- fit_one(method))[["elapsed"]] validation <- validate_cluster4d(fit, syn$vec, syn$mask) data.frame( run = run, method = method, repetition = repetitions[run], elapsed_seconds = unname(elapsed), actual_k = fit$actual_k, valid = validation$valid ) })) timings ``` ```{r benchmark-check, include=FALSE} stopifnot(nrow(timings) == 6L) stopifnot(all(timings$valid), all(timings$actual_k == 4L)) stopifnot(all(is.finite(timings$elapsed_seconds))) stopifnot(all(timings$elapsed_seconds >= 0)) stopifnot(identical(timings$method, benchmark_order)) stopifnot(all(table(timings$method) == 3L)) benchmark_summary <- aggregate(elapsed_seconds ~ method, timings, median) benchmark_summary <- benchmark_summary[match(methods, benchmark_summary$method), ] ``` ```{r benchmark-figure, echo=FALSE, fig.cap='Three elapsed-time repetitions per method on the vignette’s tiny generated workload; diamonds are medians.', fig.alt='Jittered timing points and median diamonds for SNIC and serial SLIC on a 400-voxel example.'} palette_cb <- c(snic = "#0072B2", slic = "#D55E00") x <- match(timings$method, methods) offset <- (timings$repetition - 2) * 0.08 plot(x + offset, timings$elapsed_seconds, pch = 21, cex = 1.5, bg = palette_cb[timings$method], col = "white", xaxt = "n", xlab = "method", ylab = "elapsed seconds", xlim = c(0.5, 2.5), ylim = c(0, max(timings$elapsed_seconds) * 1.15 + 1e-6)) axis(1, at = seq_along(methods), labels = toupper(methods)) points(seq_along(methods), benchmark_summary$elapsed_seconds, pch = 23, cex = 1.8, bg = "#009E73", col = "white") box() ``` **What to notice.** Repeated timings vary even on a tiny fixed input. The plot supports only this render’s workload and environment; it does not rank methods for a different volume, K, parameter set, or machine. ## What belongs in the receipt? ```{r benchmark-receipt} list( package_version = as.character(packageVersion("neurocluster")), r_version = paste(R.version$major, R.version$minor, sep = "."), platform = R.version$platform, dimensions = syn$dims, included_voxels = length(syn$truth), time_points = ncol(syn$patterns), requested_k = 4L, repetitions = 3L, run_order = benchmark_order ) ``` For a real benchmark, also record the commit, CPU, RAM, operating system, thread backend and count, full method calls, warm-up policy, allocation or peak RSS measurement method, and every repetition. Predefine quality estimands when comparing approximation settings; elapsed time alone cannot establish a useful clustering. See [Performance and memory](performance-memory.html) and [Validate and compare clusterings](validate-compare.html).