Tooling
This commit is contained in:
@@ -176,26 +176,111 @@ func (f *Field) Blur(passes int) *Field {
|
||||
// starts and each writes only into its own, so the output is identical at any GOMAXPROCS. Every parallel
|
||||
// loop in the generator goes through here; none spawns goroutines of its own.
|
||||
func Rows(h int, fn func(y0, y1 int)) {
|
||||
workers := runtime.GOMAXPROCS(0)
|
||||
if workers > h {
|
||||
workers = h
|
||||
}
|
||||
if workers <= 1 {
|
||||
fn(0, h)
|
||||
RowsIndexed(h, func(_, y0, y1 int) { fn(y0, y1) })
|
||||
}
|
||||
|
||||
// RowsIndexed is Rows with the band number, which is what a parallel loop needs when it has to reduce
|
||||
// something rather than only write into its own rows: it gives each goroutine a pre-allocated indexed
|
||||
// slot to accumulate into, so the reduction can be replayed in band order afterwards instead of
|
||||
// depending on which goroutine finished first. Size the slots with BandCount.
|
||||
func RowsIndexed(h int, fn func(band, y0, y1 int)) {
|
||||
step := rowStep(h)
|
||||
if step >= h {
|
||||
fn(0, 0, h)
|
||||
return
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
step := (h + workers - 1) / workers
|
||||
band := 0
|
||||
for y0 := 0; y0 < h; y0 += step {
|
||||
y1 := y0 + step
|
||||
if y1 > h {
|
||||
y1 = h
|
||||
}
|
||||
wg.Add(1)
|
||||
go func(a, b int) {
|
||||
go func(k, a, b int) {
|
||||
defer wg.Done()
|
||||
fn(a, b)
|
||||
}(y0, y1)
|
||||
fn(k, a, b)
|
||||
}(band, y0, y1)
|
||||
band++
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// FixedBands is RowsIndexed with a partition that does not depend on the core count: bands of exactly rows
|
||||
// rows, run by however many workers there are.
|
||||
//
|
||||
// It exists for one reason. A parallel loop that only writes into its own rows can be partitioned any way at
|
||||
// all, which is what Rows does. A loop that *reduces* into overlapping buffers cannot: floating-point addition
|
||||
// is not associative, so summing a cell's contributions in a different grouping gives a different last bit,
|
||||
// and the result would depend on GOMAXPROCS. The particle pass is that loop. Fix the partition and the
|
||||
// arithmetic is fixed with it.
|
||||
func FixedBands(h, rows int, fn func(band, y0, y1 int)) {
|
||||
if rows < 1 {
|
||||
rows = 1
|
||||
}
|
||||
n := FixedBandCount(h, rows)
|
||||
workers := runtime.GOMAXPROCS(0)
|
||||
if workers > n {
|
||||
workers = n
|
||||
}
|
||||
if workers <= 1 {
|
||||
for b := 0; b < n; b++ {
|
||||
y0 := b * rows
|
||||
y1 := min(y0+rows, h)
|
||||
fn(b, y0, y1)
|
||||
}
|
||||
return
|
||||
}
|
||||
next := make(chan int)
|
||||
go func() {
|
||||
for b := 0; b < n; b++ {
|
||||
next <- b
|
||||
}
|
||||
close(next)
|
||||
}()
|
||||
var wg sync.WaitGroup
|
||||
for w := 0; w < workers; w++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for b := range next {
|
||||
y0 := b * rows
|
||||
y1 := min(y0+rows, h)
|
||||
fn(b, y0, y1)
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// FixedBandCount is how many bands FixedBands will make.
|
||||
func FixedBandCount(h, rows int) int {
|
||||
if rows < 1 {
|
||||
rows = 1
|
||||
}
|
||||
if h <= 0 {
|
||||
return 0
|
||||
}
|
||||
return (h + rows - 1) / rows
|
||||
}
|
||||
|
||||
// BandCount is how many ranges Rows and RowsIndexed split h into. It is fixed by h and GOMAXPROCS, so it
|
||||
// can be called to size a reduction before the loop starts.
|
||||
func BandCount(h int) int {
|
||||
step := rowStep(h)
|
||||
if step >= h {
|
||||
return 1
|
||||
}
|
||||
return (h + step - 1) / step
|
||||
}
|
||||
|
||||
func rowStep(h int) int {
|
||||
workers := runtime.GOMAXPROCS(0)
|
||||
if workers > h {
|
||||
workers = h
|
||||
}
|
||||
if workers <= 1 {
|
||||
return h
|
||||
}
|
||||
return (h + workers - 1) / workers
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user