1600万个goroutine – “GC辅助等待”

huangapple go评论141阅读模式
英文:

16 million goroutines - "GC assist wait"

问题

我正在运行一个计算Mandelbrot集合的Go程序。为了计算收敛性,为每个像素启动了一个goroutine。当pixelLengthx = 1000pixelLengthy = 1000时,程序运行正常。

如果我使用相同的代码运行pixelLengthx = 4000pixelLengthy = 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.

Memory usage during program execution.

答案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 (
&quot;image&quot;
&quot;image/color&quot;
&quot;image/draw&quot;
&quot;image/png&quot;
&quot;log&quot;
&quot;math/cmplx&quot;
&quot;os&quot;
&quot;sync&quot;
)
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 &lt; numSteps; k++ {
zn = cmplx.Pow(zn, 2) + c
}
if cmplx.Abs(zn) &gt; 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 &lt; numWorker; i++ {
go converges(&amp;wg, q)
}
m := image.NewRGBA(image.Rect(0, 0, pixelLengthx, pixelLengthy))
blk := color.RGBA{0, 0, 0, 255}
draw.Draw(m, m.Bounds(), &amp;image.Uniform{blk}, image.ZP, draw.Src)
for x := 0; x &lt; pixelLengthx; x++ {
for y := 0; y &lt; pixelLengthy; y++ {
q &lt;- &amp;req{x: x, y: y, m: m}
}
}
close(q)
wg.Wait()
f, err := os.Create(&quot;img.png&quot;)
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

huangapple
  • 本文由 发表于 2017年6月29日 20:46:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/44825550.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定