diff options
author | Ian Lance Taylor <ian@gcc.gnu.org> | 2013-07-16 06:54:42 +0000 |
---|---|---|
committer | Ian Lance Taylor <ian@gcc.gnu.org> | 2013-07-16 06:54:42 +0000 |
commit | be47d6eceffd2c5dbbc1566d5eea490527fb2bd4 (patch) | |
tree | 0e8fda573576bb4181dba29d0e88380a8c38fafd /libgo/go/runtime | |
parent | efb30cdeb003fd7c585ee0d7657340086abcbd9e (diff) | |
download | gcc-be47d6eceffd2c5dbbc1566d5eea490527fb2bd4.zip gcc-be47d6eceffd2c5dbbc1566d5eea490527fb2bd4.tar.gz gcc-be47d6eceffd2c5dbbc1566d5eea490527fb2bd4.tar.bz2 |
libgo: Update to Go 1.1.1.
From-SVN: r200974
Diffstat (limited to 'libgo/go/runtime')
-rw-r--r-- | libgo/go/runtime/append_test.go | 71 | ||||
-rw-r--r-- | libgo/go/runtime/crash_cgo_test.go | 77 | ||||
-rw-r--r-- | libgo/go/runtime/crash_test.go | 158 | ||||
-rw-r--r-- | libgo/go/runtime/debug/garbage.go | 101 | ||||
-rw-r--r-- | libgo/go/runtime/debug/garbage_test.go | 102 | ||||
-rw-r--r-- | libgo/go/runtime/export_test.go | 6 | ||||
-rw-r--r-- | libgo/go/runtime/extern.go | 52 | ||||
-rw-r--r-- | libgo/go/runtime/gc_test.go | 104 | ||||
-rw-r--r-- | libgo/go/runtime/map_test.go | 380 | ||||
-rw-r--r-- | libgo/go/runtime/mapspeed_test.go | 208 | ||||
-rw-r--r-- | libgo/go/runtime/pprof/pprof.go | 18 | ||||
-rw-r--r-- | libgo/go/runtime/proc_test.go | 171 | ||||
-rw-r--r-- | libgo/go/runtime/string_test.go | 32 |
13 files changed, 1425 insertions, 55 deletions
diff --git a/libgo/go/runtime/append_test.go b/libgo/go/runtime/append_test.go index b855222..3639018 100644 --- a/libgo/go/runtime/append_test.go +++ b/libgo/go/runtime/append_test.go @@ -19,6 +19,67 @@ func BenchmarkAppend(b *testing.B) { } } +func benchmarkAppendBytes(b *testing.B, length int) { + b.StopTimer() + x := make([]byte, 0, N) + y := make([]byte, length) + b.StartTimer() + for i := 0; i < b.N; i++ { + x = x[0:0] + x = append(x, y...) + } +} + +func BenchmarkAppend1Byte(b *testing.B) { + benchmarkAppendBytes(b, 1) +} + +func BenchmarkAppend4Bytes(b *testing.B) { + benchmarkAppendBytes(b, 4) +} + +func BenchmarkAppend8Bytes(b *testing.B) { + benchmarkAppendBytes(b, 8) +} + +func BenchmarkAppend16Bytes(b *testing.B) { + benchmarkAppendBytes(b, 16) +} + +func BenchmarkAppend32Bytes(b *testing.B) { + benchmarkAppendBytes(b, 32) +} + +func benchmarkAppendStr(b *testing.B, str string) { + b.StopTimer() + x := make([]byte, 0, N) + b.StartTimer() + for i := 0; i < b.N; i++ { + x = x[0:0] + x = append(x, str...) + } +} + +func BenchmarkAppendStr1Byte(b *testing.B) { + benchmarkAppendStr(b, "1") +} + +func BenchmarkAppendStr4Bytes(b *testing.B) { + benchmarkAppendStr(b, "1234") +} + +func BenchmarkAppendStr8Bytes(b *testing.B) { + benchmarkAppendStr(b, "12345678") +} + +func BenchmarkAppendStr16Bytes(b *testing.B) { + benchmarkAppendStr(b, "1234567890123456") +} + +func BenchmarkAppendStr32Bytes(b *testing.B) { + benchmarkAppendStr(b, "12345678901234567890123456789012") +} + func BenchmarkAppendSpecialCase(b *testing.B) { b.StopTimer() x := make([]int, 0, N) @@ -50,3 +111,13 @@ func TestSideEffectOrder(t *testing.T) { 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) + } +} diff --git a/libgo/go/runtime/crash_cgo_test.go b/libgo/go/runtime/crash_cgo_test.go index 91c4bdb..6b93bd1 100644 --- a/libgo/go/runtime/crash_cgo_test.go +++ b/libgo/go/runtime/crash_cgo_test.go @@ -11,5 +11,80 @@ import ( ) func TestCgoCrashHandler(t *testing.T) { - testCrashHandler(t, &crashTest{Cgo: true}) + testCrashHandler(t, true) } + +func TestCgoSignalDeadlock(t *testing.T) { + /* gccgo does not have a go command + got := executeTest(t, cgoSignalDeadlockSource, nil) + want := "OK\n" + if got != want { + t.Fatalf("expected %q, but got %q", want, got) + } + */ +} + +const cgoSignalDeadlockSource = ` +package main + +import "C" + +import ( + "fmt" + "runtime" + "time" +) + +func main() { + runtime.GOMAXPROCS(100) + ping := make(chan bool) + go func() { + for i := 0; ; i++ { + runtime.Gosched() + select { + case done := <-ping: + if done { + ping <- true + return + } + ping <- true + default: + } + func() { + defer func() { + recover() + }() + var s *string + *s = "" + }() + } + }() + time.Sleep(time.Millisecond) + for i := 0; i < 64; i++ { + go func() { + runtime.LockOSThread() + select {} + }() + go func() { + runtime.LockOSThread() + select {} + }() + time.Sleep(time.Millisecond) + ping <- false + select { + case <-ping: + case <-time.After(time.Second): + fmt.Printf("HANG\n") + return + } + } + ping <- true + select { + case <-ping: + case <-time.After(time.Second): + fmt.Printf("HANG\n") + return + } + fmt.Printf("OK\n") +} +` diff --git a/libgo/go/runtime/crash_test.go b/libgo/go/runtime/crash_test.go index 2113cbf..918249a 100644 --- a/libgo/go/runtime/crash_test.go +++ b/libgo/go/runtime/crash_test.go @@ -7,21 +7,33 @@ package runtime_test import ( "io/ioutil" "os" - // "os/exec" + "os/exec" "path/filepath" + "strings" "testing" "text/template" ) -type crashTest struct { - Cgo bool +// testEnv excludes GOGCTRACE from the environment +// to prevent its output from breaking tests that +// are trying to parse other command output. +func testEnv(cmd *exec.Cmd) *exec.Cmd { + if cmd.Env != nil { + panic("environment already set") + } + for _, env := range os.Environ() { + if strings.HasPrefix(env, "GOGCTRACE=") { + continue + } + cmd.Env = append(cmd.Env, env) + } + return cmd } -// This test is a separate program, because it is testing -// both main (m0) and non-main threads (m). +func executeTest(t *testing.T, templ string, data interface{}) string { + checkStaleRuntime(t) -func testCrashHandler(t *testing.T, ct *crashTest) { - st := template.Must(template.New("crashSource").Parse(crashSource)) + st := template.Must(template.New("crashSource").Parse(templ)) dir, err := ioutil.TempDir("", "go-build") if err != nil { @@ -34,30 +46,79 @@ func testCrashHandler(t *testing.T, ct *crashTest) { if err != nil { t.Fatalf("failed to create %v: %v", src, err) } - err = st.Execute(f, ct) + err = st.Execute(f, data) if err != nil { f.Close() t.Fatalf("failed to execute template: %v", err) } f.Close() - /* - gccgo does not have a go command. + got, _ := testEnv(exec.Command("go", "run", src)).CombinedOutput() + return string(got) +} - got, err := exec.Command("go", "run", src).CombinedOutput() - if err != nil { - t.Fatalf("program exited with error: %v\n%v", err, string(got)) - } - want := "main: recovered done\nnew-thread: recovered done\nsecond-new-thread: recovered done\nmain-again: recovered done\n" - if string(got) != string(want) { - t.Fatalf("expected %q, but got %q", string(want), string(got)) - } +func checkStaleRuntime(t *testing.T) { + // 'go run' uses the installed copy of runtime.a, which may be out of date. + out, err := testEnv(exec.Command("go", "list", "-f", "{{.Stale}}", "runtime")).CombinedOutput() + if err != nil { + t.Fatalf("failed to execute 'go list': %v\n%v", err, string(out)) + } + if string(out) != "false\n" { + t.Fatalf("Stale runtime.a. Run 'go install runtime'.") + } +} +func testCrashHandler(t *testing.T, cgo bool) { + /* gccgo does not have a go command + type crashTest struct { + Cgo bool + } + got := executeTest(t, crashSource, &crashTest{Cgo: cgo}) + want := "main: recovered done\nnew-thread: recovered done\nsecond-new-thread: recovered done\nmain-again: recovered done\n" + if got != want { + t.Fatalf("expected %q, but got %q", want, got) + } */ } func TestCrashHandler(t *testing.T) { - testCrashHandler(t, &crashTest{Cgo: false}) + testCrashHandler(t, false) +} + +func testDeadlock(t *testing.T, source string) { + /* gccgo does not have a go command. + got := executeTest(t, source, nil) + want := "fatal error: all goroutines are asleep - deadlock!\n" + if !strings.HasPrefix(got, want) { + t.Fatalf("expected %q, but got %q", want, got) + } + */ +} + +func TestSimpleDeadlock(t *testing.T) { + testDeadlock(t, simpleDeadlockSource) +} + +func TestInitDeadlock(t *testing.T) { + testDeadlock(t, initDeadlockSource) +} + +func TestLockedDeadlock(t *testing.T) { + testDeadlock(t, lockedDeadlockSource) +} + +func TestLockedDeadlock2(t *testing.T) { + testDeadlock(t, lockedDeadlockSource2) +} + +func TestGoexitDeadlock(t *testing.T) { + /* gccgo does not have a go command + got := executeTest(t, goexitDeadlockSource, nil) + want := "" + if got != want { + t.Fatalf("expected %q, but got %q", want, got) + } + */ } const crashSource = ` @@ -103,3 +164,62 @@ func main() { test("main-again") } ` + +const simpleDeadlockSource = ` +package main +func main() { + select {} +} +` + +const initDeadlockSource = ` +package main +func init() { + select {} +} +func main() { +} +` + +const lockedDeadlockSource = ` +package main +import "runtime" +func main() { + runtime.LockOSThread() + select {} +} +` + +const lockedDeadlockSource2 = ` +package main +import ( + "runtime" + "time" +) +func main() { + go func() { + runtime.LockOSThread() + select {} + }() + time.Sleep(time.Millisecond) + select {} +} +` + +const goexitDeadlockSource = ` +package main +import ( + "runtime" +) + +func F() { + for i := 0; i < 10; i++ { + } +} + +func main() { + go F() + go F() + runtime.Goexit() +} +` diff --git a/libgo/go/runtime/debug/garbage.go b/libgo/go/runtime/debug/garbage.go new file mode 100644 index 0000000..8f30264 --- /dev/null +++ b/libgo/go/runtime/debug/garbage.go @@ -0,0 +1,101 @@ +// 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 debug + +import ( + "runtime" + "sort" + "time" +) + +// GCStats collect information about recent garbage collections. +type GCStats struct { + LastGC time.Time // time of last collection + NumGC int64 // number of garbage collections + PauseTotal time.Duration // total pause for all collections + Pause []time.Duration // pause history, most recent first + PauseQuantiles []time.Duration +} + +// Implemented in package runtime. +func readGCStats(*[]time.Duration) +func enableGC(bool) bool +func setGCPercent(int) int +func freeOSMemory() + +// ReadGCStats reads statistics about garbage collection into stats. +// The number of entries in the pause history is system-dependent; +// stats.Pause slice will be reused if large enough, reallocated otherwise. +// ReadGCStats may use the full capacity of the stats.Pause slice. +// If stats.PauseQuantiles is non-empty, ReadGCStats fills it with quantiles +// summarizing the distribution of pause time. For example, if +// len(stats.PauseQuantiles) is 5, it will be filled with the minimum, +// 25%, 50%, 75%, and maximum pause times. +func ReadGCStats(stats *GCStats) { + // Create a buffer with space for at least two copies of the + // pause history tracked by the runtime. One will be returned + // to the caller and the other will be used as a temporary buffer + // for computing quantiles. + const maxPause = len(((*runtime.MemStats)(nil)).PauseNs) + if cap(stats.Pause) < 2*maxPause { + stats.Pause = make([]time.Duration, 2*maxPause) + } + + // readGCStats fills in the pause history (up to maxPause entries) + // and then three more: Unix ns time of last GC, number of GC, + // and total pause time in nanoseconds. Here we depend on the + // fact that time.Duration's native unit is nanoseconds, so the + // pauses and the total pause time do not need any conversion. + readGCStats(&stats.Pause) + n := len(stats.Pause) - 3 + stats.LastGC = time.Unix(0, int64(stats.Pause[n])) + stats.NumGC = int64(stats.Pause[n+1]) + stats.PauseTotal = stats.Pause[n+2] + stats.Pause = stats.Pause[:n] + + if len(stats.PauseQuantiles) > 0 { + if n == 0 { + for i := range stats.PauseQuantiles { + stats.PauseQuantiles[i] = 0 + } + } else { + // There's room for a second copy of the data in stats.Pause. + // See the allocation at the top of the function. + sorted := stats.Pause[n : n+n] + copy(sorted, stats.Pause) + sort.Sort(byDuration(sorted)) + nq := len(stats.PauseQuantiles) - 1 + for i := 0; i < nq; i++ { + stats.PauseQuantiles[i] = sorted[len(sorted)*i/nq] + } + stats.PauseQuantiles[nq] = sorted[len(sorted)-1] + } + } +} + +type byDuration []time.Duration + +func (x byDuration) Len() int { return len(x) } +func (x byDuration) Swap(i, j int) { x[i], x[j] = x[j], x[i] } +func (x byDuration) Less(i, j int) bool { return x[i] < x[j] } + +// SetGCPercent sets the garbage collection target percentage: +// a collection is triggered when the ratio of freshly allocated data +// to live data remaining after the previous collection reaches this percentage. +// SetGCPercent returns the previous setting. +// The initial setting is the value of the GOGC environment variable +// at startup, or 100 if the variable is not set. +// A negative percentage disables garbage collection. +func SetGCPercent(percent int) int { + return setGCPercent(percent) +} + +// FreeOSMemory forces a garbage collection followed by an +// attempt to return as much memory to the operating system +// as possible. (Even if this is not called, the runtime gradually +// returns memory to the operating system in a background task.) +func FreeOSMemory() { + freeOSMemory() +} diff --git a/libgo/go/runtime/debug/garbage_test.go b/libgo/go/runtime/debug/garbage_test.go new file mode 100644 index 0000000..149bafc --- /dev/null +++ b/libgo/go/runtime/debug/garbage_test.go @@ -0,0 +1,102 @@ +// 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 debug + +import ( + "runtime" + "testing" + "time" +) + +func TestReadGCStats(t *testing.T) { + defer SetGCPercent(SetGCPercent(-1)) + + var stats GCStats + var mstats runtime.MemStats + var min, max time.Duration + + // First ReadGCStats will allocate, second should not, + // especially if we follow up with an explicit garbage collection. + stats.PauseQuantiles = make([]time.Duration, 10) + ReadGCStats(&stats) + runtime.GC() + + // Assume these will return same data: no GC during ReadGCStats. + ReadGCStats(&stats) + runtime.ReadMemStats(&mstats) + + if stats.NumGC != int64(mstats.NumGC) { + t.Errorf("stats.NumGC = %d, but mstats.NumGC = %d", stats.NumGC, mstats.NumGC) + } + if stats.PauseTotal != time.Duration(mstats.PauseTotalNs) { + t.Errorf("stats.PauseTotal = %d, but mstats.PauseTotalNs = %d", stats.PauseTotal, mstats.PauseTotalNs) + } + if stats.LastGC.UnixNano() != int64(mstats.LastGC) { + t.Errorf("stats.LastGC.UnixNano = %d, but mstats.LastGC = %d", stats.LastGC.UnixNano(), mstats.LastGC) + } + n := int(mstats.NumGC) + if n > len(mstats.PauseNs) { + n = len(mstats.PauseNs) + } + if len(stats.Pause) != n { + t.Errorf("len(stats.Pause) = %d, want %d", len(stats.Pause), n) + } else { + off := (int(mstats.NumGC) + len(mstats.PauseNs) - 1) % len(mstats.PauseNs) + for i := 0; i < n; i++ { + dt := stats.Pause[i] + if dt != time.Duration(mstats.PauseNs[off]) { + t.Errorf("stats.Pause[%d] = %d, want %d", i, dt, mstats.PauseNs[off]) + } + if max < dt { + max = dt + } + if min > dt || i == 0 { + min = dt + } + off = (off + len(mstats.PauseNs) - 1) % len(mstats.PauseNs) + } + } + + q := stats.PauseQuantiles + nq := len(q) + if q[0] != min || q[nq-1] != max { + t.Errorf("stats.PauseQuantiles = [%d, ..., %d], want [%d, ..., %d]", q[0], q[nq-1], min, max) + } + + for i := 0; i < nq-1; i++ { + if q[i] > q[i+1] { + t.Errorf("stats.PauseQuantiles[%d]=%d > stats.PauseQuantiles[%d]=%d", i, q[i], i+1, q[i+1]) + } + } +} + +var big = make([]byte, 1<<20) + +func TestFreeOSMemory(t *testing.T) { + var ms1, ms2 runtime.MemStats + + if big == nil { + t.Skip("test is not reliable when run multiple times") + } + big = nil + runtime.GC() + runtime.ReadMemStats(&ms1) + FreeOSMemory() + runtime.ReadMemStats(&ms2) + if ms1.HeapReleased >= ms2.HeapReleased { + t.Errorf("released before=%d; released after=%d; did not go up", ms1.HeapReleased, ms2.HeapReleased) + } +} + +func TestSetGCPercent(t *testing.T) { + // Test that the variable is being set and returned correctly. + // Assume the percentage itself is implemented fine during GC, + // which is harder to test. + old := SetGCPercent(123) + new := SetGCPercent(old) + if new != 123 { + t.Errorf("SetGCPercent(123); SetGCPercent(x) = %d, want 123", new) + } +} diff --git a/libgo/go/runtime/export_test.go b/libgo/go/runtime/export_test.go index 7551ab8..e22fa62 100644 --- a/libgo/go/runtime/export_test.go +++ b/libgo/go/runtime/export_test.go @@ -59,3 +59,9 @@ func ParForIters(desc *ParFor, tid uint32) (uint32, uint32) { begin, end := parforiters(desc, uintptr(tid)) return uint32(begin), uint32(end) } + +func testSchedLocalQueue() +func testSchedLocalQueueSteal() + +var TestSchedLocalQueue1 = testSchedLocalQueue +var TestSchedLocalQueueSteal1 = testSchedLocalQueueSteal diff --git a/libgo/go/runtime/extern.go b/libgo/go/runtime/extern.go index 6e91ef5..72e1a0a 100644 --- a/libgo/go/runtime/extern.go +++ b/libgo/go/runtime/extern.go @@ -3,10 +3,54 @@ // license that can be found in the LICENSE file. /* - Package runtime contains operations that interact with Go's runtime system, - such as functions to control goroutines. It also includes the low-level type information - used by the reflect package; see reflect's documentation for the programmable - interface to the run-time type system. +Package runtime contains operations that interact with Go's runtime system, +such as functions to control goroutines. It also includes the low-level type information +used by the reflect package; see reflect's documentation for the programmable +interface to the run-time type system. + +Environment Variables + +The following environment variables ($name or %name%, depending on the host +operating system) control the run-time behavior of Go programs. The meanings +and use may change from release to release. + +The GOGC variable sets the initial garbage collection target percentage. +A collection is triggered when the ratio of freshly allocated data to live data +remaining after the previous collection reaches this percentage. The default +is GOGC=100. Setting GOGC=off disables the garbage collector entirely. +The runtime/debug package's SetGCPercent function allows changing this +percentage at run time. See http://golang.org/pkg/runtime/debug/#SetGCPercent. + +The GOGCTRACE variable controls debug output from the garbage collector. +Setting GOGCTRACE=1 causes the garbage collector to emit a single line to standard +error at each collection, summarizing the amount of memory collected and the +length of the pause. Setting GOGCTRACE=2 emits the same summary but also +repeats each collection. + +The GOMAXPROCS variable limits the number of operating system threads that +can execute user-level Go code simultaneously. There is no limit to the number of threads +that can be blocked in system calls on behalf of Go code; those do not count against +the GOMAXPROCS limit. This package's GOMAXPROCS function queries and changes +the limit. + +The GOTRACEBACK variable controls the amount of output generated when a Go +program fails due to an unrecovered panic or an unexpected runtime condition. +By default, a failure prints a stack trace for every extant goroutine, eliding functions +internal to the run-time system, and then exits with exit code 2. +If GOTRACEBACK=0, the per-goroutine stack traces are omitted entirely. +If GOTRACEBACK=1, the default behavior is used. +If GOTRACEBACK=2, the per-goroutine stack traces include run-time functions. +If GOTRACEBACK=crash, the per-goroutine stack traces include run-time functions, +and if possible the program crashes in an operating-specific manner instead of +exiting. For example, on Unix systems, the program raises SIGABRT to trigger a +core dump. + +The GOARCH, GOOS, GOPATH, and GOROOT environment variables complete +the set of Go environment variables. They influence the building of Go programs +(see http://golang.org/cmd/go and http://golang.org/pkg/go/build). +GOARCH, GOOS, and GOROOT are recorded at compile time and made available by +constants or functions in this package, but they do not influence the execution +of the run-time system. */ package runtime diff --git a/libgo/go/runtime/gc_test.go b/libgo/go/runtime/gc_test.go index 283a681..05ee348 100644 --- a/libgo/go/runtime/gc_test.go +++ b/libgo/go/runtime/gc_test.go @@ -5,12 +5,36 @@ package runtime_test import ( + // "os" "runtime" + "runtime/debug" "testing" ) func TestGcSys(t *testing.T) { - defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1)) + /* gccgo does not have a go command + if os.Getenv("GOGC") == "off" { + t.Skip("skipping test; GOGC=off in environment") + } + data := struct{ Short bool }{testing.Short()} + got := executeTest(t, testGCSysSource, &data) + want := "OK\n" + if got != want { + t.Fatalf("expected %q, but got %q", want, got) + } + */ +} + +const testGCSysSource = ` +package main + +import ( + "fmt" + "runtime" +) + +func main() { + runtime.GOMAXPROCS(1) memstats := new(runtime.MemStats) runtime.GC() runtime.ReadMemStats(memstats) @@ -19,9 +43,9 @@ func TestGcSys(t *testing.T) { runtime.MemProfileRate = 0 // disable profiler itercount := 1000000 - if testing.Short() { - itercount = 100000 - } +{{if .Short}} + itercount = 100000 +{{end}} for i := 0; i < itercount; i++ { workthegc() } @@ -34,15 +58,17 @@ func TestGcSys(t *testing.T) { } else { sys = memstats.Sys - sys } - t.Logf("used %d extra bytes", sys) if sys > 16<<20 { - t.Fatalf("using too much memory: %d bytes", sys) + fmt.Printf("using too much memory: %d bytes\n", sys) + return } + fmt.Printf("OK\n") } func workthegc() []byte { return make([]byte, 1029) } +` func TestGcDeepNesting(t *testing.T) { type T [2][2][2][2][2][2][2][2][2][2]*int @@ -59,3 +85,69 @@ func TestGcDeepNesting(t *testing.T) { t.Fail() } } + +func TestGcHashmapIndirection(t *testing.T) { + defer debug.SetGCPercent(debug.SetGCPercent(1)) + runtime.GC() + type T struct { + a [256]int + } + m := make(map[T]T) + for i := 0; i < 2000; i++ { + var a T + a.a[0] = i + m[a] = T{} + } +} + +func TestGcArraySlice(t *testing.T) { + type X struct { + buf [1]byte + nextbuf []byte + next *X + } + var head *X + for i := 0; i < 10; i++ { + p := &X{} + p.buf[0] = 42 + p.next = head + if head != nil { + p.nextbuf = head.buf[:] + } + head = p + runtime.GC() + } + for p := head; p != nil; p = p.next { + if p.buf[0] != 42 { + t.Fatal("corrupted heap") + } + } +} + +func TestGcRescan(t *testing.T) { + type X struct { + c chan error + nextx *X + } + type Y struct { + X + nexty *Y + p *int + } + var head *Y + for i := 0; i < 10; i++ { + p := &Y{} + p.c = make(chan error) + p.nextx = &head.X + p.nexty = head + p.p = new(int) + *p.p = 42 + head = p + runtime.GC() + } + for p := head; p != nil; p = p.nexty { + if *p.p != 42 { + t.Fatal("corrupted heap") + } + } +} diff --git a/libgo/go/runtime/map_test.go b/libgo/go/runtime/map_test.go new file mode 100644 index 0000000..535c59e --- /dev/null +++ b/libgo/go/runtime/map_test.go @@ -0,0 +1,380 @@ +// 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 ( + "fmt" + "math" + "reflect" + "runtime" + "sort" + "strings" + "sync" + "testing" +) + +// negative zero is a good test because: +// 1) 0 and -0 are equal, yet have distinct representations. +// 2) 0 is represented as all zeros, -0 isn't. +// I'm not sure the language spec actually requires this behavior, +// but it's what the current map implementation does. +func TestNegativeZero(t *testing.T) { + m := make(map[float64]bool, 0) + + m[+0.0] = true + m[math.Copysign(0.0, -1.0)] = true // should overwrite +0 entry + + if len(m) != 1 { + t.Error("length wrong") + } + + /* gccgo fails this test; this is not required by the spec. + for k := range m { + if math.Copysign(1.0, k) > 0 { + t.Error("wrong sign") + } + } + */ + + m = make(map[float64]bool, 0) + m[math.Copysign(0.0, -1.0)] = true + m[+0.0] = true // should overwrite -0.0 entry + + if len(m) != 1 { + t.Error("length wrong") + } + + /* gccgo fails this test; this is not required by the spec. + for k := range m { + if math.Copysign(1.0, k) < 0 { + t.Error("wrong sign") + } + } + */ +} + +// nan is a good test because nan != nan, and nan has +// a randomized hash value. +func TestNan(t *testing.T) { + m := make(map[float64]int, 0) + nan := math.NaN() + m[nan] = 1 + m[nan] = 2 + m[nan] = 4 + if len(m) != 3 { + t.Error("length wrong") + } + s := 0 + for k, v := range m { + if k == k { + t.Error("nan disappeared") + } + if (v & (v - 1)) != 0 { + t.Error("value wrong") + } + s |= v + } + if s != 7 { + t.Error("values wrong") + } +} + +// Maps aren't actually copied on assignment. +func TestAlias(t *testing.T) { + m := make(map[int]int, 0) + m[0] = 5 + n := m + n[0] = 6 + if m[0] != 6 { + t.Error("alias didn't work") + } +} + +func TestGrowWithNaN(t *testing.T) { + t.Skip("fails with gccgo") + m := make(map[float64]int, 4) + nan := math.NaN() + m[nan] = 1 + m[nan] = 2 + m[nan] = 4 + cnt := 0 + s := 0 + growflag := true + for k, v := range m { + if growflag { + // force a hashtable resize + for i := 0; i < 100; i++ { + m[float64(i)] = i + } + growflag = false + } + if k != k { + cnt++ + s |= v + } + } + t.Log("cnt:", cnt, "s:", s) + if cnt != 3 { + t.Error("NaN keys lost during grow") + } + if s != 7 { + t.Error("NaN values lost during grow") + } +} + +type FloatInt struct { + x float64 + y int +} + +func TestGrowWithNegativeZero(t *testing.T) { + t.Skip("fails with gccgo") + negzero := math.Copysign(0.0, -1.0) + m := make(map[FloatInt]int, 4) + m[FloatInt{0.0, 0}] = 1 + m[FloatInt{0.0, 1}] = 2 + m[FloatInt{0.0, 2}] = 4 + m[FloatInt{0.0, 3}] = 8 + growflag := true + s := 0 + cnt := 0 + negcnt := 0 + // The first iteration should return the +0 key. + // The subsequent iterations should return the -0 key. + // I'm not really sure this is required by the spec, + // but it makes sense. + // TODO: are we allowed to get the first entry returned again??? + for k, v := range m { + if v == 0 { + continue + } // ignore entries added to grow table + cnt++ + if math.Copysign(1.0, k.x) < 0 { + if v&16 == 0 { + t.Error("key/value not updated together 1") + } + negcnt++ + s |= v & 15 + } else { + if v&16 == 16 { + t.Error("key/value not updated together 2", k, v) + } + s |= v + } + if growflag { + // force a hashtable resize + for i := 0; i < 100; i++ { + m[FloatInt{3.0, i}] = 0 + } + // then change all the entries + // to negative zero + m[FloatInt{negzero, 0}] = 1 | 16 + m[FloatInt{negzero, 1}] = 2 | 16 + m[FloatInt{negzero, 2}] = 4 | 16 + m[FloatInt{negzero, 3}] = 8 | 16 + growflag = false + } + } + if s != 15 { + t.Error("entry missing", s) + } + if cnt != 4 { + t.Error("wrong number of entries returned by iterator", cnt) + } + if negcnt != 3 { + t.Error("update to negzero missed by iteration", negcnt) + } +} + +func TestIterGrowAndDelete(t *testing.T) { + m := make(map[int]int, 4) + for i := 0; i < 100; i++ { + m[i] = i + } + growflag := true + for k := range m { + if growflag { + // grow the table + for i := 100; i < 1000; i++ { + m[i] = i + } + // delete all odd keys + for i := 1; i < 1000; i += 2 { + delete(m, i) + } + growflag = false + } else { + if k&1 == 1 { + t.Error("odd value returned") + } + } + } +} + +// make sure old bucket arrays don't get GCd while +// an iterator is still using them. +func TestIterGrowWithGC(t *testing.T) { + m := make(map[int]int, 4) + for i := 0; i < 16; i++ { + m[i] = i + } + growflag := true + bitmask := 0 + for k := range m { + if k < 16 { + bitmask |= 1 << uint(k) + } + if growflag { + // grow the table + for i := 100; i < 1000; i++ { + m[i] = i + } + // trigger a gc + runtime.GC() + growflag = false + } + } + if bitmask != 1<<16-1 { + t.Error("missing key", bitmask) + } +} + +func testConcurrentReadsAfterGrowth(t *testing.T, useReflect bool) { + if runtime.GOMAXPROCS(-1) == 1 { + defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(16)) + } + numLoop := 10 + numGrowStep := 250 + numReader := 16 + if testing.Short() { + numLoop, numGrowStep = 2, 500 + } + for i := 0; i < numLoop; i++ { + m := make(map[int]int, 0) + for gs := 0; gs < numGrowStep; gs++ { + m[gs] = gs + var wg sync.WaitGroup + wg.Add(numReader * 2) + for nr := 0; nr < numReader; nr++ { + go func() { + defer wg.Done() + for _ = range m { + } + }() + go func() { + defer wg.Done() + for key := 0; key < gs; key++ { + _ = m[key] + } + }() + if useReflect { + wg.Add(1) + go func() { + defer wg.Done() + mv := reflect.ValueOf(m) + keys := mv.MapKeys() + for _, k := range keys { + mv.MapIndex(k) + } + }() + } + } + wg.Wait() + } + } +} + +func TestConcurrentReadsAfterGrowth(t *testing.T) { + testConcurrentReadsAfterGrowth(t, false) +} + +func TestConcurrentReadsAfterGrowthReflect(t *testing.T) { + testConcurrentReadsAfterGrowth(t, true) +} + +func TestBigItems(t *testing.T) { + var key [256]string + for i := 0; i < 256; i++ { + key[i] = "foo" + } + m := make(map[[256]string][256]string, 4) + for i := 0; i < 100; i++ { + key[37] = fmt.Sprintf("string%02d", i) + m[key] = key + } + var keys [100]string + var values [100]string + i := 0 + for k, v := range m { + keys[i] = k[37] + values[i] = v[37] + i++ + } + sort.Strings(keys[:]) + sort.Strings(values[:]) + for i := 0; i < 100; i++ { + if keys[i] != fmt.Sprintf("string%02d", i) { + t.Errorf("#%d: missing key: %v", i, keys[i]) + } + if values[i] != fmt.Sprintf("string%02d", i) { + t.Errorf("#%d: missing value: %v", i, values[i]) + } + } +} + +type empty struct { +} + +func TestEmptyKeyAndValue(t *testing.T) { + a := make(map[int]empty, 4) + b := make(map[empty]int, 4) + c := make(map[empty]empty, 4) + a[0] = empty{} + b[empty{}] = 0 + b[empty{}] = 1 + c[empty{}] = empty{} + + if len(a) != 1 { + t.Errorf("empty value insert problem") + } + if b[empty{}] != 1 { + t.Errorf("empty key returned wrong value") + } +} + +// Tests a map with a single bucket, with same-lengthed short keys +// ("quick keys") as well as long keys. +func TestSingleBucketMapStringKeys_DupLen(t *testing.T) { + testMapLookups(t, map[string]string{ + "x": "x1val", + "xx": "x2val", + "foo": "fooval", + "bar": "barval", // same key length as "foo" + "xxxx": "x4val", + strings.Repeat("x", 128): "longval1", + strings.Repeat("y", 128): "longval2", + }) +} + +// Tests a map with a single bucket, with all keys having different lengths. +func TestSingleBucketMapStringKeys_NoDupLen(t *testing.T) { + testMapLookups(t, map[string]string{ + "x": "x1val", + "xx": "x2val", + "foo": "fooval", + "xxxx": "x4val", + "xxxxx": "x5val", + "xxxxxx": "x6val", + strings.Repeat("x", 128): "longval", + }) +} + +func testMapLookups(t *testing.T, m map[string]string) { + for k, v := range m { + if m[k] != v { + t.Fatalf("m[%q] = %q; want %q", k, m[k], v) + } + } +} diff --git a/libgo/go/runtime/mapspeed_test.go b/libgo/go/runtime/mapspeed_test.go new file mode 100644 index 0000000..3b7fbfd --- /dev/null +++ b/libgo/go/runtime/mapspeed_test.go @@ -0,0 +1,208 @@ +// 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 ( + "fmt" + "strings" + "testing" +) + +const size = 10 + +func BenchmarkHashStringSpeed(b *testing.B) { + strings := make([]string, size) + for i := 0; i < size; i++ { + strings[i] = fmt.Sprintf("string#%d", i) + } + sum := 0 + m := make(map[string]int, size) + for i := 0; i < size; i++ { + m[strings[i]] = 0 + } + idx := 0 + b.ResetTimer() + for i := 0; i < b.N; i++ { + sum += m[strings[idx]] + idx++ + if idx == size { + idx = 0 + } + } +} + +func BenchmarkHashInt32Speed(b *testing.B) { + ints := make([]int32, size) + for i := 0; i < size; i++ { + ints[i] = int32(i) + } + sum := 0 + m := make(map[int32]int, size) + for i := 0; i < size; i++ { + m[ints[i]] = 0 + } + idx := 0 + b.ResetTimer() + for i := 0; i < b.N; i++ { + sum += m[ints[idx]] + idx++ + if idx == size { + idx = 0 + } + } +} + +func BenchmarkHashInt64Speed(b *testing.B) { + ints := make([]int64, size) + for i := 0; i < size; i++ { + ints[i] = int64(i) + } + sum := 0 + m := make(map[int64]int, size) + for i := 0; i < size; i++ { + m[ints[i]] = 0 + } + idx := 0 + b.ResetTimer() + for i := 0; i < b.N; i++ { + sum += m[ints[idx]] + idx++ + if idx == size { + idx = 0 + } + } +} +func BenchmarkHashStringArraySpeed(b *testing.B) { + stringpairs := make([][2]string, size) + for i := 0; i < size; i++ { + for j := 0; j < 2; j++ { + stringpairs[i][j] = fmt.Sprintf("string#%d/%d", i, j) + } + } + sum := 0 + m := make(map[[2]string]int, size) + for i := 0; i < size; i++ { + m[stringpairs[i]] = 0 + } + idx := 0 + b.ResetTimer() + for i := 0; i < b.N; i++ { + sum += m[stringpairs[idx]] + idx++ + if idx == size { + idx = 0 + } + } +} + +func BenchmarkMegMap(b *testing.B) { + m := make(map[string]bool) + for suffix := 'A'; suffix <= 'G'; suffix++ { + m[strings.Repeat("X", 1<<20-1)+fmt.Sprint(suffix)] = true + } + key := strings.Repeat("X", 1<<20-1) + "k" + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = m[key] + } +} + +func BenchmarkMegOneMap(b *testing.B) { + m := make(map[string]bool) + m[strings.Repeat("X", 1<<20)] = true + key := strings.Repeat("Y", 1<<20) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = m[key] + } +} + +func BenchmarkMegEqMap(b *testing.B) { + m := make(map[string]bool) + key1 := strings.Repeat("X", 1<<20) + key2 := strings.Repeat("X", 1<<20) // equal but different instance + m[key1] = true + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = m[key2] + } +} + +func BenchmarkMegEmptyMap(b *testing.B) { + m := make(map[string]bool) + key := strings.Repeat("X", 1<<20) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = m[key] + } +} + +func BenchmarkSmallStrMap(b *testing.B) { + m := make(map[string]bool) + for suffix := 'A'; suffix <= 'G'; suffix++ { + m[fmt.Sprint(suffix)] = true + } + key := "k" + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = m[key] + } +} + +func BenchmarkMapStringKeysEight_16(b *testing.B) { benchmarkMapStringKeysEight(b, 16) } +func BenchmarkMapStringKeysEight_32(b *testing.B) { benchmarkMapStringKeysEight(b, 32) } +func BenchmarkMapStringKeysEight_64(b *testing.B) { benchmarkMapStringKeysEight(b, 64) } +func BenchmarkMapStringKeysEight_1M(b *testing.B) { benchmarkMapStringKeysEight(b, 1<<20) } + +func benchmarkMapStringKeysEight(b *testing.B, keySize int) { + m := make(map[string]bool) + for i := 0; i < 8; i++ { + m[strings.Repeat("K", i+1)] = true + } + key := strings.Repeat("K", keySize) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = m[key] + } +} + +func BenchmarkIntMap(b *testing.B) { + m := make(map[int]bool) + for i := 0; i < 8; i++ { + m[i] = true + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = m[7] + } +} + +// Accessing the same keys in a row. +func benchmarkRepeatedLookup(b *testing.B, lookupKeySize int) { + m := make(map[string]bool) + // At least bigger than a single bucket: + for i := 0; i < 64; i++ { + m[fmt.Sprintf("some key %d", i)] = true + } + base := strings.Repeat("x", lookupKeySize-1) + key1 := base + "1" + key2 := base + "2" + b.ResetTimer() + for i := 0; i < b.N/4; i++ { + _ = m[key1] + _ = m[key1] + _ = m[key2] + _ = m[key2] + } +} + +func BenchmarkRepeatedLookupStrMapKey32(b *testing.B) { benchmarkRepeatedLookup(b, 32) } +func BenchmarkRepeatedLookupStrMapKey1M(b *testing.B) { benchmarkRepeatedLookup(b, 1<<20) } + +func BenchmarkNewEmptyMap(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _ = make(map[int]int) + } +} diff --git a/libgo/go/runtime/pprof/pprof.go b/libgo/go/runtime/pprof/pprof.go index ee81c94..32c1098 100644 --- a/libgo/go/runtime/pprof/pprof.go +++ b/libgo/go/runtime/pprof/pprof.go @@ -318,21 +318,33 @@ func printCountProfile(w io.Writer, debug int, name string, p countProfile) erro // for a single stack trace. func printStackRecord(w io.Writer, stk []uintptr, allFrames bool) { show := allFrames - for _, pc := range stk { + wasPanic := false + for i, pc := range stk { f := runtime.FuncForPC(pc) if f == nil { show = true fmt.Fprintf(w, "#\t%#x\n", pc) + wasPanic = false } else { - file, line := f.FileLine(pc) + tracepc := pc + // Back up to call instruction. + if i > 0 && pc > f.Entry() && !wasPanic { + if runtime.GOARCH == "386" || runtime.GOARCH == "amd64" { + tracepc-- + } else { + tracepc -= 4 // arm, etc + } + } + file, line := f.FileLine(tracepc) name := f.Name() // Hide runtime.goexit and any runtime functions at the beginning. // This is useful mainly for allocation traces. + wasPanic = name == "runtime.panic" if name == "runtime.goexit" || !show && strings.HasPrefix(name, "runtime.") { continue } show = true - fmt.Fprintf(w, "#\t%#x\t%s+%#x\t%s:%d\n", pc, f.Name(), pc-f.Entry(), file, line) + fmt.Fprintf(w, "#\t%#x\t%s+%#x\t%s:%d\n", pc, name, pc-f.Entry(), file, line) } } if !show { diff --git a/libgo/go/runtime/proc_test.go b/libgo/go/runtime/proc_test.go index bf97fb1..21fb9c2 100644 --- a/libgo/go/runtime/proc_test.go +++ b/libgo/go/runtime/proc_test.go @@ -5,9 +5,11 @@ package runtime_test import ( + "math" "runtime" "sync/atomic" "testing" + "time" ) var stop = make(chan bool, 1) @@ -45,6 +47,66 @@ func TestStopTheWorldDeadlock(t *testing.T) { runtime.GOMAXPROCS(maxprocs) } +func TestYieldProgress(t *testing.T) { + testYieldProgress(t, false) +} + +func TestYieldLockedProgress(t *testing.T) { + testYieldProgress(t, true) +} + +func testYieldProgress(t *testing.T, locked bool) { + c := make(chan bool) + cack := make(chan bool) + go func() { + if locked { + runtime.LockOSThread() + } + for { + select { + case <-c: + cack <- true + return + default: + runtime.Gosched() + } + } + }() + time.Sleep(10 * time.Millisecond) + c <- true + <-cack +} + +func TestYieldLocked(t *testing.T) { + const N = 10 + c := make(chan bool) + go func() { + runtime.LockOSThread() + for i := 0; i < N; i++ { + runtime.Gosched() + time.Sleep(time.Millisecond) + } + c <- true + // runtime.UnlockOSThread() is deliberately omitted + }() + <-c +} + +func TestBlockLocked(t *testing.T) { + const N = 10 + c := make(chan bool) + go func() { + runtime.LockOSThread() + for i := 0; i < N; i++ { + c <- true + } + runtime.UnlockOSThread() + }() + for i := 0; i < N; i++ { + <-c + } +} + func stackGrowthRecursive(i int) { var pad [128]uint64 if i != 0 && pad[0] == 0 { @@ -52,6 +114,14 @@ func stackGrowthRecursive(i int) { } } +func TestSchedLocalQueue(t *testing.T) { + runtime.TestSchedLocalQueue1() +} + +func TestSchedLocalQueueSteal(t *testing.T) { + runtime.TestSchedLocalQueueSteal1() +} + func benchmarkStackGrowth(b *testing.B, rec int) { const CallsPerSched = 1000 procs := runtime.GOMAXPROCS(-1) @@ -82,31 +152,24 @@ func BenchmarkStackGrowthDeep(b *testing.B) { } func BenchmarkSyscall(b *testing.B) { - const CallsPerSched = 1000 - procs := runtime.GOMAXPROCS(-1) - N := int32(b.N / CallsPerSched) - c := make(chan bool, procs) - for p := 0; p < procs; p++ { - go func() { - for atomic.AddInt32(&N, -1) >= 0 { - runtime.Gosched() - for g := 0; g < CallsPerSched; g++ { - runtime.Entersyscall() - runtime.Exitsyscall() - } - } - c <- true - }() - } - for p := 0; p < procs; p++ { - <-c - } + benchmarkSyscall(b, 0, 1) } func BenchmarkSyscallWork(b *testing.B) { + benchmarkSyscall(b, 100, 1) +} + +func BenchmarkSyscallExcess(b *testing.B) { + benchmarkSyscall(b, 0, 4) +} + +func BenchmarkSyscallExcessWork(b *testing.B) { + benchmarkSyscall(b, 100, 4) +} + +func benchmarkSyscall(b *testing.B, work, excess int) { const CallsPerSched = 1000 - const LocalWork = 100 - procs := runtime.GOMAXPROCS(-1) + procs := runtime.GOMAXPROCS(-1) * excess N := int32(b.N / CallsPerSched) c := make(chan bool, procs) for p := 0; p < procs; p++ { @@ -116,7 +179,7 @@ func BenchmarkSyscallWork(b *testing.B) { runtime.Gosched() for g := 0; g < CallsPerSched; g++ { runtime.Entersyscall() - for i := 0; i < LocalWork; i++ { + for i := 0; i < work; i++ { foo *= 2 foo /= 2 } @@ -156,3 +219,67 @@ func benchmarkCreateGoroutines(b *testing.B, procs int) { <-c } } + +type Matrix [][]float64 + +func BenchmarkMatmult(b *testing.B) { + b.StopTimer() + // matmult is O(N**3) but testing expects O(b.N), + // so we need to take cube root of b.N + n := int(math.Cbrt(float64(b.N))) + 1 + A := makeMatrix(n) + B := makeMatrix(n) + C := makeMatrix(n) + b.StartTimer() + matmult(nil, A, B, C, 0, n, 0, n, 0, n, 8) +} + +func makeMatrix(n int) Matrix { + m := make(Matrix, n) + for i := 0; i < n; i++ { + m[i] = make([]float64, n) + for j := 0; j < n; j++ { + m[i][j] = float64(i*n + j) + } + } + return m +} + +func matmult(done chan<- struct{}, A, B, C Matrix, i0, i1, j0, j1, k0, k1, threshold int) { + di := i1 - i0 + dj := j1 - j0 + dk := k1 - k0 + if di >= dj && di >= dk && di >= threshold { + // divide in two by y axis + mi := i0 + di/2 + done1 := make(chan struct{}, 1) + go matmult(done1, A, B, C, i0, mi, j0, j1, k0, k1, threshold) + matmult(nil, A, B, C, mi, i1, j0, j1, k0, k1, threshold) + <-done1 + } else if dj >= dk && dj >= threshold { + // divide in two by x axis + mj := j0 + dj/2 + done1 := make(chan struct{}, 1) + go matmult(done1, A, B, C, i0, i1, j0, mj, k0, k1, threshold) + matmult(nil, A, B, C, i0, i1, mj, j1, k0, k1, threshold) + <-done1 + } else if dk >= threshold { + // divide in two by "k" axis + // deliberately not parallel because of data races + mk := k0 + dk/2 + matmult(nil, A, B, C, i0, i1, j0, j1, k0, mk, threshold) + matmult(nil, A, B, C, i0, i1, j0, j1, mk, k1, threshold) + } else { + // the matrices are small enough, compute directly + for i := i0; i < i1; i++ { + for j := j0; j < j1; j++ { + for k := k0; k < k1; k++ { + C[i][j] += A[i][k] * B[k][j] + } + } + } + } + if done != nil { + done <- struct{}{} + } +} diff --git a/libgo/go/runtime/string_test.go b/libgo/go/runtime/string_test.go index 8f13f0f..df3ff06 100644 --- a/libgo/go/runtime/string_test.go +++ b/libgo/go/runtime/string_test.go @@ -1,3 +1,7 @@ +// Copyright 2012 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 ( @@ -43,3 +47,31 @@ func BenchmarkCompareStringDifferentLength(b *testing.B) { } } } + +func BenchmarkCompareStringBigUnaligned(b *testing.B) { + bytes := make([]byte, 0, 1<<20) + for len(bytes) < 1<<20 { + bytes = append(bytes, "Hello Gophers!"...) + } + s1, s2 := string(bytes), "hello"+string(bytes) + for i := 0; i < b.N; i++ { + if s1 != s2[len("hello"):] { + b.Fatal("s1 != s2") + } + } + b.SetBytes(int64(len(s1))) +} + +func BenchmarkCompareStringBig(b *testing.B) { + bytes := make([]byte, 0, 1<<20) + for len(bytes) < 1<<20 { + bytes = append(bytes, "Hello Gophers!"...) + } + s1, s2 := string(bytes), string(bytes) + for i := 0; i < b.N; i++ { + if s1 != s2 { + b.Fatal("s1 != s2") + } + } + b.SetBytes(int64(len(s1))) +} |