]> git.maquefel.me Git - brevno-suite/hugo/commitdiff
hugolib/doctree: Simplify lock setup in SimpleThreadSafeTree to reduce read allocation
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Tue, 18 Nov 2025 11:30:50 +0000 (12:30 +0100)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Tue, 18 Nov 2025 18:17:15 +0000 (19:17 +0100)
```
                                      │    sec/op    │   sec/op     vs base               │
SimpleThreadSafeTree/Get-10             45.80n ± 11%   27.71n ± 7%  -39.51% (p=0.002 n=6)
SimpleThreadSafeTree/Insert-10          40.96n ±  1%   40.36n ± 0%   -1.47% (p=0.002 n=6)
SimpleThreadSafeTree/LongestPrefix-10   47.12n ±  0%   32.60n ± 1%  -30.81% (p=0.002 n=6)
geomean                                 44.54n         33.16n       -25.57%

                                      │ cmp152.bench │       perf-allloc20251118.bench        │
                                      │     B/op     │    B/op     vs base                    │
SimpleThreadSafeTree/Get-10               16.00 ± 0%    0.00 ± 0%  -100.00% (p=0.002 n=6)
SimpleThreadSafeTree/Insert-10            8.000 ± 0%   8.000 ± 0%         ~ (p=1.000 n=6) ¹
SimpleThreadSafeTree/LongestPrefix-10     16.00 ± 0%    0.00 ± 0%  -100.00% (p=0.002 n=6)
````

hugolib/doctree/simpletree.go
hugolib/doctree/simpletree_test.go [new file with mode: 0644]

index b79ef3f2f432e359aa599a70162bf83494944c38..eb535a6403c8bf0d1f6e03521e990d1ba4e22173 100644 (file)
@@ -127,33 +127,16 @@ func NewSimpleThreadSafeTree[T any]() *SimpleThreadSafeTree[T] {
 
 // SimpleThreadSafeTree is a thread safe radix tree that holds T.
 type SimpleThreadSafeTree[T any] struct {
-       mu     *sync.RWMutex
-       noLock bool
-       tree   *radix.Tree
-       zero   T
+       mu   *sync.RWMutex
+       tree *radix.Tree
+       zero T
 }
 
 var noopFunc = func() {}
 
-func (tree *SimpleThreadSafeTree[T]) readLock() func() {
-       if tree.noLock {
-               return noopFunc
-       }
-       tree.mu.RLock()
-       return tree.mu.RUnlock
-}
-
-func (tree *SimpleThreadSafeTree[T]) writeLock() func() {
-       if tree.noLock {
-               return noopFunc
-       }
-       tree.mu.Lock()
-       return tree.mu.Unlock
-}
-
 func (tree *SimpleThreadSafeTree[T]) Get(s string) T {
-       unlock := tree.readLock()
-       defer unlock()
+       tree.mu.RLock()
+       defer tree.mu.RUnlock()
 
        if v, ok := tree.tree.Get(s); ok {
                return v.(T)
@@ -162,8 +145,8 @@ func (tree *SimpleThreadSafeTree[T]) Get(s string) T {
 }
 
 func (tree *SimpleThreadSafeTree[T]) LongestPrefix(s string) (string, T) {
-       unlock := tree.readLock()
-       defer unlock()
+       tree.mu.RLock()
+       defer tree.mu.RUnlock()
 
        if s, v, ok := tree.tree.LongestPrefix(s); ok {
                return s, v.(T)
@@ -172,8 +155,8 @@ func (tree *SimpleThreadSafeTree[T]) LongestPrefix(s string) (string, T) {
 }
 
 func (tree *SimpleThreadSafeTree[T]) Insert(s string, v T) T {
-       unlock := tree.writeLock()
-       defer unlock()
+       tree.mu.Lock()
+       defer tree.mu.Unlock()
 
        tree.tree.Insert(s, v)
        return v
@@ -193,12 +176,6 @@ func (tree *SimpleThreadSafeTree[T]) Lock(lockType LockType) func() {
        return noopFunc
 }
 
-func (tree SimpleThreadSafeTree[T]) LockTree(lockType LockType) (TreeThreadSafe[T], func()) {
-       unlock := tree.Lock(lockType)
-       tree.noLock = true
-       return &tree, unlock // create a copy of tree with the noLock flag set to true.
-}
-
 func (tree *SimpleThreadSafeTree[T]) WalkPrefix(lockType LockType, s string, f func(s string, v T) (bool, error)) error {
        commit := tree.Lock(lockType)
        defer commit()
diff --git a/hugolib/doctree/simpletree_test.go b/hugolib/doctree/simpletree_test.go
new file mode 100644 (file)
index 0000000..fe4c0fa
--- /dev/null
@@ -0,0 +1,53 @@
+// Copyright 2025 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package doctree
+
+import (
+       "fmt"
+       "testing"
+)
+
+func BenchmarkSimpleThreadSafeTree(b *testing.B) {
+       newTestTree := func() TreeThreadSafe[int] {
+               t := NewSimpleThreadSafeTree[int]()
+               for i := 0; i < 1000; i++ {
+                       t.Insert(fmt.Sprintf("key%d", i), i)
+               }
+               return t
+       }
+
+       b.Run("Get", func(b *testing.B) {
+               t := newTestTree()
+               b.ResetTimer()
+               for b.Loop() {
+                       t.Get("key500")
+               }
+       })
+
+       b.Run("Insert", func(b *testing.B) {
+               t := newTestTree()
+               b.ResetTimer()
+               for b.Loop() {
+                       t.Insert("key500", 501)
+               }
+       })
+
+       b.Run("LongestPrefix", func(b *testing.B) {
+               t := newTestTree()
+               b.ResetTimer()
+               for b.Loop() {
+                       t.LongestPrefix("key500extra")
+               }
+       })
+}