]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/compile: change the way we handle large map values
authorKeith Randall <khr@golang.org>
Tue, 19 Apr 2016 15:31:04 +0000 (08:31 -0700)
committerKeith Randall <khr@golang.org>
Wed, 20 Apr 2016 21:15:31 +0000 (21:15 +0000)
mapaccess{1,2} returns a pointer to the value.  When the key
is not in the map, it returns a pointer to zeroed memory.
Currently, for large map values we have a complicated scheme which
dynamically allocates zeroed memory for this purpose.  It is ugly
code and requires an atomic.Load in a bunch of places we'd rather
not have it.

Switch to a scheme where callsites of mapaccess{1,2} which expect
large return values pass in a pointer to zeroed memory that
mapaccess can return if the key is not found.  This avoids the
atomic.Load on all map accesses with a few extra instructions only
for the large value acccesses, plus a bit of bss space.

There was a time (1.4 & 1.5?) where we did something like this but
all the tricks to make the right size zero value were done by the
linker.  That scheme broke in the presence of dyamic linking.
The scheme in this CL works even when dynamic linking.

Fixes #12337

Change-Id: Ic2d0319944af33bbb59785938d9ab80958d1b4b1
Reviewed-on: https://go-review.googlesource.com/22221
Run-TryBot: Keith Randall <khr@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Michael Hudson-Doyle <michael.hudson@canonical.com>
src/cmd/compile/internal/gc/builtin.go
src/cmd/compile/internal/gc/builtin/runtime.go
src/cmd/compile/internal/gc/go.go
src/cmd/compile/internal/gc/main.go
src/cmd/compile/internal/gc/obj.go
src/cmd/compile/internal/gc/reflect.go
src/cmd/compile/internal/gc/walk.go
src/runtime/hashmap.go
src/runtime/hashmap_fast.go
src/runtime/map_test.go

index 411c7b860554e3efddc1b0d4fc488b4ded3d3dfa..b593d11296695b2655714248c711ec1af1c82359 100644 (file)
@@ -70,10 +70,12 @@ const runtimeimport = "" +
        "func @\"\".mapaccess1_fast32 (@\"\".mapType·2 *byte, @\"\".hmap·3 map[any]any, @\"\".key·4 any) (@\"\".val·1 *any)\n" +
        "func @\"\".mapaccess1_fast64 (@\"\".mapType·2 *byte, @\"\".hmap·3 map[any]any, @\"\".key·4 any) (@\"\".val·1 *any)\n" +
        "func @\"\".mapaccess1_faststr (@\"\".mapType·2 *byte, @\"\".hmap·3 map[any]any, @\"\".key·4 any) (@\"\".val·1 *any)\n" +
+       "func @\"\".mapaccess1_fat (@\"\".mapType·2 *byte, @\"\".hmap·3 map[any]any, @\"\".key·4 *any, @\"\".zero·5 *byte) (@\"\".val·1 *any)\n" +
        "func @\"\".mapaccess2 (@\"\".mapType·3 *byte, @\"\".hmap·4 map[any]any, @\"\".key·5 *any) (@\"\".val·1 *any, @\"\".pres·2 bool)\n" +
        "func @\"\".mapaccess2_fast32 (@\"\".mapType·3 *byte, @\"\".hmap·4 map[any]any, @\"\".key·5 any) (@\"\".val·1 *any, @\"\".pres·2 bool)\n" +
        "func @\"\".mapaccess2_fast64 (@\"\".mapType·3 *byte, @\"\".hmap·4 map[any]any, @\"\".key·5 any) (@\"\".val·1 *any, @\"\".pres·2 bool)\n" +
        "func @\"\".mapaccess2_faststr (@\"\".mapType·3 *byte, @\"\".hmap·4 map[any]any, @\"\".key·5 any) (@\"\".val·1 *any, @\"\".pres·2 bool)\n" +
+       "func @\"\".mapaccess2_fat (@\"\".mapType·3 *byte, @\"\".hmap·4 map[any]any, @\"\".key·5 *any, @\"\".zero·6 *byte) (@\"\".val·1 *any, @\"\".pres·2 bool)\n" +
        "func @\"\".mapassign1 (@\"\".mapType·1 *byte, @\"\".hmap·2 map[any]any, @\"\".key·3 *any, @\"\".val·4 *any)\n" +
        "func @\"\".mapiterinit (@\"\".mapType·1 *byte, @\"\".hmap·2 map[any]any, @\"\".hiter·3 *any)\n" +
        "func @\"\".mapdelete (@\"\".mapType·1 *byte, @\"\".hmap·2 map[any]any, @\"\".key·3 *any)\n" +
index 584368a144243b1d4c1324dc5ebb70aa48d7bbef..e9316cb31317f445cf010d16fe47d3c917727694 100644 (file)
@@ -89,10 +89,12 @@ func mapaccess1(mapType *byte, hmap map[any]any, key *any) (val *any)
 func mapaccess1_fast32(mapType *byte, hmap map[any]any, key any) (val *any)
 func mapaccess1_fast64(mapType *byte, hmap map[any]any, key any) (val *any)
 func mapaccess1_faststr(mapType *byte, hmap map[any]any, key any) (val *any)
+func mapaccess1_fat(mapType *byte, hmap map[any]any, key *any, zero *byte) (val *any)
 func mapaccess2(mapType *byte, hmap map[any]any, key *any) (val *any, pres bool)
 func mapaccess2_fast32(mapType *byte, hmap map[any]any, key any) (val *any, pres bool)
 func mapaccess2_fast64(mapType *byte, hmap map[any]any, key any) (val *any, pres bool)
 func mapaccess2_faststr(mapType *byte, hmap map[any]any, key any) (val *any, pres bool)
+func mapaccess2_fat(mapType *byte, hmap map[any]any, key *any, zero *byte) (val *any, pres bool)
 func mapassign1(mapType *byte, hmap map[any]any, key *any, val *any)
 func mapiterinit(mapType *byte, hmap map[any]any, hiter *any)
 func mapdelete(mapType *byte, hmap map[any]any, key *any)
index af9aaf0dae76b15a73aa0d637d6d01295581c0b9..87b6121c8e80b70c37ae7053567d9a9f33f51459 100644 (file)
@@ -175,6 +175,9 @@ var unsafepkg *Pkg // package unsafe
 
 var trackpkg *Pkg // fake package for field tracking
 
+var mappkg *Pkg // fake package for map zero value
+var zerosize int64
+
 var Tptr EType // either TPTR32 or TPTR64
 
 var myimportpath string
index 37e8a17886201998477fe0c10f08e875b227071a..2afd262fedda8301156771c281853c6a5e11dddb 100644 (file)
@@ -137,6 +137,11 @@ func Main() {
        typepkg = mkpkg("type")
        typepkg.Name = "type"
 
+       // pseudo-package used for map zero values
+       mappkg = mkpkg("go.map")
+       mappkg.Name = "go.map"
+       mappkg.Prefix = "go.map"
+
        goroot = obj.Getgoroot()
        goos = obj.Getgoos()
 
index b60f78f6382d7df5472e031c379eb74d4967ee32..fab611fdb59e2509bd95894d659a214cd6ec375d 100644 (file)
@@ -87,6 +87,11 @@ func dumpobj() {
        dumpglobls()
        externdcl = tmp
 
+       if zerosize > 0 {
+               zero := Pkglookup("zero", mappkg)
+               ggloblsym(zero, int32(zerosize), obj.DUPOK|obj.RODATA)
+       }
+
        dumpdata()
        obj.Writeobjdirect(Ctxt, bout.Writer)
 
index 5031045c64eff2db968e04c678b3739594e27285..4792f88abeb18f879f493360bdb1694b9badf101 100644 (file)
@@ -1689,3 +1689,27 @@ func (p *GCProg) emit(t *Type, offset int64) {
                }
        }
 }
+
+// zeroaddr returns the address of a symbol with at least
+// size bytes of zeros.
+func zeroaddr(size int64) *Node {
+       if size >= 1<<31 {
+               Fatalf("map value too big %d", size)
+       }
+       if zerosize < size {
+               zerosize = size
+       }
+       s := Pkglookup("zero", mappkg)
+       if s.Def == nil {
+               x := newname(s)
+               x.Type = Types[TUINT8]
+               x.Class = PEXTERN
+               x.Typecheck = 1
+               s.Def = x
+       }
+       z := Nod(OADDR, s.Def, nil)
+       z.Type = Ptrto(Types[TUINT8])
+       z.Addable = true
+       z.Typecheck = 1
+       return z
+}
index 82ac74ae336fe6e176b813cd53a26bcdf96b9d74..8cce85de9a94a4c36d8f4acc0d381d96a5b2749a 100644 (file)
@@ -864,8 +864,14 @@ opswitch:
                //   a = *var
                a := n.List.First()
 
-               fn := mapfn(p, t)
-               r = mkcall1(fn, fn.Type.Results(), init, typename(t), r.Left, key)
+               if w := t.Val().Width; w <= 1024 { // 1024 must match ../../../../runtime/hashmap.go:maxZero
+                       fn := mapfn(p, t)
+                       r = mkcall1(fn, fn.Type.Results(), init, typename(t), r.Left, key)
+               } else {
+                       fn := mapfn("mapaccess2_fat", t)
+                       z := zeroaddr(w)
+                       r = mkcall1(fn, fn.Type.Results(), init, typename(t), r.Left, key, z)
+               }
 
                // mapaccess2* returns a typed bool, but due to spec changes,
                // the boolean result of i.(T) is now untyped so we make it the
@@ -1222,7 +1228,13 @@ opswitch:
                        p = "mapaccess1"
                }
 
-               n = mkcall1(mapfn(p, t), Ptrto(t.Val()), init, typename(t), n.Left, key)
+               if w := t.Val().Width; w <= 1024 { // 1024 must match ../../../../runtime/hashmap.go:maxZero
+                       n = mkcall1(mapfn(p, t), Ptrto(t.Val()), init, typename(t), n.Left, key)
+               } else {
+                       p = "mapaccess1_fat"
+                       z := zeroaddr(w)
+                       n = mkcall1(mapfn(p, t), Ptrto(t.Val()), init, typename(t), n.Left, key, z)
+               }
                n = Nod(OIND, n, nil)
                n.Type = t.Val()
                n.Typecheck = 1
index 4f5d03d9837e0c4de7c6e52608e3b1a1813cb7b4..ff59faab5d92bbb9f2d3fbe88b96f5e91e6b5c4e 100644 (file)
@@ -236,9 +236,6 @@ func makemap(t *maptype, hint int64, h *hmap, bucket unsafe.Pointer) *hmap {
                throw("need padding in bucket (value)")
        }
 
-       // make sure zeroptr is large enough
-       mapzero(t.elem)
-
        // find size parameter which will hold the requested # of elements
        B := uint8(0)
        for ; hint > bucketCnt && float32(hint) > loadFactor*float32(uintptr(1)<<B); B++ {
@@ -283,7 +280,7 @@ func mapaccess1(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
                msanread(key, t.key.size)
        }
        if h == nil || h.count == 0 {
-               return atomic.Loadp(unsafe.Pointer(&zeroptr))
+               return unsafe.Pointer(&zeroVal[0])
        }
        if h.flags&hashWriting != 0 {
                throw("concurrent map read and map write")
@@ -321,7 +318,7 @@ func mapaccess1(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
                }
                b = b.overflow(t)
                if b == nil {
-                       return atomic.Loadp(unsafe.Pointer(&zeroptr))
+                       return unsafe.Pointer(&zeroVal[0])
                }
        }
 }
@@ -337,7 +334,7 @@ func mapaccess2(t *maptype, h *hmap, key unsafe.Pointer) (unsafe.Pointer, bool)
                msanread(key, t.key.size)
        }
        if h == nil || h.count == 0 {
-               return atomic.Loadp(unsafe.Pointer(&zeroptr)), false
+               return unsafe.Pointer(&zeroVal[0]), false
        }
        if h.flags&hashWriting != 0 {
                throw("concurrent map read and map write")
@@ -375,7 +372,7 @@ func mapaccess2(t *maptype, h *hmap, key unsafe.Pointer) (unsafe.Pointer, bool)
                }
                b = b.overflow(t)
                if b == nil {
-                       return atomic.Loadp(unsafe.Pointer(&zeroptr)), false
+                       return unsafe.Pointer(&zeroVal[0]), false
                }
        }
 }
@@ -426,6 +423,22 @@ func mapaccessK(t *maptype, h *hmap, key unsafe.Pointer) (unsafe.Pointer, unsafe
        }
 }
 
+func mapaccess1_fat(t *maptype, h *hmap, key, zero unsafe.Pointer) unsafe.Pointer {
+       v := mapaccess1(t, h, key)
+       if v == unsafe.Pointer(&zeroVal[0]) {
+               return zero
+       }
+       return v
+}
+
+func mapaccess2_fat(t *maptype, h *hmap, key, zero unsafe.Pointer) (unsafe.Pointer, bool) {
+       v := mapaccess1(t, h, key)
+       if v == unsafe.Pointer(&zeroVal[0]) {
+               return zero, false
+       }
+       return v, true
+}
+
 func mapassign1(t *maptype, h *hmap, key unsafe.Pointer, val unsafe.Pointer) {
        if h == nil {
                panic(plainError("assignment to entry in nil map"))
@@ -1044,39 +1057,5 @@ func reflect_ismapkey(t *_type) bool {
        return ismapkey(t)
 }
 
-var zerolock mutex
-
-const initialZeroSize = 1024
-
-var zeroinitial [initialZeroSize]byte
-
-// All accesses to zeroptr and zerosize must be atomic so that they
-// can be accessed without locks in the common case.
-var zeroptr unsafe.Pointer = unsafe.Pointer(&zeroinitial)
-var zerosize uintptr = initialZeroSize
-
-// mapzero ensures that zeroptr points to a buffer large enough to
-// serve as the zero value for t.
-func mapzero(t *_type) {
-       // Is the type small enough for existing buffer?
-       cursize := uintptr(atomic.Loadp(unsafe.Pointer(&zerosize)))
-       if t.size <= cursize {
-               return
-       }
-
-       // Allocate a new buffer.
-       lock(&zerolock)
-       cursize = uintptr(atomic.Loadp(unsafe.Pointer(&zerosize)))
-       if cursize < t.size {
-               for cursize < t.size {
-                       cursize *= 2
-                       if cursize == 0 {
-                               // need >2GB zero on 32-bit machine
-                               throw("map element too large")
-                       }
-               }
-               atomic.StorepNoWB(unsafe.Pointer(&zeroptr), persistentalloc(cursize, 64, &memstats.other_sys))
-               atomic.StorepNoWB(unsafe.Pointer(&zerosize), unsafe.Pointer(zerosize))
-       }
-       unlock(&zerolock)
-}
+const maxZero = 1024 // must match value in ../cmd/compile/internal/gc/walk.go
+var zeroVal [maxZero]byte
index 6a5484edeecde4a9f54b38ea5e1241f76e7ec3db..8f9bb5a6fcd2c1544d7ec7770c01c96b057c6fca 100644 (file)
@@ -5,7 +5,6 @@
 package runtime
 
 import (
-       "runtime/internal/atomic"
        "runtime/internal/sys"
        "unsafe"
 )
@@ -16,7 +15,7 @@ func mapaccess1_fast32(t *maptype, h *hmap, key uint32) unsafe.Pointer {
                racereadpc(unsafe.Pointer(h), callerpc, funcPC(mapaccess1_fast32))
        }
        if h == nil || h.count == 0 {
-               return atomic.Loadp(unsafe.Pointer(&zeroptr))
+               return unsafe.Pointer(&zeroVal[0])
        }
        if h.flags&hashWriting != 0 {
                throw("concurrent map read and map write")
@@ -50,7 +49,7 @@ func mapaccess1_fast32(t *maptype, h *hmap, key uint32) unsafe.Pointer {
                }
                b = b.overflow(t)
                if b == nil {
-                       return atomic.Loadp(unsafe.Pointer(&zeroptr))
+                       return unsafe.Pointer(&zeroVal[0])
                }
        }
 }
@@ -61,7 +60,7 @@ func mapaccess2_fast32(t *maptype, h *hmap, key uint32) (unsafe.Pointer, bool) {
                racereadpc(unsafe.Pointer(h), callerpc, funcPC(mapaccess2_fast32))
        }
        if h == nil || h.count == 0 {
-               return atomic.Loadp(unsafe.Pointer(&zeroptr)), false
+               return unsafe.Pointer(&zeroVal[0]), false
        }
        if h.flags&hashWriting != 0 {
                throw("concurrent map read and map write")
@@ -95,7 +94,7 @@ func mapaccess2_fast32(t *maptype, h *hmap, key uint32) (unsafe.Pointer, bool) {
                }
                b = b.overflow(t)
                if b == nil {
-                       return atomic.Loadp(unsafe.Pointer(&zeroptr)), false
+                       return unsafe.Pointer(&zeroVal[0]), false
                }
        }
 }
@@ -106,7 +105,7 @@ func mapaccess1_fast64(t *maptype, h *hmap, key uint64) unsafe.Pointer {
                racereadpc(unsafe.Pointer(h), callerpc, funcPC(mapaccess1_fast64))
        }
        if h == nil || h.count == 0 {
-               return atomic.Loadp(unsafe.Pointer(&zeroptr))
+               return unsafe.Pointer(&zeroVal[0])
        }
        if h.flags&hashWriting != 0 {
                throw("concurrent map read and map write")
@@ -140,7 +139,7 @@ func mapaccess1_fast64(t *maptype, h *hmap, key uint64) unsafe.Pointer {
                }
                b = b.overflow(t)
                if b == nil {
-                       return atomic.Loadp(unsafe.Pointer(&zeroptr))
+                       return unsafe.Pointer(&zeroVal[0])
                }
        }
 }
@@ -151,7 +150,7 @@ func mapaccess2_fast64(t *maptype, h *hmap, key uint64) (unsafe.Pointer, bool) {
                racereadpc(unsafe.Pointer(h), callerpc, funcPC(mapaccess2_fast64))
        }
        if h == nil || h.count == 0 {
-               return atomic.Loadp(unsafe.Pointer(&zeroptr)), false
+               return unsafe.Pointer(&zeroVal[0]), false
        }
        if h.flags&hashWriting != 0 {
                throw("concurrent map read and map write")
@@ -185,7 +184,7 @@ func mapaccess2_fast64(t *maptype, h *hmap, key uint64) (unsafe.Pointer, bool) {
                }
                b = b.overflow(t)
                if b == nil {
-                       return atomic.Loadp(unsafe.Pointer(&zeroptr)), false
+                       return unsafe.Pointer(&zeroVal[0]), false
                }
        }
 }
@@ -196,7 +195,7 @@ func mapaccess1_faststr(t *maptype, h *hmap, ky string) unsafe.Pointer {
                racereadpc(unsafe.Pointer(h), callerpc, funcPC(mapaccess1_faststr))
        }
        if h == nil || h.count == 0 {
-               return atomic.Loadp(unsafe.Pointer(&zeroptr))
+               return unsafe.Pointer(&zeroVal[0])
        }
        if h.flags&hashWriting != 0 {
                throw("concurrent map read and map write")
@@ -220,7 +219,7 @@ func mapaccess1_faststr(t *maptype, h *hmap, ky string) unsafe.Pointer {
                                        return add(unsafe.Pointer(b), dataOffset+bucketCnt*2*sys.PtrSize+i*uintptr(t.valuesize))
                                }
                        }
-                       return atomic.Loadp(unsafe.Pointer(&zeroptr))
+                       return unsafe.Pointer(&zeroVal[0])
                }
                // long key, try not to do more comparisons than necessary
                keymaybe := uintptr(bucketCnt)
@@ -258,7 +257,7 @@ func mapaccess1_faststr(t *maptype, h *hmap, ky string) unsafe.Pointer {
                                return add(unsafe.Pointer(b), dataOffset+bucketCnt*2*sys.PtrSize+keymaybe*uintptr(t.valuesize))
                        }
                }
-               return atomic.Loadp(unsafe.Pointer(&zeroptr))
+               return unsafe.Pointer(&zeroVal[0])
        }
 dohash:
        hash := t.key.alg.hash(noescape(unsafe.Pointer(&ky)), uintptr(h.hash0))
@@ -290,7 +289,7 @@ dohash:
                }
                b = b.overflow(t)
                if b == nil {
-                       return atomic.Loadp(unsafe.Pointer(&zeroptr))
+                       return unsafe.Pointer(&zeroVal[0])
                }
        }
 }
@@ -301,7 +300,7 @@ func mapaccess2_faststr(t *maptype, h *hmap, ky string) (unsafe.Pointer, bool) {
                racereadpc(unsafe.Pointer(h), callerpc, funcPC(mapaccess2_faststr))
        }
        if h == nil || h.count == 0 {
-               return atomic.Loadp(unsafe.Pointer(&zeroptr)), false
+               return unsafe.Pointer(&zeroVal[0]), false
        }
        if h.flags&hashWriting != 0 {
                throw("concurrent map read and map write")
@@ -325,7 +324,7 @@ func mapaccess2_faststr(t *maptype, h *hmap, ky string) (unsafe.Pointer, bool) {
                                        return add(unsafe.Pointer(b), dataOffset+bucketCnt*2*sys.PtrSize+i*uintptr(t.valuesize)), true
                                }
                        }
-                       return atomic.Loadp(unsafe.Pointer(&zeroptr)), false
+                       return unsafe.Pointer(&zeroVal[0]), false
                }
                // long key, try not to do more comparisons than necessary
                keymaybe := uintptr(bucketCnt)
@@ -361,7 +360,7 @@ func mapaccess2_faststr(t *maptype, h *hmap, ky string) (unsafe.Pointer, bool) {
                                return add(unsafe.Pointer(b), dataOffset+bucketCnt*2*sys.PtrSize+keymaybe*uintptr(t.valuesize)), true
                        }
                }
-               return atomic.Loadp(unsafe.Pointer(&zeroptr)), false
+               return unsafe.Pointer(&zeroVal[0]), false
        }
 dohash:
        hash := t.key.alg.hash(noescape(unsafe.Pointer(&ky)), uintptr(h.hash0))
@@ -393,7 +392,7 @@ dohash:
                }
                b = b.overflow(t)
                if b == nil {
-                       return atomic.Loadp(unsafe.Pointer(&zeroptr)), false
+                       return unsafe.Pointer(&zeroVal[0]), false
                }
        }
 }
index 9d2894cb6f9b2272261e41550e45450d5fc0459c..496f8e88686873d60a3a8a12895c1ffc803a920b 100644 (file)
@@ -317,6 +317,22 @@ func TestBigItems(t *testing.T) {
        }
 }
 
+func TestMapHugeZero(t *testing.T) {
+       type T [4000]byte
+       m := map[int]T{}
+       x := m[0]
+       if x != (T{}) {
+               t.Errorf("map value not zero")
+       }
+       y, ok := m[0]
+       if ok {
+               t.Errorf("map value should be missing")
+       }
+       if y != (T{}) {
+               t.Errorf("map value not zero")
+       }
+}
+
 type empty struct {
 }