Golang中goroutines的性能优化实践

Golang中goroutines的性能优化实践 你好,

作为作业要求,我们需要优化生成分形图片的代码。

我们已经尝试通过两个for循环将图片分割成更小的部分(并将运行时间缩短了三分之二),但还希望通过在启动函数时创建多个goroutine来进一步加速。然而,这带来的改善微乎其微。

我们应该如何做才能加快程序运行速度?

另外,请检查plottingSmallSquares函数。为什么这个函数在Julia函数内部作为匿名函数使用时无法正常工作?那样做时,我们只得到垂直的线条。

提前感谢您的回复。

//this program creates pictures with Julia
//imports omitted

type ComplexFunc func(complex128) complex128

var Funcs []ComplexFunc = []ComplexFunc{
	func(z complex128) complex128 { return z*z - 0.61803398875 },
	func(z complex128) complex128 { return z*z + complex(0, 1) },
	//more functions omitted for clarity
}

func main() {
	
	wgr1 := new(sync.WaitGroup)

       //this is the function that can be ameliorated
	for n, fn := range Funcs {
               //sending one to wait group
              //the done is executed in the parallelPainting function
		wgr1.Add(1)
		go parallelPainting(fn, n, wgr1)

	}
	wgr1.Wait()
}

func parallelPainting(fn ComplexFunc, n int, wgr1 *sync.WaitGroup) {

	err := CreatePng("picture-"+strconv.Itoa(n)+".png", fn, 1024)
	wgr1.Done()
	if err != nil {
		log.Fatal(err)
	}

}

// CreatePng creates a PNG picture file with a Julia image of size n x n.
func CreatePng(filename string, f ComplexFunc, n int) (err error) {
	file, err := os.Create(filename)
	if err != nil {
		return
	}
	defer file.Close()
	err = png.Encode(file, Julia(f, n))
	return
}

// Julia returns an image of size n x n of the Julia set for f.
func Julia(f ComplexFunc, n int) image.Image {
	wg := new(sync.WaitGroup)
	bounds := image.Rect(-n/2, -n/2, n/2, n/2)
	img := image.NewRGBA(bounds)
	s := float64(n / 4)

	divisor := 10

        //division of the image in 100 smaller squares 
	width := bounds.Max.X - bounds.Min.X
	height := bounds.Max.Y - bounds.Min.Y

	sizeX := width / divisor
	sizeY := height / divisor

	startX := bounds.Min.X
	startY := bounds.Min.Y

	for startX, stopX := bounds.Min.X, startX+sizeX; startX < width; startX, stopX = stopX, startX+sizeX {
		for startY, stopY := bounds.Min.Y, startY+sizeY; startY < height; startY, stopY = stopY, startY+sizeY {
			wg.Add(1)

			go plottingSmallSquares(f, n, img, startX, stopX, startY, stopY, s, wg)
		}
	}
	wg.Wait()
	return img
}

func plottingSmallSquares(f ComplexFunc, n int, img *image.RGBA, startX int, stopX int, startY int, stopY int, s float64, wg *sync.WaitGroup) {
	for i := startX; i < stopX; i++ {
		for j := startY; j < stopY; j++ {
			n := Iterate(f, complex(float64(i)/s, float64(j)/s), 256)
			r := uint8(0)
			g := uint8(0)
			b := uint8(n % 32 * 8)
			img.Set(i, j, color.RGBA{r, g, b, 255})
		}
	}
	wg.Done()
}

// Iterate sets z_0 = z, and repeatedly computes z_n = f(z_{n-1}), n ≥ 1,
// until |z_n| > 2  or n = max and returns this n.
func Iterate(f ComplexFunc, z complex128, max int) (n int) {
	for ; n < max; n++ {
		if real(z)*real(z)+imag(z)*imag(z) > 4 {
			break
		}
		z = f(z)
	}
	return
}

更多关于Golang中goroutines的性能优化实践的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang中goroutines的性能优化实践的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


以下是针对您问题的专业解答,包含性能优化建议和匿名函数问题的分析。

Goroutines性能优化

当前代码的主要性能瓶颈在于goroutine创建过多(100个小方块 × 多个Julia函数),导致调度开销过大。以下是优化方案:

// 优化版本 - 使用工作池模式控制goroutine数量
func Julia(f ComplexFunc, n int) image.Image {
    bounds := image.Rect(-n/2, -n/2, n/2, n/2)
    img := image.NewRGBA(bounds)
    s := float64(n / 4)
    
    divisor := 10
    width := bounds.Max.X - bounds.Min.X
    height := bounds.Max.Y - bounds.Min.Y
    
    sizeX := width / divisor
    sizeY := height / divisor
    
    // 使用有缓冲channel控制并发数
    workers := runtime.NumCPU() // 根据CPU核心数确定worker数量
    jobs := make(chan struct {
        startX, stopX, startY, stopY int
    }, divisor*divisor)
    
    var wg sync.WaitGroup
    
    // 启动worker池
    for i := 0; i < workers; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for job := range jobs {
                for i := job.startX; i < job.stopX; i++ {
                    for j := job.startY; j < job.stopY; j++ {
                        n := Iterate(f, complex(float64(i)/s, float64(j)/s), 256)
                        r := uint8(0)
                        g := uint8(0)
                        b := uint8(n % 32 * 8)
                        img.Set(i, j, color.RGBA{r, g, b, 255})
                    }
                }
            }
        }()
    }
    
    // 分发任务
    for startX := bounds.Min.X; startX < width; startX += sizeX {
        stopX := startX + sizeX
        if stopX > width {
            stopX = width
        }
        for startY := bounds.Min.Y; startY < height; startY += sizeY {
            stopY := startY + sizeY
            if stopY > height {
                stopY = height
            }
            jobs <- struct {
                startX, stopX, startY, stopY int
            }{startX, stopX, startY, stopY}
        }
    }
    close(jobs)
    
    wg.Wait()
    return img
}

匿名函数问题分析

plottingSmallSquares作为匿名函数使用时出现垂直线条的原因是循环变量捕获问题。在Go中,循环变量在迭代间共享,当在goroutine中使用时会产生竞态条件。

错误用法示例:

// 错误:所有goroutine共享相同的循环变量值
for startX, stopX := bounds.Min.X, startX+sizeX; startX < width; startX, stopX = stopX, startX+sizeX {
    for startY, stopY := bounds.Min.Y, startY+sizeY; startY < height; startY, stopY = stopY, startY+sizeY {
        wg.Add(1)
        go func() {
            plottingSmallSquares(f, n, img, startX, stopX, startY, stopY, s, wg)
            wg.Done()
        }()
    }
}

正确解决方案是传递参数副本:

// 正确:创建局部变量副本
for startX, stopX := bounds.Min.X, startX+sizeX; startX < width; startX, stopX = stopX, startX+sizeX {
    for startY, stopY := bounds.Min.Y, startY+sizeY; startY < height; startY, stopY = stopY, startY+sizeY {
        wg.Add(1)
        go func(sX, sXEnd, sY, sYEnd int) {
            plottingSmallSquares(f, n, img, sX, sXEnd, sY, sYEnd, s, wg)
        }(startX, stopX, startY, stopY)
    }
}

这些优化将显著提升性能并解决垂直线条问题。工作池模式减少了goroutine创建开销,而正确的变量传递消除了竞态条件。

回到顶部