Commit c120e449 authored by Bryan C. Mills's avatar Bryan C. Mills Committed by Bryan Mills

encoding/gob: replace RWMutex usage with sync.Map

This provides a significant speedup for encoding and decoding when
using many CPU cores.

name                        old time/op  new time/op  delta
EndToEndPipe                5.26µs ± 2%  5.38µs ± 7%     ~     (p=0.121 n=8+7)
EndToEndPipe-6              1.86µs ± 5%  1.80µs ±11%     ~     (p=0.442 n=8+8)
EndToEndPipe-48             1.39µs ± 2%  1.41µs ± 4%     ~     (p=0.645 n=8+8)
EndToEndByteBuffer          1.54µs ± 5%  1.57µs ± 5%     ~     (p=0.130 n=8+8)
EndToEndByteBuffer-6         620ns ± 6%   310ns ± 8%  -50.04%  (p=0.000 n=8+8)
EndToEndByteBuffer-48        506ns ± 4%   110ns ± 3%  -78.22%  (p=0.000 n=8+8)
EndToEndSliceByteBuffer      149µs ± 3%   153µs ± 5%   +2.80%  (p=0.021 n=8+8)
EndToEndSliceByteBuffer-6    103µs ±17%    31µs ±12%  -70.06%  (p=0.000 n=8+8)
EndToEndSliceByteBuffer-48  93.2µs ± 2%  18.0µs ± 5%  -80.66%  (p=0.000 n=7+8)
EncodeComplex128Slice       20.6µs ± 5%  20.9µs ± 8%     ~     (p=0.959 n=8+8)
EncodeComplex128Slice-6     4.10µs ±10%  3.75µs ± 8%   -8.58%  (p=0.004 n=8+7)
EncodeComplex128Slice-48    1.14µs ± 2%  0.81µs ± 2%  -28.98%  (p=0.000 n=8+8)
EncodeFloat64Slice          10.2µs ± 7%  10.1µs ± 6%     ~     (p=0.694 n=7+8)
EncodeFloat64Slice-6        2.01µs ± 6%  1.80µs ±11%  -10.30%  (p=0.004 n=8+8)
EncodeFloat64Slice-48        701ns ± 3%   408ns ± 2%  -41.72%  (p=0.000 n=8+8)
EncodeInt32Slice            11.8µs ± 7%  11.7µs ± 6%     ~     (p=0.463 n=8+7)
EncodeInt32Slice-6          2.32µs ± 4%  2.06µs ± 5%  -10.89%  (p=0.000 n=8+8)
EncodeInt32Slice-48          731ns ± 2%   445ns ± 2%  -39.10%  (p=0.000 n=7+8)
EncodeStringSlice           9.13µs ± 9%  9.18µs ± 8%     ~     (p=0.798 n=8+8)
EncodeStringSlice-6         1.91µs ± 5%  1.70µs ± 5%  -11.07%  (p=0.000 n=8+8)
EncodeStringSlice-48         679ns ± 3%   397ns ± 3%  -41.50%  (p=0.000 n=8+8)
EncodeInterfaceSlice         449µs ±11%   461µs ± 9%     ~     (p=0.328 n=8+8)
EncodeInterfaceSlice-6       503µs ± 7%    88µs ± 7%  -82.51%  (p=0.000 n=7+8)
EncodeInterfaceSlice-48      335µs ± 8%    22µs ± 1%  -93.55%  (p=0.000 n=8+7)
DecodeComplex128Slice       67.2µs ± 4%  67.0µs ± 6%     ~     (p=0.721 n=8+8)
DecodeComplex128Slice-6     22.0µs ± 8%  18.9µs ± 5%  -14.44%  (p=0.000 n=8+8)
DecodeComplex128Slice-48    46.8µs ± 3%  34.9µs ± 3%  -25.48%  (p=0.000 n=8+8)
DecodeFloat64Slice          39.4µs ± 4%  40.3µs ± 3%     ~     (p=0.105 n=8+8)
DecodeFloat64Slice-6        16.1µs ± 2%  11.2µs ± 7%  -30.64%  (p=0.001 n=6+7)
DecodeFloat64Slice-48       38.1µs ± 3%  24.0µs ± 7%  -37.10%  (p=0.000 n=8+8)
DecodeInt32Slice            39.1µs ± 4%  40.1µs ± 5%     ~     (p=0.083 n=8+8)
DecodeInt32Slice-6          16.3µs ±21%  10.6µs ± 1%  -35.17%  (p=0.000 n=8+7)
DecodeInt32Slice-48         36.5µs ± 6%  21.9µs ± 9%  -39.89%  (p=0.000 n=8+8)
DecodeStringSlice           82.9µs ± 6%  85.5µs ± 5%     ~     (p=0.121 n=8+7)
DecodeStringSlice-6         32.4µs ±11%  26.8µs ±16%  -17.37%  (p=0.000 n=8+8)
DecodeStringSlice-48        76.0µs ± 2%  57.0µs ± 5%  -25.02%  (p=0.000 n=8+8)
DecodeInterfaceSlice         718µs ± 4%   752µs ± 5%   +4.83%  (p=0.038 n=8+8)
DecodeInterfaceSlice-6       500µs ± 6%   165µs ± 7%  -66.95%  (p=0.000 n=7+8)
DecodeInterfaceSlice-48      470µs ± 5%   120µs ± 6%  -74.55%  (p=0.000 n=8+7)
DecodeMap                   3.29ms ± 5%  3.34ms ± 5%     ~     (p=0.279 n=8+8)
DecodeMap-6                 7.73ms ± 8%  7.53ms ±18%     ~     (p=0.779 n=7+8)
DecodeMap-48                7.46ms ± 6%  7.71ms ± 3%     ~     (p=0.161 n=8+8)

https://perf.golang.org/search?q=upload:20170426.4

Change-Id: I335874028ef8d7c991051004f8caadd16c92d5cc
Reviewed-on: https://go-review.googlesource.com/41872
Run-TryBot: Bryan Mills <bcmills@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: 's avatarIan Lance Taylor <iant@golang.org>
parent 33b92cd6
......@@ -655,12 +655,12 @@ func (dec *Decoder) decodeInterface(ityp reflect.Type, state *decoderState, valu
errorf("name too long (%d bytes): %.20q...", len(name), name)
}
// The concrete type must be registered.
registerLock.RLock()
typ, ok := nameToConcreteType[string(name)]
registerLock.RUnlock()
typi, ok := nameToConcreteType.Load(string(name))
if !ok {
errorf("name not registered for interface: %q", name)
}
typ := typi.(reflect.Type)
// Read the type id of the concrete value.
concreteId := dec.decodeTypeSequence(true)
if concreteId < 0 {
......
......@@ -398,12 +398,12 @@ func (enc *Encoder) encodeInterface(b *encBuffer, iv reflect.Value) {
}
ut := userType(iv.Elem().Type())
registerLock.RLock()
name, ok := concreteTypeToName[ut.base]
registerLock.RUnlock()
namei, ok := concreteTypeToName.Load(ut.base)
if !ok {
errorf("type not registered for interface: %s", ut.base)
}
name := namei.(string)
// Send the name.
state.encodeUint(uint64(len(name)))
state.b.WriteString(name)
......
......@@ -36,31 +36,21 @@ const (
xText // encoding.TextMarshaler or encoding.TextUnmarshaler
)
var (
// Protected by an RWMutex because we read it a lot and write
// it only when we see a new type, typically when compiling.
userTypeLock sync.RWMutex
userTypeCache = make(map[reflect.Type]*userTypeInfo)
)
var userTypeCache sync.Map // map[reflect.Type]*userTypeInfo
// validType returns, and saves, the information associated with user-provided type rt.
// If the user type is not valid, err will be non-nil. To be used when the error handler
// is not set up.
func validUserType(rt reflect.Type) (ut *userTypeInfo, err error) {
userTypeLock.RLock()
ut = userTypeCache[rt]
userTypeLock.RUnlock()
if ut != nil {
return
}
// Now set the value under the write lock.
userTypeLock.Lock()
defer userTypeLock.Unlock()
if ut = userTypeCache[rt]; ut != nil {
// Lost the race; not a problem.
return
func validUserType(rt reflect.Type) (*userTypeInfo, error) {
if ui, ok := userTypeCache.Load(rt); ok {
return ui.(*userTypeInfo), nil
}
ut = new(userTypeInfo)
// Construct a new userTypeInfo and atomically add it to the userTypeCache.
// If we lose the race, we'll waste a little CPU and create a little garbage
// but return the existing value anyway.
ut := new(userTypeInfo)
ut.base = rt
ut.user = rt
// A type that is just a cycle of pointers (such as type T *T) cannot
......@@ -108,8 +98,8 @@ func validUserType(rt reflect.Type) (ut *userTypeInfo, err error) {
// ut.externalDec, ut.decIndir = xText, indir
// }
userTypeCache[rt] = ut
return
ui, _ := userTypeCache.LoadOrStore(rt, ut)
return ui.(*userTypeInfo), nil
}
var (
......@@ -808,9 +798,8 @@ type GobDecoder interface {
}
var (
registerLock sync.RWMutex
nameToConcreteType = make(map[string]reflect.Type)
concreteTypeToName = make(map[reflect.Type]string)
nameToConcreteType sync.Map // map[string]reflect.Type
concreteTypeToName sync.Map // map[reflect.Type]string
)
// RegisterName is like Register but uses the provided name rather than the
......@@ -820,21 +809,22 @@ func RegisterName(name string, value interface{}) {
// reserved for nil
panic("attempt to register empty name")
}
registerLock.Lock()
defer registerLock.Unlock()
ut := userType(reflect.TypeOf(value))
// Check for incompatible duplicates. The name must refer to the
// same user type, and vice versa.
if t, ok := nameToConcreteType[name]; ok && t != ut.user {
// Store the name and type provided by the user....
if t, dup := nameToConcreteType.LoadOrStore(name, reflect.TypeOf(value)); dup && t != ut.user {
panic(fmt.Sprintf("gob: registering duplicate types for %q: %s != %s", name, t, ut.user))
}
if n, ok := concreteTypeToName[ut.base]; ok && n != name {
// but the flattened type in the type table, since that's what decode needs.
if n, dup := concreteTypeToName.LoadOrStore(ut.base, name); dup && n != name {
nameToConcreteType.Delete(name)
panic(fmt.Sprintf("gob: registering duplicate names for %s: %q != %q", ut.user, n, name))
}
// Store the name and type provided by the user....
nameToConcreteType[name] = reflect.TypeOf(value)
// but the flattened type in the type table, since that's what decode needs.
concreteTypeToName[ut.base] = name
}
// Register records a type, identified by a value for that type, under its
......
......@@ -178,9 +178,7 @@ func TestRegistrationNaming(t *testing.T) {
Register(tc.t)
tct := reflect.TypeOf(tc.t)
registerLock.RLock()
ct := nameToConcreteType[tc.name]
registerLock.RUnlock()
ct, _ := nameToConcreteType.Load(tc.name)
if ct != tct {
t.Errorf("nameToConcreteType[%q] = %v, want %v", tc.name, ct, tct)
}
......@@ -188,7 +186,7 @@ func TestRegistrationNaming(t *testing.T) {
if tct.Kind() == reflect.Ptr {
tct = tct.Elem()
}
if n := concreteTypeToName[tct]; n != tc.name {
if n, _ := concreteTypeToName.Load(tct); n != tc.name {
t.Errorf("concreteTypeToName[%v] got %v, want %v", tct, n, tc.name)
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment