]> Cypherpunks repositories - gostls13.git/commitdiff
encoding/gob: replace RWMutex usage with sync.Map
authorBryan C. Mills <bcmills@google.com>
Thu, 16 Feb 2017 22:37:30 +0000 (17:37 -0500)
committerBryan Mills <bcmills@google.com>
Thu, 27 Apr 2017 15:34:57 +0000 (15:34 +0000)
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: Ian Lance Taylor <iant@golang.org>
src/encoding/gob/decode.go
src/encoding/gob/encode.go
src/encoding/gob/type.go
src/encoding/gob/type_test.go

index 645aa71c3896068aee7b17768e07faf6d8725a8d..013f71ccdb7ef03e241a724110e5716d9ac10062 100644 (file)
@@ -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 {
index edf204f47d2b31d39f01c183d56a8874f8ad0092..5371e7245f4ebb6e4744b72870b09e52241c9f40 100644 (file)
@@ -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)
index c27f7e9707e91922f872ff36de6ba3fd20216dd3..31c0ef7af1513f462f008a85c485d2549bbcc919 100644 (file)
@@ -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
index e230d22d4315584702e5f89ace19f6ee8088b547..14f25d8ac4c7fee172041d63cf08d54852a5630c 100644 (file)
@@ -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)
                }
        }