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/

Другие вопросы по тегам