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 }