mirror of
				https://github.com/golang/go.git
				synced 2025-10-31 08:40:55 +00:00 
			
		
		
		
	 92cf05daf3
			
		
	
	
		92cf05daf3
		
	
	
	
	
		
			
			Fixes #19276. Change-Id: I64f8f80331d09956b6698c0b004ed7f7d70857fc Reviewed-on: https://go-review.googlesource.com/39591 Run-TryBot: Russ Cox <rsc@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Josh Bleecher Snyder <josharian@gmail.com>
		
			
				
	
	
		
			165 lines
		
	
	
	
		
			3.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			165 lines
		
	
	
	
		
			3.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // 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()
 | |
| 		}
 | |
| 	})
 | |
| 
 | |
| 	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()
 | |
| 		}
 | |
| 	})
 | |
| }
 |