• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
7package slog
8
9import (
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
19func 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
112type 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
122func (v *setVisitor) String(s string)          { v.s = s }
123func (v *setVisitor) Int64(i int64)            { v.i = i }
124func (v *setVisitor) Uint64(x uint64)          { v.u = x }
125func (v *setVisitor) Float64(x float64)        { v.f = x }
126func (v *setVisitor) Bool(x bool)              { v.b = x }
127func (v *setVisitor) Duration(x time.Duration) { v.d = x }
128func (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
134func (a Value) AsString() (string, bool) {
135	if a.Kind() == KindString {
136		return a.str(), true
137	}
138	return "", false
139}
140
141func (a Value) AsInt64() (int64, bool) {
142	if a.Kind() == KindInt64 {
143		return int64(a.num), true
144	}
145	return 0, false
146}
147
148func (a Value) AsUint64() (uint64, bool) {
149	if a.Kind() == KindUint64 {
150		return a.num, true
151	}
152	return 0, false
153}
154
155func (a Value) AsFloat64() (float64, bool) {
156	if a.Kind() == KindFloat64 {
157		return a.float(), true
158	}
159	return 0, false
160}
161
162func (a Value) AsBool() (bool, bool) {
163	if a.Kind() == KindBool {
164		return a.bool(), true
165	}
166	return false, false
167}
168
169func (a Value) AsDuration() (time.Duration, bool) {
170	if a.Kind() == KindDuration {
171		return a.duration(), true
172	}
173	return 0, false
174}
175
176func (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.
186type 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
196func (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}
216