189 lines
5.5 KiB
Go
189 lines
5.5 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||
// SPDX-License-Identifier: MIT
|
||
|
||
package core
|
||
|
||
import "testing"
|
||
|
||
// Dtype and split companions to the matmul benchmarks: bench_test.go,
|
||
// mat_tile_bench_test.go and mat_kernel_bench_test.go pin the float64
|
||
// shapes, so these pin the complex 2-D product, the float32, int and
|
||
// complex vector walks, and the vector split's per-worker floor. The
|
||
// operands are fixed literals, so a run is comparable to the next.
|
||
|
||
// benchMatMulComplexRect runs the n×k·k×m complex product under the
|
||
// default worker policy.
|
||
func benchMatMulComplexRect(b *testing.B, n, k, m int) {
|
||
b.Helper()
|
||
av := make([]complex128, n*k)
|
||
bv := make([]complex128, k*m)
|
||
for i := range av {
|
||
av[i] = complex(float64(i%7)-3, float64(i%5)-2)
|
||
}
|
||
for i := range bv {
|
||
bv[i] = complex(float64(i%5)-2, float64(i%11)-5)
|
||
}
|
||
a, err := FromComplexes(av, n, k)
|
||
if err != nil {
|
||
b.Fatal(err)
|
||
}
|
||
c, err := FromComplexes(bv, k, m)
|
||
if err != nil {
|
||
b.Fatal(err)
|
||
}
|
||
b.ReportAllocs()
|
||
for b.Loop() {
|
||
if _, err := MatMul2D(a, c); err != nil {
|
||
b.Fatal(err)
|
||
}
|
||
}
|
||
}
|
||
|
||
// BenchmarkMatMulComplex256 is the smallest complex square the split
|
||
// engages, one size above matMulComplexParallelMin.
|
||
func BenchmarkMatMulComplex256(b *testing.B) { benchMatMulComplexRect(b, 256, 256, 256) }
|
||
|
||
// BenchmarkMatMulComplex512 is the complex square product: its b
|
||
// payload is sixteen bytes per element, so sharing one b row across a
|
||
// panel carries the most weight here.
|
||
func BenchmarkMatMulComplex512(b *testing.B) { benchMatMulComplexRect(b, 512, 512, 512) }
|
||
|
||
// The complex rectangular extremes, mirroring the float64 shape set.
|
||
func BenchmarkMatMulComplexFat64x512x2048(b *testing.B) {
|
||
benchMatMulComplexRect(b, 64, 512, 2048)
|
||
}
|
||
|
||
func BenchmarkMatMulComplexTall512x64x2048(b *testing.B) {
|
||
benchMatMulComplexRect(b, 512, 64, 2048)
|
||
}
|
||
|
||
func BenchmarkMatMulComplexWide2048x64x512(b *testing.B) {
|
||
benchMatMulComplexRect(b, 2048, 64, 512)
|
||
}
|
||
|
||
func BenchmarkMatMulComplexSkinny2048x512x64(b *testing.B) {
|
||
benchMatMulComplexRect(b, 2048, 512, 64)
|
||
}
|
||
|
||
// benchMatVecTyped runs the 2-D×1-D (swap true) or 1-D×2-D (swap
|
||
// false) product of a typed pair built from fixed literals, under the
|
||
// default worker policy. The vector carries the inner dimension: the
|
||
// matrix's column count for a row-dot product, its row count for a
|
||
// column-dot one.
|
||
func benchMatVecTyped(b *testing.B, dt Dtype, rows, cols int, swap bool) {
|
||
b.Helper()
|
||
n := rows * cols
|
||
flat := make([]float64, n)
|
||
for i := range flat {
|
||
flat[i] = float64(i%7) - 3
|
||
}
|
||
vlen := cols
|
||
if !swap {
|
||
vlen = rows
|
||
}
|
||
vecf := make([]float64, vlen)
|
||
for i := range vecf {
|
||
vecf[i] = float64(i%5) - 2
|
||
}
|
||
var mat, vec *Array
|
||
var err error
|
||
switch dt {
|
||
case Float32:
|
||
f32 := make([]float32, n)
|
||
for i, v := range flat {
|
||
f32[i] = float32(v)
|
||
}
|
||
v32 := make([]float32, vlen)
|
||
for i, v := range vecf {
|
||
v32[i] = float32(v)
|
||
}
|
||
mat, err = FromFloat32s(f32, rows, cols)
|
||
if err == nil {
|
||
vec, err = FromFloat32s(v32, vlen)
|
||
}
|
||
case Int:
|
||
iv := make([]int64, n)
|
||
for i, v := range flat {
|
||
iv[i] = int64(v)
|
||
}
|
||
vv := make([]int64, vlen)
|
||
for i, v := range vecf {
|
||
vv[i] = int64(v)
|
||
}
|
||
mat, err = FromInts(iv, rows, cols)
|
||
if err == nil {
|
||
vec, err = FromInts(vv, vlen)
|
||
}
|
||
default:
|
||
cv := make([]complex128, n)
|
||
for i, v := range flat {
|
||
cv[i] = complex(v, float64(i%5)-2)
|
||
}
|
||
cvv := make([]complex128, vlen)
|
||
for i, v := range vecf {
|
||
cvv[i] = complex(v, float64(i%3)-1)
|
||
}
|
||
mat, err = FromComplexes(cv, rows, cols)
|
||
if err == nil {
|
||
vec, err = FromComplexes(cvv, vlen)
|
||
}
|
||
}
|
||
if err != nil {
|
||
b.Fatal(err)
|
||
}
|
||
b.ReportAllocs()
|
||
for b.Loop() {
|
||
var err error
|
||
if swap {
|
||
_, err = MatMul2D(mat, vec)
|
||
} else {
|
||
_, err = MatMul2D(vec, mat)
|
||
}
|
||
if err != nil {
|
||
b.Fatal(err)
|
||
}
|
||
}
|
||
}
|
||
|
||
// BenchmarkMatVecF32RowDots512x512 pins the float32 row dots, whose
|
||
// four-row walk is the float64 twin's structure.
|
||
func BenchmarkMatVecF32RowDots512x512(b *testing.B) {
|
||
benchMatVecTyped(b, Float32, 512, 512, true)
|
||
}
|
||
|
||
// BenchmarkMatVecIntRowDots512x512 pins the int row dots.
|
||
func BenchmarkMatVecIntRowDots512x512(b *testing.B) { benchMatVecTyped(b, Int, 512, 512, true) }
|
||
|
||
// BenchmarkMatVecComplexRowDots256x256 pins the complex row dots, whose
|
||
// two-row walk is what a sixteen-byte sum chain can hold in registers.
|
||
func BenchmarkMatVecComplexRowDots256x256(b *testing.B) {
|
||
benchMatVecTyped(b, Complex, 256, 256, true)
|
||
}
|
||
|
||
// BenchmarkVecMatF32ColDots128x2048 pins the float32 column dots.
|
||
func BenchmarkVecMatF32ColDots128x2048(b *testing.B) {
|
||
benchMatVecTyped(b, Float32, 128, 2048, false)
|
||
}
|
||
|
||
// BenchmarkVecMatComplexColDots128x2048 pins the complex column dots.
|
||
func BenchmarkVecMatComplexColDots128x2048(b *testing.B) {
|
||
benchMatVecTyped(b, Complex, 128, 2048, false)
|
||
}
|
||
|
||
// BenchmarkMatVecRowDots128x128 sits just above the vector split's
|
||
// per-worker floor: 16,384 terms fill at most one worker, so the shape
|
||
// measures the lone walk the floor keeps it on.
|
||
func BenchmarkMatVecRowDots128x128(b *testing.B) { benchMatVecTyped(b, Float, 128, 128, true) }
|
||
|
||
// BenchmarkVecMatColDots16x16384 pins the wide column dots: sixteen
|
||
// rows by 16,384 output columns, so each worker's band is far wider
|
||
// than a cache line.
|
||
func BenchmarkVecMatColDots16x16384(b *testing.B) { benchMatVecTyped(b, Float, 16, 16384, false) }
|
||
|
||
// BenchmarkVecMatColNarrowDots16384x16 is the thin-band shape: sixteen
|
||
// output columns of 16,384 terms, where the split hands a worker a band
|
||
// no wider than a cache line.
|
||
func BenchmarkVecMatColNarrowDots16384x16(b *testing.B) {
|
||
benchMatVecTyped(b, Float, 16384, 16, false)
|
||
}
|