go/src/runtime/malloc_test.go
Keith Randall 2c05ba4ae0 runtime: top align tinyallocs in race mode
Top align allocations in tinyalloc buckets when in race mode.
This will make checkptr checks more reliable, because any code
that modifies a pointer past the end of the object will trigger
a checkptr error.

No test, because we need -race for this to actually kick in.  We could
add it to the race detector tests, but the race detector tests are all
geared towards race detector reports, not checkptr reports. Mucking
with parsing reports is more than a test is worth.

Fixes #38872

Change-Id: Ie56f0fbd1a9385539f6631fd1ac40c3de5600154
Reviewed-on: https://go-review.googlesource.com/c/go/+/315029
Trust: Keith Randall <khr@golang.org>
Run-TryBot: Keith Randall <khr@golang.org>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
2021-04-29 17:39:31 +00:00

461 lines
11 KiB
Go

// Copyright 2013 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 (
"flag"
"fmt"
"internal/race"
"internal/testenv"
"os"
"os/exec"
"reflect"
"runtime"
. "runtime"
"strings"
"sync/atomic"
"testing"
"time"
"unsafe"
)
var testMemStatsCount int
func TestMemStats(t *testing.T) {
testMemStatsCount++
// Make sure there's at least one forced GC.
GC()
// Test that MemStats has sane values.
st := new(MemStats)
ReadMemStats(st)
nz := func(x interface{}) error {
if x != reflect.Zero(reflect.TypeOf(x)).Interface() {
return nil
}
return fmt.Errorf("zero value")
}
le := func(thresh float64) func(interface{}) error {
return func(x interface{}) error {
// These sanity tests aren't necessarily valid
// with high -test.count values, so only run
// them once.
if testMemStatsCount > 1 {
return nil
}
if reflect.ValueOf(x).Convert(reflect.TypeOf(thresh)).Float() < thresh {
return nil
}
return fmt.Errorf("insanely high value (overflow?); want <= %v", thresh)
}
}
eq := func(x interface{}) func(interface{}) error {
return func(y interface{}) error {
if x == y {
return nil
}
return fmt.Errorf("want %v", x)
}
}
// Of the uint fields, HeapReleased, HeapIdle can be 0.
// PauseTotalNs can be 0 if timer resolution is poor.
fields := map[string][]func(interface{}) error{
"Alloc": {nz, le(1e10)}, "TotalAlloc": {nz, le(1e11)}, "Sys": {nz, le(1e10)},
"Lookups": {eq(uint64(0))}, "Mallocs": {nz, le(1e10)}, "Frees": {nz, le(1e10)},
"HeapAlloc": {nz, le(1e10)}, "HeapSys": {nz, le(1e10)}, "HeapIdle": {le(1e10)},
"HeapInuse": {nz, le(1e10)}, "HeapReleased": {le(1e10)}, "HeapObjects": {nz, le(1e10)},
"StackInuse": {nz, le(1e10)}, "StackSys": {nz, le(1e10)},
"MSpanInuse": {nz, le(1e10)}, "MSpanSys": {nz, le(1e10)},
"MCacheInuse": {nz, le(1e10)}, "MCacheSys": {nz, le(1e10)},
"BuckHashSys": {nz, le(1e10)}, "GCSys": {nz, le(1e10)}, "OtherSys": {nz, le(1e10)},
"NextGC": {nz, le(1e10)}, "LastGC": {nz},
"PauseTotalNs": {le(1e11)}, "PauseNs": nil, "PauseEnd": nil,
"NumGC": {nz, le(1e9)}, "NumForcedGC": {nz, le(1e9)},
"GCCPUFraction": {le(0.99)}, "EnableGC": {eq(true)}, "DebugGC": {eq(false)},
"BySize": nil,
}
rst := reflect.ValueOf(st).Elem()
for i := 0; i < rst.Type().NumField(); i++ {
name, val := rst.Type().Field(i).Name, rst.Field(i).Interface()
checks, ok := fields[name]
if !ok {
t.Errorf("unknown MemStats field %s", name)
continue
}
for _, check := range checks {
if err := check(val); err != nil {
t.Errorf("%s = %v: %s", name, val, err)
}
}
}
if st.Sys != st.HeapSys+st.StackSys+st.MSpanSys+st.MCacheSys+
st.BuckHashSys+st.GCSys+st.OtherSys {
t.Fatalf("Bad sys value: %+v", *st)
}
if st.HeapIdle+st.HeapInuse != st.HeapSys {
t.Fatalf("HeapIdle(%d) + HeapInuse(%d) should be equal to HeapSys(%d), but isn't.", st.HeapIdle, st.HeapInuse, st.HeapSys)
}
if lpe := st.PauseEnd[int(st.NumGC+255)%len(st.PauseEnd)]; st.LastGC != lpe {
t.Fatalf("LastGC(%d) != last PauseEnd(%d)", st.LastGC, lpe)
}
var pauseTotal uint64
for _, pause := range st.PauseNs {
pauseTotal += pause
}
if int(st.NumGC) < len(st.PauseNs) {
// We have all pauses, so this should be exact.
if st.PauseTotalNs != pauseTotal {
t.Fatalf("PauseTotalNs(%d) != sum PauseNs(%d)", st.PauseTotalNs, pauseTotal)
}
for i := int(st.NumGC); i < len(st.PauseNs); i++ {
if st.PauseNs[i] != 0 {
t.Fatalf("Non-zero PauseNs[%d]: %+v", i, st)
}
if st.PauseEnd[i] != 0 {
t.Fatalf("Non-zero PauseEnd[%d]: %+v", i, st)
}
}
} else {
if st.PauseTotalNs < pauseTotal {
t.Fatalf("PauseTotalNs(%d) < sum PauseNs(%d)", st.PauseTotalNs, pauseTotal)
}
}
if st.NumForcedGC > st.NumGC {
t.Fatalf("NumForcedGC(%d) > NumGC(%d)", st.NumForcedGC, st.NumGC)
}
}
func TestStringConcatenationAllocs(t *testing.T) {
n := testing.AllocsPerRun(1e3, func() {
b := make([]byte, 10)
for i := 0; i < 10; i++ {
b[i] = byte(i) + '0'
}
s := "foo" + string(b)
if want := "foo0123456789"; s != want {
t.Fatalf("want %v, got %v", want, s)
}
})
// Only string concatenation allocates.
if n != 1 {
t.Fatalf("want 1 allocation, got %v", n)
}
}
func TestTinyAlloc(t *testing.T) {
if runtime.Raceenabled {
t.Skip("tinyalloc suppressed when running in race mode")
}
const N = 16
var v [N]unsafe.Pointer
for i := range v {
v[i] = unsafe.Pointer(new(byte))
}
chunks := make(map[uintptr]bool, N)
for _, p := range v {
chunks[uintptr(p)&^7] = true
}
if len(chunks) == N {
t.Fatal("no bytes allocated within the same 8-byte chunk")
}
}
var (
tinyByteSink *byte
tinyUint32Sink *uint32
tinyObj12Sink *obj12
)
type obj12 struct {
a uint64
b uint32
}
func TestTinyAllocIssue37262(t *testing.T) {
if runtime.Raceenabled {
t.Skip("tinyalloc suppressed when running in race mode")
}
// Try to cause an alignment access fault
// by atomically accessing the first 64-bit
// value of a tiny-allocated object.
// See issue 37262 for details.
// GC twice, once to reach a stable heap state
// and again to make sure we finish the sweep phase.
runtime.GC()
runtime.GC()
// Make 1-byte allocations until we get a fresh tiny slot.
aligned := false
for i := 0; i < 16; i++ {
tinyByteSink = new(byte)
if uintptr(unsafe.Pointer(tinyByteSink))&0xf == 0xf {
aligned = true
break
}
}
if !aligned {
t.Fatal("unable to get a fresh tiny slot")
}
// Create a 4-byte object so that the current
// tiny slot is partially filled.
tinyUint32Sink = new(uint32)
// Create a 12-byte object, which fits into the
// tiny slot. If it actually gets place there,
// then the field "a" will be improperly aligned
// for atomic access on 32-bit architectures.
// This won't be true if issue 36606 gets resolved.
tinyObj12Sink = new(obj12)
// Try to atomically access "x.a".
atomic.StoreUint64(&tinyObj12Sink.a, 10)
// Clear the sinks.
tinyByteSink = nil
tinyUint32Sink = nil
tinyObj12Sink = nil
}
func TestPageCacheLeak(t *testing.T) {
defer GOMAXPROCS(GOMAXPROCS(1))
leaked := PageCachePagesLeaked()
if leaked != 0 {
t.Fatalf("found %d leaked pages in page caches", leaked)
}
}
func TestPhysicalMemoryUtilization(t *testing.T) {
got := runTestProg(t, "testprog", "GCPhys")
want := "OK\n"
if got != want {
t.Fatalf("expected %q, but got %q", want, got)
}
}
func TestScavengedBitsCleared(t *testing.T) {
var mismatches [128]BitsMismatch
if n, ok := CheckScavengedBitsCleared(mismatches[:]); !ok {
t.Errorf("uncleared scavenged bits")
for _, m := range mismatches[:n] {
t.Logf("\t@ address 0x%x", m.Base)
t.Logf("\t| got: %064b", m.Got)
t.Logf("\t| want: %064b", m.Want)
}
t.FailNow()
}
}
type acLink struct {
x [1 << 20]byte
}
var arenaCollisionSink []*acLink
func TestArenaCollision(t *testing.T) {
testenv.MustHaveExec(t)
// Test that mheap.sysAlloc handles collisions with other
// memory mappings.
if os.Getenv("TEST_ARENA_COLLISION") != "1" {
cmd := testenv.CleanCmdEnv(exec.Command(os.Args[0], "-test.run=TestArenaCollision", "-test.v"))
cmd.Env = append(cmd.Env, "TEST_ARENA_COLLISION=1")
out, err := cmd.CombinedOutput()
if race.Enabled {
// This test runs the runtime out of hint
// addresses, so it will start mapping the
// heap wherever it can. The race detector
// doesn't support this, so look for the
// expected failure.
if want := "too many address space collisions"; !strings.Contains(string(out), want) {
t.Fatalf("want %q, got:\n%s", want, string(out))
}
} else if !strings.Contains(string(out), "PASS\n") || err != nil {
t.Fatalf("%s\n(exit status %v)", string(out), err)
}
return
}
disallowed := [][2]uintptr{}
// Drop all but the next 3 hints. 64-bit has a lot of hints,
// so it would take a lot of memory to go through all of them.
KeepNArenaHints(3)
// Consume these 3 hints and force the runtime to find some
// fallback hints.
for i := 0; i < 5; i++ {
// Reserve memory at the next hint so it can't be used
// for the heap.
start, end := MapNextArenaHint()
disallowed = append(disallowed, [2]uintptr{start, end})
// Allocate until the runtime tries to use the hint we
// just mapped over.
hint := GetNextArenaHint()
for GetNextArenaHint() == hint {
ac := new(acLink)
arenaCollisionSink = append(arenaCollisionSink, ac)
// The allocation must not have fallen into
// one of the reserved regions.
p := uintptr(unsafe.Pointer(ac))
for _, d := range disallowed {
if d[0] <= p && p < d[1] {
t.Fatalf("allocation %#x in reserved region [%#x, %#x)", p, d[0], d[1])
}
}
}
}
}
var mallocSink uintptr
func BenchmarkMalloc8(b *testing.B) {
var x uintptr
for i := 0; i < b.N; i++ {
p := new(int64)
x ^= uintptr(unsafe.Pointer(p))
}
mallocSink = x
}
func BenchmarkMalloc16(b *testing.B) {
var x uintptr
for i := 0; i < b.N; i++ {
p := new([2]int64)
x ^= uintptr(unsafe.Pointer(p))
}
mallocSink = x
}
func BenchmarkMallocTypeInfo8(b *testing.B) {
var x uintptr
for i := 0; i < b.N; i++ {
p := new(struct {
p [8 / unsafe.Sizeof(uintptr(0))]*int
})
x ^= uintptr(unsafe.Pointer(p))
}
mallocSink = x
}
func BenchmarkMallocTypeInfo16(b *testing.B) {
var x uintptr
for i := 0; i < b.N; i++ {
p := new(struct {
p [16 / unsafe.Sizeof(uintptr(0))]*int
})
x ^= uintptr(unsafe.Pointer(p))
}
mallocSink = x
}
type LargeStruct struct {
x [16][]byte
}
func BenchmarkMallocLargeStruct(b *testing.B) {
var x uintptr
for i := 0; i < b.N; i++ {
p := make([]LargeStruct, 2)
x ^= uintptr(unsafe.Pointer(&p[0]))
}
mallocSink = x
}
var n = flag.Int("n", 1000, "number of goroutines")
func BenchmarkGoroutineSelect(b *testing.B) {
quit := make(chan struct{})
read := func(ch chan struct{}) {
for {
select {
case _, ok := <-ch:
if !ok {
return
}
case <-quit:
return
}
}
}
benchHelper(b, *n, read)
}
func BenchmarkGoroutineBlocking(b *testing.B) {
read := func(ch chan struct{}) {
for {
if _, ok := <-ch; !ok {
return
}
}
}
benchHelper(b, *n, read)
}
func BenchmarkGoroutineForRange(b *testing.B) {
read := func(ch chan struct{}) {
for range ch {
}
}
benchHelper(b, *n, read)
}
func benchHelper(b *testing.B, n int, read func(chan struct{})) {
m := make([]chan struct{}, n)
for i := range m {
m[i] = make(chan struct{}, 1)
go read(m[i])
}
b.StopTimer()
b.ResetTimer()
GC()
for i := 0; i < b.N; i++ {
for _, ch := range m {
if ch != nil {
ch <- struct{}{}
}
}
time.Sleep(10 * time.Millisecond)
b.StartTimer()
GC()
b.StopTimer()
}
for _, ch := range m {
close(ch)
}
time.Sleep(10 * time.Millisecond)
}
func BenchmarkGoroutineIdle(b *testing.B) {
quit := make(chan struct{})
fn := func() {
<-quit
}
for i := 0; i < *n; i++ {
go fn()
}
GC()
b.ResetTimer()
for i := 0; i < b.N; i++ {
GC()
}
b.StopTimer()
close(quit)
time.Sleep(10 * time.Millisecond)
}