190 lines
5.0 KiB
Go
190 lines
5.0 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||
// SPDX-License-Identifier: MIT
|
||
|
||
package base
|
||
|
||
import (
|
||
"fmt"
|
||
"sync"
|
||
"testing"
|
||
"time"
|
||
|
||
"sourcedock.dev/petrbalvin/tensor/internal/engine"
|
||
)
|
||
|
||
// Paired crossover probe for Factor's dispatch: the serial elimination
|
||
// against the shipped crew constants on the same deterministic matrix,
|
||
// interleaved iteration by iteration so a drift in the machine's speed
|
||
// lands on both styles equally. The reported ns/op per style is the
|
||
// paired comparison; the ns/op of the group as a whole is not
|
||
// comparable across groups. The spawn floor factorSpawnFloor sits where
|
||
// the crew starts winning.
|
||
|
||
// probeFactorRows builds the deterministic n×n fixture: a diagonally
|
||
// dominant integer pattern every style factors identically.
|
||
func probeFactorRows(n int) (rows [][]float64, pristine []float64) {
|
||
flat := make([]float64, n*n)
|
||
for i := range n {
|
||
for j := range n {
|
||
flat[i*n+j] = float64((i*7+j*13)%11) - 5
|
||
}
|
||
flat[i*n+i] += float64(n)
|
||
}
|
||
rows = make([][]float64, n)
|
||
for i := range n {
|
||
rows[i] = flat[i*n : (i+1)*n]
|
||
}
|
||
pristine = make([]float64, len(flat))
|
||
copy(pristine, flat)
|
||
return rows, pristine
|
||
}
|
||
|
||
func probeFactorReset(rows [][]float64, pristine []float64) {
|
||
off := 0
|
||
for _, row := range rows {
|
||
copy(row, pristine[off:off+len(row)])
|
||
off += len(row)
|
||
}
|
||
}
|
||
|
||
// probeFactorSerial is Factor with the dispatch removed: the reference
|
||
// every candidate must match byte for byte.
|
||
func probeFactorSerial(m [][]float64) {
|
||
n := len(m)
|
||
for k := range n {
|
||
pivot := k
|
||
for i := k + 1; i < n; i++ {
|
||
if absOf(m[i][k]) > absOf(m[pivot][k]) {
|
||
pivot = i
|
||
}
|
||
}
|
||
if m[pivot][k] == 0 {
|
||
continue
|
||
}
|
||
if pivot != k {
|
||
m[pivot], m[k] = m[k], m[pivot]
|
||
}
|
||
factorRows(m[k+1:], k, n, m[k])
|
||
}
|
||
}
|
||
|
||
// probeFactorJob mirrors factorJob for the probe's own dispatch, so the
|
||
// probe keeps compiling whatever the production type later gains.
|
||
type probeFactorJob struct {
|
||
rows [][]float64
|
||
pivotRow []float64
|
||
k, n int
|
||
start int
|
||
end int
|
||
wg *sync.WaitGroup
|
||
}
|
||
|
||
func probeFactorWorker(job *probeFactorJob) {
|
||
factorRows(job.rows[job.start:job.end], job.k, job.n, job.pivotRow)
|
||
job.wg.Done()
|
||
}
|
||
|
||
// probeFactorCrew mirrors Factor's dispatch at the production constants,
|
||
// which it reads directly: the quantum sizes the crew, the spawn floor
|
||
// decides whether any crew runs, and the per-row update is the same
|
||
// factorRows call the serial reference makes.
|
||
func probeFactorCrew(m [][]float64) {
|
||
n := len(m)
|
||
var wg sync.WaitGroup
|
||
var jobs []probeFactorJob
|
||
for k := range n {
|
||
pivot := k
|
||
for i := k + 1; i < n; i++ {
|
||
if absOf(m[i][k]) > absOf(m[pivot][k]) {
|
||
pivot = i
|
||
}
|
||
}
|
||
if m[pivot][k] == 0 {
|
||
continue
|
||
}
|
||
if pivot != k {
|
||
m[pivot], m[k] = m[k], m[pivot]
|
||
}
|
||
pivotRow := m[k]
|
||
rows := m[k+1:]
|
||
work := len(rows) * (n - k)
|
||
w := min(work/factorWorkQuantum+1, engine.WorkersFor(len(rows)))
|
||
w = min(w, factorMaxCrew)
|
||
if work < factorSpawnFloor {
|
||
w = 1
|
||
}
|
||
if w < 2 {
|
||
factorRows(rows, k, n, pivotRow)
|
||
continue
|
||
}
|
||
chunk := (len(rows) + w - 1) / w
|
||
if chunk < factorMinRows {
|
||
w = max(len(rows)/factorMinRows, 1)
|
||
chunk = (len(rows) + w - 1) / w
|
||
if w < 2 {
|
||
factorRows(rows, k, n, pivotRow)
|
||
continue
|
||
}
|
||
}
|
||
if jobs == nil {
|
||
jobs = make([]probeFactorJob, factorMaxCrew)
|
||
}
|
||
spawned := 0
|
||
for start := 0; start < len(rows); start += chunk {
|
||
job := &jobs[spawned]
|
||
job.rows, job.pivotRow, job.k, job.n = rows, pivotRow, k, n
|
||
job.start, job.end, job.wg = start, min(start+chunk, len(rows)), &wg
|
||
spawned++
|
||
}
|
||
wg.Add(spawned)
|
||
for i := range spawned {
|
||
go probeFactorWorker(&jobs[i])
|
||
}
|
||
wg.Wait()
|
||
}
|
||
}
|
||
|
||
// TestFactorSpawnFloorBitIdentical pins the crew mirror against the
|
||
// serial reference at the sizes the crossover probe sweeps, whatever
|
||
// the dispatch decides: crew size never moves a bit.
|
||
func TestFactorSpawnFloorBitIdentical(t *testing.T) {
|
||
for _, n := range []int{2, 3, 16, 64, 256, 320, 384} {
|
||
want, _ := probeFactorRows(n)
|
||
probeFactorSerial(want)
|
||
got, _ := probeFactorRows(n)
|
||
probeFactorCrew(got)
|
||
for i := range n {
|
||
for j := range n {
|
||
if got[i][j] != want[i][j] {
|
||
t.Fatalf("n=%d factor[%d][%d] = %v, want %v", n, i, j, got[i][j], want[i][j])
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// BenchmarkFactorCrossoverAB interleaves the serial elimination with the
|
||
// shipped dispatch iteration by iteration across the sizes around the
|
||
// spawn floor.
|
||
func BenchmarkFactorCrossoverAB(b *testing.B) {
|
||
for _, n := range []int{256, 320, 384, 448, 512} {
|
||
b.Run(fmt.Sprintf("n=%d", n), func(b *testing.B) {
|
||
rowsS, prS := probeFactorRows(n)
|
||
rowsC, prC := probeFactorRows(n)
|
||
var tSerial, tCrew time.Duration
|
||
for b.Loop() {
|
||
probeFactorReset(rowsS, prS)
|
||
probeFactorReset(rowsC, prC)
|
||
start := time.Now()
|
||
probeFactorSerial(rowsS)
|
||
tSerial += time.Since(start)
|
||
start = time.Now()
|
||
probeFactorCrew(rowsC)
|
||
tCrew += time.Since(start)
|
||
}
|
||
b.ReportMetric(float64(tSerial.Nanoseconds())/float64(b.N), "ns/op-serial")
|
||
b.ReportMetric(float64(tCrew.Nanoseconds())/float64(b.N), "ns/op-crew")
|
||
})
|
||
}
|
||
}
|