πŸ“– 4 min read (~ 900 words).

Benchmarks

Last Updated: 2026-01-20

Overview

While the primary motivation for adding generic assertion functions to testify v2 was compile-time type safety (see Generics Guide for details), our benchmarking revealed an unexpected bonus: dramatic performance improvements ranging from 1.2x to 81x faster, with up to 99% reduction in memory allocations for collection operations.

This document focuses on the performance measurements and explains why these improvements occur.

Type Safety First, Performance Second

Generic assertions catch type errors when writing tests, not when running them. For example:

// Reflection: Compiles, fails at runtime
assert.ElementsMatch(t, []int{1, 2}, []string{"a", "b"})

// Generic: Compiler catches the error immediately
assert.ElementsMatchT(t, []int{1, 2}, []string{"a", "b"})  // ❌ Compile error!

See the Generics Guide for comprehensive coverage of type safety benefits, refactoring safety, and when to use generic vs reflection variants.

Performance Results by Category

πŸ† Collection Operations: Exceptional Gains

Collection operations see the most dramatic improvements due to elimination of per-element reflection overhead:

FunctionSpeedupMemory ImpactWhy It Matters
ElementsMatch (10 items)21x faster568 B β†’ 320 B (44% reduction)Common test operation
ElementsMatch (100 items)39x faster41 KB β†’ 3.6 KB (91% reduction)Scales superlinearly
ElementsMatch (1000 items)81x faster4 MB β†’ 33 KB (99% reduction)Large collection testing
SliceContains16x faster4 allocs β†’ 0Membership testing
SeqContains (iter.Seq)25x faster55 allocs β†’ 9Go 1.23+ iterators
SliceSubset43x faster17 allocs β†’ 0Subset verification

Key insight: ElementsMatch’s O(nΒ²) complexity amplifies the benefitsβ€”the speedup increases with collection size (21x β†’ 39x β†’ 81x).

⚑ Comparison Operations: Zero-Allocation Wins

Direct operator usage (>, <, ==) eliminates reflection overhead and boxing entirely:

FunctionSpeedupAllocationsBenchmark Data
Greater/Less10-15x faster1 β†’ 0 allocs139.1ns β†’ 17.9ns
Positive/Negative16-22x faster1 β†’ 0 allocs121.5ns β†’ 7.6ns
GreaterOrEqual/LessOrEqual10-11x faster1 β†’ 0 allocsSimilar pattern
Equal10-13x faster0 allocs (both)44.8ns β†’ 3.5ns
NotEqual11x faster0 allocs (both)Comparable to Equal

Key insight: Comparison operations are frequently used in tests. 10-15x speedup on common assertions accumulates quickly across large test suites.

πŸ“Š Ordering Operations: Eliminating Per-Element Overhead

Ordering checks iterate over collections, so eliminating per-element reflection creates significant gains:

FunctionSpeedupAllocation Impact
IsIncreasing7.4x faster11 allocs β†’ 0
IsDecreasing9.5x faster11 allocs β†’ 0
IsNonDecreasing8x faster4 allocs β†’ 0
IsNonIncreasing6.5x faster4 allocs β†’ 0

πŸ” Type Checks: Cleaner API, Better Performance

Generic type checks eliminate reflection and provide a cleaner API:

FunctionSpeedupNotes
IsOfType9-11x fasterNo dummy value needed with generics
IsNotOfTypeSimilar gainsType parameter makes intent explicit

βš–οΈ Modest Gains: Where Processing Dominates

Some operations see smaller improvements because expensive processing dominates:

CategorySpeedupWhy Gains Are Limited
Same/NotSame1.5-2xPointer comparison already fast
Boolean checks~2xSimple bool comparison
JSONEqMarginalJSON parsing/unmarshaling dominates
RegexpMarginalRegex compilation dominates

Key insight: Even modest performance gains come with the benefit of compile-time type safety.

Understanding the Performance Gains

Allocation Elimination

The most dramatic speedups come from eliminating allocations entirely:

  • ElementsMatch (1000 elements): 501,503 β†’ 3 allocations (99.999% reduction)
  • All comparison operations: 1 β†’ 0 allocations
  • Ordering checks: 4-11 β†’ 0 allocations

Less allocation pressure means faster execution and reduced GC overhead, especially impactful in large test suites.

Superlinear Scaling

For operations with O(nΒ²) or O(n) complexity, eliminating per-element reflection overhead creates superlinear gains:

  • ElementsMatch: 21x (10 items) β†’ 39x (100 items) β†’ 81x (1000 items)
  • The speedup increases with collection size

Cumulative Impact

Test suites typically run thousands of assertions:

  • Small test suite (1,000 assertions): 10x average speedup = significantly faster CI runs
  • Large test suite (10,000+ assertions): Cumulative savings become substantial
  • Particularly valuable in CI/CD pipelines where test execution time directly affects deployment velocity

Sample Benchmark Data

Representative results from go test -bench=. ./internal/assertions:

# Collection operations
BenchmarkElementsMatch/reflect/1000-16   25.5 ms/op     4.0 MB/op   501503 allocs/op
BenchmarkElementsMatch/generic/1000-16    316 Β΅s/op      33 KB/op        3 allocs/op
                                          ↑ 81x faster   ↑ 99% less memory

# Comparison operations
BenchmarkGreater/reflect/int-16          139.1 ns/op      34 B/op        1 allocs/op
BenchmarkGreater/generic/int-16           17.9 ns/op       0 B/op        0 allocs/op
                                          ↑ 7.8x faster

# Equality checks
BenchmarkEqual/reflect/int-16             44.8 ns/op       0 B/op        0 allocs/op
BenchmarkEqual/generic/int-16              3.5 ns/op       0 B/op        0 allocs/op
                                          ↑ 13x faster

Adopting Generic Assertions

See the Migration Guide for step-by-step instructions on migrating to generic assertions, and the Generics Guide for comprehensive coverage of type safety benefits and usage patterns.


Running Benchmarks

To run the benchmarks yourself:

go test -run=^$ -bench=. -benchmem ./internal/assertions

Benchmark Coverage

38 generic functions benchmarked across 10 domains:

  • Boolean (2): TrueT, FalseT
  • Collection (12): StringContainsT, SliceContainsT, MapContainsT, SeqContainsT, ElementsMatchT, SliceSubsetT, and negative variants
  • Comparison (6): GreaterT, LessT, GreaterOrEqualT, LessOrEqualT, PositiveT, NegativeT
  • Equality (4): EqualT, NotEqualT, SameT, NotSameT
  • JSON (1): JSONEqT
  • Number (2): InDeltaT, InEpsilonT
  • Ordering (6): IsIncreasingT, IsDecreasingT, IsNonIncreasingT, IsNonDecreasingT, SortedT, NotSortedT
  • String (2): RegexpT, NotRegexpT
  • Type (2): IsOfTypeT, IsNotOfTypeT
  • YAML (1): YAMLEqT (benchmarked separately in enable/yaml module)