From 4d1303512b6e1d7c2fd89777f30abded914e2279 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Mon, 29 Sep 2025 11:34:25 +0200 Subject: [PATCH] common/hreflect: Speed up IsTrutfulValue MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit By caching the calculation of whether a type implements `IsZero`: ``` IsTruthFulVAlue-10 467.95n ± ∞ ¹ 79.13n ± ∞ ¹ -83.09% (p=0.029 n=4) ``` --- common/hreflect/helpers.go | 14 +++++++++++++- common/hreflect/helpers_test.go | 22 +++++++++++++++++----- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/common/hreflect/helpers.go b/common/hreflect/helpers.go index abb9df6b1..81fc0cde4 100644 --- a/common/hreflect/helpers.go +++ b/common/hreflect/helpers.go @@ -86,6 +86,18 @@ func IsSlice(v any) bool { var zeroType = reflect.TypeOf((*types.Zeroer)(nil)).Elem() +var isZeroCache sync.Map + +func implementsIsZero(tp reflect.Type) bool { + v, ok := isZeroCache.Load(tp) + if ok { + return v.(bool) + } + implements := tp.Implements(zeroType) + isZeroCache.Store(tp, implements) + return implements +} + // IsTruthfulValue returns whether the given value has a meaningful truth value. // This is based on template.IsTrue in Go's stdlib, but also considers // IsZero and any interface value will be unwrapped before it's considered @@ -105,7 +117,7 @@ func IsTruthfulValue(val reflect.Value) (truth bool) { return } - if val.Type().Implements(zeroType) { + if implementsIsZero(val.Type()) { return !val.Interface().(types.Zeroer).IsZero() } diff --git a/common/hreflect/helpers_test.go b/common/hreflect/helpers_test.go index 211172d39..61361c56c 100644 --- a/common/hreflect/helpers_test.go +++ b/common/hreflect/helpers_test.go @@ -106,14 +106,26 @@ func BenchmarkIsContextType(b *testing.B) { }) } -func BenchmarkIsTruthFul(b *testing.B) { - v := reflect.ValueOf("Hugo") +func BenchmarkIsTruthFulValue(b *testing.B) { + var ( + stringHugo = reflect.ValueOf("Hugo") + stringEmpty = reflect.ValueOf("") + zero = reflect.ValueOf(time.Time{}) + timeNow = reflect.ValueOf(time.Now()) + boolTrue = reflect.ValueOf(true) + boolFalse = reflect.ValueOf(false) + nilPointer = reflect.ValueOf((*zeroStruct)(nil)) + ) b.ResetTimer() for i := 0; i < b.N; i++ { - if !IsTruthfulValue(v) { - b.Fatal("not truthful") - } + IsTruthfulValue(stringHugo) + IsTruthfulValue(stringEmpty) + IsTruthfulValue(zero) + IsTruthfulValue(timeNow) + IsTruthfulValue(boolTrue) + IsTruthfulValue(boolFalse) + IsTruthfulValue(nilPointer) } } -- 2.39.5