4

我在 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 多路复用到多个内核是否有任何重大开销?我意识到 goroutine 没有做任何事情,如果我不得不等待例程实际做某事,那可能会是另一回事。

4

2 回答 2

9

在单核上运行时,goroutine 分配和切换只是内部核算的问题。Goroutines 永远不会被抢占,因此切换逻辑非常简单且非常快速。更重要的是,在这种情况下,您的主例程根本不会产生,因此 goroutines 在终止之前甚至都不会开始执行。你分配结构然后删除它,就是这样。编辑这可能不适用于较新版本的go,但只有1个进程肯定更有序)

但是,当您将例程映射到多个线程上时,您会突然涉及到操作系统级别的上下文切换,这会慢几个数量级且更复杂。即使您使用多个内核,也有很多工作要做。另外,现在您的 gouroutines 可能实际上在程序终止之前正在运行。

尝试strace在这两种情况下运行程序,看看它的行为有何不同。

于 2013-04-15T17:34:09.183 回答
4

衡量多核性能总是很困难,除非您有大量工作负载可以从多核工作中受益。问题是代码需要在线程和内核之间共享,这意味着虽然可能不会有很大的开销,但仍然有很大的开销,特别是对于简单的代码,会降低整体性能。

就像你提到的那样,如果你做一些 CPU 密集型的事情,那将是一个完全不同的故事。

于 2013-04-15T17:29:41.993 回答