英文:
16 million goroutines - "GC assist wait"
问题
我正在运行一个计算Mandelbrot集合的Go程序。为了计算收敛性,为每个像素启动了一个goroutine。当pixelLengthx = 1000
,pixelLengthy = 1000
时,程序运行正常。
如果我使用相同的代码运行pixelLengthx = 4000
,pixelLengthy = 4000
,程序在几十秒后开始打印以下内容:
goroutine 650935 [GC assist wait]:
main.converges(0xa2, 0xb6e, 0xc04200c680)
.../fractals/fractals.go:41 +0x17e
created by main.main
.../fractals/fractals.go:52 +0x2af
程序不会终止,只会继续打印。
这里发生了什么?为什么程序会打印东西?
我正在使用go版本go1.8 windows/amd64。
英文:
I'm running a go program that computes the mandelbrot set. A gouroutine is started for every pixel to compute convergence. The program runs fine for pixelLengthx = 1000
, pixelLengthy = 1000
.
If I run the same code for pixelLengthx = 4000
, pixelLengthy = 4000
, the program starts printing this after a few dozen seconds:
goroutine 650935 [GC assist wait]:
main.converges(0xa2, 0xb6e, 0xc04200c680)
.../fractals/fractals.go:41 +0x17e
created by main.main
.../fractals/fractals.go:52 +0x2af
The program does not terminate and just continues printing.
package main
import (
"image"
"image/color"
"image/draw"
"image/png"
"log"
"math/cmplx"
"os"
"sync"
)
var sidex float64 = 4.0
var sidey float64 = 4.0
var pixelLengthx int = 4000
var pixelLengthy int = 4000
var numSteps int = 100
func converges(wg *sync.WaitGroup, i, j int, m *image.RGBA) {
wht := color.RGBA{255, 50, 128, 255}
plx := float64(pixelLengthx)
ply := float64(pixelLengthy)
fi := float64(i)
fj := float64(j)
c := complex((fi-plx/2)/plx*sidex, (fj-ply/2)/ply*sidey)
zn := complex(0, 0)
for k := 0; k < numSteps; k++ {
zn = cmplx.Pow(zn, 2) + c
}
if cmplx.Abs(zn) > 0.1 {
m.Set(i, j, wht)
}
wg.Done()
}
func main() {
err := Main()
if err != nil {
log.Fatal(err)
}
}
func Main() error {
m := image.NewRGBA(image.Rect(0, 0, pixelLengthx, pixelLengthy))
blk := color.RGBA{0, 0, 0, 255}
draw.Draw(m, m.Bounds(), &image.Uniform{blk}, image.ZP, draw.Src)
numGoroutines := pixelLengthx * pixelLengthy
wg := &sync.WaitGroup{}
wg.Add(numGoroutines)
for x := 0; x < pixelLengthx; x++ {
for y := 0; y < pixelLengthy; y++ {
go converges(wg, x, y, m)
}
}
wg.Wait()
f, err := os.Create("img.png")
if err != nil {
return err
}
defer f.Close()
err = png.Encode(f, m)
if err != nil {
return err
}
return nil
}
What is going on here? Why does the program even print something?
I'm using go version go1.8 windows/amd64.
答案1
得分: 2
goroutine是轻量级的,但你过于自信了。我认为你应该像下面这样创建一个worker。
package main
import (
"image"
"image/color"
"image/draw"
"image/png"
"log"
"math/cmplx"
"os"
"sync"
)
var sidex float64 = 4.0
var sidey float64 = 4.0
var pixelLengthx int = 4000
var pixelLengthy int = 4000
var numSteps int = 100
func main() {
err := Main()
if err != nil {
log.Fatal(err)
}
}
type req struct {
x int
y int
m *image.RGBA
}
func converges(wg *sync.WaitGroup, q chan *req) {
defer wg.Done()
wht := color.RGBA{255, 50, 128, 255}
plx := float64(pixelLengthx)
ply := float64(pixelLengthy)
for r := range q {
fi := float64(r.x)
fj := float64(r.x)
c := complex((fi-plx/2)/plx*sidex, (fj-ply/2)/ply*sidey)
zn := complex(0, 0)
for k := 0; k < numSteps; k++ {
zn = cmplx.Pow(zn, 2) + c
}
if cmplx.Abs(zn) > 0.1 {
r.m.Set(r.x, r.y, wht)
}
}
}
const numWorker = 10
func Main() error {
q := make(chan *req, numWorker)
var wg sync.WaitGroup
wg.Add(numWorker)
for i := 0; i < numWorker; i++ {
go converges(&wg, q)
}
m := image.NewRGBA(image.Rect(0, 0, pixelLengthx, pixelLengthy))
blk := color.RGBA{0, 0, 0, 255}
draw.Draw(m, m.Bounds(), &image.Uniform{blk}, image.ZP, draw.Src)
for x := 0; x < pixelLengthx; x++ {
for y := 0; y < pixelLengthy; y++ {
q <- &req{x: x, y: y, m: m}
}
}
close(q)
wg.Wait()
f, err := os.Create("img.png")
if err != nil {
return err
}
defer f.Close()
err = png.Encode(f, m)
if err != nil {
return err
}
return nil
}
希望对你有帮助!
英文:
goroutine is lightweight but you have too much overconfident. You should make worker like below, I think.
package main
import (
"image"
"image/color"
"image/draw"
"image/png"
"log"
"math/cmplx"
"os"
"sync"
)
var sidex float64 = 4.0
var sidey float64 = 4.0
var pixelLengthx int = 4000
var pixelLengthy int = 4000
var numSteps int = 100
func main() {
err := Main()
if err != nil {
log.Fatal(err)
}
}
type req struct {
x int
y int
m *image.RGBA
}
func converges(wg *sync.WaitGroup, q chan *req) {
defer wg.Done()
wht := color.RGBA{255, 50, 128, 255}
plx := float64(pixelLengthx)
ply := float64(pixelLengthy)
for r := range q {
fi := float64(r.x)
fj := float64(r.x)
c := complex((fi-plx/2)/plx*sidex, (fj-ply/2)/ply*sidey)
zn := complex(0, 0)
for k := 0; k < numSteps; k++ {
zn = cmplx.Pow(zn, 2) + c
}
if cmplx.Abs(zn) > 0.1 {
r.m.Set(r.x, r.y, wht)
}
}
}
const numWorker = 10
func Main() error {
q := make(chan *req, numWorker)
var wg sync.WaitGroup
wg.Add(numWorker)
for i := 0; i < numWorker; i++ {
go converges(&wg, q)
}
m := image.NewRGBA(image.Rect(0, 0, pixelLengthx, pixelLengthy))
blk := color.RGBA{0, 0, 0, 255}
draw.Draw(m, m.Bounds(), &image.Uniform{blk}, image.ZP, draw.Src)
for x := 0; x < pixelLengthx; x++ {
for y := 0; y < pixelLengthy; y++ {
q <- &req{x: x, y: y, m: m}
}
}
close(q)
wg.Wait()
f, err := os.Create("img.png")
if err != nil {
return err
}
defer f.Close()
err = png.Encode(f, m)
if err != nil {
return err
}
return nil
}
答案2
得分: 1
这是由于垃圾收集器尝试进行全停顿扫描。1.8版本的垃圾收集器最小化了但并没有消除全停顿收集。实际的收集过程很快,但首先它必须在完成垃圾收集之前抢占所有的goroutine。当goroutine进行函数调用时,调度器可以抢占它。如果你在进行所有的内联数学计算和紧密循环,并且有很多活跃的goroutine,这可能需要很长时间。
此外,正如@JimB和@putu指出的,虽然goroutine非常节约资源,并且在生产环境中已经使用了非常大的数量,但这些情况下都有非常丰富的资源可用(例如Google的生产基础设施)。Goroutine很轻量级,但1600万个feathers仍然会很重。如果你的系统没有32GB以上的内存,你可能会过度使用机器资源,而不是Go本身。
尝试使用GOTRACEBACK=crash和GODEBUG=gctrace=1运行,并查看在程序崩溃时是否可以从堆栈跟踪中获取一些有用的信息。
在网络搜索中,关于"GC assist wait"的结果中有一个有用的讨论帖子:https://groups.google.com/forum/#!topic/golang-dev/PVwDFD7gDuk
英文:
This is due to the garbage collector attempting a stop-the-world sweep. The 1.8 GC minimizes but doesn't eliminate STW collection. The actual collection is fast, but first it has to preempt all goroutines before it can finish GC. A goroutine can be preempted by the scheduler when it makes a function call. If you're doing all inline math and tight loops, with many goroutines live, this could take a very long time.
Also, as @JimB and @putu noted, while goroutines are extremely resource-efficient and very large numbers have been used in production circumstances, these circumstances have been with extraordinary resources available (e.g. Google's production infrastructure). Goroutines are light weight, but 16M feathers is still going to be heavy. If your system does not have 32GB+ memory, you're likely over-taxing your machine, not Go itself.
Try running with GOTRACEBACK=crash and GODEBUG=gctrace=1 and see if you can get some probative info from the stack trace when it dies.
A web search for "GC assist wait" turned up this useful thread: https://groups.google.com/forum/#!topic/golang-dev/PVwDFD7gDuk
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论