Golang을 사용한 병렬 만델브로 집합

이 게시물에서는 Golanggoroutines을 사용하여 Mandelbrot 세트를 병렬로 생성하는 방법을 설명합니다.

여기 소스 코드: https://github.com/GiselaMD/parallel-mandelbrot-go

만델브로 집합



Mandelbrot 집합이 무엇인지에 관심이 있는 사람들은 확인하십시오https://en.wikipedia.org/wiki/Mandelbrot_set.

설정된 수식은 xy 좌표의 위치를 ​​기반으로 합니다.

x = x*x - y*y + a
y = 2*x*y + b


색상을 설정하기 위해 x*x + y*y > 4 여부도 확인합니다.

그러나 수학 세부 사항으로 들어가는 대신 화면에 Mandelbrot 세트를 렌더링하기 위해 gourotines를 사용할 수 있는 방법을 설명하고 싶습니다.

코드에 들어가기



이 프로그램은 Mandelbrot 집합의 성능과 해상도에 영향을 미칠 4가지 주요 값을 기반으로 합니다.

maxIter = 1000
samples = 200

numBlocks  = 64
numThreads = 16


  • maxIter는 mandelbrot 공식이 계산되는 횟수를 정의하여 xy 값을 생성합니다.
  • samples는 RGB 색상 값을 생성하는 상호 작용의 수입니다.
  • numBlocks는 이미지를 몇 조각으로 나눌 것인지에 있습니다.
  • numThreads는 생성될 gourotines의 수입니다.

  • 결과를 화면에 렌더링하기 위해 Pixel 라이브러리(github.com/faiface/pixel)를 사용했습니다. main 함수에는 다음과 같은 것이 있습니다.

    func main() {
        pixelgl.Run(run)
    }
    


    pixelgl.Run을 호출하면 PixelGL이 기본 기능을 제어하게 되며 더 이상 기본 기능에서 코드를 실행할 방법이 없습니다. 그렇기 때문에 pixelgl.Run 내부에 run 함수인 또 다른 함수를 전달해야 합니다.

    func run() {
        log.Println("Initial processing...")
        pixelCount = 0
        img = image.NewRGBA(image.Rect(0, 0, imgWidth, imgHeight))
        cfg := pixelgl.WindowConfig{
            Title:  "Parallel Mandelbrot in Go",
            Bounds: pixel.R(0, 0, imgWidth, imgHeight),
            VSync:  true,
        }
    
        win, err := pixelgl.NewWindow(cfg)
        if err != nil {
            panic(err)
        }
        log.Println("Rendering...")
        start := time.Now()
        workBuffer := make(chan WorkItem, numBlocks)
        threadBuffer := make(chan bool, numThreads)
        drawBuffer := make(chan Pix, pixelTotal)
    
        workBufferInit(workBuffer)
        go workersInit(drawBuffer, workBuffer, threadBuffer)
        go drawThread(drawBuffer, win)
    
        for !win.Closed() {
            pic := pixel.PictureDataFromImage(img)
            sprite := pixel.NewSprite(pic, pic.Bounds())
            sprite.Draw(win, pixel.IM.Moved(win.Bounds().Center()))
            win.Update()
    
            if showProgress {
                fmt.Printf("\r%d/%d (%d%%)", pixelCount, pixelTotal, int(100*(float64(pixelCount)/float64(pixelTotal))))
            }
    
            if pixelCount == pixelTotal {
                end := time.Now()
                fmt.Println("\nFinished with time = ", end.Sub(start))
                pixelCount++
    
                if closeOnEnd {
                    break
                }
            }
        }
    }
    

    run 함수는 창을 초기화 및 업데이트하고 gourotines에 사용할 채널을 만드는 역할을 합니다.
    workBuffer는 (numBlocks를 기준으로) 각 블록의 정보를 추가하는 역할을 하는 채널입니다. workBufferInit 내부에서 초기 및 최종 xy 값이 채널로 전송되어 작업할 이미지 조각을 가져오는 각 gourotines가 전역 데이터를 알 필요 없이 색상을 계산할 수 있습니다. , 해당 블록의 xy 범위만 해당됩니다.

    func workBufferInit(workBuffer chan WorkItem) {
        var sqrt = int(math.Sqrt(numBlocks))
    
        for i := sqrt - 1; i >= 0; i-- {
            for j := 0; j < sqrt; j++ {
                workBuffer <- WorkItem{
                    initialX: i * (imgWidth / sqrt),
                    finalX:   (i + 1) * (imgWidth / sqrt),
                    initialY: j * (imgHeight / sqrt),
                    finalY:   (j + 1) * (imgHeight / sqrt),
                }
            }
        }
    }
    

    threadBuffergoroutines를 기반으로 numThreads를 생성하고 goroutine가 작업을 완료하는 시기를 제어하여 다른 작업을 대신 실행할 수 있도록 합니다. 그 논리는 workersInit goroutine .

    func workersInit(drawBuffer chan Pix, workBuffer chan WorkItem, threadBuffer chan bool) {
        for i := 1; i <= numThreads; i++ {
            threadBuffer <- true
        }
    
        for range threadBuffer {
            workItem := <-workBuffer
    
            go workerThread(workItem, drawBuffer, threadBuffer)
        }
    }
    

    workItem(각 블록)에서 수신한 각 workBuffer에 대해 모든 Mandelbrot 집합 논리를 처리하기 위해 goroutine라는 workerThread를 생성합니다.

    func workerThread(workItem WorkItem, drawBuffer chan Pix, threadBuffer chan bool) {
        for x := workItem.initialX; x < workItem.finalX; x++ {
            for y := workItem.initialY; y < workItem.finalY; y++ {
                var colorR, colorG, colorB int
                for k := 0; k < samples; k++ {
                    a := height*ratio*((float64(x)+RandFloat64())/float64(imgWidth)) + posX
                    b := height*((float64(y)+RandFloat64())/float64(imgHeight)) + posY
                    c := pixelColor(mandelbrotIteraction(a, b, maxIter))
                    colorR += int(c.R)
                    colorG += int(c.G)
                    colorB += int(c.B)
                }
                var cr, cg, cb uint8
                cr = uint8(float64(colorR) / float64(samples))
                cg = uint8(float64(colorG) / float64(samples))
                cb = uint8(float64(colorB) / float64(samples))
    
                drawBuffer <- Pix{
                    x, y, cr, cg, cb,
                }
    
            }
        }
        threadBuffer <- true
    }
    



    func mandelbrotIteraction(a, b float64, maxIter int) (float64, int) {
        var x, y, xx, yy, xy float64
    
        for i := 0; i < maxIter; i++ {
            xx, yy, xy = x*x, y*y, x*y
            if xx+yy > 4 {
                return xx + yy, i
            }
            // xn+1 = x^2 - y^2 + a
            x = xx - yy + a
            // yn+1 = 2xy + b
            y = 2*xy + b
        }
    
        return xx + yy, maxIter
    }
    
    func pixelColor(r float64, iter int) color.RGBA {
        insideSet := color.RGBA{R: 0, G: 0, B: 0, A: 255}
    
        // check if it's inside the set
        if r > 4 {
            // return hslToRGB(float64(0.70)-float64(iter)/3500*r, 1, 0.5)
            return hslToRGB(float64(iter)/100*r, 1, 0.5)
        }
    
        return insideSet
    }
    

    drawBuffer는 Mandelbrot 집합을 계산하는 goroutines에서 값을 수신하는 채널이며 데이터를 수신하면 drawThread goroutine가 픽셀 RGB 값을 이미지에 설정한 다음 run 함수 창을 업데이트합니다.

    func drawThread(drawBuffer chan Pix, win *pixelgl.Window) {
        for i := range drawBuffer {
            img.SetRGBA(i.x, i.y, color.RGBA{R: i.cr, G: i.cg, B: i.cb, A: 255})
            pixelCount++
        }
    }
    


    또한 임의의 데이터를 생성하고 hsl 및 색상을 RGB로 변환하기 위한 몇 가지 utils 함수가 있습니다.

    var randState = uint64(time.Now().UnixNano())
    
    func RandUint64() uint64 {
        randState = ((randState ^ (randState << 13)) ^ (randState >> 7)) ^ (randState << 17)
        return randState
    }
    
    func RandFloat64() float64 {
        return float64(RandUint64() / 2) / (1 << 63)
    }
    
    func hueToRGB(p, q, t float64) float64 {
        if t < 0 { t += 1 }
        if t > 1 { t -= 1 }
        switch {
        case t < 1.0 / 6.0:
            return p + (q - p) * 6 * t
        case t < 1.0 / 2.0:
            return q
        case t < 2.0 / 3.0:
            return p + (q - p) * (2.0 / 3.0 - t) * 6
        default:
            return p
        }
    }
    
    func hslToRGB(h, s, l float64) color.RGBA {
        var r, g, b float64
        if s == 0 {
            r, g, b = l, l, l
        } else {
            var q, p float64
            if l < 0.5 {
                q = l * (1 + s)
            } else {
                q = l + s - l * s
            }
            p = 2 * l - q
            r = hueToRGB(p, q, h + 1.0 / 3.0)
            g = hueToRGB(p, q, h)
            b = hueToRGB(p, q, h - 1.0 / 3.0)
        }
        return color.RGBA{ R: uint8(r * 255), G: uint8(g * 255), B: uint8(b * 255), A: 255 }
    }
    


    최종 결과:




    오늘은 여기까지입니다!

    즐기시기 바랍니다 😊

    🇧🇷 이 게시물은 이 프로젝트에 협력한 다니엘이 게시한 포르투갈어로도 볼 수 있습니다. 게시물 확인: https://danielferreiradev.medium.com/fractal-de-mandelbrot-paralelo-usando-golang-4ba497d9bbc5

    여기 소스 코드: https://github.com/GiselaMD/parallel-mandelbrot-go

    좋은 웹페이지 즐겨찾기