mirror of
https://github.com/gohugoio/hugo.git
synced 2026-08-24 07:18:54 +00:00
hugolib/doctree: Simplify lock setup in SimpleThreadSafeTree to reduce read allocation
```
│ 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)
````
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user