| // run | 
 |  | 
 | // Copyright 2017 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. | 
 |  | 
 | // Test that locks don't go quadratic due to runtime hash table collisions. | 
 |  | 
 | package main | 
 |  | 
 | import ( | 
 | 	"bytes" | 
 | 	"fmt" | 
 | 	"log" | 
 | 	"os" | 
 | 	"runtime" | 
 | 	"runtime/pprof" | 
 | 	"sync" | 
 | 	"time" | 
 | ) | 
 |  | 
 | const debug = false | 
 |  | 
 | // checkLinear asserts that the running time of f(n) is at least linear but sub-quadratic. | 
 | // tries is the initial number of iterations. | 
 | func checkLinear(typ string, tries int, f func(n int)) { | 
 | 	// Depending on the machine and OS, this test might be too fast | 
 | 	// to measure with accurate enough granularity. On failure, | 
 | 	// make it run longer, hoping that the timing granularity | 
 | 	// is eventually sufficient. | 
 |  | 
 | 	timeF := func(n int) time.Duration { | 
 | 		t1 := time.Now() | 
 | 		f(n) | 
 | 		return time.Since(t1) | 
 | 	} | 
 |  | 
 | 	n := tries | 
 | 	fails := 0 | 
 | 	var buf bytes.Buffer | 
 | 	inversions := 0 | 
 | 	for { | 
 | 		t1 := timeF(n) | 
 | 		t2 := timeF(2 * n) | 
 | 		if debug { | 
 | 			println(n, t1.String(), 2*n, t2.String()) | 
 | 		} | 
 | 		fmt.Fprintf(&buf, "%d %v %d %v (%.1fX)\n", n, t1, 2*n, t2, float64(t2)/float64(t1)) | 
 | 		// should be 2x (linear); allow up to 3x | 
 | 		if t1*3/2 < t2 && t2 < t1*3 { | 
 | 			return | 
 | 		} | 
 | 		if t2 < t1 { | 
 | 			if inversions++; inversions >= 5 { | 
 | 				// The system must be overloaded (some builders). Give up. | 
 | 				return | 
 | 			} | 
 | 			continue // try again; don't increment fails | 
 | 		} | 
 | 		// Once the test runs long enough for n ops, | 
 | 		// try to get the right ratio at least once. | 
 | 		// If many in a row all fail, give up. | 
 | 		if fails++; fails >= 5 { | 
 | 			// If 2n ops run in under a second and the ratio | 
 | 			// doesn't work out, make n bigger, trying to reduce | 
 | 			// the effect that a constant amount of overhead has | 
 | 			// on the computed ratio. | 
 | 			if t2 < time.Second*4/10 { | 
 | 				fails = 0 | 
 | 				n *= 2 | 
 | 				continue | 
 | 			} | 
 | 			panic(fmt.Sprintf("%s: too slow: %d ops: %v; %d ops: %v\n\n%s", | 
 | 				typ, n, t1, 2*n, t2, buf.String())) | 
 | 		} | 
 | 	} | 
 | } | 
 |  | 
 | const offset = 251 // known size of runtime hash table | 
 |  | 
 | const profile = false | 
 |  | 
 | func main() { | 
 | 	if profile { | 
 | 		f, err := os.Create("lock.prof") | 
 | 		if err != nil { | 
 | 			log.Fatal(err) | 
 | 		} | 
 | 		pprof.StartCPUProfile(f) | 
 | 		defer pprof.StopCPUProfile() | 
 | 	} | 
 |  | 
 | 	checkLinear("lockone", 1000, func(n int) { | 
 | 		ch := make(chan int) | 
 | 		locks := make([]sync.RWMutex, offset+1) | 
 | 		for i := 0; i < n; i++ { | 
 | 			go func() { | 
 | 				locks[0].Lock() | 
 | 				ch <- 1 | 
 | 			}() | 
 | 		} | 
 | 		time.Sleep(1 * time.Millisecond) | 
 |  | 
 | 		go func() { | 
 | 			for j := 0; j < n; j++ { | 
 | 				locks[1].Lock() | 
 | 				locks[offset].Lock() | 
 | 				locks[1].Unlock() | 
 | 				runtime.Gosched() | 
 | 				locks[offset].Unlock() | 
 | 			} | 
 | 		}() | 
 |  | 
 | 		for j := 0; j < n; j++ { | 
 | 			locks[1].Lock() | 
 | 			locks[offset].Lock() | 
 | 			locks[1].Unlock() | 
 | 			runtime.Gosched() | 
 | 			locks[offset].Unlock() | 
 | 		} | 
 |  | 
 | 		for i := 0; i < n; i++ { | 
 | 			<-ch | 
 | 			locks[0].Unlock() | 
 | 		} | 
 | 	}) | 
 |  | 
 | 	if runtime.GOARCH == "arm" && os.Getenv("GOARM") == "5" { | 
 | 		// lockmany reliably fails on the linux-arm-arm5spacemonkey | 
 | 		// builder. See https://golang.org/issue/24221. | 
 | 		return | 
 | 	} | 
 |  | 
 | 	checkLinear("lockmany", 1000, func(n int) { | 
 | 		locks := make([]sync.RWMutex, n*offset+1) | 
 |  | 
 | 		var wg sync.WaitGroup | 
 | 		for i := 0; i < n; i++ { | 
 | 			wg.Add(1) | 
 | 			go func(i int) { | 
 | 				locks[(i+1)*offset].Lock() | 
 | 				wg.Done() | 
 | 				locks[(i+1)*offset].Lock() | 
 | 				locks[(i+1)*offset].Unlock() | 
 | 			}(i) | 
 | 		} | 
 | 		wg.Wait() | 
 |  | 
 | 		go func() { | 
 | 			for j := 0; j < n; j++ { | 
 | 				locks[1].Lock() | 
 | 				locks[0].Lock() | 
 | 				locks[1].Unlock() | 
 | 				runtime.Gosched() | 
 | 				locks[0].Unlock() | 
 | 			} | 
 | 		}() | 
 |  | 
 | 		for j := 0; j < n; j++ { | 
 | 			locks[1].Lock() | 
 | 			locks[0].Lock() | 
 | 			locks[1].Unlock() | 
 | 			runtime.Gosched() | 
 | 			locks[0].Unlock() | 
 | 		} | 
 |  | 
 | 		for i := 0; i < n; i++ { | 
 | 			locks[(i+1)*offset].Unlock() | 
 | 		} | 
 | 	}) | 
 | } |