aboutsummaryrefslogtreecommitdiff
path: root/src/sync/oncefunc_test.go
diff options
context:
space:
mode:
authorBrian Byrne <bdbyrne@gmail.com>2024-07-28 07:42:54 -0700
committerGopher Robot <gobot@golang.org>2025-02-25 11:52:33 -0800
commitc5ff26a7a0ba7a8aa6320d70d0933f117d882dde (patch)
tree37d9911b610a4ea7feaa9f18080b99022a27ac6b /src/sync/oncefunc_test.go
parent61641c11455af9571e6e01449c7ea774b0069594 (diff)
downloadgo-c5ff26a7a0ba7a8aa6320d70d0933f117d882dde.tar.xz
sync: reduce OnceFunc (and variants) heap allocations
The lifetime of the variables are identical; capture them in a single struct to avoid individual allocations. The inner closure can also avoid allocation by using the capture of the outer closure. Escape analysis for OnceValues: /go/src/sync/oncefunc.go:74:29: moved to heap: sync.f /go/src/sync/oncefunc.go:76:3: moved to heap: sync.once /go/src/sync/oncefunc.go:77:3: moved to heap: sync.valid /go/src/sync/oncefunc.go:78:3: moved to heap: sync.p /go/src/sync/oncefunc.go:79:3: moved to heap: sync.r1 /go/src/sync/oncefunc.go:80:3: moved to heap: sync.r2 /go/src/sync/oncefunc.go:82:7: func literal escapes to heap /go/src/sync/oncefunc.go:83:9: func literal does not escape /go/src/sync/oncefunc.go:93:9: func literal escapes to heap After provided changes: /go/src/sync/oncefunc.go:86:2: moved to heap: sync.d /go/src/sync/oncefunc.go:96:9: func literal escapes to heap /go/src/sync/oncefunc.go:99:13: func literal does not escape /go/src/sync/oncefunc.go:100:10: func literal does not escape Change-Id: Ib06e650fd427b57e0bdbdf1fe759fe436104ff79 Reviewed-on: https://go-review.googlesource.com/c/go/+/601596 Auto-Submit: Austin Clements <austin@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Michael Pratt <mpratt@google.com> Reviewed-by: Austin Clements <austin@google.com>
Diffstat (limited to 'src/sync/oncefunc_test.go')
-rw-r--r--src/sync/oncefunc_test.go123
1 files changed, 115 insertions, 8 deletions
diff --git a/src/sync/oncefunc_test.go b/src/sync/oncefunc_test.go
index daf094571f..743a816b65 100644
--- a/src/sync/oncefunc_test.go
+++ b/src/sync/oncefunc_test.go
@@ -19,22 +19,30 @@ import (
func TestOnceFunc(t *testing.T) {
calls := 0
- f := sync.OnceFunc(func() { calls++ })
+ of := func() { calls++ }
+ f := sync.OnceFunc(of)
allocs := testing.AllocsPerRun(10, f)
if calls != 1 {
t.Errorf("want calls==1, got %d", calls)
}
if allocs != 0 {
- t.Errorf("want 0 allocations per call, got %v", allocs)
+ t.Errorf("want 0 allocations per call to f, got %v", allocs)
+ }
+ allocs = testing.AllocsPerRun(10, func() {
+ f = sync.OnceFunc(of)
+ })
+ if allocs > 2 {
+ t.Errorf("want at most 2 allocations per call to OnceFunc, got %v", allocs)
}
}
func TestOnceValue(t *testing.T) {
calls := 0
- f := sync.OnceValue(func() int {
+ of := func() int {
calls++
return calls
- })
+ }
+ f := sync.OnceValue(of)
allocs := testing.AllocsPerRun(10, func() { f() })
value := f()
if calls != 1 {
@@ -44,16 +52,23 @@ func TestOnceValue(t *testing.T) {
t.Errorf("want value==1, got %d", value)
}
if allocs != 0 {
- t.Errorf("want 0 allocations per call, got %v", allocs)
+ t.Errorf("want 0 allocations per call to f, got %v", allocs)
+ }
+ allocs = testing.AllocsPerRun(10, func() {
+ f = sync.OnceValue(of)
+ })
+ if allocs > 2 {
+ t.Errorf("want at most 2 allocations per call to OnceValue, got %v", allocs)
}
}
func TestOnceValues(t *testing.T) {
calls := 0
- f := sync.OnceValues(func() (int, int) {
+ of := func() (int, int) {
calls++
return calls, calls + 1
- })
+ }
+ f := sync.OnceValues(of)
allocs := testing.AllocsPerRun(10, func() { f() })
v1, v2 := f()
if calls != 1 {
@@ -63,7 +78,13 @@ func TestOnceValues(t *testing.T) {
t.Errorf("want v1==1 and v2==2, got %d and %d", v1, v2)
}
if allocs != 0 {
- t.Errorf("want 0 allocations per call, got %v", allocs)
+ t.Errorf("want 0 allocations per call to f, got %v", allocs)
+ }
+ allocs = testing.AllocsPerRun(10, func() {
+ f = sync.OnceValues(of)
+ })
+ if allocs > 2 {
+ t.Errorf("want at most 2 allocations per call to OnceValues, got %v", allocs)
}
}
@@ -234,6 +255,8 @@ var (
onceFunc = sync.OnceFunc(func() {})
onceFuncOnce sync.Once
+
+ onceFuncFunc func()
)
func doOnceFunc() {
@@ -267,6 +290,12 @@ func BenchmarkOnceFunc(b *testing.B) {
f()
}
})
+ b.Run("v=Make", func(b *testing.B) {
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ onceFuncFunc = sync.OnceFunc(func() {})
+ }
+ })
}
var (
@@ -274,6 +303,8 @@ var (
onceValueOnce sync.Once
onceValueValue int
+
+ onceValueFunc func() int
)
func doOnceValue() int {
@@ -310,4 +341,80 @@ func BenchmarkOnceValue(b *testing.B) {
}
}
})
+ b.Run("v=Make", func(b *testing.B) {
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ onceValueFunc = sync.OnceValue(func() int { return 42 })
+ }
+ })
+}
+
+const (
+ onceValuesWant1 = 42
+ onceValuesWant2 = true
+)
+
+var (
+ onceValues = sync.OnceValues(func() (int, bool) {
+ return onceValuesWant1, onceValuesWant2
+ })
+
+ onceValuesOnce sync.Once
+ onceValuesValue1 int
+ onceValuesValue2 bool
+
+ onceValuesFunc func() (int, bool)
+)
+
+func doOnceValues() (int, bool) {
+ onceValuesOnce.Do(func() {
+ onceValuesValue1 = onceValuesWant1
+ onceValuesValue2 = onceValuesWant2
+ })
+ return onceValuesValue1, onceValuesValue2
+}
+
+func BenchmarkOnceValues(b *testing.B) {
+ // See BenchmarkOnceFunc
+ b.Run("v=Once", func(b *testing.B) {
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ if got1, got2 := doOnceValues(); got1 != onceValuesWant1 {
+ b.Fatalf("value 1: got %d, want %d", got1, onceValuesWant1)
+ } else if got2 != onceValuesWant2 {
+ b.Fatalf("value 2: got %v, want %v", got2, onceValuesWant2)
+ }
+ }
+ })
+ b.Run("v=Global", func(b *testing.B) {
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ if got1, got2 := onceValues(); got1 != onceValuesWant1 {
+ b.Fatalf("value 1: got %d, want %d", got1, onceValuesWant1)
+ } else if got2 != onceValuesWant2 {
+ b.Fatalf("value 2: got %v, want %v", got2, onceValuesWant2)
+ }
+ }
+ })
+ b.Run("v=Local", func(b *testing.B) {
+ b.ReportAllocs()
+ onceValues := sync.OnceValues(func() (int, bool) {
+ return onceValuesWant1, onceValuesWant2
+ })
+ for i := 0; i < b.N; i++ {
+ if got1, got2 := onceValues(); got1 != onceValuesWant1 {
+ b.Fatalf("value 1: got %d, want %d", got1, onceValuesWant1)
+ } else if got2 != onceValuesWant2 {
+ b.Fatalf("value 2: got %v, want %v", got2, onceValuesWant2)
+ }
+ }
+ })
+ b.Run("v=Make", func(b *testing.B) {
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ onceValuesFunc = sync.OnceValues(func() (int, bool) {
+ return onceValuesWant1, onceValuesWant2
+ })
+ }
+ })
}