benchmark

package module
v0.0.0-...-3cc267f Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 17, 2026 License: BSD-3-Clause Imports: 3 Imported by: 0

README

go-ruby-benchmark/benchmark

benchmark — go-ruby-benchmark

Docs License Go Coverage

A pure-Go (no cgo) reimplementation of Ruby's standard-library Benchmark module — the deterministic, interpreter-independent core of MRI 4.0.5 (benchmark 0.5.0): the Tms measurement value type, its memberwise arithmetic and %-directive formatting, and the report layout (CAPTION, label justification, the bm / bmbm / benchmark tables). It reproduces MRI's formatted output byte-for-bytewithout any Ruby runtime.

It is the Benchmark backend for go-embedded-ruby, but is a standalone, reusable module with no dependency on the Ruby runtime — a sibling of go-ruby-regexp (the Onigmo engine), go-ruby-erb (the ERB compiler), and go-ruby-yaml (the Psych emitter/loader).

What it is — and isn't. Formatting a Tms, doing its memberwise arithmetic, and laying out the report tables is fully deterministic and needs no interpreter, so it lives here as pure Go. The one impure ingredient — the clock — is injected: MRI's Benchmark.measure reads Process.times (the four CPU times) and Process.clock_gettime(CLOCK_MONOTONIC) (real time), and this library takes those readings through a small Clock interface. The host (go-embedded-ruby) wires in the real process clock; tests feed a fixed clock so every formatted line is reproducible.

Features

Faithful port of Benchmark / Benchmark::Tms, validated against the ruby binary on every supported platform:

  • Tms value typeutime, stime, cutime, cstime, real, the derived total = utime+stime+cutime+cstime, and a label; with ToA, ToS, and the MRI-exact constructor semantics (a sum's label clears to "").
  • Memberwise arithmeticAdd / Sub / Mul / Div against another Tms, and AddScalar / SubScalar / MulScalar / DivScalar against a number (the scalar is applied to all five fields, real included), exactly as MRI's Tms#+ - * / do.
  • Format — the Benchmark %-extensions on top of standard printf directives: %u user, %y system, %U/%Y children's user/system, %t total, %r real (wrapped in parentheses), %n label — each preserving its flag/width/precision run (e.g. %10.6u). %% collapses and surplus args are dropped, matching Ruby's String#%. The default FORMAT is "%10.6u %10.6y %10.6t %10.6r\n".
  • Report layoutCAPTION (" user system total real\n"), label left-justification, and the full bm(label_width), bmbm (rehearsal banner + total footer + take table), and general benchmark(caption, width, format, *labels) tables with trailing summary lines — all as pure functions over Tms values.
  • Injected clockMeasureWith(clock, label, fn), RealtimeWith, MsWith, and Tms.AddWith take a Clock seam; the host supplies the real process clock, tests a deterministic one.

CGO-free, dependency-free, 100% test coverage, gofmt + go vet clean, and green across the six 64-bit Go targets (amd64, arm64, riscv64, loong64, ppc64le, s390x) and three operating systems (Linux, macOS, Windows).

Install

go get github.com/go-ruby-benchmark/benchmark

Usage

package main

import (
	"fmt"
	"time"

	benchmark "github.com/go-ruby-benchmark/benchmark"
)

// realClock implements benchmark.Clock with the process's real wall clock. A
// real host also fills in Process.times-style CPU numbers; here we only populate
// the monotonic real clock (as MRI does when CPU accounting is unavailable).
type realClock struct{ start time.Time }

func (c realClock) Times() (u, s, cu, cs float64) { return 0, 0, 0, 0 }
func (c realClock) Monotonic() float64            { return time.Since(c.start).Seconds() }

func main() {
	clock := realClock{start: time.Now()}

	// Benchmark.measure { ... } → a Tms.
	t := benchmark.MeasureWith(clock, "build", func() {
		_ = make([]byte, 1<<20)
	})
	fmt.Print(t.ToS())
	// e.g. "  0.000000   0.000000   0.000000 (  0.000123)"

	// Benchmark.bm(7) { |x| x.report("...") { ... } } → the table.
	out, _ := benchmark.Bm(clock, 7, nil, func(r *benchmark.Report) []benchmark.Tms {
		r.Run("warm:", func() { _ = make([]byte, 1<<20) })
		r.Run("cold:", func() { _ = make([]byte, 1<<20) })
		return nil
	})
	fmt.Print(out)
	//               user     system      total        real
	// warm:     0.000000   0.000000   0.000000 (  0.000xxx)
	// cold:     0.000000   0.000000   0.000000 (  0.000xxx)
}

API

// Constants matching Benchmark::CAPTION / FORMAT / BENCHMARK_VERSION.
const CAPTION = "      user     system      total        real\n"
const FORMAT  = "%10.6u %10.6y %10.6t %10.6r\n"
const BenchmarkVersion = "2002-04-25"

// Tms — the measurement value type (Benchmark::Tms).
func NewTms(utime, stime, cutime, cstime, real float64, label string) Tms
func (t Tms) Utime() float64
func (t Tms) Stime() float64
func (t Tms) Cutime() float64
func (t Tms) Cstime() float64
func (t Tms) Real() float64
func (t Tms) Total() float64   // utime+stime+cutime+cstime
func (t Tms) Label() string
func (t Tms) ToA() []any       // [label, utime, stime, cutime, cstime, real]
func (t Tms) ToS() string
func (t Tms) Format(format string, args ...any) string

func (t Tms) Add(o Tms) Tms        // Tms#+
func (t Tms) Sub(o Tms) Tms        // Tms#-
func (t Tms) Mul(o Tms) Tms        // Tms#*
func (t Tms) Div(o Tms) Tms        // Tms#/
func (t Tms) AddScalar(x float64) Tms
func (t Tms) SubScalar(x float64) Tms
func (t Tms) MulScalar(x float64) Tms
func (t Tms) DivScalar(x float64) Tms

// The injected clock seam (Process.times + clock_gettime(CLOCK_MONOTONIC)).
type Clock interface {
	Times() (utime, stime, cutime, cstime float64)
	Monotonic() float64
}
func MeasureWith(clock Clock, label string, fn func()) Tms   // Benchmark.measure
func RealtimeWith(clock Clock, fn func()) float64            // Benchmark.realtime
func MsWith(clock Clock, fn func()) float64                  // Benchmark.ms
func (t Tms) AddWith(clock Clock, fn func()) Tms             // Tms#add

// Report layout (Benchmark::Report / #benchmark / #bm / #bmbm).
type Report struct{ /* … */ }
func NewReport(clock Clock, width int, format string) *Report
func (r *Report) Run(label string, fn func()) Tms
func (r *Report) Line(t Tms) string
func (r *Report) Caption() string
func (r *Report) ExtraLine(labelWidth int, label string, t Tms) string

type Job struct{ /* … */ }
func NewJob(width int) *Job
func (j *Job) Item(label string, fn func()) *Job

func Benchmark(clock Clock, caption string, labelWidth int, format string,
	extraLabels []string, build func(*Report) []Tms) (string, []Tms)
func Bm(clock Clock, labelWidth int, extraLabels []string,
	build func(*Report) []Tms) (string, []Tms)
func Bmbm(clock Clock, job *Job) (string, []Tms)

Tests & coverage

The suite pairs deterministic, ruby-free tests (which alone hold coverage at 100%, so the qemu cross-arch and Windows lanes pass the gate) with a differential MRI oracle: the same fixed numbers and a patched, deterministic clock are fed to the system ruby's Benchmark, and the formatted output is compared byte-for-byte. The oracle scripts $stdout.binmode so Windows text-mode never pollutes the bytes, and skip themselves where ruby is absent or on Windows.

COVERPKG=$(go list ./... | paste -sd, -)
go test -race -coverpkg="$COVERPKG" -coverprofile=cover.out ./...
go tool cover -func=cover.out | tail -1   # 100.0%

License

BSD-3-Clause — see LICENSE. Copyright the go-ruby-benchmark/benchmark authors.

WebAssembly

Being pure Go (CGO=0), this library also compiles to WebAssembly — both GOOS=js GOARCH=wasm (browser / Node.js) and GOOS=wasip1 GOARCH=wasm (WASI). CI builds both targets on every push, alongside the six 64-bit native/qemu arches.

GOOS=js     GOARCH=wasm go build ./...   # browser / Node
GOOS=wasip1 GOARCH=wasm go build ./...   # WASI (wasmtime, wasmer, wasmedge, …)

Documentation

Overview

Package benchmark is a pure-Go (no cgo) reimplementation of the deterministic, interpreter-independent core of Ruby's standard-library Benchmark module (MRI 4.0.5 / benchmark 0.5.0): the Tms measurement value type, its memberwise arithmetic and %-directive formatting, and the report layout (caption, label justification, the bm/bmbm tables) — all expressed as pure functions over Tms values.

The only impure ingredient, the clock, is injected. MRI's Benchmark.measure reads Process.times (user/system CPU, plus children's) and a monotonic real clock; here a Clock seam supplies those numbers, so the host (go-embedded-ruby) wires in the real process clock while tests feed a fixed one for byte-for-byte deterministic output.

Index

Constants

View Source
const BenchmarkVersion = "2002-04-25"

BenchmarkVersion mirrors Benchmark::BENCHMARK_VERSION.

View Source
const CAPTION = "      user     system      total        real\n"

CAPTION is the default heading printed above a column of times. It matches Benchmark::Tms::CAPTION (and Benchmark::CAPTION) exactly, trailing newline included.

View Source
const FORMAT = "%10.6u %10.6y %10.6t %10.6r\n"

FORMAT is the default per-line format string, matching Benchmark::Tms::FORMAT (and Benchmark::FORMAT). The %u/%y/%t/%r directives are the Benchmark extensions resolved by Tms.Format.

Variables

This section is empty.

Functions

func MsWith

func MsWith(clock Clock, fn func()) float64

MsWith runs fn and returns the elapsed real time in milliseconds, mirroring Benchmark.ms with an injected clock.

func RealtimeWith

func RealtimeWith(clock Clock, fn func()) float64

RealtimeWith runs fn and returns the elapsed real time in seconds, mirroring Benchmark.realtime with an injected clock.

func RehearsalFooter

func RehearsalFooter(width int, total Tms) string

RehearsalFooter returns the bmbm rehearsal total line, matching " #{ets}\n\n".rjust(width+CAPTION.length+2, '-') where ets is the summed Tms formatted with "total: %tsec".

func RehearsalHeader

func RehearsalHeader(width int) string

RehearsalHeader returns the bmbm rehearsal banner line, matching 'Rehearsal '.ljust(width+CAPTION.length, '-'), where width is the bmbm label offset (job width + 1).

func TakeCaption

func TakeCaption(width int) string

TakeCaption returns the bmbm "take" (real run) caption line, matching ' '*width + CAPTION.

Types

type Clock

type Clock interface {
	// Times returns the cumulative user, system, children's-user and
	// children's-system CPU seconds, like Process.times.
	Times() (utime, stime, cutime, cstime float64)
	// Monotonic returns a monotonically increasing real-time reading in
	// seconds, like Process.clock_gettime(Process::CLOCK_MONOTONIC).
	Monotonic() float64
}

Clock is the injected time source for MeasureWith. It abstracts the two clocks MRI's Benchmark.measure reads: Process.times (the four CPU times) and a monotonic real clock (Process.clock_gettime(CLOCK_MONOTONIC)).

The host (go-embedded-ruby) supplies a Clock backed by the real process clock; tests supply a fixed or scripted Clock so the formatted output is fully deterministic.

type Job

type Job struct {
	// contains filtered or unexported fields
}

Job is one labelled block awaiting measurement, as collected by Bmbm. It mirrors Benchmark::Job's (label, block) pairs and tracks the widest label.

func NewJob

func NewJob(width int) *Job

NewJob returns a Job with the given initial label-offset width, mirroring Benchmark::Job.new(width).

func (*Job) Item

func (j *Job) Item(label string, fn func()) *Job

Item registers a labelled block, widening the recorded label width, mirroring Benchmark::Job#item / #report.

func (*Job) Width

func (j *Job) Width() int

Width returns the widest label length seen.

type Report

type Report struct {
	// contains filtered or unexported fields
}

Report accumulates the measurements taken inside a bm/benchmark block and renders the table. It mirrors Benchmark::Report: a label offset (width) and a per-line format, growing the offset to fit the widest label, with the same off-by-one as MRI (the caller adds one to label_width before constructing it).

func NewReport

func NewReport(clock Clock, width int, format string) *Report

NewReport returns a Report with the given label offset and per-line format, timing its blocks with clock. An empty format selects FORMAT, matching Benchmark::Report.new(width, format) where a nil format defers to Tms#format's default.

func (*Report) Caption

func (r *Report) Caption() string

Caption returns the heading line for the report: width leading spaces followed by CAPTION, exactly as Benchmark#benchmark prints it (" "*report.width + caption).

func (*Report) ExtraLine

func (r *Report) ExtraLine(labelWidth int, label string, t Tms) string

ExtraLine renders a trailing summary row built from a Tms the block returned (e.g. a total or average). label overrides the Tms label when non-empty, matching `(labels.shift || t.label || "").ljust(label_width)` in Benchmark#benchmark; labelWidth is the original label_width+1 offset.

func (*Report) Format

func (r *Report) Format() string

Format returns the per-line format string (empty means the FORMAT default).

func (*Report) Line

func (r *Report) Line(t Tms) string

Line renders one report row for t: the label left-justified to the report offset, then the formatted time columns. This is the pure rendering MRI does in Benchmark#benchmark's per-item loop.

func (*Report) List

func (r *Report) List() []Tms

List returns the measurements collected so far.

func (*Report) Run

func (r *Report) Run(label string, fn func()) Tms

Run times fn with the report's clock under the given label, appends the measurement (widening the label offset to fit a longer label), and returns the resulting Tms. It mirrors Benchmark::Report#item / #report, which call Benchmark.measure(label, &blk) and stash the result.

func (*Report) Width

func (r *Report) Width() int

Width returns the current label offset.

type Tms

type Tms struct {
	// contains filtered or unexported fields
}

Tms holds the times associated with one benchmark measurement, mirroring Benchmark::Tms: user CPU time, system CPU time, the children's user and system CPU times, the elapsed real time, and a label. Total is the sum of the four CPU times.

func Benchmark

func Benchmark(clock Clock, caption string, labelWidth int, format string, extraLabels []string, build func(report *Report) []Tms) (string, []Tms)

Benchmark is the general driver behind Bm: it prints caption, runs each block registered through the report builder, and appends summary lines built from the Tms slice the builder returns. It mirrors Benchmark.benchmark, returning the rendered table and the collected measurements.

labelWidth and the per-line format match the Ruby parameters; the report's effective offset is labelWidth+1 (MRI's `label_width += 1`). build receives a *Report whose Record both times (via clock) and renders each row, and may return extra Tms summary rows; extraLabels override their labels in order.

func Bm

func Bm(clock Clock, labelWidth int, extraLabels []string, build func(report *Report) []Tms) (string, []Tms)

Bm renders the simple bm table: CAPTION on top, one timed line per report item, with labels left-justified to labelWidth. extraLabels label any summary Tms the builder returns. It mirrors Benchmark.bm.

func Bmbm

func Bmbm(clock Clock, job *Job) (string, []Tms)

Bmbm renders the rehearsal-then-real bmbm report and returns the rendered text plus the real-run measurements. It mirrors Benchmark.bmbm: it computes the label width from the registered jobs (job width + 1), prints the rehearsal banner, a rehearsal line per job, the rehearsal total footer, then the take caption and a real line per job.

Each block is measured twice with the injected clock (rehearsal then take), so a scripted clock makes the whole report deterministic.

func MeasureWith

func MeasureWith(clock Clock, label string, fn func()) Tms

MeasureWith runs fn and returns a Tms holding the CPU and real time it took, labelled label. It mirrors Benchmark.measure: it samples the clock before and after fn and takes memberwise differences. The clock is injected via clock.

func NewTms

func NewTms(utime, stime, cutime, cstime, real float64, label string) Tms

NewTms returns a Tms with the given times and label. As in MRI, total is computed as utime+stime+cutime+cstime and a nil-equivalent (empty) label is stored verbatim (MRI calls label.to_s, so nil becomes "").

func (Tms) Add

func (t Tms) Add(other Tms) Tms

Add returns a new Tms whose times are the memberwise sum of t and other, matching Tms#+. The result label is empty, exactly as MRI's memberwise leaves it.

func (Tms) AddScalar

func (t Tms) AddScalar(x float64) Tms

AddScalar returns a new Tms with x added to every time, matching Tms#+ with a numeric operand (MRI applies the scalar to all five fields, real included).

func (Tms) AddWith

func (t Tms) AddWith(clock Clock, fn func()) Tms

AddWith returns the memberwise sum of t and a fresh measurement of fn, mirroring Tms#add. The block is timed with the injected clock.

func (Tms) Cstime

func (t Tms) Cstime() float64

Cstime returns the children's system CPU time.

func (Tms) Cutime

func (t Tms) Cutime() float64

Cutime returns the children's user CPU time.

func (Tms) Div

func (t Tms) Div(other Tms) Tms

Div returns the memberwise quotient t/other, matching Tms#/ with a Tms operand.

func (Tms) DivScalar

func (t Tms) DivScalar(x float64) Tms

DivScalar returns a new Tms with every time divided by x, matching Tms#/.

func (Tms) Format

func (t Tms) Format(format string, args ...any) string

Format renders t according to format, honouring the Benchmark extensions on top of standard %-directives, matching Tms#format byte-for-byte:

%n  label    %u  utime   %y  stime (system)   %U  cutime
%t  total    %r  real (wrapped in parentheses) %Y  cstime

The flag/width/precision run before the extension letter is preserved (e.g. %10.6u). An empty format selects FORMAT, in which case the trailing args are ignored (as MRI does when format is nil). With an explicit format, any remaining standard directives are filled from args.

func (Tms) Label

func (t Tms) Label() string

Label returns the measurement label.

func (Tms) Mul

func (t Tms) Mul(other Tms) Tms

Mul returns the memberwise product t*other, matching Tms#* with a Tms operand.

func (Tms) MulScalar

func (t Tms) MulScalar(x float64) Tms

MulScalar returns a new Tms with every time multiplied by x, matching Tms#*.

func (Tms) Real

func (t Tms) Real() float64

Real returns the elapsed real (wall-clock) time.

func (Tms) Stime

func (t Tms) Stime() float64

Stime returns the system CPU time.

func (Tms) Sub

func (t Tms) Sub(other Tms) Tms

Sub returns the memberwise difference t-other, matching Tms#-.

func (Tms) SubScalar

func (t Tms) SubScalar(x float64) Tms

SubScalar returns a new Tms with x subtracted from every time.

func (Tms) ToA

func (t Tms) ToA() []any

ToA returns the 6-element slice [label, utime, stime, cutime, cstime, real], mirroring Tms#to_a.

func (Tms) ToS

func (t Tms) ToS() string

ToS formats t with the default FORMAT, mirroring Tms#to_s.

func (Tms) Total

func (t Tms) Total() float64

Total returns utime+stime+cutime+cstime, matching Tms#total.

func (Tms) Utime

func (t Tms) Utime() float64

Utime returns the user CPU time.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL