16 миллионов горутин - "GC Assist Wait"
Я запускаю программу go, которая вычисляет множество Мандельброта. Для каждого пикселя запускается гурутин для вычисления сходимости. Программа отлично работает для 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
Программа не завершается и просто продолжает печать.
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
}
Что здесь происходит? Почему программа вообще что-то печатает?
Я использую go версию go1.8 windows/amd64.
2 ответа
Горутин легкий, но у вас слишком много самоуверенности. Вы должны сделать работника, как показано ниже, я думаю.
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
}
Это происходит из-за того, что сборщик мусора пытается остановить мир. 1.8 GC минимизирует, но не устраняет сбор STW. Фактическая коллекция быстрая, но сначала она должна выгрузить все goroutines, прежде чем она сможет закончить GC. Планировщик может быть заблокирован при выполнении функции. Если вы выполняете всю встроенную математику и плотные циклы, а многие горутины живут, это может занять очень много времени.
Кроме того, как отметили @JimB и @putu, несмотря на то, что подпрограммы чрезвычайно эффективны в использовании ресурсов, а в производственных условиях использовались очень большие количества, эти обстоятельства были доступны с экстраординарными ресурсами (например, производственной инфраструктурой Google). Горутины легкие, но перья 16М все еще будут тяжелыми. Если ваша система не имеет 32 ГБ памяти +, вы, вероятно, перегружаете свою машину, а не самим Go.
Попробуйте запустить с GOTRACEBACK=crash и GODEBUG=gctrace=1 и посмотрите, сможете ли вы получить некоторую доказательную информацию из трассировки стека, когда он умрет.
Поисковый запрос "GC assist wait" обнаружил эту полезную тему: https://groups.google.com/forum/