-
Notifications
You must be signed in to change notification settings - Fork 158
Expand file tree
/
Copy patharena_thread_safety_bench_test.go
More file actions
98 lines (87 loc) · 2.58 KB
/
arena_thread_safety_bench_test.go
File metadata and controls
98 lines (87 loc) · 2.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package resolve
import (
"strconv"
"sync"
"testing"
"github.com/wundergraph/astjson"
"github.com/wundergraph/go-arena"
)
// cacheLoadAllocs simulates the allocation pattern of tryL2CacheLoad:
// parse cached JSON bytes, create wrapper objects, allocate slices.
func cacheLoadAllocs(a arena.Arena) {
// 1. extractCacheKeysStrings: allocate slice + string bytes
keys := arena.AllocateSlice[string](a, 0, 4)
for range 4 {
buf := arena.AllocateSlice[byte](a, 0, 64)
buf = arena.SliceAppend(a, buf, []byte("cache:entity:Product:id:prod-1234")...)
keys = arena.SliceAppend(a, keys, string(buf))
}
_ = keys
// 2. populateFromCache: parse JSON bytes
v, _ := astjson.ParseBytesWithArena(a, []byte(`{"__typename":"Product","id":"prod-1234","name":"Test Product","price":29.99}`))
// 3. EntityMergePath wrapping: create wrapper objects
obj := astjson.ObjectValue(a)
obj.Set(a, "product", v)
outer := astjson.ObjectValue(a)
outer.Set(a, "data", obj)
// 4. denormalizeFromCache: create new object tree
result := astjson.ObjectValue(a)
result.Set(a, "productName", v.Get("name"))
result.Set(a, "productPrice", v.Get("price"))
}
// BenchmarkConcurrentArena measures Option A: single arena wrapped with NewConcurrentArena.
// All goroutines allocate from the same mutex-protected arena.
func BenchmarkConcurrentArena(b *testing.B) {
for _, goroutines := range []int{1, 4, 8, 16} {
b.Run(goroutineName(goroutines), func(b *testing.B) {
a := arena.NewConcurrentArena(arena.NewMonotonicArena(arena.WithMinBufferSize(64 * 1024)))
b.ResetTimer()
for b.Loop() {
var wg sync.WaitGroup
for range goroutines {
wg.Go(func() {
cacheLoadAllocs(a)
})
}
wg.Wait()
a.Reset()
}
})
}
}
// BenchmarkPerGoroutineArena measures Option B: each goroutine gets its own arena from sync.Pool.
// Zero lock contention on allocations.
func BenchmarkPerGoroutineArena(b *testing.B) {
pool := sync.Pool{
New: func() any {
return arena.NewMonotonicArena(arena.WithMinBufferSize(4096))
},
}
for _, goroutines := range []int{1, 4, 8, 16} {
b.Run(goroutineName(goroutines), func(b *testing.B) {
b.ResetTimer()
for b.Loop() {
arenas := make([]arena.Arena, goroutines)
var wg sync.WaitGroup
for i := range goroutines {
ga := pool.Get().(arena.Arena)
arenas[i] = ga
wg.Go(func() {
cacheLoadAllocs(ga)
})
}
wg.Wait()
for _, ga := range arenas {
ga.Reset()
pool.Put(ga)
}
}
})
}
}
func goroutineName(n int) string {
return "goroutines=" + stringFromInt(n)
}
func stringFromInt(n int) string {
return strconv.Itoa(n)
}