简体   繁体   English

使用图像 package 并发的奇怪异常行为

[英]Strange anomalous behavior using concurrency with image package

The program im trying to get working is a generator for images of 1D cellular automate and it needs to be robust enough to handle extremely large simulations on orders of several millions of individual cells so multi-threading the image generation process is necessary.我试图开始工作的程序是一维细胞自动图像的生成器,它需要足够强大以处理数百万个单个单元的订单的超大型模拟,因此多线程图像生成过程是必要的。 I chose Go for this reason because go-routines were going to make the issue of dividing work for the CPU much easier and efficient.出于这个原因,我选择了 Go,因为 go-routines 将使 CPU 的工作分配问题变得更加容易和高效。 Now because writing each cell with a individual go-routine would not be very performant at all i decided to create a function that calls the image object and is responsible for generating an entire row of cells instead.现在因为用单独的 go-routine 编写每个单元格根本不会非常高效,我决定创建一个 function 调用图像 object 并负责生成一整行单元格。 This function is referencing a 2D array object containing a bitsliced ( see this ) array of all the cells to be drawn hence the many loops however this is not important to the issue at hand.这个 function 引用了一个 2D 数组 object ,其中包含要绘制的所有单元格的位切片( 请参阅此)数组,因此有许多循环,但这对于手头的问题并不重要。 What the program is supposed to do is simply read all the individual bits and write a square to the image rectangle in the correct position denoting the presence of a cell (based on the variable pSize noting the side length of the square).程序应该做的是简单地读取所有单独的位并将一个正方形写入正确的 position 中的图像矩形,表示存在一个单元格(基于变量 pSize 指出正方形的边长)。 Here is that function...这是function...

func renderRow(wg *sync.WaitGroup, img *image.RGBA, i int, pSize int) {
    defer wg.Done()
    var lpc = 0
    for j := 0; j < 64; j++ {
        for k := range sim[i] {
            for l := lpc * pSize; l <= (lpc*pSize)+pSize; l++ {
                for m := i * pSize; m <= (i*pSize)+pSize; m++ {
                    if getBit(sim[i][k], j) == 1 {
                        img.Set(l, m, black)
                    } else {
                        img.Set(l, m, white)
                    }
                }
            }
            lpc++
        }
    }
}

Now im happy to say that this function here performs just as expected when run sequentially on one thread.现在我很高兴地说,这个 function 在一个线程上按顺序运行时性能与预期一样。 Here is the non parallel function call (ignoring the waitgroup)这是非并行 function 调用(忽略等待组)

img = image.NewRGBA(image.Rectangle{Min: upLeft, Max: lowRight})

for i := range sim {
    renderRow(&wg, img, i, pSize)
}

f, _ := os.Create("export/image.png")
_ = png.Encode(f, img)

Now on the other hand when we make the simple change to a concurrent implementation the output has several individual pixel errors and seems to shrink and extend certain rows randomly as the amount of errors changes with each run.另一方面,当我们对并发实现进行简单更改时,output 有几个单独的像素错误,并且随着每次运行的错误数量的变化,似乎会随机收缩和扩展某些行。 Here's the concurrent function call.这是并发的 function 调用。 Here's the concurrent function call...这是并发的 function 调用...

img = image.NewRGBA(image.Rectangle{Min: upLeft, Max: lowRight})

for i := range sim {
    go renderRow(&wg, img, i, pSize) // TODO make multithreaded again
}

wg.Wait()

f, _ := os.Create("export/image.png")
_ = png.Encode(f, img)

Now what does the output look like for these two respective implementations?现在,对于这两个各自的实现,output 看起来像什么? using these starting conditions {0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1} and a evolution space of 11 (pSize 2 ).使用这些起始条件{0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1}和进化空间11 (pSize 2 )。 We get this as output from the single thread implementation...我们从单线程实现中得到 output ......

非并发版本

Now if you zoom in on that image you'll find all the squares all evenly spaced vertically and horizontally with no anomalies.现在,如果您放大该图像,您会发现所有正方形都垂直和水平均匀分布,没有异常。 However now lets take a look at the concurrent output.不过现在让我们看看并发的 output。

在此处输入图像描述

This version seems to have several anomalies many rows have been shrunk there are individual pixel errors in many places and although it follows the general pattern of the simulation correctly it is most certainly not visually pleasing.这个版本似乎有几个异常,很多行都被缩小了,很多地方都有个别像素错误,虽然它正确地遵循了模拟的一般模式,但它肯定在视觉上并不令人愉悦。 While i was investigating this issue i looked for issues related to concurrency and so i thought that perhaps a dynamic allocation of the pixel array in the image package might be causing conflicts of some sort and so i investigated img.Set() which looks like this...当我在调查这个问题时,我寻找与并发相关的问题,所以我认为图像 package 中像素数组的动态分配可能会导致某种冲突,所以我调查了看起来像这样的img.Set() ...

func (p *NRGBA) Set(x, y int, c color.Color) {
    if !(Point{x, y}.In(p.Rect)) {
        return
    }
    i := p.PixOffset(x, y)
    c1 := color.NRGBAModel.Convert(c).(color.NRGBA)
    s := p.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
    s[0] = c1.R
    s[1] = c1.G
    s[2] = c1.B
    s[3] = c1.A
}

However when i look at this it seems to make no sense.但是,当我看这个时,它似乎没有任何意义。 As it appears that img.Pix element is storing all the pixel data in a sequential 1D array of integers representing colors but the .Set() function immediately returns if the (x,y) elements passed to it are already found in the.Pix slice.看起来img.Pix元素将所有像素数据存储在表示 colors 的顺序一维整数数组中,但如果传递给它的 (x,y .Set()元素已经在 .Pix片。 But whats even more strange is what appears to be some sort of implicit assignment (which iv'e never seen in Go) where 4 elements of the.Pix slice are taken out representing an individual pixel's color and assigned to s .但更奇怪的是,这似乎是某种隐式赋值(在 Go 中从未见过),其中 .Pix 切片的 4 个元素被取出来表示单个像素的颜色并分配给s And the strangest part being that s , c1 and i are never referenced again, returned, or stored in memory simply thrown to garbage collection.最奇怪的部分是sc1i再也不会被引用、返回或存储在 memory 中,只是被扔到垃圾收集中。 But somehow this function appears to work sequentially so i just decided to let it do its thing and take a look at what the differences were in the .Pix slice between the concurrent and non concurrent implementations.但不知何故,这个 function 似乎按顺序工作,所以我决定让它做它的事情,看看并发和非并发实现之间的.Pix切片有什么区别。

Now here's the links to four paste bins, they contain the img.Pix objects data for 2 separate trials arranged with each row belonging to an individual pixel's colors starting from the top left of each image and moving down.现在这里是四个粘贴箱的链接,它们包含两个独立试验的img.Pix对象数据,每一行都属于单个像素的 colors,从每个图像的左上角开始向下移动。 The reason for two trials is to verify consistency for the single threaded approach which appears to be consistent but as you can observe by going to a website like diffchecker.com is that both the multi threaded tests show differences between them and the single threaded output.进行两次试验的原因是为了验证单线程方法的一致性,这似乎是一致的,但您可以通过访问diffchecker.com之类的网站观察到,多线程测试显示它们与单线程 output 之间存在差异。

Multithreaded Test 1多线程测试 1

Single-threaded Test 1单线程测试1

Multithreaded Test 2多线程测试 2

Single-threaded Test 2单线程测试2

Now here I'll share some observations about this data.现在在这里我将分享一些关于这些数据的观察。

  • There are differences and different quantities of differences between the different multi-threaded and the single-threaded tests不同的多线程和单线程测试之间存在差异和不同数量的差异
  • there are identical quantities of additions and deletions between single thread and multithread implying that all the data is present and that its simply in the wrong order.在单线程和多线程之间存在相同数量的添加和删除,这意味着所有数据都存在并且只是顺序错误。

Now these observations may imply that as we call the Set function threads are colliding with each other on certain indices in the Pix array but from looking at the set function every single pixel is supposed to have a distinct place in the array which is preallocated based on the length and width of the provided rectangle which should make ordering absolute and collisions impossible between threads.现在,这些观察结果可能意味着,正如我们所说的 Set function 线程在 Pix 数组中的某些索引上相互冲突,但是从查看集合 function 来看,每个像素都应该在数组中具有不同的位置,该位置是基于预先分配的提供的矩形的长度和宽度,这应该使线程之间的绝对排序和冲突不可能。 Heres the function thats responsible for creating the image object...这是负责创建映像 object 的 function ...

// NewRGBA returns a new RGBA image with the given bounds.
func NewRGBA(r Rectangle) *RGBA {
    return &RGBA{
        Pix:    make([]uint8, pixelBufferLength(4, r, "RGBA")),
        Stride: 4 * r.Dx(),
        Rect:   r,
    }
}

So all in all I really have no idea whats going on.所以总而言之,我真的不知道发生了什么。 There seems to be some weird behaviors arising from the image package as multiple go-routines access the same slice but since the indices of the slice are theoretically absolute (meaning unique for each variable) there shouldn't be any ordering issues.图像 package 似乎出现了一些奇怪的行为,因为多个 go-routines 访问同一个切片,但由于切片的索引在理论上是绝对的(意味着每个变量都是唯一的),所以不应该有任何排序问题。 The only possible issue i could think of is that the slice despite being defined in the manner it was is somehow being resized by that set function or at least shifted around causing collisions.我能想到的唯一可能的问题是,尽管切片是以某种方式定义的,但它在某种程度上被该集合 function 调整大小或至少四处移动导致碰撞。 Any help figuring out whats going wrong or any theories about what might be causing the problem are greatly appreciated.非常感谢任何帮助找出问题所在或有关可能导致问题的任何理论。 Cheers!干杯!

The code above produces many race conflicts arising from go-routines attempting to write to the same pixel coordinate in the.Pix object.上面的代码产生了许多竞争冲突,原因是 go-routines 试图写入 .Pix object 中的相同像素坐标。 The fix was within the renderRow function where the calculations for the width and height of the current pixel were overlapping on each iteration due to <= instead of '<'.修复在renderRow function 中,由于<=而不是“<”,当前像素的宽度和高度的计算在每次迭代中都会重叠。 Moral of the story is use -race to look for collisions and always look for overwrites or concurrent reads of the same variable.故事的寓意是使用-race来查找冲突并始终查找相同变量的覆盖或并发读取。 Credit to @rustyx.感谢@rustyx。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM