我在Go做了一些实验,我发现了一些非常奇怪的东西。当我在计算机上运行以下代码时,它会在约0.5秒内执行。
package main
import (
"fmt"
"runtime"
"time"
)
func waitAround(die chan bool) {
<- die
}
func main() {
var startMemory runtime.MemStats
runtime.ReadMemStats(&startMemory)
start := time.Now()
cpus := runtime.NumCPU()
runtime.GOMAXPROCS(cpus)
die := make(chan bool)
count := 100000
for i := 0; i < count; i++ {
go waitAround(die)
}
elapsed := time.Since(start)
var endMemory runtime.MemStats
runtime.ReadMemStats(&endMemory)
fmt.Printf("Started %d goroutines\n%d CPUs\n%f seconds\n",
count, cpus, elapsed.Seconds())
fmt.Printf("Memory before %d\nmemory after %d\n", startMemory.Alloc,
endMemory.Alloc)
fmt.Printf("%d goroutines running\n", runtime.NumGoroutine())
fmt.Printf("%d bytes per goroutine\n", (endMemory.Alloc - startMemory.Alloc)/uint64(runtime.NumGoroutine()))
close(die)
}
然而,当我使用runtime.GOMAXPROCS(1)
执行它时,它执行得更快(~0.15秒)。任何人都可以向我解释为什么使用更多内核运行许多goroutine会更慢?将goroutine多路复用到多个内核上是否有任何重大开销?我意识到goroutines没有做任何事情,如果我不得不等待例程实际做某事,那可能会是一个不同的故事。
答案 0 :(得分:9)
在单核上运行时,goroutine分配和切换只是内部核算的问题。 Goroutines永远不会被抢占,因此切换逻辑非常简单且非常快。更重要的是,在这种情况下,你的主程序根本不会产生,所以goroutines甚至在它们被终止之前甚至都没有开始执行。您分配结构然后删除它,就是这样。 (编辑这可能不适用于较新版本的go,但只有1个流程肯定会更有序)
但是当你在多个线程上映射例程时,你会突然得到涉及os级别的上下文切换,这个数量级更慢,更复杂。即使你在多核上,还有很多工作要做。此外,现在您的gouroutines实际上可能在程序终止之前运行。
在两种情况下尝试strace
程序,看看它的行为有何不同。
答案 1 :(得分:4)
在多核上测量性能总是很困难,除非您有大量的工作负载,这可以从多核处理中受益。问题是代码需要在线程和内核之间共享,这意味着虽然可能没有巨大的开销,但仍然是一个很大的数量,特别是对于简单的代码,降低了整体性能。
就像你提到的那样,如果你做了一些CPU密集型的事情,这将是一个完全不同的故事。