优化 Go 代码的一些技巧。

Goroutines

The key feature of Go that makes it a great fit for modern hardware are goroutines.

Goroutines are so easy to use, and so cheap to create, you could think of them as almost free.

The Go runtime has been written for programs with tens of thousands of goroutines as the norm, hundreds of thousands are not unexpected.(Go runtime 是为成千上万的 goroutine 编写的,所有开很多很多 goroutine 是意料之中的行为!)

However, each goroutine does consume a minimum amount of memory for the goroutine’s stack which is currently at least 2k.(每个 goroutine 都消耗最小的内存目前是 2kb)

2048 * 1,000,000 goroutines == 2GB of memory, and they haven’t done anything yet.(开 100 万个也就 2GB 内存)

Maybe this is a lot, maybe it isn’t given the other usages of your application.

IO

The Go runtime handles network IO using an efficient operating system polling mechanism (kqueue, epoll, windows IOCP, etc). Many waiting goroutines will be serviced by a single operating system thread.(对于网络 IO,使用 epoll 等机制,只会有一个系统线程处理这些请求)

However, for local file IO, Go does not implement any IO polling. Each operation on a *os.File consumes one operating system thread while in progress.(对于本地文件 IO,Go 并不实现任何 IO 轮询,每个 *os.File 都会对应一个实际的操作系统线程!)

Heavy use of local file IO can cause your program to spawn hundreds or thousands of threads; possibly more than your operating system allows.(大量使用文件 IO 会导致产生上千个线程,可能会超过系统允许的数量)

Your disk subsystem does not expect to be able to handle hundreds or thousands of concurrent IO requests.(磁盘并不期望处理上千个 IO 请求)

To limit the amount of concurrent blocking IO, use a pool of worker goroutines, or a buffered channel as a semaphore.(限制文件 IO 的并发数量)

var semaphore = make(chan struct{}, 10)

func processRequest(work *Work) {
	semaphore <- struct{}{} // acquire semaphore
	// process request
	<-semaphore // release semaphore
}

Defer

defer 的开销!

defer is expensive because it has to record a closure for defer’s arguments.

defer mu.Unlock()

is equivalent to

defer func() {
        mu.Unlock()
}()

defer is expensive if the work being done is small, the classic example is defer ing a mutex unlock around a struct variable or map lookup. You may choose to avoid defer in those situations.

This is a case where readability and maintenance is sacrificed for a performance win.

Always revisit these decisions.

Always use the latest released version of Go

  • Go 1.4 should not be used.
  • Go 1.5 and 1.6 had a slower compiler, but it produces faster code, and has a faster GC.
  • Go 1.7 delivered roughly a 30% improvement in compilation speed over 1.6, a 2x improvement in linking speed (better than any previous version of Go).
  • Go 1.8 will deliver a smaller improvement in compilation speed (at this point), but a significant improvement in code quality for non Intel architectures.
  • Go 1.9-1.12 continue to improve the performance of generated code, fix bugs, and improve inlining and improve debuging.

time.After()

select {
    case v <- call():
        fmt.Println(v)
    case <- time.After(5 * time.Second): // 产生内存泄露
        fmt.Println("timeout")
}

// 保证 close 不会造成内存泄露
t := time.NewTimer(5 * time.Second)
defer t.Close()