diff options
| author | Brian Byrne <bdbyrne@gmail.com> | 2024-07-28 07:42:54 -0700 |
|---|---|---|
| committer | Gopher Robot <gobot@golang.org> | 2025-02-25 11:52:33 -0800 |
| commit | c5ff26a7a0ba7a8aa6320d70d0933f117d882dde (patch) | |
| tree | 37d9911b610a4ea7feaa9f18080b99022a27ac6b /src/sync/oncefunc_test.go | |
| parent | 61641c11455af9571e6e01449c7ea774b0069594 (diff) | |
| download | go-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.go | 123 |
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 + }) + } + }) } |
