This commit is contained in:
Rainer Leit
2026-09-25 17:02:24 +03:00
parent cc43ed8dc8
commit 9597629951
2149 changed files with 460234 additions and 1770 deletions
+95 -10
View File
@@ -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
}