Sign in

    #Bench Package Documentation

    This package provides benchmarking utilities for measuring the performance of MoonBit code. It includes functions for timing code execution, collecting statistics, and generating performance reports.

    #Basic Benchmarking

    Use the single_bench function to benchmark individual operations:

    ///|
    #skip("slow tests")
    test "basic benchmarking" {
    fn simple_calc(n : Int32) -> Int32 {
    n * 2 + 1
    }
    // Benchmark a simple computation
    let summary = @bench.single_bench(name="simple_calc", fn() {
    ignore(simple_calc(5))
    })

    // The benchmark ran successfully (we can't inspect exact timing)
    inspect(@json.to_json(summary).stringify().length() > 0, content="true")
    }

    #Benchmark Collection

    Use the Bench type to collect multiple benchmarks:

    ///|
    #skip("slow tests")
    test "benchmark collection" {
    let bencher = @bench.Bench()

    // Add multiple benchmarks to the collection
    bencher.bench(name="array_creation", fn() {
    let arr = Array()
    for i in 0..<5 {
    arr.push(i)
    }
    })
    bencher.bench(name="array_iteration", fn() {
    let arr = [1, 2, 3, 4, 5]
    let mut sum = 0
    for x in arr {
    sum = sum + x
    }
    })

    // Generate benchmark report
    let report = bencher.dump_summaries()
    inspect(report.length() > 0, content="true")
    }

    #Benchmarking Different Algorithms

    Compare the performance of different implementations:

    ///|
    #skip("slow tests")
    test "algorithm comparison" {
    let bencher = @bench.Bench()

    // Benchmark linear search
    bencher.bench(name="linear_search", fn() {
    let arr = [1, 2, 3, 4, 5]
    let target = 3
    let mut found = false
    for x in arr {
    if x == target {
    found = true
    break
    }
    }
    ignore(found)
    })

    // Benchmark using built-in contains (likely optimized)
    bencher.bench(name="builtin_contains", fn() {
    let arr = [1, 2, 3, 4, 5]
    ignore(arr.contains(3))
    })
    let results = bencher.dump_summaries()
    inspect(results.length() > 10, content="true") // Should have benchmark data
    }

    #Data Structure Benchmarks

    Benchmark different data structure operations:

    ///|
    #skip("slow tests")
    test "data structure benchmarks" {
    let bencher = @bench.Bench()

    // Benchmark Array operations
    bencher.bench(name="array_append", fn() {
    let arr = Array()
    for i in 0..<5 {
    arr.push(i)
    }
    })

    // Benchmark FixedArray access
    bencher.bench(name="fixedarray_access", fn() {
    let arr : ReadOnlyArray[Int32] = [0, 1, 2, 3, 4]
    let mut sum = 0
    for i in 0..<arr.length() {
    sum = sum + arr[i]
    }
    ignore(sum)
    })
    let report = bencher.dump_summaries()
    inspect(report.length() > 50, content="true") // Should have benchmark data
    }

    #String Operations Benchmarking

    Measure string manipulation performance:

    ///|
    #skip("slow tests")
    test "string benchmarks" {
    let bencher = @bench.Bench()

    // Benchmark string concatenation
    bencher.bench(name="string_concat", fn() {
    let mut result = ""
    for _ in 0..<5 {
    result = result + "x"
    }
    })

    // Benchmark StringBuilder (should be faster)
    bencher.bench(name="stringbuilder", fn() {
    let builder = StringBuilder()
    for _ in 0..<5 {
    builder.write_string("x")
    }
    ignore(builder.to_string())
    })
    let results = bencher.dump_summaries()
    inspect(results.length() > 50, content="true") // Should have benchmark data
    }

    #Memory Usage Prevention

    Use keep to prevent compiler optimizations from eliminating benchmarked code:

    ///|
    #skip("slow tests")
    test "preventing optimization" {
    let bencher = @bench.Bench()
    bencher.bench(name="with_keep", fn() {
    let result = Array::makei(5, fn(i) { i * i })
    // Prevent the compiler from optimizing away the computation
    bencher.keep(result)
    })
    let report = bencher.dump_summaries()
    inspect(report.length() > 30, content="true") // Should have benchmark data
    }

    #Iteration Count Control

    Control the number of benchmark iterations:

    ///|
    #skip("slow tests")
    test "iteration control" {
    let bencher = @bench.Bench()

    // Run with more iterations for more stable results
    bencher.bench(
    name="stable_benchmark",
    fn() {
    let arr = [1, 2, 3, 4, 5]
    let sum = arr.fold(init=0, fn(acc, x) { acc + x })
    ignore(sum)
    },
    count=20,
    )

    // Run with fewer iterations for quick testing
    bencher.bench(
    name="quick_benchmark",
    fn() {
    let mut result = 0
    for i in 0..<10 {
    result = result + i
    }
    ignore(result)
    },
    count=2,
    )
    let results = bencher.dump_summaries()
    inspect(results.length() > 50, content="true") // Should have benchmark data
    }

    #Benchmarking Best Practices

    #1. Isolate What You're Measuring

    ///|
    #skip("slow tests")
    test "isolation example" {
    let bencher = @bench.Bench()

    // Good: Measure only the operation of interest
    let data = Array::makei(10, fn(i) { i }) // Setup outside benchmark
    bencher.bench(name="array_sum", fn() {
    let mut sum = 0
    for x in data {
    sum = sum + x
    }
    bencher.keep(sum) // Prevent optimization
    })
    let results = bencher.dump_summaries()
    inspect(results.length() > 0, content="true")
    }

    #2. Warm Up Before Measuring

    ///|
    #skip("slow tests")
    test "warmup example" {
    let bencher = @bench.Bench()
    fn expensive_operation() -> Int32 {
    let mut result = 0
    for i in 0..<5 {
    result = result + i * i
    }
    result
    }

    // Warm up the function (not measured)
    for _ in 0..<5 {
    ignore(expensive_operation())
    }

    // Now benchmark the warmed-up function
    bencher.bench(name="warmed_up", fn() {
    let result = expensive_operation()
    bencher.keep(result)
    })
    let report = bencher.dump_summaries()
    inspect(report.length() > 30, content="true") // Should have benchmark data
    }

    #3. Use Meaningful Names

    ///|
    #skip("slow tests")
    test "meaningful names" {
    let bencher = @bench.Bench()

    // Good: Descriptive names that explain what's being measured
    bencher.bench(name="array_insert_10_items", fn() {
    let arr = Array()
    for i in 0..<10 {
    arr.push(i * 2)
    }
    bencher.keep(arr)
    })
    bencher.bench(name="array_search_sorted_10", fn() {
    let arr = Array::makei(10, fn(i) { i })
    let result = arr.contains(5) // Linear search in this case
    bencher.keep(result)
    })
    let results = bencher.dump_summaries()
    inspect(results.length() > 50, content="true") // Should have benchmark data
    }

    #Performance Analysis

    The benchmark results include statistical information:

    • Timing measurements: Microsecond precision timing
    • Statistical analysis: Median, percentiles, and outlier detection
    • Batch sizing: Automatic adjustment for stable measurements
    • JSON output: Machine-readable results for analysis

    #Integration with Testing

    Benchmarks can be integrated into your testing workflow:

    ///|
    #skip("slow tests")
    test "performance regression test" {
    let bencher = @bench.Bench()

    // Benchmark a critical path
    bencher.bench(name="critical_algorithm", fn() {
    let data = [5, 2, 8, 1, 9, 3, 7, 4, 6]
    let sorted = Array()
    for x in data {
    sorted.push(x)
    }
    sorted.sort()
    bencher.keep(sorted)
    })
    let results = bencher.dump_summaries()
    // In a real scenario, you might parse results and assert performance bounds
    inspect(results.length() > 50, content="true") // Should have substantial data
    }

    #Common Benchmarking Patterns

    1. Before/After comparisons: Benchmark code before and after optimizations
    2. Algorithm comparison: Compare different implementations of the same functionality
    3. Scaling analysis: Benchmark with different input sizes
    4. Memory vs. speed tradeoffs: Compare memory-efficient vs. speed-optimized approaches
    5. Platform differences: Compare performance across different targets (JS, WASM, native)

    #Tips for Accurate Benchmarks

    • Run benchmarks multiple times and look for consistency
    • Be aware of system load and other processes affecting timing
    • Use appropriate iteration counts (more for stable results, fewer for quick feedback)
    • Measure what matters to your use case
    • Consider both average case and worst case performance
    • Profile memory usage separately if memory performance is important

    The bench package provides essential tools for performance analysis and optimization in MoonBit applications.

    Bench

    #alias(T)
    type Bench

    Bench::Bench

    #as_free_fn(new, deprecated="Use `Bench()` instead")
    #as_free_fn
    #alias(new, deprecated="Use `Bench()` instead")
    fn Bench::Bench() -> Bench

    Create an empty benchmark collector.

    The returned Bench value can run benchmarks via bench and export collected summaries with dump_summaries.

    Example:

    test {
    let b = @bench.Bench()
    let json = b.dump_summaries()
    inspect(json, content="[]")
    }

    Bench::bench

    fn Bench::bench(self : Bench, name? : String, f : () -> Unit, count? : UInt) -> Unit

    Run a benchmark in batch mode. count must be between 1 and 2147483647 inclusive; otherwise panics before invoking the benchmark callback.

    Bench::dump_summaries

    fn Bench::dump_summaries(self : Bench) -> String

    Returns a JSON array string containing all collected benchmark summaries. This helper is mainly used internally for tests and diagnostics.

    Bench::keep

    fn[Any] Bench::keep(self : Bench, value : Any) -> Unit

    Keep a value to prevent the optimizer from removing the calculation that produces it.

    Summary

    Timestamp

    type Timestamp

    monotonic_clock_end

    fn monotonic_clock_end(ts : Timestamp) -> Double

    Compute elapsed time in microseconds since ts.

    Parameters:

    • ts: a timestamp created by monotonic_clock_start.

    Returns elapsed time in microseconds.

    monotonic_clock_start

    fn monotonic_clock_start() -> Timestamp

    Capture the current monotonic timestamp.

    Use the returned timestamp with monotonic_clock_end to measure elapsed time. A monotonic clock is not affected by system clock adjustments.

    single_bench

    fn single_bench(name? : String, f : () -> Unit, count? : UInt) -> Summary

    Run a benchmark and return its summary. count must be between 1 and 2147483647 inclusive; otherwise panics before invoking the benchmark callback.