Skip to content

Commit 0d076d9

Browse files
authored
rlp: use atomic.Value for type cache (ethereum#22902)
All encoding/decoding operations read the type cache to find the writer/decoder function responsible for a type. When analyzing CPU profiles of geth during sync, I found that the use of sync.RWMutex in cache lookups appears in the profiles. It seems we are running into CPU cache contention problems when package rlp is heavily used on all CPU cores during sync. This change makes it use atomic.Value + a writer lock instead of sync.RWMutex. In the common case where the typeinfo entry is present in the cache, we simply fetch the map and lookup the type.
1 parent 59f259b commit 0d076d9

File tree

4 files changed

+94
-29
lines changed

4 files changed

+94
-29
lines changed

rlp/decode.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ func makeListDecoder(typ reflect.Type, tag tags) (decoder, error) {
245245
}
246246
return decodeByteSlice, nil
247247
}
248-
etypeinfo := cachedTypeInfo1(etype, tags{})
248+
etypeinfo := theTC.infoWhileGenerating(etype, tags{})
249249
if etypeinfo.decoderErr != nil {
250250
return nil, etypeinfo.decoderErr
251251
}
@@ -424,7 +424,7 @@ func zeroFields(structval reflect.Value, fields []field) {
424424
// makePtrDecoder creates a decoder that decodes into the pointer's element type.
425425
func makePtrDecoder(typ reflect.Type, tag tags) (decoder, error) {
426426
etype := typ.Elem()
427-
etypeinfo := cachedTypeInfo1(etype, tags{})
427+
etypeinfo := theTC.infoWhileGenerating(etype, tags{})
428428
switch {
429429
case etypeinfo.decoderErr != nil:
430430
return nil, etypeinfo.decoderErr

rlp/encode.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,7 @@ func writeInterface(val reflect.Value, w *encbuf) error {
517517
}
518518

519519
func makeSliceWriter(typ reflect.Type, ts tags) (writer, error) {
520-
etypeinfo := cachedTypeInfo1(typ.Elem(), tags{})
520+
etypeinfo := theTC.infoWhileGenerating(typ.Elem(), tags{})
521521
if etypeinfo.writerErr != nil {
522522
return nil, etypeinfo.writerErr
523523
}
@@ -585,7 +585,7 @@ func makeStructWriter(typ reflect.Type) (writer, error) {
585585
}
586586

587587
func makePtrWriter(typ reflect.Type, ts tags) (writer, error) {
588-
etypeinfo := cachedTypeInfo1(typ.Elem(), tags{})
588+
etypeinfo := theTC.infoWhileGenerating(typ.Elem(), tags{})
589589
if etypeinfo.writerErr != nil {
590590
return nil, etypeinfo.writerErr
591591
}

rlp/encode_test.go

+33
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"io"
2424
"io/ioutil"
2525
"math/big"
26+
"runtime"
2627
"sync"
2728
"testing"
2829

@@ -480,3 +481,35 @@ func BenchmarkEncodeBigInts(b *testing.B) {
480481
}
481482
}
482483
}
484+
485+
func BenchmarkEncodeConcurrentInterface(b *testing.B) {
486+
type struct1 struct {
487+
A string
488+
B *big.Int
489+
C [20]byte
490+
}
491+
value := []interface{}{
492+
uint(999),
493+
&struct1{A: "hello", B: big.NewInt(0xFFFFFFFF)},
494+
[10]byte{1, 2, 3, 4, 5, 6},
495+
[]string{"yeah", "yeah", "yeah"},
496+
}
497+
498+
var wg sync.WaitGroup
499+
for cpu := 0; cpu < runtime.NumCPU(); cpu++ {
500+
wg.Add(1)
501+
go func() {
502+
defer wg.Done()
503+
504+
var buffer bytes.Buffer
505+
for i := 0; i < b.N; i++ {
506+
buffer.Reset()
507+
err := Encode(&buffer, value)
508+
if err != nil {
509+
panic(err)
510+
}
511+
}
512+
}()
513+
}
514+
wg.Wait()
515+
}

rlp/typecache.go

+57-25
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,10 @@ import (
2121
"reflect"
2222
"strings"
2323
"sync"
24+
"sync/atomic"
2425
)
2526

26-
var (
27-
typeCacheMutex sync.RWMutex
28-
typeCache = make(map[typekey]*typeinfo)
29-
)
30-
27+
// typeinfo is an entry in the type cache.
3128
type typeinfo struct {
3229
decoder decoder
3330
decoderErr error // error from makeDecoder
@@ -65,41 +62,76 @@ type decoder func(*Stream, reflect.Value) error
6562

6663
type writer func(reflect.Value, *encbuf) error
6764

65+
var theTC = newTypeCache()
66+
67+
type typeCache struct {
68+
cur atomic.Value
69+
70+
// This lock synchronizes writers.
71+
mu sync.Mutex
72+
next map[typekey]*typeinfo
73+
}
74+
75+
func newTypeCache() *typeCache {
76+
c := new(typeCache)
77+
c.cur.Store(make(map[typekey]*typeinfo))
78+
return c
79+
}
80+
6881
func cachedDecoder(typ reflect.Type) (decoder, error) {
69-
info := cachedTypeInfo(typ, tags{})
82+
info := theTC.info(typ)
7083
return info.decoder, info.decoderErr
7184
}
7285

7386
func cachedWriter(typ reflect.Type) (writer, error) {
74-
info := cachedTypeInfo(typ, tags{})
87+
info := theTC.info(typ)
7588
return info.writer, info.writerErr
7689
}
7790

78-
func cachedTypeInfo(typ reflect.Type, tags tags) *typeinfo {
79-
typeCacheMutex.RLock()
80-
info := typeCache[typekey{typ, tags}]
81-
typeCacheMutex.RUnlock()
82-
if info != nil {
91+
func (c *typeCache) info(typ reflect.Type) *typeinfo {
92+
key := typekey{Type: typ}
93+
if info := c.cur.Load().(map[typekey]*typeinfo)[key]; info != nil {
8394
return info
8495
}
85-
// not in the cache, need to generate info for this type.
86-
typeCacheMutex.Lock()
87-
defer typeCacheMutex.Unlock()
88-
return cachedTypeInfo1(typ, tags)
96+
97+
// Not in the cache, need to generate info for this type.
98+
return c.generate(typ, tags{})
99+
}
100+
101+
func (c *typeCache) generate(typ reflect.Type, tags tags) *typeinfo {
102+
c.mu.Lock()
103+
defer c.mu.Unlock()
104+
105+
cur := c.cur.Load().(map[typekey]*typeinfo)
106+
if info := cur[typekey{typ, tags}]; info != nil {
107+
return info
108+
}
109+
110+
// Copy cur to next.
111+
c.next = make(map[typekey]*typeinfo, len(cur)+1)
112+
for k, v := range cur {
113+
c.next[k] = v
114+
}
115+
116+
// Generate.
117+
info := c.infoWhileGenerating(typ, tags)
118+
119+
// next -> cur
120+
c.cur.Store(c.next)
121+
c.next = nil
122+
return info
89123
}
90124

91-
func cachedTypeInfo1(typ reflect.Type, tags tags) *typeinfo {
125+
func (c *typeCache) infoWhileGenerating(typ reflect.Type, tags tags) *typeinfo {
92126
key := typekey{typ, tags}
93-
info := typeCache[key]
94-
if info != nil {
95-
// another goroutine got the write lock first
127+
if info := c.next[key]; info != nil {
96128
return info
97129
}
98-
// put a dummy value into the cache before generating.
99-
// if the generator tries to lookup itself, it will get
130+
// Put a dummy value into the cache before generating.
131+
// If the generator tries to lookup itself, it will get
100132
// the dummy value and won't call itself recursively.
101-
info = new(typeinfo)
102-
typeCache[key] = info
133+
info := new(typeinfo)
134+
c.next[key] = info
103135
info.generate(typ, tags)
104136
return info
105137
}
@@ -133,7 +165,7 @@ func structFields(typ reflect.Type) (fields []field, err error) {
133165
} else if anyOptional {
134166
return nil, fmt.Errorf(`rlp: struct field %v.%s needs "optional" tag`, typ, f.Name)
135167
}
136-
info := cachedTypeInfo1(f.Type, tags)
168+
info := theTC.infoWhileGenerating(f.Type, tags)
137169
fields = append(fields, field{i, info, tags.optional})
138170
}
139171
}

0 commit comments

Comments
 (0)