mirror of
https://github.com/golang/go
synced 2024-11-19 13:54:56 -07:00
365594ad59
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: Keith Randall <khr@golang.org>
339 lines
7.0 KiB
Go
339 lines
7.0 KiB
Go
// Copyright 2011 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
package runtime_test
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
)
|
|
|
|
const N = 20
|
|
|
|
func BenchmarkMakeSlice(b *testing.B) {
|
|
var x []byte
|
|
for i := 0; i < b.N; i++ {
|
|
x = make([]byte, 32)
|
|
_ = x
|
|
}
|
|
}
|
|
|
|
type (
|
|
struct24 struct{ a, b, c int64 }
|
|
struct32 struct{ a, b, c, d int64 }
|
|
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++ {
|
|
_ = append([]byte(nil), x...)
|
|
}
|
|
})
|
|
b.Run("Int", func(b *testing.B) {
|
|
x := make([]int, 9)
|
|
for i := 0; i < b.N; i++ {
|
|
_ = append([]int(nil), x...)
|
|
}
|
|
})
|
|
b.Run("Ptr", func(b *testing.B) {
|
|
x := make([]*byte, 9)
|
|
for i := 0; i < b.N; i++ {
|
|
_ = append([]*byte(nil), x...)
|
|
}
|
|
})
|
|
b.Run("Struct", func(b *testing.B) {
|
|
b.Run("24", func(b *testing.B) {
|
|
x := make([]struct24, 9)
|
|
for i := 0; i < b.N; i++ {
|
|
_ = 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) {
|
|
b.StopTimer()
|
|
x := make([]int, 0, N)
|
|
b.StartTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
x = x[0:0]
|
|
for j := 0; j < N; j++ {
|
|
x = append(x, j)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkAppendGrowByte(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
var x []byte
|
|
for j := 0; j < 1<<20; j++ {
|
|
x = append(x, byte(j))
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkAppendGrowString(b *testing.B) {
|
|
var s string
|
|
for i := 0; i < b.N; i++ {
|
|
var x []string
|
|
for j := 0; j < 1<<20; j++ {
|
|
x = append(x, s)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkAppendSlice(b *testing.B) {
|
|
for _, length := range []int{1, 4, 7, 8, 15, 16, 32} {
|
|
b.Run(fmt.Sprint(length, "Bytes"), func(b *testing.B) {
|
|
x := make([]byte, 0, N)
|
|
y := make([]byte, length)
|
|
for i := 0; i < b.N; i++ {
|
|
x = x[0:0]
|
|
x = append(x, y...)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
var (
|
|
blackhole []byte
|
|
)
|
|
|
|
func BenchmarkAppendSliceLarge(b *testing.B) {
|
|
for _, length := range []int{1 << 10, 4 << 10, 16 << 10, 64 << 10, 256 << 10, 1024 << 10} {
|
|
y := make([]byte, length)
|
|
b.Run(fmt.Sprint(length, "Bytes"), func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
blackhole = nil
|
|
blackhole = append(blackhole, y...)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func BenchmarkAppendStr(b *testing.B) {
|
|
for _, str := range []string{
|
|
"1",
|
|
"1234",
|
|
"12345678",
|
|
"1234567890123456",
|
|
"12345678901234567890123456789012",
|
|
} {
|
|
b.Run(fmt.Sprint(len(str), "Bytes"), func(b *testing.B) {
|
|
x := make([]byte, 0, N)
|
|
for i := 0; i < b.N; i++ {
|
|
x = x[0:0]
|
|
x = append(x, str...)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func BenchmarkAppendSpecialCase(b *testing.B) {
|
|
b.StopTimer()
|
|
x := make([]int, 0, N)
|
|
b.StartTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
x = x[0:0]
|
|
for j := 0; j < N; j++ {
|
|
if len(x) < cap(x) {
|
|
x = x[:len(x)+1]
|
|
x[len(x)-1] = j
|
|
} else {
|
|
x = append(x, j)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var x []int
|
|
|
|
func f() int {
|
|
x[:1][0] = 3
|
|
return 2
|
|
}
|
|
|
|
func TestSideEffectOrder(t *testing.T) {
|
|
x = make([]int, 0, 10)
|
|
x = append(x, 1, f())
|
|
if x[0] != 1 || x[1] != 2 {
|
|
t.Error("append failed: ", x[0], x[1])
|
|
}
|
|
}
|
|
|
|
func TestAppendOverlap(t *testing.T) {
|
|
x := []byte("1234")
|
|
x = append(x[1:], x...) // p > q in runtime·appendslice.
|
|
got := string(x)
|
|
want := "2341234"
|
|
if got != want {
|
|
t.Errorf("overlap failed: got %q want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func BenchmarkCopy(b *testing.B) {
|
|
for _, l := range []int{1, 2, 4, 8, 12, 16, 32, 128, 1024} {
|
|
buf := make([]byte, 4096)
|
|
b.Run(fmt.Sprint(l, "Byte"), func(b *testing.B) {
|
|
s := make([]byte, l)
|
|
var n int
|
|
for i := 0; i < b.N; i++ {
|
|
n = copy(buf, s)
|
|
}
|
|
b.SetBytes(int64(n))
|
|
})
|
|
b.Run(fmt.Sprint(l, "String"), func(b *testing.B) {
|
|
s := string(make([]byte, l))
|
|
var n int
|
|
for i := 0; i < b.N; i++ {
|
|
n = copy(buf, s)
|
|
}
|
|
b.SetBytes(int64(n))
|
|
})
|
|
}
|
|
}
|
|
|
|
var (
|
|
sByte []byte
|
|
s1Ptr []uintptr
|
|
s2Ptr [][2]uintptr
|
|
s3Ptr [][3]uintptr
|
|
s4Ptr [][4]uintptr
|
|
)
|
|
|
|
// BenchmarkAppendInPlace tests the performance of append
|
|
// when the result is being written back to the same slice.
|
|
// In order for the in-place optimization to occur,
|
|
// the slice must be referred to by address;
|
|
// using a global is an easy way to trigger that.
|
|
// We test the "grow" and "no grow" paths separately,
|
|
// but not the "normal" (occasionally grow) path,
|
|
// because it is a blend of the other two.
|
|
// We use small numbers and small sizes in an attempt
|
|
// to avoid benchmarking memory allocation and copying.
|
|
// We use scalars instead of pointers in an attempt
|
|
// to avoid benchmarking the write barriers.
|
|
// We benchmark four common sizes (byte, pointer, string/interface, slice),
|
|
// and one larger size.
|
|
func BenchmarkAppendInPlace(b *testing.B) {
|
|
b.Run("NoGrow", func(b *testing.B) {
|
|
const C = 128
|
|
|
|
b.Run("Byte", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
sByte = make([]byte, C)
|
|
for j := 0; j < C; j++ {
|
|
sByte = append(sByte, 0x77)
|
|
}
|
|
}
|
|
})
|
|
|
|
b.Run("1Ptr", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
s1Ptr = make([]uintptr, C)
|
|
for j := 0; j < C; j++ {
|
|
s1Ptr = append(s1Ptr, 0x77)
|
|
}
|
|
}
|
|
})
|
|
|
|
b.Run("2Ptr", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
s2Ptr = make([][2]uintptr, C)
|
|
for j := 0; j < C; j++ {
|
|
s2Ptr = append(s2Ptr, [2]uintptr{0x77, 0x88})
|
|
}
|
|
}
|
|
})
|
|
|
|
b.Run("3Ptr", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
s3Ptr = make([][3]uintptr, C)
|
|
for j := 0; j < C; j++ {
|
|
s3Ptr = append(s3Ptr, [3]uintptr{0x77, 0x88, 0x99})
|
|
}
|
|
}
|
|
})
|
|
|
|
b.Run("4Ptr", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
s4Ptr = make([][4]uintptr, C)
|
|
for j := 0; j < C; j++ {
|
|
s4Ptr = append(s4Ptr, [4]uintptr{0x77, 0x88, 0x99, 0xAA})
|
|
}
|
|
}
|
|
})
|
|
|
|
})
|
|
|
|
b.Run("Grow", func(b *testing.B) {
|
|
const C = 5
|
|
|
|
b.Run("Byte", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
sByte = make([]byte, 0)
|
|
for j := 0; j < C; j++ {
|
|
sByte = append(sByte, 0x77)
|
|
sByte = sByte[:cap(sByte)]
|
|
}
|
|
}
|
|
})
|
|
|
|
b.Run("1Ptr", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
s1Ptr = make([]uintptr, 0)
|
|
for j := 0; j < C; j++ {
|
|
s1Ptr = append(s1Ptr, 0x77)
|
|
s1Ptr = s1Ptr[:cap(s1Ptr)]
|
|
}
|
|
}
|
|
})
|
|
|
|
b.Run("2Ptr", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
s2Ptr = make([][2]uintptr, 0)
|
|
for j := 0; j < C; j++ {
|
|
s2Ptr = append(s2Ptr, [2]uintptr{0x77, 0x88})
|
|
s2Ptr = s2Ptr[:cap(s2Ptr)]
|
|
}
|
|
}
|
|
})
|
|
|
|
b.Run("3Ptr", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
s3Ptr = make([][3]uintptr, 0)
|
|
for j := 0; j < C; j++ {
|
|
s3Ptr = append(s3Ptr, [3]uintptr{0x77, 0x88, 0x99})
|
|
s3Ptr = s3Ptr[:cap(s3Ptr)]
|
|
}
|
|
}
|
|
})
|
|
|
|
b.Run("4Ptr", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
s4Ptr = make([][4]uintptr, 0)
|
|
for j := 0; j < C; j++ {
|
|
s4Ptr = append(s4Ptr, [4]uintptr{0x77, 0x88, 0x99, 0xAA})
|
|
s4Ptr = s4Ptr[:cap(s4Ptr)]
|
|
}
|
|
}
|
|
})
|
|
|
|
})
|
|
}
|