Skip to content

Commit dd12918

Browse files
committed
优化exsync.Once,使其性能接近sync.Once
1 parent 8890931 commit dd12918

File tree

3 files changed

+86
-34
lines changed

3 files changed

+86
-34
lines changed

exsync/benchmark/once_test.go

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// MIT License
2+
// Copyright (c) 2020 Qi Yin <[email protected]>
3+
4+
package benchmark
5+
6+
import (
7+
"sync"
8+
"testing"
9+
"unsafe"
10+
11+
"github.com/thinkeridea/go-extend/exsync"
12+
)
13+
14+
type one int
15+
16+
func (o *one) Increment() {
17+
*o++
18+
}
19+
20+
func BenchmarkSyncOnce(b *testing.B) {
21+
var once sync.Once
22+
f := func() {
23+
_ = new(one)
24+
}
25+
b.RunParallel(func(pb *testing.PB) {
26+
for pb.Next() {
27+
once.Do(f)
28+
}
29+
})
30+
}
31+
32+
func BenchmarkOnce(b *testing.B) {
33+
var once exsync.Once
34+
f := func() interface{} { return new(one) }
35+
b.RunParallel(func(pb *testing.PB) {
36+
for pb.Next() {
37+
_ = once.Do(f).(*one)
38+
}
39+
})
40+
}
41+
42+
func BenchmarkOncePointer(b *testing.B) {
43+
var once exsync.OncePointer
44+
f := func() unsafe.Pointer { return unsafe.Pointer(new(one)) }
45+
b.RunParallel(func(pb *testing.PB) {
46+
for pb.Next() {
47+
_ = (*one)(once.Do(f))
48+
}
49+
})
50+
}

exsync/once.go

+36-10
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package exsync
55

66
import (
77
"sync"
8+
"sync/atomic"
89
"unsafe"
910
)
1011

@@ -30,9 +31,15 @@ import (
3031
// return res[0].(*mysql.Client), res[1].(error)
3132
// }
3233
//
33-
// 使用该方法需要一些取舍,它简单实用,相比 sync.Once 性能有所下降,不过它依然很快,这不会形成性能问题
34+
// 使用该方法需要一些取舍,它简单实用,性能无限接近 sync.Once。
3435
type Once struct {
35-
once sync.Once
36+
// done indicates whether the action has been performed.
37+
// It is first in the struct because it is used in the hot path.
38+
// The hot path is inlined at every call site.
39+
// Placing done first allows more compact instructions on some architectures (amd64/x86),
40+
// and fewer instructions (to calculate offset) on other architectures.
41+
done uint32
42+
m sync.Mutex
3643
v interface{}
3744
}
3845

@@ -55,16 +62,26 @@ type Once struct {
5562
// without calling f.
5663
//
5764
func (o *Once) Do(f func() interface{}) interface{} {
58-
o.once.Do(func() {
59-
o.v = f()
60-
})
65+
if atomic.LoadUint32(&o.done) == 0 {
66+
o.doSlow(f)
67+
}
6168

6269
return o.v
6370
}
6471

65-
// OncePointer 性能方面略好于 Once,但不会有太大改善,依然落后于 sync.Once, 在某些场景下可以使用,更推荐使用 Once
72+
func (o *Once) doSlow(f func() interface{}) {
73+
o.m.Lock()
74+
defer o.m.Unlock()
75+
if o.done == 0 {
76+
defer atomic.StoreUint32(&o.done, 1)
77+
o.v = f()
78+
}
79+
}
80+
81+
// OncePointer 性能方面略好于 Once,但不会有太大改善, 在某些场景下可以使用,更推荐使用 Once
6682
type OncePointer struct {
67-
once sync.Once
83+
done uint32
84+
m sync.Mutex
6885
v unsafe.Pointer
6986
}
7087

@@ -87,9 +104,18 @@ type OncePointer struct {
87104
// without calling f.
88105
//
89106
func (o *OncePointer) Do(f func() unsafe.Pointer) unsafe.Pointer {
90-
o.once.Do(func() {
91-
o.v = f()
92-
})
107+
if atomic.LoadUint32(&o.done) == 0 {
108+
o.doSlow(f)
109+
}
93110

94111
return o.v
95112
}
113+
114+
func (o *OncePointer) doSlow(f func() unsafe.Pointer) {
115+
o.m.Lock()
116+
defer o.m.Unlock()
117+
if o.done == 0 {
118+
defer atomic.StoreUint32(&o.done, 1)
119+
o.v = f()
120+
}
121+
}

exsync/once_test.go

-24
Original file line numberDiff line numberDiff line change
@@ -63,17 +63,6 @@ func TestOncePanic(t *testing.T) {
6363
})
6464
}
6565

66-
func BenchmarkOnce(b *testing.B) {
67-
var once Once
68-
var o = new(one)
69-
f := func() interface{} { return o }
70-
b.RunParallel(func(pb *testing.PB) {
71-
for pb.Next() {
72-
_ = once.Do(f).(*one)
73-
}
74-
})
75-
}
76-
7766
func runPointer(t *testing.T, once *OncePointer, o *one, c chan bool) {
7867
v := once.Do(func() unsafe.Pointer {
7968
o.Increment()
@@ -122,16 +111,3 @@ func TestOncePointerPanic(t *testing.T) {
122111
return nil
123112
})
124113
}
125-
126-
func BenchmarkOncePointer(b *testing.B) {
127-
var once OncePointer
128-
var o = new(one)
129-
f := func() unsafe.Pointer {
130-
return unsafe.Pointer(o)
131-
}
132-
b.RunParallel(func(pb *testing.PB) {
133-
for pb.Next() {
134-
_ = (*one)(once.Do(f))
135-
}
136-
})
137-
}

0 commit comments

Comments
 (0)