Source file src/testing/benchmark_test.go

     1  // Copyright 2013 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package testing_test
     6  
     7  import (
     8  	"bytes"
     9  	"cmp"
    10  	"runtime"
    11  	"slices"
    12  	"strings"
    13  	"sync/atomic"
    14  	"testing"
    15  	"text/template"
    16  	"time"
    17  )
    18  
    19  var prettyPrintTests = []struct {
    20  	v        float64
    21  	expected string
    22  }{
    23  	{0, "         0 x"},
    24  	{1234.1, "      1234 x"},
    25  	{-1234.1, "     -1234 x"},
    26  	{999.950001, "      1000 x"},
    27  	{999.949999, "       999.9 x"},
    28  	{99.9950001, "       100.0 x"},
    29  	{99.9949999, "        99.99 x"},
    30  	{-99.9949999, "       -99.99 x"},
    31  	{0.000999950001, "         0.001000 x"},
    32  	{0.000999949999, "         0.0009999 x"}, // smallest case
    33  	{0.0000999949999, "         0.0001000 x"},
    34  }
    35  
    36  func TestPrettyPrint(t *testing.T) {
    37  	for _, tt := range prettyPrintTests {
    38  		buf := new(strings.Builder)
    39  		testing.PrettyPrint(buf, tt.v, "x")
    40  		if tt.expected != buf.String() {
    41  			t.Errorf("prettyPrint(%v): expected %q, actual %q", tt.v, tt.expected, buf.String())
    42  		}
    43  	}
    44  }
    45  
    46  func TestResultString(t *testing.T) {
    47  	// Test fractional ns/op handling
    48  	r := testing.BenchmarkResult{
    49  		N: 100,
    50  		T: 240 * time.Nanosecond,
    51  	}
    52  	if r.NsPerOp() != 2 {
    53  		t.Errorf("NsPerOp: expected 2, actual %v", r.NsPerOp())
    54  	}
    55  	if want, got := "     100\t         2.400 ns/op", r.String(); want != got {
    56  		t.Errorf("String: expected %q, actual %q", want, got)
    57  	}
    58  
    59  	// Test sub-1 ns/op (issue #31005)
    60  	r.T = 40 * time.Nanosecond
    61  	if want, got := "     100\t         0.4000 ns/op", r.String(); want != got {
    62  		t.Errorf("String: expected %q, actual %q", want, got)
    63  	}
    64  
    65  	// Test 0 ns/op
    66  	r.T = 0
    67  	if want, got := "     100", r.String(); want != got {
    68  		t.Errorf("String: expected %q, actual %q", want, got)
    69  	}
    70  }
    71  
    72  func TestRunParallel(t *testing.T) {
    73  	if testing.Short() {
    74  		t.Skip("skipping in short mode")
    75  	}
    76  	testing.Benchmark(func(b *testing.B) {
    77  		procs := uint32(0)
    78  		iters := uint64(0)
    79  		b.SetParallelism(3)
    80  		b.RunParallel(func(pb *testing.PB) {
    81  			atomic.AddUint32(&procs, 1)
    82  			for pb.Next() {
    83  				atomic.AddUint64(&iters, 1)
    84  			}
    85  		})
    86  		if want := uint32(3 * runtime.GOMAXPROCS(0)); procs != want {
    87  			t.Errorf("got %v procs, want %v", procs, want)
    88  		}
    89  		if iters != uint64(b.N) {
    90  			t.Errorf("got %v iters, want %v", iters, b.N)
    91  		}
    92  	})
    93  }
    94  
    95  func TestRunParallelFail(t *testing.T) {
    96  	testing.Benchmark(func(b *testing.B) {
    97  		b.RunParallel(func(pb *testing.PB) {
    98  			// The function must be able to log/abort
    99  			// w/o crashing/deadlocking the whole benchmark.
   100  			b.Log("log")
   101  			b.Error("error")
   102  		})
   103  	})
   104  }
   105  
   106  func TestRunParallelFatal(t *testing.T) {
   107  	testing.Benchmark(func(b *testing.B) {
   108  		b.RunParallel(func(pb *testing.PB) {
   109  			for pb.Next() {
   110  				if b.N > 1 {
   111  					b.Fatal("error")
   112  				}
   113  			}
   114  		})
   115  	})
   116  }
   117  
   118  func TestRunParallelSkipNow(t *testing.T) {
   119  	testing.Benchmark(func(b *testing.B) {
   120  		b.RunParallel(func(pb *testing.PB) {
   121  			for pb.Next() {
   122  				if b.N > 1 {
   123  					b.SkipNow()
   124  				}
   125  			}
   126  		})
   127  	})
   128  }
   129  
   130  func ExampleB_RunParallel() {
   131  	// Parallel benchmark for text/template.Template.Execute on a single object.
   132  	testing.Benchmark(func(b *testing.B) {
   133  		templ := template.Must(template.New("test").Parse("Hello, {{.}}!"))
   134  		// RunParallel will create GOMAXPROCS goroutines
   135  		// and distribute work among them.
   136  		b.RunParallel(func(pb *testing.PB) {
   137  			// Each goroutine has its own bytes.Buffer.
   138  			var buf bytes.Buffer
   139  			for pb.Next() {
   140  				// The loop body is executed b.N times total across all goroutines.
   141  				buf.Reset()
   142  				templ.Execute(&buf, "World")
   143  			}
   144  		})
   145  	})
   146  }
   147  
   148  func TestReportMetric(t *testing.T) {
   149  	res := testing.Benchmark(func(b *testing.B) {
   150  		b.ReportMetric(12345, "ns/op")
   151  		b.ReportMetric(0.2, "frobs/op")
   152  	})
   153  	// Test built-in overriding.
   154  	if res.NsPerOp() != 12345 {
   155  		t.Errorf("NsPerOp: expected %v, actual %v", 12345, res.NsPerOp())
   156  	}
   157  	// Test stringing.
   158  	res.N = 1 // Make the output stable
   159  	want := "       1\t     12345 ns/op\t         0.2000 frobs/op"
   160  	if want != res.String() {
   161  		t.Errorf("expected %q, actual %q", want, res.String())
   162  	}
   163  }
   164  
   165  func ExampleB_ReportMetric() {
   166  	// This reports a custom benchmark metric relevant to a
   167  	// specific algorithm (in this case, sorting).
   168  	testing.Benchmark(func(b *testing.B) {
   169  		var compares int64
   170  		for i := 0; i < b.N; i++ {
   171  			s := []int{5, 4, 3, 2, 1}
   172  			slices.SortFunc(s, func(a, b int) int {
   173  				compares++
   174  				return cmp.Compare(a, b)
   175  			})
   176  		}
   177  		// This metric is per-operation, so divide by b.N and
   178  		// report it as a "/op" unit.
   179  		b.ReportMetric(float64(compares)/float64(b.N), "compares/op")
   180  		// This metric is per-time, so divide by b.Elapsed and
   181  		// report it as a "/ns" unit.
   182  		b.ReportMetric(float64(compares)/float64(b.Elapsed().Nanoseconds()), "compares/ns")
   183  	})
   184  }
   185  
   186  func ExampleB_ReportMetric_parallel() {
   187  	// This reports a custom benchmark metric relevant to a
   188  	// specific algorithm (in this case, sorting) in parallel.
   189  	testing.Benchmark(func(b *testing.B) {
   190  		var compares atomic.Int64
   191  		b.RunParallel(func(pb *testing.PB) {
   192  			for pb.Next() {
   193  				s := []int{5, 4, 3, 2, 1}
   194  				slices.SortFunc(s, func(a, b int) int {
   195  					// Because RunParallel runs the function many
   196  					// times in parallel, we must increment the
   197  					// counter atomically to avoid racing writes.
   198  					compares.Add(1)
   199  					return cmp.Compare(a, b)
   200  				})
   201  			}
   202  		})
   203  
   204  		// NOTE: Report each metric once, after all of the parallel
   205  		// calls have completed.
   206  
   207  		// This metric is per-operation, so divide by b.N and
   208  		// report it as a "/op" unit.
   209  		b.ReportMetric(float64(compares.Load())/float64(b.N), "compares/op")
   210  		// This metric is per-time, so divide by b.Elapsed and
   211  		// report it as a "/ns" unit.
   212  		b.ReportMetric(float64(compares.Load())/float64(b.Elapsed().Nanoseconds()), "compares/ns")
   213  	})
   214  }
   215  

View as plain text