Commit 365594ad authored by Martin Möhrmann's avatar Martin Möhrmann

runtime: simplify memory capacity check in growslice

Instead of comparing if the number of elements will
not fit into memory check if the memory size of the
slices backing memory is higher then the memory limit.

This avoids a division or maxElems lookup.

With et.size > 0:
   uintptr(newcap)                > maxSliceCap(et.size)
-> uintptr(int(capmem / et.size)) > _MaxMem  /  et.size
->             capmem / et.size   > _MaxMem  /  et.size
->             capmem             > _MaxMem

Note that due to integer division from capmem > _MaxMem
it does not follow that uintptr(newcap) > maxSliceCap(et.size).

Consolidated runtime GrowSlice benchmarks by using sub-benchmarks and
added more struct sizes to show performance improvement when division
is avoided for element sizes larger than 32 bytes.

AMD64:
GrowSlice/Byte       38.9ns ± 2%  38.9ns ± 1%    ~     (p=0.974 n=20+20)
GrowSlice/Int        58.3ns ± 3%  58.0ns ± 2%    ~     (p=0.154 n=20+19)
GrowSlice/Ptr        95.7ns ± 2%  95.1ns ± 2%  -0.60%  (p=0.034 n=20+20)
GrowSlice/Struct/24  95.4ns ± 1%  93.9ns ± 1%  -1.54%  (p=0.000 n=19+19)
GrowSlice/Struct/32   110ns ± 1%   108ns ± 1%  -1.76%  (p=0.000 n=19+20)
GrowSlice/Struct/40   138ns ± 1%   128ns ± 1%  -7.09%  (p=0.000 n=20+20)

Change-Id: I1c37857c74ea809da373e668791caffb6a5cbbd3
Reviewed-on: https://go-review.googlesource.com/53471
Run-TryBot: Martin Möhrmann <moehrmann@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarKeith Randall <khr@golang.org>
parent ef6978b2
...@@ -18,42 +18,52 @@ func BenchmarkMakeSlice(b *testing.B) { ...@@ -18,42 +18,52 @@ func BenchmarkMakeSlice(b *testing.B) {
} }
} }
func BenchmarkGrowSliceBytes(b *testing.B) { type (
b.StopTimer() struct24 struct{ a, b, c int64 }
var x = make([]byte, 9) struct32 struct{ a, b, c, d int64 }
b.StartTimer() struct40 struct{ a, b, c, d, e int64 }
)
func BenchmarkGrowSlice(b *testing.B) {
b.Run("Byte", func(b *testing.B) {
x := make([]byte, 9)
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = append([]byte(nil), x...) _ = append([]byte(nil), x...)
} }
} })
b.Run("Int", func(b *testing.B) {
func BenchmarkGrowSliceInts(b *testing.B) { x := make([]int, 9)
b.StopTimer()
var x = make([]int, 9)
b.StartTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = append([]int(nil), x...) _ = append([]int(nil), x...)
} }
} })
b.Run("Ptr", func(b *testing.B) {
func BenchmarkGrowSlicePtr(b *testing.B) { x := make([]*byte, 9)
b.StopTimer()
var x = make([]*byte, 9)
b.StartTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = append([]*byte(nil), x...) _ = append([]*byte(nil), x...)
} }
} })
b.Run("Struct", func(b *testing.B) {
type struct24 struct{ a, b, c int64 } b.Run("24", func(b *testing.B) {
x := make([]struct24, 9)
func BenchmarkGrowSliceStruct24Bytes(b *testing.B) {
b.StopTimer()
var x = make([]struct24, 9)
b.StartTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = append([]struct24(nil), x...) _ = append([]struct24(nil), x...)
} }
})
b.Run("32", func(b *testing.B) {
x := make([]struct32, 9)
for i := 0; i < b.N; i++ {
_ = append([]struct32(nil), x...)
}
})
b.Run("40", func(b *testing.B) {
x := make([]struct40, 9)
for i := 0; i < b.N; i++ {
_ = append([]struct40(nil), x...)
}
})
})
} }
func BenchmarkAppend(b *testing.B) { func BenchmarkAppend(b *testing.B) {
......
...@@ -131,7 +131,7 @@ func growslice(et *_type, old slice, cap int) slice { ...@@ -131,7 +131,7 @@ func growslice(et *_type, old slice, cap int) slice {
newcap = int(capmem / et.size) newcap = int(capmem / et.size)
} }
if cap < old.cap || uintptr(newcap) > maxSliceCap(et.size) { if cap < old.cap || capmem > _MaxMem {
panic(errorString("growslice: cap out of range")) panic(errorString("growslice: cap out of range"))
} }
......
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