// Copyright 2022 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Benchmark for accessing Value values. package slog import ( "testing" "time" ) // The "As" form is the slowest. // The switch-panic and visitor times are almost the same. // BenchmarkDispatch/switch-checked-8 8669427 137.7 ns/op // BenchmarkDispatch/As-8 8212087 145.3 ns/op // BenchmarkDispatch/Visit-8 8926146 135.3 ns/op func BenchmarkDispatch(b *testing.B) { vs := []Value{ Int64Value(32768), Uint64Value(0xfacecafe), StringValue("anything"), BoolValue(true), Float64Value(1.2345), DurationValue(time.Second), AnyValue(b), } var ( ii int64 s string bb bool u uint64 d time.Duration f float64 a any ) b.Run("switch-checked", func(b *testing.B) { for i := 0; i < b.N; i++ { for _, v := range vs { switch v.Kind() { case KindString: s = v.String() case KindInt64: ii = v.Int64() case KindUint64: u = v.Uint64() case KindFloat64: f = v.Float64() case KindBool: bb = v.Bool() case KindDuration: d = v.Duration() case KindAny: a = v.Any() default: panic("bad kind") } } } _ = ii _ = s _ = bb _ = u _ = d _ = f _ = a }) b.Run("As", func(b *testing.B) { for i := 0; i < b.N; i++ { for _, kv := range vs { if v, ok := kv.AsString(); ok { s = v } else if v, ok := kv.AsInt64(); ok { ii = v } else if v, ok := kv.AsUint64(); ok { u = v } else if v, ok := kv.AsFloat64(); ok { f = v } else if v, ok := kv.AsBool(); ok { bb = v } else if v, ok := kv.AsDuration(); ok { d = v } else if v, ok := kv.AsAny(); ok { a = v } else { panic("bad kind") } } } _ = ii _ = s _ = bb _ = u _ = d _ = f _ = a }) b.Run("Visit", func(b *testing.B) { v := &setVisitor{} b.ResetTimer() for i := 0; i < b.N; i++ { for _, kv := range vs { kv.Visit(v) } } }) } type setVisitor struct { i int64 s string b bool u uint64 d time.Duration f float64 a any } func (v *setVisitor) String(s string) { v.s = s } func (v *setVisitor) Int64(i int64) { v.i = i } func (v *setVisitor) Uint64(x uint64) { v.u = x } func (v *setVisitor) Float64(x float64) { v.f = x } func (v *setVisitor) Bool(x bool) { v.b = x } func (v *setVisitor) Duration(x time.Duration) { v.d = x } func (v *setVisitor) Any(x any) { v.a = x } // When dispatching on all types, the "As" functions are slightly slower // than switching on the kind and then calling a function that checks // the kind again. See BenchmarkDispatch above. func (a Value) AsString() (string, bool) { if a.Kind() == KindString { return a.str(), true } return "", false } func (a Value) AsInt64() (int64, bool) { if a.Kind() == KindInt64 { return int64(a.num), true } return 0, false } func (a Value) AsUint64() (uint64, bool) { if a.Kind() == KindUint64 { return a.num, true } return 0, false } func (a Value) AsFloat64() (float64, bool) { if a.Kind() == KindFloat64 { return a.float(), true } return 0, false } func (a Value) AsBool() (bool, bool) { if a.Kind() == KindBool { return a.bool(), true } return false, false } func (a Value) AsDuration() (time.Duration, bool) { if a.Kind() == KindDuration { return a.duration(), true } return 0, false } func (a Value) AsAny() (any, bool) { if a.Kind() == KindAny { return a.any, true } return nil, false } // Problem: adding a type means adding a method, which is a breaking change. // Using an unexported method to force embedding will make programs compile, // But they will panic at runtime when we call the new method. type Visitor interface { String(string) Int64(int64) Uint64(uint64) Float64(float64) Bool(bool) Duration(time.Duration) Any(any) } func (a Value) Visit(v Visitor) { switch a.Kind() { case KindString: v.String(a.str()) case KindInt64: v.Int64(int64(a.num)) case KindUint64: v.Uint64(a.num) case KindBool: v.Bool(a.bool()) case KindFloat64: v.Float64(a.float()) case KindDuration: v.Duration(a.duration()) case KindAny: v.Any(a.any) default: panic("bad kind") } }