|  | // 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() | 
|  | } | 
|  | }) | 
|  | } |