golang.org/x/exp@v0.0.0-20240506185415-9bf2ced13842/slog/value_access_benchmark_test.go (about)

     1  // Copyright 2022 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  // Benchmark for accessing Value values.
     6  
     7  package slog
     8  
     9  import (
    10  	"testing"
    11  	"time"
    12  )
    13  
    14  // The "As" form is the slowest.
    15  // The switch-panic and visitor times are almost the same.
    16  // BenchmarkDispatch/switch-checked-8         	 8669427	       137.7 ns/op
    17  // BenchmarkDispatch/As-8                     	 8212087	       145.3 ns/op
    18  // BenchmarkDispatch/Visit-8                  	 8926146	       135.3 ns/op
    19  func BenchmarkDispatch(b *testing.B) {
    20  	vs := []Value{
    21  		Int64Value(32768),
    22  		Uint64Value(0xfacecafe),
    23  		StringValue("anything"),
    24  		BoolValue(true),
    25  		Float64Value(1.2345),
    26  		DurationValue(time.Second),
    27  		AnyValue(b),
    28  	}
    29  	var (
    30  		ii int64
    31  		s  string
    32  		bb bool
    33  		u  uint64
    34  		d  time.Duration
    35  		f  float64
    36  		a  any
    37  	)
    38  	b.Run("switch-checked", func(b *testing.B) {
    39  		for i := 0; i < b.N; i++ {
    40  			for _, v := range vs {
    41  				switch v.Kind() {
    42  				case KindString:
    43  					s = v.String()
    44  				case KindInt64:
    45  					ii = v.Int64()
    46  				case KindUint64:
    47  					u = v.Uint64()
    48  				case KindFloat64:
    49  					f = v.Float64()
    50  				case KindBool:
    51  					bb = v.Bool()
    52  				case KindDuration:
    53  					d = v.Duration()
    54  				case KindAny:
    55  					a = v.Any()
    56  				default:
    57  					panic("bad kind")
    58  				}
    59  			}
    60  		}
    61  		_ = ii
    62  		_ = s
    63  		_ = bb
    64  		_ = u
    65  		_ = d
    66  		_ = f
    67  		_ = a
    68  
    69  	})
    70  	b.Run("As", func(b *testing.B) {
    71  		for i := 0; i < b.N; i++ {
    72  			for _, kv := range vs {
    73  				if v, ok := kv.AsString(); ok {
    74  					s = v
    75  				} else if v, ok := kv.AsInt64(); ok {
    76  					ii = v
    77  				} else if v, ok := kv.AsUint64(); ok {
    78  					u = v
    79  				} else if v, ok := kv.AsFloat64(); ok {
    80  					f = v
    81  				} else if v, ok := kv.AsBool(); ok {
    82  					bb = v
    83  				} else if v, ok := kv.AsDuration(); ok {
    84  					d = v
    85  				} else if v, ok := kv.AsAny(); ok {
    86  					a = v
    87  				} else {
    88  					panic("bad kind")
    89  				}
    90  			}
    91  		}
    92  		_ = ii
    93  		_ = s
    94  		_ = bb
    95  		_ = u
    96  		_ = d
    97  		_ = f
    98  		_ = a
    99  	})
   100  
   101  	b.Run("Visit", func(b *testing.B) {
   102  		v := &setVisitor{}
   103  		b.ResetTimer()
   104  		for i := 0; i < b.N; i++ {
   105  			for _, kv := range vs {
   106  				kv.Visit(v)
   107  			}
   108  		}
   109  	})
   110  }
   111  
   112  type setVisitor struct {
   113  	i int64
   114  	s string
   115  	b bool
   116  	u uint64
   117  	d time.Duration
   118  	f float64
   119  	a any
   120  }
   121  
   122  func (v *setVisitor) String(s string)          { v.s = s }
   123  func (v *setVisitor) Int64(i int64)            { v.i = i }
   124  func (v *setVisitor) Uint64(x uint64)          { v.u = x }
   125  func (v *setVisitor) Float64(x float64)        { v.f = x }
   126  func (v *setVisitor) Bool(x bool)              { v.b = x }
   127  func (v *setVisitor) Duration(x time.Duration) { v.d = x }
   128  func (v *setVisitor) Any(x any)                { v.a = x }
   129  
   130  // When dispatching on all types, the "As" functions are slightly slower
   131  // than switching on the kind and then calling a function that checks
   132  // the kind again. See BenchmarkDispatch above.
   133  
   134  func (a Value) AsString() (string, bool) {
   135  	if a.Kind() == KindString {
   136  		return a.str(), true
   137  	}
   138  	return "", false
   139  }
   140  
   141  func (a Value) AsInt64() (int64, bool) {
   142  	if a.Kind() == KindInt64 {
   143  		return int64(a.num), true
   144  	}
   145  	return 0, false
   146  }
   147  
   148  func (a Value) AsUint64() (uint64, bool) {
   149  	if a.Kind() == KindUint64 {
   150  		return a.num, true
   151  	}
   152  	return 0, false
   153  }
   154  
   155  func (a Value) AsFloat64() (float64, bool) {
   156  	if a.Kind() == KindFloat64 {
   157  		return a.float(), true
   158  	}
   159  	return 0, false
   160  }
   161  
   162  func (a Value) AsBool() (bool, bool) {
   163  	if a.Kind() == KindBool {
   164  		return a.bool(), true
   165  	}
   166  	return false, false
   167  }
   168  
   169  func (a Value) AsDuration() (time.Duration, bool) {
   170  	if a.Kind() == KindDuration {
   171  		return a.duration(), true
   172  	}
   173  	return 0, false
   174  }
   175  
   176  func (a Value) AsAny() (any, bool) {
   177  	if a.Kind() == KindAny {
   178  		return a.any, true
   179  	}
   180  	return nil, false
   181  }
   182  
   183  // Problem: adding a type means adding a method, which is a breaking change.
   184  // Using an unexported method to force embedding will make programs compile,
   185  // But they will panic at runtime when we call the new method.
   186  type Visitor interface {
   187  	String(string)
   188  	Int64(int64)
   189  	Uint64(uint64)
   190  	Float64(float64)
   191  	Bool(bool)
   192  	Duration(time.Duration)
   193  	Any(any)
   194  }
   195  
   196  func (a Value) Visit(v Visitor) {
   197  	switch a.Kind() {
   198  	case KindString:
   199  		v.String(a.str())
   200  	case KindInt64:
   201  		v.Int64(int64(a.num))
   202  	case KindUint64:
   203  		v.Uint64(a.num)
   204  	case KindBool:
   205  		v.Bool(a.bool())
   206  	case KindFloat64:
   207  		v.Float64(a.float())
   208  	case KindDuration:
   209  		v.Duration(a.duration())
   210  	case KindAny:
   211  		v.Any(a.any)
   212  	default:
   213  		panic("bad kind")
   214  	}
   215  }