From: Bryan C. Mills Date: Thu, 16 Feb 2017 22:37:30 +0000 (-0500) Subject: encoding/gob: replace RWMutex usage with sync.Map X-Git-Tag: go1.9beta1~434 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=c120e449fbc618f9510387d718de0cef5f73af3a;p=gostls13.git 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 TryBot-Result: Gobot Gobot Reviewed-by: Ian Lance Taylor --- diff --git a/src/encoding/gob/decode.go b/src/encoding/gob/decode.go index 645aa71c38..013f71ccdb 100644 --- a/src/encoding/gob/decode.go +++ b/src/encoding/gob/decode.go @@ -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 { diff --git a/src/encoding/gob/encode.go b/src/encoding/gob/encode.go index edf204f47d..5371e7245f 100644 --- a/src/encoding/gob/encode.go +++ b/src/encoding/gob/encode.go @@ -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) diff --git a/src/encoding/gob/type.go b/src/encoding/gob/type.go index c27f7e9707..31c0ef7af1 100644 --- a/src/encoding/gob/type.go +++ b/src/encoding/gob/type.go @@ -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 diff --git a/src/encoding/gob/type_test.go b/src/encoding/gob/type_test.go index e230d22d43..14f25d8ac4 100644 --- a/src/encoding/gob/type_test.go +++ b/src/encoding/gob/type_test.go @@ -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) } }