Documentation
¶
Overview ¶
Package simd provides SIMD-accelerated operations over slices, on every architecture that has a vector unit, without cgo.
You call ordinary Go functions on ordinary Go slices. There is no vector type, no lane count, no target selection, and nothing to initialize.
In place by default ¶
The plain name operates in place on its first argument, which allocates nothing and reuses memory you already have:
simd.Add(a, b) // a[i] += b[i] simd.Scale(a, 2.5) // a[i] *= 2.5 simd.Abs(a) // a[i] = |a[i]|
When the result belongs somewhere else, the same name with an Into suffix takes a destination first:
simd.AddInto(dst, a, b) // dst[i] = a[i] + b[i] simd.ScaleInto(dst, a, 2.5)
Reductions return a single value and write nothing:
total := simd.Sum(a) d := simd.Dot(a, b) lo, hi := simd.MinMax(a)
This package never allocates. Every function writes only into memory the caller supplied, and no variant returns a freshly made slice. If you want one, make it yourself and use the Into form.
The functions are generic over float32, float64, int32 and int64, so the element type is inferred and there are no per-type name suffixes.
The right instructions are chosen for you ¶
The instruction set is selected once, at process start, from the CPU the program is actually running on. A binary built on a laptop with AVX-512 runs correctly on a server without it, using AVX2 or SSE2 instead; on an architecture with no backend it uses portable Go. Nothing to configure, nothing to build twice.
Results do not depend on the hardware ¶
Every operation returns bit-identical results on every instruction set, including for NaN, ±Inf, ±0 and denormals. Reductions such as Sum and Dot use a fixed accumulation order that a 128-bit and a 512-bit machine both reproduce exactly, so a computation cannot change answer because it moved to a different server. Operations that trade this away for speed are named Fast* and say so.
This costs some throughput and is deliberate: the alternative is a library where results shift with slice length or CPU model, which is the most common bug in this class of package.
Sizing ¶
Lengths need not match. Every operation processes the minimum length of its slice arguments, so slicing is how you bound work:
simd.AddInto(dst[:n], a, b)
Small inputs use an inlined scalar path rather than a call into assembly, because below roughly sixteen elements the call costs more than the arithmetic saves. You do not need to check for this.
Environment ¶
GOSIMD names an instruction-set tier to use instead of the detected one, for benchmarking and debugging: GOSIMD=sse2, GOSIMD=avx2, GOSIMD=scalar. It can only select down; naming a tier the CPU lacks falls back to portable Go rather than crashing.
SIMD_DISABLE masks tiers out of consideration, as a comma-separated list: SIMD_DISABLE=avx512. Useful on CPUs where wide vectors cause frequency throttling.
Tier and Describe report what was selected.
Example ¶
The plain name works in place and allocates nothing.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
a := []float32{1, 2, 3, 4}
b := []float32{10, 20, 30, 40}
simd.Add(a, b) // a += b
fmt.Println("a += b:", a)
simd.Scale(a, 0.5) // a *= 0.5
fmt.Println("a *= .5:", a)
fmt.Println("sum:", simd.Sum(a))
fmt.Println("max:", simd.Max(a))
}
Output: a += b: [11 22 33 44] a *= .5: [5.5 11 16.5 22] sum: 55 max: 22
Example (Bytes) ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
b := []byte("hello world")
fmt.Println(simd.IndexByte(b, 'w'))
fmt.Println(simd.CountByte(b, 'l'))
fmt.Println(simd.Equal(b, []byte("hello world")))
data := []byte{0xab, 0xcd, 0xef}
mask := []byte{0x0f, 0x0f, 0x0f}
simd.And(data, mask) // in place
fmt.Printf("%x\n", data)
}
Output: 6 3 true 0b0d0f
Example (FeatureScaling) ¶
Scaling features into [0,1] — the pass in front of most models, and three calls with no temporary.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
raw := []float32{-5, 0, 12, 7, 30}
lo, hi := simd.MinMax(raw)
simd.AddScalar(raw, -lo) // shift the minimum to zero
simd.Scale(raw, 1/(hi-lo)) // and the maximum to one
simd.Clamp(raw, 0, 1) // rounding at the ends cannot escape
for _, v := range raw {
fmt.Printf("%.2f ", v)
}
fmt.Println()
}
Output: 0.00 0.14 0.49 0.34 1.00
Example (Fused) ¶
AddScaled is AXPY: one pass over memory instead of two.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
a := []float32{1, 2, 3, 4}
b := []float32{10, 10, 10, 10}
simd.AddScaled(a, b, 0.5) // a += b * 0.5
fmt.Println(a)
}
Output: [6 7 8 9]
Example (Into) ¶
Use the Into form when the result belongs somewhere else.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
a := []float64{1, 2, 3, 4}
b := []float64{10, 20, 30, 40}
dst := make([]float64, 4)
simd.AddInto(dst, a, b)
fmt.Println(dst)
fmt.Println(a, "unchanged")
}
Output: [11 22 33 44] [1 2 3 4] unchanged
Example (Scenarios) ¶
Whole tasks are one call and reuse the same accelerated primitives.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
a := []float64{2, 4, 4, 4, 5, 5, 7, 9}
fmt.Printf("mean %.2f\n", simd.Mean(a))
fmt.Printf("stddev %.2f\n", simd.StdDev(a))
x := []float64{1, 0}
y := []float64{0, 1}
fmt.Printf("cosine %.2f\n", simd.CosineSimilarity(x, y))
fmt.Printf("dist %.4f\n", simd.Distance(x, y))
v := []float64{3, 4}
simd.Normalize(v)
fmt.Println("unit ", v)
}
Output: mean 5.00 stddev 2.00 cosine 0.00 dist 1.4142 unit [0.6 0.8]
Example (Sizing) ¶
Lengths need not match; slicing bounds the work.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
a := []float64{1, 2, 3, 4, 5, 6, 7, 8}
b := []float64{1, 1, 1, 1, 1, 1, 1, 1}
simd.Add(a[:3], b) // only the first three
fmt.Println(a)
}
Output: [2 3 4 4 5 6 7 8]
Example (Types) ¶
The element type is inferred; there are no per-type function names.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
i64 := []int64{1 << 40, 2 << 40}
simd.Add(i64, i64)
fmt.Println(i64)
f64 := []float64{0.5, 0.25, 0.125}
fmt.Println(simd.Sum(f64))
i32 := []int32{-3, 7, -1}
simd.Abs(i32)
fmt.Println(i32)
}
Output: [2199023255552 4398046511104] 0.875 [3 7 1]
Index ¶
- Constants
- func Abs[T Number](a []T)
- func AbsComplexInto[R Float, C Complex](dst []R, a []C)
- func AbsInto[T Number](dst, a []T)
- func Acos[T Float](a []T)
- func AcosInto[T Float](dst, a []T)
- func Acosh[T Float](a []T)
- func AcoshInto[T Float](dst, a []T)
- func Add[T Number](a, b []T)
- func AddAll[T Number](dst []T, srcs ...[]T)
- func AddComplex[C Complex](a, b []C)
- func AddComplexInto[C Complex](dst, a, b []C)
- func AddInto[T Number](dst, a, b []T)
- func AddScalar[T Number](a []T, s T)
- func AddScalarInto[T Number](dst, a []T, s T)
- func AddScaled[T Number](a, b []T, s T)
- func AddScaledInto[T Number](dst, a, b []T, s T)
- func All(m []bool) bool
- func And(a, b []byte)
- func AndInto(dst, a, b []byte)
- func AndMask(a, b []bool)
- func AndMaskInto(dst, a, b []bool)
- func AndNot(a, b []byte)
- func AndNotInto(dst, a, b []byte)
- func Any(m []bool) bool
- func AnyNaN[T Float](a []T, mask []bool) bool
- func AppendEscapeJSON[S Text](dst []byte, s S) []byte
- func AppendRunes[S Text](dst []rune, s S) []rune
- func AppendUTF8(dst []byte, s []uint16) []byte
- func AppendUTF8FromRunes(dst []byte, s []rune) []byte
- func AppendUTF16[S Text](dst []uint16, s S) []uint16
- func AppendVarints[T VarintValue](dst []byte, a []T) []byte
- func ApplyWindowInto[T Float](dst, a, w []T)
- func ArgMax[T Number](a []T) int
- func ArgMin[T Number](a []T) int
- func Argsort[T Number](idx []int32, a []T) int
- func Asin[T Float](a []T)
- func AsinInto[T Float](dst, a []T)
- func Asinh[T Float](a []T)
- func AsinhInto[T Float](dst, a []T)
- func Atan[T Float](a []T)
- func Atan2[T Float](a, b []T)
- func Atan2Into[T Float](dst, a, b []T)
- func AtanInto[T Float](dst, a []T)
- func Atanh[T Float](a []T)
- func AtanhInto[T Float](dst, a []T)
- func AvailableTiers() []string
- func BFloat16ToFloat32Into(dst []float32, a []uint16)
- func Bartlett[T Float](dst []T)
- func Base64Decode[S Text](dst []byte, src S) int
- func Base64DecodedLen(n int) int
- func Base64Encode[S Text](dst []byte, src S) int
- func Base64EncodedLen(n int) int
- func Bincount[T Integer](a []T, n int) []int32
- func BincountInto[T Integer](counts []int32, a []T)
- func BitPackInto(dst, a []uint32, bits int32)
- func BitUnpackInto(dst, a []uint32, bits int32)
- func Blackman[T Float](dst []T)
- func BottomK[T Number](a []T, k int) []T
- func BottomKInto[T Number](dst []T, a []T, k int, scratch []T) int
- func ByteSwapInto[T Integer](dst, a []T)
- func Cbrt[T Float](a []T)
- func CbrtInto[T Float](dst, a []T)
- func Ceil[T Float](a []T)
- func CeilInto[T Float](dst, a []T)
- func Clamp[T Number](a []T, lo, hi T)
- func ClampInto[T Number](dst, a []T, lo, hi T)
- func CommonPrefixLen[S, T Text](a S, b T) int
- func Compare[S, T Text](a S, b T) int
- func CompressInto[T Number](dst, src []T, mask []bool) int
- func ConjComplex[C Complex](a []C)
- func ConjComplexInto[C Complex](dst, a []C)
- func Contains[S, T Text](haystack S, needle T) bool
- func ContainsAny[S, T Text](s S, chars T) bool
- func ContainsByte[S Text](s S, c byte) bool
- func ContainsFoldASCII[S, T Text](haystack S, needle T, scratch []byte) bool
- func ConvertInto[D, S Number](dst []D, src []S)
- func ConvolveFull[T Float](a, b []T) []T
- func ConvolveFullInto[T Float](dst, a, b []T)
- func ConvolveInto[T Number](dst, sig, ker []T)
- func CorrelateFull[T Float](a, b []T) []T
- func CorrelateFullInto[T Float](dst, a, b, scratch []T)
- func CorrelateInto[T Number](dst, sig, ker []T)
- func Correlation[T Float](x, y []T) T
- func Cos[T Float](a []T)
- func CosInto[T Float](dst, a []T)
- func Cosh[T Float](a []T)
- func CoshInto[T Float](dst, a []T)
- func CosineSimilarity[T Float](a, b []T) T
- func Count[S, T Text](haystack S, needle T) int
- func CountAny[S, T Text](s S, chars T) int
- func CountByte[S Text](s S, c byte) int
- func CountFoldASCII[S, T Text](haystack S, needle T, scratch []byte) int
- func CountNaN[T Float](a []T, mask []bool) int
- func CountTrue(m []bool) int
- func Covariance[T Float](x, y []T) T
- func CumMax[T Number](a []T)
- func CumMaxInto[T Number](dst, a []T)
- func CumMin[T Number](a []T)
- func CumMinInto[T Number](dst, a []T)
- func CumProd[T Number](a []T)
- func CumProdInto[T Number](dst, a []T)
- func CumSum[T Number](a []T)
- func CumSumInto[T Number](dst, a []T)
- func DequantizeInt8(dst []float32, a []int8, scale float32, zeroPoint int32)
- func DequantizePerChannelInt8(dst []float32, a []int8, scale []float32, zeroPoint []int32, ...)
- func DequantizePerChannelUint8(dst []float32, a []uint8, scale []float32, zeroPoint []int32, ...)
- func DequantizeUint8(dst []float32, a []uint8, scale float32, zeroPoint int32)
- func Describe() string
- func DiffInto[T Number](dst, a []T)
- func DifferenceInto[T Integer](dst, a, b []T) int
- func Distance[T Float](a, b []T) T
- func Div[T Float](a, b []T)
- func DivComplex[C Complex](a, b []C)
- func DivComplexInto[C Complex](dst, a, b []C)
- func DivInto[T Float](dst, a, b []T)
- func DivScalar[T Number](a []T, s T)
- func DivScalarInto[T Number](dst, a []T, s T)
- func Dot[T Number](a, b []T) T
- func DotComplex[C Complex](a, b []C) C
- func DotComplexConj[C Complex](a, b []C) C
- func EMAInto[T Number](dst, a []T, alpha T)
- func Equal[S, T Text](a S, b T) bool
- func EqualFoldASCII[S, T Text](a S, b T) bool
- func EqualInto[T Number](dst []bool, a, b []T)
- func EqualScalarInto[T Number](dst []bool, a []T, v T)
- func Erf[T Float](a []T)
- func ErfInto[T Float](dst, a []T)
- func Erfc[T Float](a []T)
- func ErfcInto[T Float](dst, a []T)
- func EulerStep[T Float](y, dydt []T, t, h T, f Derivative[T])
- func Exp[T Float](a []T)
- func Exp2[T Float](a []T)
- func Exp2Into[T Float](dst, a []T)
- func ExpInto[T Float](dst, a []T)
- func ExpandInto[T Number](dst, src []T, mask []bool) int
- func Expm1[T Float](a []T)
- func Expm1Into[T Float](dst, a []T)
- func FFT(a []complex128) []complex128
- func FFTInto(p *FFTPlan, dst, src []complex128)
- func FastAcos[T Float](a []T)
- func FastAcosInto[T Float](dst, a []T)
- func FastAcosh[T Float](a []T)
- func FastAcoshInto[T Float](dst, a []T)
- func FastAsin[T Float](a []T)
- func FastAsinInto[T Float](dst, a []T)
- func FastAsinh[T Float](a []T)
- func FastAsinhInto[T Float](dst, a []T)
- func FastAtan[T Float](a []T)
- func FastAtan2[T Float](a, b []T)
- func FastAtan2Into[T Float](dst, a, b []T)
- func FastAtanInto[T Float](dst, a []T)
- func FastAtanh[T Float](a []T)
- func FastAtanhInto[T Float](dst, a []T)
- func FastCbrt[T Float](a []T)
- func FastCbrtInto[T Float](dst, a []T)
- func FastCos[T Float](a []T)
- func FastCosInto[T Float](dst, a []T)
- func FastCosh[T Float](a []T)
- func FastCoshInto[T Float](dst, a []T)
- func FastCumProd[T Float](a []T)
- func FastCumProdInto[T Float](dst, a []T)
- func FastCumSum[T Float](a []T)
- func FastCumSumInto[T Float](dst, a []T)
- func FastErf[T Float](a []T)
- func FastErfInto[T Float](dst, a []T)
- func FastExp[T Float](a []T)
- func FastExp2[T Float](a []T)
- func FastExp2Into[T Float](dst, a []T)
- func FastExpInto[T Float](dst, a []T)
- func FastExpm1[T Float](a []T)
- func FastExpm1Into[T Float](dst, a []T)
- func FastHypot[T Float](a, b []T)
- func FastHypotInto[T Float](dst, a, b []T)
- func FastLog[T Float](a []T)
- func FastLog1p[T Float](a []T)
- func FastLog1pInto[T Float](dst, a []T)
- func FastLog2[T Float](a []T)
- func FastLog2Into[T Float](dst, a []T)
- func FastLog10[T Float](a []T)
- func FastLog10Into[T Float](dst, a []T)
- func FastLogInto[T Float](dst, a []T)
- func FastPow[T Float](a, b []T)
- func FastPowInto[T Float](dst, a, b []T)
- func FastSigmoid[T Float](a []T)
- func FastSigmoidInto[T Float](dst, a []T)
- func FastSin[T Float](a []T)
- func FastSinInto[T Float](dst, a []T)
- func FastSinh[T Float](a []T)
- func FastSinhInto[T Float](dst, a []T)
- func FastTan[T Float](a []T)
- func FastTanInto[T Float](dst, a []T)
- func FastTanh[T Float](a []T)
- func FastTanhInto[T Float](dst, a []T)
- func Fill[T Number](a []T, v T)
- func FilterInto[T Number](dst, src []T, pred func(T) bool) int
- func Float8E4M3ToFloat32Into(dst []float32, a []byte)
- func Float8E5M2ToFloat32Into(dst []float32, a []byte)
- func Float16ToFloat32Into(dst []float32, a []uint16)
- func Float32ToBFloat16Into(dst []uint16, a []float32)
- func Float32ToFloat8E4M3Into(dst []byte, a []float32)
- func Float32ToFloat8E5M2Into(dst []byte, a []float32)
- func Float32ToFloat16Into(dst []uint16, a []float32)
- func Floor[T Float](a []T)
- func FloorInto[T Float](dst, a []T)
- func FormatInts(dst []byte, vals []int64, sep byte) int
- func FromPartsInto[C Complex, R Float](dst []C, re, im []R)
- func GELU[T Float](a []T)
- func GatherInto[T Number](dst, src []T, idx []int32)
- func GemmPackLen[T Float](k, n int) int
- func GemvInto[T Number](dst, a, x []T, m, k int)
- func GemvParallelInto[T Number](dst, a, x []T, m, k int)
- func GrayscaleInto(dst, r, g, b []byte)
- func GreaterEqualInto[T Number](dst []bool, a, b []T)
- func GreaterEqualScalarInto[T Number](dst []bool, a []T, v T)
- func GreaterInto[T Number](dst []bool, a, b []T)
- func GreaterScalarInto[T Number](dst []bool, a []T, v T)
- func Hamming[T Float](dst []T)
- func HammingDistance[S, T Text](a S, b T) int
- func HammingDistanceWords(a, b []uint64) int
- func Hann[T Float](dst []T)
- func HannPeriodic[T Float](dst []T)
- func HasPrefix[S, T Text](s S, prefix T) bool
- func HasSuffix[S, T Text](s S, suffix T) bool
- func HexDecode[S Text](dst []byte, src S) (int, bool)
- func HexEncode[S Text](dst []byte, src S) int
- func Hilbert(src []float64) []complex128
- func HilbertInto(p *FFTPlan, dst []complex128, src []float64)
- func Histogram[T Number](a []T, n int, lo, hi T) []int32
- func HistogramInto[T Number](counts []int32, a []T, lo, hi T)
- func Hypot[T Float](a, b []T)
- func HypotInto[T Float](dst, a, b []T)
- func IFFT(a []complex128) []complex128
- func IFFTInto(p *FFTPlan, dst, src []complex128)
- func ImagInto[R Float, C Complex](dst []R, a []C)
- func Index[S, T Text](haystack S, needle T) int
- func IndexAll[S Text](dst []int32, s S, c byte) int
- func IndexAny[S, T Text](s S, chars T) int
- func IndexByte[S Text](s S, c byte) int
- func IndexFoldASCII[S, T Text](haystack S, needle T, scratch []byte) int
- func IndexNotAny[S, T Text](s S, chars T) int
- func Interp[T Float](x, xp, fp []T) []T
- func InterpInto[T Float](dst []T, x, xp, fp []T)
- func IntersectInto[T Integer](dst, a, b []T) int
- func IsASCII[S Text](s S) bool
- func IsFiniteInto[T Float](dst []bool, a []T, scratch []T)
- func IsInfInto[T Float](dst []bool, a []T, scratch []T)
- func IsNaNInto[T Float](dst []bool, a []T)
- func L1Norm[T Number](a []T) T
- func Lanes[T float32 | float64 | int32 | int64 | uint8]() int
- func LastIndex[S, T Text](haystack S, needle T) int
- func LastIndexByte[S Text](s S, c byte) int
- func LastIndexNotAny[S, T Text](s S, chars T) int
- func LayerNorm[T Float](a []T, eps T)
- func LayerNormInto[T Float](dst, a, gamma, beta []T, eps T)
- func LeadingZerosInto[T Integer](dst, a []T)
- func LeakyReLU[T Float](a []T, slope T)
- func Lerp[T Number](a, b []T, t T)
- func LerpInto[T Number](dst, a, b []T, t T)
- func LessEqualInto[T Number](dst []bool, a, b []T)
- func LessEqualScalarInto[T Number](dst []bool, a []T, v T)
- func LessInto[T Number](dst []bool, a, b []T)
- func LessScalarInto[T Number](dst []bool, a []T, v T)
- func LinearRegression[T Float](x, y []T) (slope, intercept T)
- func Log[T Float](a []T)
- func Log1p[T Float](a []T)
- func Log1pInto[T Float](dst, a []T)
- func Log2[T Float](a []T)
- func Log2Into[T Float](dst, a []T)
- func Log10[T Float](a []T)
- func Log10Into[T Float](dst, a []T)
- func LogInto[T Float](dst, a []T)
- func LogSumExp[T Float](a []T) T
- func LowerBoundInto[T Number](dst []int32, a, q []T)
- func ManhattanDistance[T Number](a, b []T) T
- func MatMulInto[T Number](dst, a, b []T, m, k, n int)
- func MatMulIntoPacked[T Float](dst, a, bp []T, m, k, n int)
- func MatMulIntoScratch[T Float](dst, a, b, scratch []T, m, k, n int)
- func MatMulParallelInto[T Number](dst, a, b []T, m, k, n int)
- func Max[T Number](a []T) T
- func Maximum[T Number](a, b []T)
- func MaximumInto[T Number](dst, a, b []T)
- func Mean[T Float](a []T) T
- func Median[T Number](a []T) T
- func MedianInto[T Number](a, scratch []T) T
- func Min[T Number](a []T) T
- func MinMax[T Number](a []T) (lo, hi T)
- func Minimum[T Number](a, b []T)
- func MinimumInto[T Number](dst, a, b []T)
- func MovingAverageInto[T Number](dst, a []T, width int)
- func Mul[T Number](a, b []T)
- func MulAll[T Number](dst []T, srcs ...[]T)
- func MulComplex[C Complex](a, b []C)
- func MulComplexInto[C Complex](dst, a, b []C)
- func MulInto[T Number](dst, a, b []T)
- func NanMean[T Float](a []T, scratch []T, mask []bool) (T, int)
- func NanSum[T Float](a []T, scratch []T, mask []bool) T
- func NeedsEscapeJSON[S Text](s S) bool
- func Neg[T Number](a []T)
- func NegComplex[C Complex](a []C)
- func NegComplexInto[C Complex](dst, a []C)
- func NegInto[T Number](dst, a []T)
- func Norm[T Float](a []T) T
- func Normalize[T Float](a []T)
- func NotEqualInto[T Number](dst []bool, a, b []T)
- func NotEqualScalarInto[T Number](dst []bool, a []T, v T)
- func NotMask(a []bool)
- func NotMaskInto(dst, a []bool)
- func OnesCountInto[T Integer](dst, a []T)
- func Or(a, b []byte)
- func OrInto(dst, a, b []byte)
- func OrMask(a, b []bool)
- func OrMaskInto(dst, a, b []bool)
- func PackBInto[T Float](bp, b []T, k, n int)
- func ParseFloats[S Text](dst []float64, src S, idx []int32) (int, bool)
- func ParseInts[S Text](dst []int64, src S, idx []int32) (int, bool)
- func ParseUints[S Text](dst []uint64, src S, idx []int32) (int, bool)
- func PartitionInto[T Number](dst, src []T, pivot T) int
- func PolyEval[T Number](x, coeffs []T)
- func PolyEvalInto[T Number](dst, x, coeffs []T)
- func PopCount[S Text](s S) int
- func Pow[T Float](a, b []T)
- func PowInto[T Float](dst, a, b []T)
- func Prod[T Number](a []T) T
- func QMatMulInt8Into(dst []int32, a, b []int8, m, k, n int)
- func Quantile[T Number](a []T, q float64) T
- func QuantileInto[T Number](a, scratch []T, q float64) T
- func QuantizeInt8(dst []int8, a []float32, scale float32, zeroPoint int32)
- func QuantizePerChannelInt8(dst []int8, a []float32, scale []float32, zeroPoint []int32, ...)
- func QuantizePerChannelUint8(dst []uint8, a []float32, scale []float32, zeroPoint []int32, ...)
- func QuantizeUint8(dst []uint8, a []float32, scale float32, zeroPoint int32)
- func RFFT(src []float64) []complex128
- func RFFTInto(p *RFFTPlan, dst []complex128, src []float64, scratch []complex128)
- func RGBToUVInto(u, v, r, g, b []byte)
- func RK4Step[T Float](y []T, t, h T, f Derivative[T], w *RK4Workspace[T])
- func RMSNorm[T Float](a []T, eps T)
- func Ramp[T Number](a []T, start, step T)
- func RandomInto[T float32 | float64 | uint64](dst []T, seed uint64)
- func Rank(v, table []uint64, p int) int
- func RankTableInto(dst, v []uint64)
- func ReLU[T Float](a []T)
- func RealInto[R Float, C Complex](dst []R, a []C)
- func Reciprocal[T Float](a []T)
- func ReciprocalInto[T Float](dst, a []T)
- func ReplaceByte(b []byte, old, new byte)
- func ReplaceByteInto[S Text](dst []byte, s S, old, new byte)
- func RequantizeInt8Into(dst []int8, a []int32, scale float32, zeroPoint int32)
- func Rescale[T Float](a []T, lo, hi T)
- func Reverse[T Number](a []T)
- func ReverseBitsInto[T Integer](dst, a []T)
- func ReverseInto[T Number](dst, a []T)
- func RollingMaxInto[T Number](dst, a []T, window int)
- func RollingMinInto[T Number](dst, a []T, window int)
- func Rotl[T Integer](a []T, s uint64)
- func RotlInto[T Integer](dst, a []T, s uint64)
- func Rotr[T Integer](a []T, s uint64)
- func RotrInto[T Integer](dst, a []T, s uint64)
- func Round[T Float](a []T)
- func RoundInto[T Float](dst, a []T)
- func RoundToEven[T Float](a []T)
- func RoundToEvenInto[T Float](dst, a []T)
- func RunLengthDecodeInt32(dst, values, lengths []int32) int
- func RunLengthEncodeInt32(values, lengths, a []int32, scratch []bool) int
- func RunStartsBytesInto(dst []bool, a []byte)
- func RunStartsInt64Into(dst []bool, a []int64)
- func RunStartsInto(dst []bool, a []int32)
- func RuneCount[S Text](s S) int
- func SampleStdDev[T Float](a []T) T
- func SampleVariance[T Float](a []T) T
- func SatAdd[T Saturating](a, b []T)
- func SatAddInto[T Saturating](dst, a, b []T)
- func SatSub[T Saturating](a, b []T)
- func SatSubInto[T Saturating](dst, a, b []T)
- func Scale[T Number](a []T, s T)
- func ScaleComplex[C Complex, R Float](a []C, s R)
- func ScaleComplexInto[C Complex, R Float](dst, a []C, s R)
- func ScaleInto[T Number](dst, a []T, s T)
- func ScatterInto[T Number](dst []T, idx []int32, src []T)
- func Select(v, table []uint64, k int) int
- func SelectInto[T Number](dst []T, mask []bool, yes, no []T)
- func Shl[T Integer](a []T, s uint64)
- func ShlInto[T Integer](dst, a []T, s uint64)
- func Shr[T Integer](a []T, s uint64)
- func ShrInto[T Integer](dst, a []T, s uint64)
- func SiLU[T Float](a []T)
- func Sigmoid[T Float](a []T)
- func SigmoidInto[T Float](dst, a []T)
- func SignInto[T Float](dst []T, a []T, scratch []T, mask []bool)
- func Simpson[T Float](y []T, h T) T
- func Sin[T Float](a []T)
- func SinInto[T Float](dst, a []T)
- func Sinh[T Float](a []T)
- func SinhInto[T Float](dst, a []T)
- func Softmax[T Float](a []T)
- func Softplus[T Float](a []T)
- func Sort[T Number](a []T)
- func SortInto[T Number](a, scratch []T)
- func SortedIndex[T Number](a []T, v T) (int, bool)
- func SpMVInto[T Float](dst []T, values []T, colIdx []int32, rowPtr []int32, x []T)
- func SparseDot[T Float](v []T, idx []int32, x []T) T
- func Sqrt[T Float](a []T)
- func SqrtInto[T Float](dst, a []T)
- func SquaredDistance[T Number](a, b []T) T
- func Standardize[T Float](a []T)
- func StdDev[T Float](a []T) T
- func Sub[T Number](a, b []T)
- func SubComplex[C Complex](a, b []C)
- func SubComplexInto[C Complex](dst, a, b []C)
- func SubInto[T Number](dst, a, b []T)
- func SubScalar[T Number](a []T, s T)
- func SubScalarInto[T Number](dst, a []T, s T)
- func Sum[T Number](a []T) T
- func SumComplex[C Complex](a []C) C
- func SumSquares[T Number](a []T) T
- func Tan[T Float](a []T)
- func TanInto[T Float](dst, a []T)
- func Tanh[T Float](a []T)
- func TanhInto[T Float](dst, a []T)
- func Tier() string
- func Tile[T Number](a, pattern []T)
- func ToLowerASCII(b []byte)
- func ToLowerASCIIInto[S Text](dst []byte, s S)
- func ToUpperASCII(b []byte)
- func ToUpperASCIIInto[S Text](dst []byte, s S)
- func TopK[T Number](a []T, k int) []T
- func TopKInto[T Number](dst []T, a []T, k int, scratch []T) int
- func TrailingZerosInto[T Integer](dst, a []T)
- func Transpose[T Number](a []T, m, n int) []T
- func TransposeInto[T Number](dst, a []T, m, n int)
- func Trapezoid[T Float](y []T, h T) T
- func TrimAny[S, T Text](s S, cutset T) S
- func TrimLeftAny[S, T Text](s S, cutset T) S
- func TrimRightAny[S, T Text](s S, cutset T) S
- func TrimSpaceASCII[S Text](s S) S
- func Trunc[T Float](a []T)
- func TruncInto[T Float](dst, a []T)
- func UTF8Len(s []uint16) int
- func UTF16Len[S Text](s S) int
- func ValidUTF8[S Text](s S) bool
- func Variance[T Float](a []T) T
- func VarintLenInto[T VarintValue](dst []int32, a []T)
- func VarintSize[T VarintValue](a []T) int
- func VerletStep[T Float](pos, vel, acc []T, h T, accel func(pos, out []T))
- func Xor(a, b []byte)
- func XorInto(dst, a, b []byte)
- func XorMask(a, b []bool)
- func XorMaskInto(dst, a, b []bool)
- func Zero[T Number](a []T)
- func ZigzagDecodeInt8Into(dst []int8, a []byte)
- func ZigzagDecodeInt16Into(dst []int16, a []uint16)
- func ZigzagDecodeInt32Into(dst []int32, a []uint32)
- func ZigzagDecodeInt64Into(dst []int64, a []uint64)
- func ZigzagEncodeInt8Into(dst []byte, a []int8)
- func ZigzagEncodeInt16Into(dst []uint16, a []int16)
- func ZigzagEncodeInt32Into(dst []uint32, a []int32)
- func ZigzagEncodeInt64Into(dst []uint64, a []int64)
- type Accumulator
- type Complex
- type Derivative
- type FFTPlan
- type Float
- type IntAccumulator
- type Integer
- type MinMaxAccumulator
- type MultiSearcher
- type Number
- type RFFTPlan
- type RK4Workspace
- type Saturating
- type Text
- type VarintValue
Examples ¶
- Package
- Package (Bytes)
- Package (FeatureScaling)
- Package (Fused)
- Package (Into)
- Package (Scenarios)
- Package (Sizing)
- Package (Types)
- AbsComplexInto
- Add
- AddAll
- AddScalar
- AddScaled
- AppendEscapeJSON
- AppendVarints
- ApplyWindowInto
- ArgMax
- ArgMin
- Argsort
- Base64Decode
- Base64Encode
- Bincount
- BitPackInto
- Blackman
- BottomK
- ByteSwapInto
- Clamp
- CommonPrefixLen
- CompressInto
- ConvolveFull
- ConvolveFullInto
- CosineSimilarity
- CountAny
- CountNaN
- CumSum
- DiffInto
- DifferenceInto
- Distance
- EqualFoldASCII
- Exp
- FFTInto
- FilterInto
- Float16ToFloat32Into
- Float32ToFloat8E4M3Into
- GatherInto
- GemvInto
- GemvParallelInto
- GrayscaleInto
- Hamming
- HammingDistance
- Hann
- HexEncode
- HilbertInto
- Histogram
- Index
- IndexAll
- IndexAny
- IndexByte
- IndexFoldASCII
- IndexNotAny
- InterpInto
- IntersectInto
- IsNaNInto
- LastIndex
- LayerNorm
- LayerNormInto
- LeadingZerosInto
- Log
- LowerBoundInto
- MatMulInto
- MatMulParallelInto
- Mean
- Median
- MedianInto
- MinMax
- MulAll
- NanMean
- NanSum
- Norm
- Normalize
- OnesCountInto
- ParseInts
- PartitionInto
- PopCount
- QMatMulInt8Into
- Quantile
- QuantizeInt8
- QuantizePerChannelInt8
- RFFT
- RGBToUVInto
- RandomInto
- Rank
- RankTableInto
- RequantizeInt8Into
- RollingMaxInto
- RollingMinInto
- Rotl
- RunLengthEncodeInt32
- RunStartsInto
- Scale
- Select
- SelectInto
- Shl
- Sigmoid
- Sin
- Sort
- SortInto
- SpMVInto
- SparseDot
- StdDev
- Sum
- TopK
- TransposeInto
- TrimAny
- TrimSpaceASCII
- ValidUTF8
- Variance
- VarintLenInto
- VarintSize
- Xor
- ZigzagEncodeInt32Into
Constants ¶
const HasVectorType = false
HasVectorType reports whether this build has the vector type — that is, whether it is amd64 and GOEXPERIMENT=simd is set. It is false here.
It is a constant, so a caller can branch on it and have the dead side compiled away:
if simd.HasVectorType {
// ... vector path
} else {
// ... slice API, which is accelerated either way
}
Variables ¶
This section is empty.
Functions ¶
func Abs ¶
func Abs[T Number](a []T)
Abs replaces every element with its absolute value.
For floats this clears the sign bit, so -0 becomes +0 and NaN keeps its payload. For integers it wraps: the absolute value of the most negative value is itself, matching the hardware instruction.
func AbsComplexInto ¶
AbsComplexInto writes the magnitude of each element of a into dst.
The magnitude is formed through the larger component, so an intermediate cannot overflow for a result that is representable — the same reasoning as Hypot, and it matters more here because both components are caller data.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
dst := make([]float64, 2)
simd.AbsComplexInto(dst, []complex128{complex(3, 4), complex(0, 1)})
fmt.Println(dst)
}
Output: [5 1]
func AbsInto ¶
func AbsInto[T Number](dst, a []T)
AbsInto sets dst[i] to the absolute value of a[i]. See Abs for semantics.
func Acos ¶
func Acos[T Float](a []T)
Acos replaces every element with its arccosine, in radians. Inputs outside [-1, 1] yield NaN.
func AcosInto ¶
func AcosInto[T Float](dst, a []T)
AcosInto sets dst[i] to the arccosine of a[i]. dst may alias a.
func Acosh ¶
func Acosh[T Float](a []T)
Acosh replaces every element with its inverse hyperbolic cosine. It is NaN below 1, where the function is undefined.
func AcoshInto ¶
func AcoshInto[T Float](dst, a []T)
AcoshInto sets dst[i] to the inverse hyperbolic cosine of a[i], which is NaN below 1. dst may alias a.
func Add ¶
func Add[T Number](a, b []T)
Add adds b into a, elementwise: a[i] += b[i].
It processes min(len(a), len(b)) elements and allocates nothing. Use AddInto to write the result elsewhere.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
a := []float64{1, 2, 3}
simd.Add(a, []float64{10, 20, 30})
fmt.Println(a)
}
Output: [11 22 33]
func AddAll ¶
func AddAll[T Number](dst []T, srcs ...[]T)
AddAll sets dst[i] to the sum of the corresponding element of every source, making a single pass over memory rather than one per source.
The sum is accumulated left to right, so the result is bit-identical to writing the binary calls out by hand. Floating-point addition is not associative, so that is a real guarantee and not a formality: reordering the sources changes the answer, and this function will not reorder them.
The work is bounded by the shortest slice, dst included. With no sources dst is zeroed; with one it is copied.
Sources beyond the fourth are folded in groups, so a sixteen-way sum is four passes rather than fifteen.
Why there is no general ZipInto(dst, f, srcs...) ¶
Because it would be slower than the loop you would write without it. A closure cannot be vectorized, so the combinator's only advantage is making one pass instead of several — and that is not nearly enough. Measured on dst = a*b + c, nanoseconds:
n your own loop this catalogue ZipInto with a closure 1024 728.7 52.6 1163 262144 195722 60707 310461 4194304 4377370 3597582 5446108
The ZipInto column is the *generous* version, specialized to a fixed arity so there is no per-element argument slice; the honest variadic form is another 1.6x to 2.6x worse again. It loses to a plain Go loop at every size.
So the guidance is: use these, and where your expression is not here, write the loop. AddScaled covers a*s + b, MulAll and AddAll cover the n-ary products and sums, and a loop you write yourself will beat any closure this package could call on your behalf. See entry 47 of docs/wrong.md.
Example ¶
AddAll sums any number of slices in one pass over memory, rather than one pass per slice.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
dst := make([]float64, 3)
simd.AddAll(dst,
[]float64{1, 2, 3},
[]float64{10, 20, 30},
[]float64{100, 200, 300})
fmt.Println(dst)
}
Output: [111 222 333]
func AddComplex ¶
func AddComplex[C Complex](a, b []C)
AddComplex adds b into a elementwise: a[i] += b[i].
It processes min(len(a), len(b)) elements and allocates nothing. Use AddComplexInto to write the result elsewhere.
func AddComplexInto ¶
func AddComplexInto[C Complex](dst, a, b []C)
AddComplexInto sets dst[i] = a[i] + b[i]. dst may alias a or b.
func AddInto ¶
func AddInto[T Number](dst, a, b []T)
AddInto sets dst[i] = a[i] + b[i].
It processes min(len(dst), len(a), len(b)) elements. dst may alias a or b.
func AddScalar ¶
func AddScalar[T Number](a []T, s T)
AddScalar adds s to every element: a[i] += s.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
a := []float64{1, 2, 3}
simd.AddScalar(a, 100)
fmt.Println(a)
}
Output: [101 102 103]
func AddScalarInto ¶
func AddScalarInto[T Number](dst, a []T, s T)
AddScalarInto sets dst[i] = a[i] + s. dst may alias a.
func AddScaled ¶
func AddScaled[T Number](a, b []T, s T)
AddScaled adds a scaled b into a: a[i] += b[i] * s.
This is the AXPY of BLAS, and it is the reason a fused catalogue exists: written as separate Mul and Add calls the same work costs two passes over memory, which is what makes naive slice libraries memory-bound.
Example ¶
AddScaled is axpy: y += a*x, in one pass rather than a multiply pass and an add pass.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
y := []float64{1, 2, 3}
simd.AddScaled(y, []float64{10, 10, 10}, 0.5)
fmt.Println(y)
}
Output: [6 7 8]
func AddScaledInto ¶
func AddScaledInto[T Number](dst, a, b []T, s T)
AddScaledInto sets dst[i] = a[i] + b[i]*s in a single pass over memory.
func And ¶
func And(a, b []byte)
And clears in a every bit not set in b: a[i] &= b[i].
It processes min(len(a), len(b)) bytes and allocates nothing. Use AndInto to write the result elsewhere.
func AndInto ¶
func AndInto(dst, a, b []byte)
AndInto sets dst[i] = a[i] & b[i].
It processes min(len(dst), len(a), len(b)) bytes. dst may alias a or b.
func AndMaskInto ¶
func AndMaskInto(dst, a, b []bool)
AndMaskInto sets dst[i] = a[i] && b[i]. dst may alias a or b.
func AndNot ¶
func AndNot(a, b []byte)
AndNot clears in a every bit set in b: a[i] &^= b[i].
It processes min(len(a), len(b)) bytes and allocates nothing. Use AndNotInto to write the result elsewhere.
func AndNotInto ¶
func AndNotInto(dst, a, b []byte)
AndNotInto sets dst[i] = a[i] &^ b[i], clearing in a every bit set in b.
It processes min(len(dst), len(a), len(b)) bytes. dst may alias a or b.
func AnyNaN ¶
AnyNaN reports whether a contains a NaN.
This is the cheap question and it has a cheaper answer than counting: a single reduction that stops at the first one. It still needs the mask, because the comparison and the reduction are separate kernels.
func AppendEscapeJSON ¶
AppendEscapeJSON appends s to dst with JSON string escaping applied — the body of a JSON string, without the surrounding quotes — and returns the extended slice.
buf := make([]byte, 0, 4096) // once
for _, rec := range records {
buf = append(buf[:0], '"')
buf = simd.AppendEscapeJSON(buf, rec.Name)
buf = append(buf, '"')
...
}
Escapes are the standard ones: \" \\ \b \f \n \r \t, and \u00XX for the other control bytes. Bytes 0x80 and above pass through, so valid UTF-8 in gives valid UTF-8 out. It does not escape < > & — see the package note; use encoding/json if you are embedding the result in HTML.
Example ¶
JSON string escaping in the append style, so a serializer brings one buffer. NeedsEscapeJSON is one accelerated scan, and on clean input the writer can skip escaping entirely.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
buf := make([]byte, 0, 64)
buf = append(buf, '"')
buf = simd.AppendEscapeJSON(buf, "say \"hi\"\n")
buf = append(buf, '"')
fmt.Println(string(buf))
fmt.Println(simd.NeedsEscapeJSON("clean text"))
}
Output: "say \"hi\"\n" false
func AppendRunes ¶
AppendRunes appends the runes of s to dst and returns the extended slice.
It is AppendUTF16's shape for UTF-32, and it exists for the same reason: `[]rune(s)` allocates a fresh slice every call, and a loop over lines or records pays for that once per record. This one appends into a buffer you own, so the same idiom as everywhere else here applies:
runes = simd.AppendRunes(runes[:0], line)
The general UTF-8 decode is a dependent scan — a rune's length decides where the next one starts — so only the ASCII runs are accelerated. Below 0x80 a byte is a whole rune, which makes that case a plain widen with no dependence between lanes, and in real text it is nearly all of the input. The runes between runs are decoded one at a time.
Invalid UTF-8 becomes utf8.RuneError, one per offending byte, matching `[]rune(s)` and utf8.DecodeRune.
func AppendUTF8 ¶
AppendUTF8 appends the UTF-8 encoding of the UTF-16 units in s to dst and returns the extended slice.
Unpaired surrogates become U+FFFD, matching unicode/utf16.Decode.
func AppendUTF8FromRunes ¶
AppendUTF8FromRunes encodes the runes of s as UTF-8 and appends them to dst, returning the extended slice.
It is the direction AppendRunes does not go, and completes the UTF-32 pair the way AppendUTF8 completes the UTF-16 one. The alternative is `append(dst, string(runes)...)`, which allocates a string per call for no reason other than that Go has no other spelling for it.
Only the ASCII runs are accelerated, and unlike the decode direction that is not because of a dependence: encoding is independent per rune, but a rune below 0x80 is one byte and a rune above it is two to four, so the output offset depends on every rune before it. Below 0x80 that question disappears — one rune is one byte — which makes the run a plain narrow.
A surrogate half or a value above utf8.MaxRune is encoded as utf8.RuneError, matching utf8.AppendRune and `string(runes)`.
func AppendUTF16 ¶
AppendUTF16 appends the UTF-16 encoding of s to dst and returns the extended slice.
Invalid UTF-8 is handled as encoding/utf8 and unicode/utf16 handle it: each malformed byte becomes U+FFFD, one unit, which is what utf16.Encode of a []rune conversion produces for the same input. Runes above the BMP become surrogate pairs, so the result is longer than the rune count.
func AppendVarints ¶
func AppendVarints[T VarintValue](dst []byte, a []T) []byte
AppendVarints encodes every element of a and appends the result to dst.
The buffer is grown once, to exactly the size VarintSize reports, and the bytes are then written without any further bounds growth. The emission itself is the serial loop it has to be; what the vectorized pass buys is that it happens exactly once into exactly the right amount of memory.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
buf := simd.AppendVarints(nil, []uint64{1, 300})
fmt.Printf("% x\n", buf)
}
Output: 01 ac 02
func ApplyWindowInto ¶
func ApplyWindowInto[T Float](dst, a, w []T)
ApplyWindowInto multiplies a by a window into dst, which is what a caller actually does with one:
w := make([]float64, n) simd.Hann(w) simd.ApplyWindowInto(dst, samples, w)
It is MulInto under a clearer name, and exists so the pairing is discoverable from the window functions.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
w := make([]float64, 4)
simd.Hann(w)
dst := make([]float64, 4)
simd.ApplyWindowInto(dst, []float64{1, 1, 1, 1}, w)
fmt.Printf("%.2f %.2f\n", dst[0], dst[1])
}
Output: 0.00 0.75
func ArgMax ¶
ArgMax returns the index of the first largest element. It panics on an empty slice.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
fmt.Println(simd.ArgMax([]float64{3, 1, 4, 1, 5}))
}
Output: 4
func ArgMin ¶
ArgMin returns the index of the first smallest element. It panics on an empty slice.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
fmt.Println(simd.ArgMin([]float64{3, 1, 4, 1, 5}))
}
Output: 1
func Argsort ¶
Argsort writes into idx a permutation of 0..len(a)-1 that would sort a in ascending order, leaving a untouched.
This is the operation Go has no good answer for: sorting one slice by the values of another usually means building a slice of structs or a slice of indices closed over the data, and both allocate and both are slow. Here idx is yours and nothing else is allocated.
It writes min(len(idx), len(a)) entries and returns that count. Ties keep their original relative order, so the permutation is stable. NaN sorts to the end, as in Sort.
Applying the result is GatherInto:
n := simd.Argsort(idx, a) simd.GatherInto(sorted, a, idx[:n])
Example ¶
Argsort returns the permutation rather than reordering, which is what you want when several columns share one ordering.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
names := []string{"cherry", "apple", "banana"}
score := []float64{3, 1, 2}
order := make([]int32, len(score))
simd.Argsort(order, score)
for _, i := range order {
fmt.Print(names[i], " ")
}
fmt.Println()
}
Output: apple banana cherry
func Asin ¶
func Asin[T Float](a []T)
Asin replaces every element with its arcsine, in radians. Inputs outside [-1, 1] yield NaN.
func AsinInto ¶
func AsinInto[T Float](dst, a []T)
AsinInto sets dst[i] to the arcsine of a[i]. dst may alias a.
func Asinh ¶
func Asinh[T Float](a []T)
Asinh replaces every element with its inverse hyperbolic sine. It is defined for every finite input.
func AsinhInto ¶
func AsinhInto[T Float](dst, a []T)
AsinhInto sets dst[i] to the inverse hyperbolic sine of a[i]. dst may alias a.
func Atan2 ¶
func Atan2[T Float](a, b []T)
Atan2 replaces each element of a with the angle, in radians, of the point (b[i], a[i]) — that is, atan(a[i]/b[i]) resolved to the correct quadrant.
The argument order follows math.Atan2: y first, then x.
func Atan2Into ¶
func Atan2Into[T Float](dst, a, b []T)
Atan2Into sets dst[i] to the angle of the point (b[i], a[i]) in radians.
func AtanInto ¶
func AtanInto[T Float](dst, a []T)
AtanInto sets dst[i] to the arctangent of a[i]. dst may alias a.
func Atanh ¶
func Atanh[T Float](a []T)
Atanh replaces every element with its inverse hyperbolic tangent. It is the signed infinity at exactly -1 and 1, and NaN outside [-1, 1], as C99 specifies.
func AtanhInto ¶
func AtanhInto[T Float](dst, a []T)
AtanhInto sets dst[i] to the inverse hyperbolic tangent of a[i]: the signed infinity at exactly -1 and 1, NaN outside [-1, 1]. dst may alias a.
func AvailableTiers ¶
func AvailableTiers() []string
AvailableTiers returns the names of every instruction-set tier this CPU can execute, weakest first, always beginning with "scalar".
Use it to drive a benchmark or test matrix over the tiers a machine actually supports, rather than a hardcoded list that would be wrong elsewhere:
for _, t := range simd.AvailableTiers() {
exec.Command(...).Env = append(os.Environ(), "GOSIMD="+t)
}
Tiers masked out by SIMD_DISABLE are excluded.
func BFloat16ToFloat32Into ¶
BFloat16ToFloat32Into widens each bfloat16 in a into a float32 in dst.
Exact: every bfloat16 is a float32 with its low mantissa bits zero, so nothing is approximated, including NaN payloads and both infinities.
func Bartlett ¶
func Bartlett[T Float](dst []T)
Bartlett fills dst with a symmetric Bartlett (triangular) window.
This one is not a raised cosine, so it is a ramp and an absolute value rather than a transcendental — much the cheapest of the set.
func Base64Decode ¶
Base64Decode writes the decoded bytes of src into dst and returns how many it wrote, or -1 if src is not valid standard base64 or dst is too short.
It matches base64.StdEncoding.Decode, except in how it reports a problem: one number rather than a count and an error, because returning an error would allocate.
Validation is not a separate pass. Every character's value is folded together as it is decoded and one bit says whether anything was outside the alphabet, so a rejected input costs the same as an accepted one rather than a branch per character.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
dst := make([]byte, simd.Base64DecodedLen(len("aGVsbG8=")))
n := simd.Base64Decode(dst, "aGVsbG8=")
fmt.Println(string(dst[:n]))
}
Output: hello
func Base64DecodedLen ¶
Base64DecodedLen is the largest length Base64Decode can write for n input bytes, matching base64.StdEncoding.DecodedLen. The actual count is smaller when the input is padded, and is what Base64Decode returns.
func Base64Encode ¶
Base64Encode writes the standard base64 encoding of src into dst and returns how many bytes it wrote, or -1 if dst is shorter than Base64EncodedLen.
It matches base64.StdEncoding: the RFC 4648 alphabet, with padding. Nothing is allocated — which is the difference from encoding/base64's EncodeToString and the reason the length is the caller's to provide.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
src := []byte("any carnal pleasure")
dst := make([]byte, simd.Base64EncodedLen(len(src)))
n := simd.Base64Encode(dst, src)
fmt.Println(string(dst[:n]))
// Decode returns the number of bytes written, or -1 if the input is not
// valid base64 — there is no error value to ignore by accident.
back := make([]byte, simd.Base64DecodedLen(n))
m := simd.Base64Decode(back, dst[:n])
fmt.Println(string(back[:m]))
}
Output: YW55IGNhcm5hbCBwbGVhc3VyZQ== any carnal pleasure
func Base64EncodedLen ¶
Base64EncodedLen is the length Base64Encode needs in dst for n input bytes, matching base64.StdEncoding.EncodedLen.
func Bincount ¶
Bincount is BincountInto allocating the counts. n is the number of bins.
Example ¶
Bincount is the histogram of small integers: counts[v]++ for each v, skipping anything out of range rather than panicking.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
dice := []int32{3, 6, 3, 1, 6, 6, 2, 3}
fmt.Println(simd.Bincount(dice, 7)[1:]) // index 0 unused for a die
}
Output: [1 1 3 0 0 3]
func BincountInto ¶
BincountInto counts occurrences of each value in a, adding the count of value v to counts[v]. Values outside [0, len(counts)) are skipped.
counts is not zeroed first, so repeated calls accumulate. Clear it yourself if that is not what you want:
clear(counts) simd.BincountInto(counts, a)
func BitPackInto ¶
BitPackInto packs the low `bits` bits of each value in a into a dense bitstream in dst, least significant bit first and with no padding.
This is the representation Parquet, Arrow and Lucene use for an integer column after delta encoding: once the deltas are small, storing each in a full 32 bits is mostly zeroes. Pair it with DiffInto to produce the deltas and ZigzagEncodeInt32Into if they can be negative.
dst must have room for ceil(len(a)*bits/32) words. bits must be 1 to 32. It does nothing if either is not so, and it allocates nothing.
Example ¶
The columnar compression pipeline, in the order the pieces are meant to be used: differences first, then zigzag so small negatives stay small, then pack to the width that actually fits.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
a := []uint32{1, 2, 3, 4, 5, 6, 7, 0}
// Three bits hold 0..7, so eight values need 24 bits — one 32-bit word.
// Unpacking needs one word MORE than packing: a value whose bits straddle a
// word boundary reads the next word, and the last one straddles whenever
// the total is not a multiple of 32. Size for the reader.
packed := make([]uint32, (len(a)*3+31)/32+1)
simd.BitPackInto(packed, a, 3)
back := make([]uint32, len(a))
simd.BitUnpackInto(back, packed, 3)
fmt.Println(back)
}
Output: [1 2 3 4 5 6 7 0]
func BitUnpackInto ¶
BitUnpackInto is the inverse: it reads len(dst) values of `bits` bits each from the bitstream in a.
The number of values is taken from len(dst), because a bitstream does not record how many values it holds — the caller knows, and a packed block in any real format carries the count beside it.
a must hold ceil(len(dst)*bits/32)+1 words. The extra word is not slack: a value whose bits straddle a word boundary reads the next word, and the last value straddles whenever len(dst)*bits is not a multiple of 32. Requiring it in the guard is what lets the kernel read unconditionally rather than branching on every element.
func Blackman ¶
func Blackman[T Float](dst []T)
Blackman fills dst with a symmetric Blackman window.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
w := make([]float64, 3)
simd.Blackman(w)
fmt.Printf("%.4f %.4f %.4f\n", w[0], w[1], w[2])
}
Output: -0.0000 1.0000 -0.0000
func BottomK ¶
BottomK is BottomKInto allocating both the destination and the scratch.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
fmt.Println(simd.BottomK([]float64{5, 1, 4, 2, 3}, 3))
}
Output: [1 2 3]
func BottomKInto ¶
BottomKInto is TopKInto for the k smallest, in ascending order.
func ByteSwapInto ¶
func ByteSwapInto[T Integer](dst, a []T)
ByteSwapInto writes each element of a with its bytes in reverse order to dst, which is the byte-order conversion a network or file format needs over a whole slice.
It does nothing for the eight-bit types, where a byte is its own reversal and there is no kernel.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
dst := make([]uint32, 1)
simd.ByteSwapInto(dst, []uint32{0x11223344})
fmt.Printf("%#08x\n", dst[0])
}
Output: 0x44332211
func Cbrt ¶
func Cbrt[T Float](a []T)
Cbrt replaces every element with its cube root. Negative inputs are fine.
func CbrtInto ¶
func CbrtInto[T Float](dst, a []T)
CbrtInto sets dst[i] to the cube root of a[i]. dst may alias a.
func CeilInto ¶
func CeilInto[T Float](dst, a []T)
CeilInto sets dst[i] to a[i] rounded up. dst may alias a.
func Clamp ¶
func Clamp[T Number](a []T, lo, hi T)
Clamp limits every element to the range [lo, hi].
For floats, NaN inputs propagate rather than being clamped.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
a := []float64{-5, 0.5, 99}
simd.Clamp(a, 0, 1)
fmt.Println(a)
}
Output: [0 0.5 1]
func ClampInto ¶
func ClampInto[T Number](dst, a []T, lo, hi T)
ClampInto sets dst[i] to a[i] limited to [lo, hi]. dst may alias a.
func CommonPrefixLen ¶
CommonPrefixLen returns how many leading bytes a and b share, at most the length of the shorter.
This is Compare without the ordering, and it is the operation suffix-array construction, trie descent and the LCP array spend their time in. A byte-at-a-time loop pays a compare and a branch for every byte the two share; this reduces sixty-four at a time to a single "did anything differ", which is the case worth vectorizing precisely because a long shared prefix is what these callers usually have.
Bytes, not runes: the answer may land in the middle of a UTF-8 sequence. A caller that needs a rune boundary should back up to one, which is a constant-time step from any byte index.
Example ¶
CommonPrefixLen is Compare without the ordering: how far two slices agree. It is the inner loop of suffix-array construction and trie descent.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
fmt.Println(simd.CommonPrefixLen("/usr/local/bin", "/usr/local/lib"))
}
Output: 11
func Compare ¶
Compare returns -1, 0 or +1 ordering a before, equal to, or after b, lexicographically by content and then by length.
It matches bytes.Compare.
func CompressInto ¶
CompressInto writes the elements of src whose mask entry is true into dst, in order, and returns how many it wrote.
dst is not resized; the return value is the meaningful length, so the usual call is followed by a reslice to it. If dst is too short to hold every match the result is truncated rather than a panic, which is deliberate — dst is normally sized from an estimate of how many will match, and an estimate that comes in low should cost a short answer the caller can detect, not a crash.
The mask is read as far as the shorter of src and mask.
This is accelerated only on AVX-512 and SVE2, and that is not a gap waiting to be filled. Where dst[k] goes depends on how many earlier elements matched, so the iterations are genuinely serial and no compiler on any target vectorizes the loop. The two instruction sets that have a compress instruction break the dependency in hardware; the other seven run the portable loop, which is the same loop their compilers would have produced.
Example ¶
Filtering is two steps on purpose: a comparison writes the mask, and CompressInto packs the elements that passed. Splitting it that way is what lets one Compress serve every predicate, including ones this library has never heard of, instead of shipping GreaterThanFilter and the rest.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
a := []float64{-3, 7, -1, 4, 0, 9}
mask := make([]bool, len(a))
simd.GreaterScalarInto(mask, a, 0) // which elements pass
out := make([]float64, len(a))
n := simd.CompressInto(out, a, mask) // pack the ones that did
fmt.Println(out[:n])
}
Output: [7 4 9]
func ConjComplex ¶
func ConjComplex[C Complex](a []C)
ConjComplex replaces each element with its complex conjugate.
func ConjComplexInto ¶
func ConjComplexInto[C Complex](dst, a []C)
ConjComplexInto sets dst[i] to the conjugate of a[i]. dst may alias a.
func ContainsAny ¶
ContainsAny reports whether any byte of chars is within s.
func ContainsByte ¶
ContainsByte reports whether c is within s.
func ContainsFoldASCII ¶
ContainsFoldASCII reports whether needle occurs in haystack under ASCII case folding. It is IndexFoldASCII >= 0.
func ConvertInto ¶
func ConvertInto[D, S Number](dst []D, src []S)
ConvertInto converts each element of src to the destination element type and writes it to dst, processing min(len(dst), len(src)) elements.
It covers every pair among float32, float64, int32 and int64:
simd.ConvertInto(f64, f32) // widen simd.ConvertInto(f32, f64) // narrow, rounding to nearest even simd.ConvertInto(i32, f64) // truncate toward zero simd.ConvertInto(f64, i64) // widen, rounding if beyond 2^53
The conversions follow Go's own rules, which is to say the hardware's: float to integer truncates toward zero, and the result is implementation-defined if the value does not fit — so range-check first with Clamp if the input is untrusted.
This is a single generic loop rather than a per-pair kernel. Widening and narowing conversions are single vector instructions (VCVTPS2PD and friends) and will get their own kernels; the signature will not change when they do.
func ConvolveFull ¶
func ConvolveFull[T Float](a, b []T) []T
ConvolveFull is ConvolveFullInto allocating the destination.
Example ¶
An FIR filter is a convolution, which is why this package has no dedicated FIR function. The taps are the kernel; ConvolveFullInto picks direct or frequency-domain by a measured crossover. Here a 4-tap moving average smooths a step — the quarters are exact in float64, so the output is too.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
signal := []float64{0, 0, 0, 4, 4, 4, 4, 4}
taps := []float64{0.25, 0.25, 0.25, 0.25}
full := simd.ConvolveFull(signal, taps)
// For a causal filter, output[i] depends on samples up to i, which is
// exactly the first len(signal) entries of the full convolution.
fmt.Println(full[:len(signal)])
}
Output: [0 0 0 1 2 3 4 4]
func ConvolveFullInto ¶
func ConvolveFullInto[T Float](dst, a, b []T)
ConvolveFullInto writes the full linear convolution of a and b to dst, which must be at least len(a)+len(b)-1 long.
It does nothing if dst is too short, if either input is empty, or if dst overlaps either input.
The algorithm is chosen by the shorter input's length: direct below convCutoff taps and frequency-domain above it, for the reason given there.
Example ¶
ConvolveFullInto picks a direct or FFT convolution by a measured crossover, so the caller does not have to know where it is.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
a := []float64{1, 2, 3}
b := []float64{1, 1}
dst := make([]float64, len(a)+len(b)-1)
simd.ConvolveFullInto(dst, a, b)
fmt.Println(dst)
}
Output: [1 3 5 3]
func ConvolveInto ¶
func ConvolveInto[T Number](dst, sig, ker []T)
ConvolveInto writes the discrete convolution of sig with ker.
It produces len(sig)-len(ker)+1 elements: the region where the kernel fully overlaps the signal, with no edge padding. Size dst accordingly, or slice it.
func CorrelateFull ¶
func CorrelateFull[T Float](a, b []T) []T
CorrelateFull is CorrelateFullInto allocating both the destination and the scratch.
func CorrelateFullInto ¶
func CorrelateFullInto[T Float](dst, a, b, scratch []T)
CorrelateFullInto writes the full cross-correlation of a and b to dst, which must be at least len(a)+len(b)-1 long.
Correlation is convolution with one operand reversed, so this reverses b into scratch and convolves. scratch must be at least len(b) long; a shorter one is replaced by an allocation.
dst[k] is the overlap at lag k-(len(b)-1), so the zero lag is at index len(b)-1 — the same indexing numpy.correlate uses in "full" mode.
func CorrelateInto ¶
func CorrelateInto[T Number](dst, sig, ker []T)
CorrelateInto writes the cross-correlation of sig with ker.
This is ConvolveInto without reversing the kernel, which is what you want for template matching and for filters whose taps are already in signal order. It also produces len(sig)-len(ker)+1 elements.
func Correlation ¶
func Correlation[T Float](x, y []T) T
Correlation returns the Pearson correlation coefficient of x and y, in [-1, 1]. It returns zero if either input has no variation, since the coefficient is undefined there.
func CosInto ¶
func CosInto[T Float](dst, a []T)
CosInto sets dst[i] to the cosine of a[i]. dst may alias a.
func CoshInto ¶
func CoshInto[T Float](dst, a []T)
CoshInto sets dst[i] to the hyperbolic cosine of a[i]. dst may alias a.
func CosineSimilarity ¶
func CosineSimilarity[T Float](a, b []T) T
CosineSimilarity returns the cosine of the angle between a and b: their dot product divided by the product of their lengths.
The result lies in [-1, 1], where 1 means the vectors point the same way. If either vector is all zeros the angle is undefined and the result is 0.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
fmt.Println(simd.CosineSimilarity([]float64{1, 0}, []float64{0, 1}))
}
Output: 0
func Count ¶
Count returns the number of non-overlapping occurrences of needle in haystack.
It matches bytes.Count and strings.Count, including for the empty needle, which counts the runes of haystack plus one. That case is answered here rather than in a kernel, because it is a question about UTF-8 rather than about bytes.
func CountAny ¶
CountAny returns how many bytes of s are in chars.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
fmt.Println(simd.CountAny("a,b;c,d", ",;"))
}
Output: 3
func CountFoldASCII ¶
CountFoldASCII counts non-overlapping occurrences of needle in haystack under ASCII case folding, matching bytes.Count's overlap rule.
An empty needle returns the rune-independent answer len(haystack)+1, as bytes.Count does for an empty separator on ASCII input.
func CountNaN ¶
CountNaN reports how many elements of a are NaN.
mask is working space of at least len(a); a shorter one is replaced by an allocation, so pass one in a loop.
Example ¶
The mask is supplied rather than allocated, so the whole NaN family runs without touching the heap.
package main
import (
"fmt"
"math"
"github.com/sebishogun/simd"
)
func main() {
a := []float64{1, math.NaN(), 3, math.NaN()}
fmt.Println(simd.CountNaN(a, make([]bool, len(a))))
}
Output: 2
func Covariance ¶
func Covariance[T Float](x, y []T) T
Covariance returns the population covariance of x and y over min(len(x), len(y)) elements, or zero for fewer than two.
func CumMax ¶
func CumMax[T Number](a []T)
CumMax replaces every element with the largest value seen so far.
func CumMaxInto ¶
func CumMaxInto[T Number](dst, a []T)
CumMaxInto writes the running maximum of a into dst. dst may alias a.
func CumMin ¶
func CumMin[T Number](a []T)
CumMin replaces every element with the smallest value seen so far.
func CumMinInto ¶
func CumMinInto[T Number](dst, a []T)
CumMinInto writes the running minimum of a into dst. dst may alias a.
func CumProd ¶
func CumProd[T Number](a []T)
CumProd replaces every element with the running product up to and including it.
Accelerated for int32 and portable everywhere else, which is the one place in this family where the two halves of the rule pull apart. Two's-complement multiplication is associative — wrapping does not change that, since the arithmetic is exact in Z/2^32 — so the blocked scan computes bit for bit what the serial loop does, and a three-cycle multiply leaves enough latency to be worth hiding: 2.04x, verified over four million deliberately overflowing values.
int64 is portable because there is no 64-bit vector multiply below AVX-512DQ (0.67x), and float is portable for the reason CumSum gives. FastCumProd is the opt-in float version.
func CumProdInto ¶
func CumProdInto[T Number](dst, a []T)
CumProdInto writes the running products of a into dst. dst may alias a.
func CumSum ¶
func CumSum[T Number](a []T)
CumSum replaces every element with the running total up to and including it.
This one is permanently portable, and that is a decision rather than a gap ¶
There is no accelerated CumSum on any architecture and there will not be. Each output depends on the one before it, so the only way to vectorize a scan is to regroup the arithmetic — and unlike a reduction, every partial result is written to dst, so the regrouping is visible in the answer. This package's contract is that the accelerated and portable paths agree bit for bit, which forbids exactly that.
For integers the contract is not the obstacle, since integer addition is associative; the measurement is. A blocked scan replaces a chain of dependent adds with a shuffle chain, which only pays when the serial chain is latency-bound, and a one-cycle add is not: int32 measured 980µs serial against 1082µs blocked, and int64 1441µs against 2165µs.
FastCumSum is the opt-in float version, which drops agreement with a naive serial loop — and is, measurably, closer to the true sum. CumMin and CumMax are accelerated for integers, because minimum and maximum are associative and regrouping them changes nothing at all.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
a := []float64{1, 2, 3, 4}
simd.CumSum(a)
fmt.Println(a)
}
Output: [1 3 6 10]
func CumSumInto ¶
func CumSumInto[T Number](dst, a []T)
CumSumInto writes the running totals of a into dst. dst may alias a.
func DequantizeInt8 ¶
DequantizeInt8 is the inverse: x = (q - zeroPoint) * scale.
func DequantizePerChannelInt8 ¶
func DequantizePerChannelInt8(dst []float32, a []int8, scale []float32, zeroPoint []int32, channels, inner int)
DequantizePerChannelInt8 is the inverse of QuantizePerChannelInt8.
func DequantizePerChannelUint8 ¶
func DequantizePerChannelUint8(dst []float32, a []uint8, scale []float32, zeroPoint []int32, channels, inner int)
DequantizePerChannelUint8 is the inverse of QuantizePerChannelUint8.
func DequantizeUint8 ¶
DequantizeUint8 is the inverse of QuantizeUint8.
func Describe ¶
func Describe() string
Describe returns a one-line summary of instruction-set selection: the architecture, the chosen tier, every tier the CPU supports, and whether GOSIMD or SIMD_DISABLE altered the outcome.
It is intended for logs and bug reports.
func DiffInto ¶
func DiffInto[T Number](dst, a []T)
DiffInto writes the successive differences of a into dst: dst[i] = a[i+1] - a[i].
It produces one fewer element than a has, so size dst accordingly. dst may alias a.
Example ¶
DiffInto writes successive differences, so it produces one fewer element than it consumes.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
dst := make([]float64, 3)
simd.DiffInto(dst, []float64{1, 3, 6, 10})
fmt.Println(dst)
}
Output: [2 3 4]
func DifferenceInto ¶
DifferenceInto writes the elements of a that are not in b to dst, in ascending order, and returns how many there were.
a and b must be sorted ascending with no duplicates. dst must have room for len(a) elements; it panics otherwise, for the reason IntersectInto gives.
Example ¶
DifferenceInto keeps the elements of a that are not in b.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
a := []int32{1, 3, 5, 7, 9}
b := []int32{3, 4, 5, 6, 9}
dst := make([]int32, len(a))
n := simd.DifferenceInto(dst, a, b)
fmt.Println(dst[:n])
}
Output: [1 7]
func Distance ¶
func Distance[T Float](a, b []T) T
Distance returns the Euclidean distance between a and b over min(len(a), len(b)) elements.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
fmt.Println(simd.Distance([]float64{0, 0}, []float64{3, 4}))
}
Output: 5
func Div ¶
func Div[T Float](a, b []T)
Div divides a by b, elementwise: a[i] /= b[i].
Division by zero follows IEEE 754 and yields ±Inf or NaN; it does not panic.
func DivComplex ¶
func DivComplex[C Complex](a, b []C)
DivComplex divides a by b elementwise: a[i] /= b[i].
The quotient is formed by Smith's method, which divides through by the larger component first, so an intermediate cannot overflow for a result that is representable.
func DivComplexInto ¶
func DivComplexInto[C Complex](dst, a, b []C)
DivComplexInto sets dst[i] = a[i] / b[i]. dst may alias a or b.
func DivInto ¶
func DivInto[T Float](dst, a, b []T)
DivInto sets dst[i] = a[i] / b[i]. dst may alias a or b.
func DivScalar ¶
func DivScalar[T Number](a []T, s T)
DivScalar divides every element by s: a[i] /= s.
It really divides rather than multiplying by a precomputed reciprocal, which is slower but exact: 3*(1/5) is 0.6000000000000001 where 3/5 is 0.6. Integer division truncates toward zero and panics on a zero divisor.
func DivScalarInto ¶
func DivScalarInto[T Number](dst, a []T, s T)
DivScalarInto sets dst[i] = a[i] / s. dst may alias a. See DivScalar.
func Dot ¶
func Dot[T Number](a, b []T) T
Dot returns the dot product of a and b over min(len(a), len(b)) elements, or zero if either is empty.
The multiplication and the addition round separately; they are not fused, which matches a plain scalar loop.
func DotComplex ¶
func DotComplex[C Complex](a, b []C) C
DotComplex returns the bilinear product, the sum of a[i]*b[i].
This is not the inner product of a complex vector space; see DotComplexConj for that one. Both are offered because both are wanted and neither is obviously what "dot" should mean.
func DotComplexConj ¶
func DotComplexConj[C Complex](a, b []C) C
DotComplexConj returns the Hermitian inner product, the sum of conj(a[i])*b[i]. This is the one that makes DotComplexConj(a, a) the squared norm, and the one linear algebra usually means.
func EMAInto ¶
func EMAInto[T Number](dst, a []T, alpha T)
EMAInto writes the exponentially weighted moving average of a, where alpha is the weight given to each new sample: dst[i] = alpha*a[i] + (1-alpha)*dst[i-1].
This one is permanently portable, on every architecture, and the reason has nothing to do with any instruction set. Each output depends on the one before it, so the only way to vectorize it is to regroup the arithmetic — and every partial result is written to dst, so the regrouping is visible in the answer. The package's contract is that the accelerated and portable paths agree bit for bit, which forbids exactly that. CumSum and CumProd are portable for the same reason; CumMin and CumMax are not, because minimum and maximum are associative and regrouping them changes nothing.
It is here because it belongs next to MovingAverageInto, not for speed. A FastEMA that truncates the geometric tail and states its error bound would vectorize, and is the shape any future version of this would take.
func Equal ¶
Equal reports whether a and b are the same length and hold the same bytes.
It matches bytes.Equal. A nil argument is equivalent to an empty slice.
func EqualFoldASCII ¶
EqualFoldASCII reports whether a and b are equal ignoring ASCII case.
Unlike bytes.EqualFold it does not perform Unicode case folding, which makes it both faster and wrong for non-ASCII input — use it for protocol tokens (HTTP headers, keywords, hex digits), not for user-facing text.
Example ¶
Only ASCII letters are folded, which is what makes it safe to run over UTF-8: every continuation byte is 0x80 or above and outside both ranges.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
fmt.Println(simd.EqualFoldASCII("Content-Type", "content-type"))
}
Output: true
func EqualInto ¶
EqualInto writes whether a[i] == b[i]. NaN compares equal to nothing, itself included.
func EqualScalarInto ¶
EqualScalarInto writes whether a[i] == v.
func Erf ¶
func Erf[T Float](a []T)
Erf is the error function, in place. It carries an absolute error bound of 1.4e-7 rather than a ULP one; see ErfInto.
func ErfInto ¶
func ErfInto[T Float](dst, a []T)
ErfInto writes the error function of each element of a to dst.
The bound is 1.4e-7 ABSOLUTE, measured over [-6, 6], not a ULP bound. That is the right form of claim here: erf is bounded by 1 and its interesting range is where it is O(1), so an absolute bound is what a caller can use. It comes from the Abramowitz and Stegun 7.1.26 rational form, which is a float32-grade approximation — for float64 the answer is accurate to about seven digits, not sixteen.
func Erfc ¶
func Erfc[T Float](a []T)
Erfc is the complementary error function, in place.
This one has no kernel on any architecture and runs Go's math.Erfc, which is correctly rounded. That is deliberate: see ErfcInto.
func ErfcInto ¶
func ErfcInto[T Float](dst, a []T)
ErfcInto writes the complementary error function of each element of a to dst.
This has no kernel and runs Go's math.Erfc on every architecture. The decision is measured, not conservative: erfc's whole purpose is the tail, where it decays to nothing, and the same 1.4e-7 absolute error that is fine for erf is a RELATIVE error of 1.2e-2 at x=6, where erfc is about 2e-17. An operation whose only interesting regime is wrong to one percent is worse than no kernel at all.
If you want the fast approximation over the range where erfc is O(1), it is 1-ErfInto, and writing that yourself makes the trade explicit.
func EulerStep ¶
func EulerStep[T Float](y, dydt []T, t, h T, f Derivative[T])
EulerStep advances y by one step of size h using the forward Euler method.
It is first order, so its error per step is proportional to h². Use it when the derivative is expensive and accuracy is not critical; prefer RK4Step otherwise.
dydt is scratch the caller owns, the same length as y.
func Exp ¶
func Exp[T Float](a []T)
Exp replaces every element with e raised to it.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
a := []float64{0, 1}
simd.Exp(a)
fmt.Printf("%.4f %.4f\n", a[0], a[1])
}
Output: 1.0000 2.7183
func ExpandInto ¶
ExpandInto is the inverse of CompressInto: it walks mask, and wherever it is true takes the next element of src. It returns how many elements of src were consumed.
Positions where the mask is false are left untouched, so filling dst with a default first and expanding over it is the way to scatter a packed buffer back into a full-width one.
This one is portable on every architecture, and unlike Compress that is permanent rather than pending. Compression's serial half is the store, which a compress instruction fixes; expansion's is the load, which it does not. Both AVX-512 and SVE2 compile this to the same scalar loop everything else gets, so there is no kernel to ship.
func Expm1 ¶
func Expm1[T Float](a []T)
Expm1 replaces every element x with e**x - 1.
Use it instead of Exp followed by subtracting one when x is near zero, where that form loses almost all its significant digits.
func Expm1Into ¶
func Expm1Into[T Float](dst, a []T)
Expm1Into sets dst[i] = e**a[i] - 1, accurately near zero. dst may alias a.
func FFT ¶
func FFT(a []complex128) []complex128
FFT returns the discrete Fourier transform of a, allocating both the plan and the result. len(a) must be a power of two; it returns nil otherwise.
Use NewFFTPlan and FFTInto in a loop — building the plan is the expensive part and it depends only on the length.
func FFTInto ¶
func FFTInto(p *FFTPlan, dst, src []complex128)
FFTInto writes the discrete Fourier transform of src to dst.
dst and src must both be at least p.Len() long and must not overlap. src is not modified.
The sign convention is the usual one for a forward transform, exp(-2*pi*i*k* n/N), matching numpy.fft and gonum. IFFTInto inverts it exactly, including the 1/N scaling.
Example ¶
The plan holds the twiddle factors, so a loop over many transforms of the same size computes them once.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
p := simd.NewFFTPlan(4)
src := []complex128{1, 1, 1, 1}
dst := make([]complex128, 4)
simd.FFTInto(p, dst, src)
fmt.Println(real(dst[0]), real(dst[1]))
}
Output: 4 0
func FastAcos ¶
func FastAcos[T Float](a []T)
FastAcos replaces every element with its arccosine, to within 3.5 ULP. See Acos for the accurate form.
func FastAcosInto ¶
func FastAcosInto[T Float](dst, a []T)
FastAcosInto writes the result into dst. dst may alias a.
func FastAcosh ¶
func FastAcosh[T Float](a []T)
FastAcosh replaces every element with its inverse hyperbolic cosine, to within 3.5 ULP. It is NaN below 1. See Acosh for the accurate form.
func FastAcoshInto ¶
func FastAcoshInto[T Float](dst, a []T)
FastAcoshInto writes the result into dst. dst may alias a.
func FastAsin ¶
func FastAsin[T Float](a []T)
FastAsin replaces every element with its arcsine, to within 3.5 ULP. See Asin for the accurate form.
func FastAsinInto ¶
func FastAsinInto[T Float](dst, a []T)
FastAsinInto writes the result into dst. dst may alias a.
func FastAsinh ¶
func FastAsinh[T Float](a []T)
FastAsinh replaces every element with its inverse hyperbolic sine, to within 3.5 ULP. See Asinh for the accurate form.
func FastAsinhInto ¶
func FastAsinhInto[T Float](dst, a []T)
FastAsinhInto writes the result into dst. dst may alias a.
func FastAtan ¶
func FastAtan[T Float](a []T)
FastAtan replaces every element with its arctangent, to within 3.5 ULP. See Atan for the accurate form.
func FastAtan2 ¶
func FastAtan2[T Float](a, b []T)
FastAtan2 sets a[i] to atan(a[i]/b[i]) in the correct quadrant, to within 3.5 ULP. See Atan2 for the accurate form.
func FastAtan2Into ¶
func FastAtan2Into[T Float](dst, a, b []T)
FastAtan2Into writes the result into dst. dst may alias a or b.
func FastAtanInto ¶
func FastAtanInto[T Float](dst, a []T)
FastAtanInto writes the result into dst. dst may alias a.
func FastAtanh ¶
func FastAtanh[T Float](a []T)
FastAtanh replaces every element with its inverse hyperbolic tangent, to within 3.5 ULP. It is the signed infinity at exactly -1 and 1, and NaN outside [-1, 1]. See Atanh for the accurate form.
func FastAtanhInto ¶
func FastAtanhInto[T Float](dst, a []T)
FastAtanhInto writes the result into dst. dst may alias a.
func FastCbrt ¶
func FastCbrt[T Float](a []T)
FastCbrt replaces every element with its cube root, to within 3.5 ULP. See Cbrt for the accurate form.
func FastCbrtInto ¶
func FastCbrtInto[T Float](dst, a []T)
FastCbrtInto writes the result into dst. dst may alias a.
func FastCos ¶
func FastCos[T Float](a []T)
FastCos replaces every element with its cosine, to within 3.5 ULP. See Cos for the accurate form.
func FastCosInto ¶
func FastCosInto[T Float](dst, a []T)
FastCosInto writes the result into dst. dst may alias a.
func FastCosh ¶
func FastCosh[T Float](a []T)
FastCosh replaces every element with its hyperbolic cosine, to within 3.5 ULP. See Cosh for the accurate form.
func FastCoshInto ¶
func FastCoshInto[T Float](dst, a []T)
FastCoshInto writes the result into dst. dst may alias a.
func FastCumProd ¶
func FastCumProd[T Float](a []T)
FastCumProd is CumProd grouped for the vector unit, trading agreement with a serial loop exactly as FastCumSum does.
Measured at four million elements against CumProd: 3.65x on float32 and 1.76x on float64 — the largest speedup in this family, and unlike FastCumSum it is worth having for both types. A floating-point multiply has the longest serial latency of the four combines, so there is the most to hide behind the shuffles.
Integer CumProd needs no Fast form: two's-complement multiplication is associative, so CumProd on int32 is already this algorithm and already bit-identical to the serial loop, at 2.17x.
func FastCumProdInto ¶
func FastCumProdInto[T Float](dst, a []T)
FastCumProdInto writes the running products of a into dst. dst may alias a.
func FastCumSum ¶
func FastCumSum[T Float](a []T)
FastCumSum is CumSum with the prefix sums grouped for the vector unit.
What is traded ¶
A prefix scan writes every partial result, so its grouping is observable: the serial loop computes ((a0+a1)+a2) where the vector form computes (a0+(a1+a2)), and floating-point addition is not associative, so the two differ in the last place. CumSum therefore stays serial, permanently, and this is the opt-in that does not.
What is NOT traded is agreement between machines. The block is sixteen elements for float32 and eight for float64, on every tier and in the portable path, so this returns identical bits on a Graviton, an AVX-512 box and `-tags purego`. Only agreement with a naive loop is given up.
And it is not less accurate ¶
The obvious reading of Fast* — as it is for the transcendentals, which really do trade 1.0 ULP for 3.5 — is wrong here. Blocked summation has O(log n) error growth where a running accumulator has O(n), so measured against a long-double scan of a million values this is *closer* to the true result than CumSum on every corpus tried: 680,000 of a million elements closer on uniform positive input, and on the case that breaks serial accumulation — 1e16 followed by a million ones, where the running total cannot represent the increment — a mean absolute error of 1.0 against CumSum's 5.0e+05.
float64 is the serial loop, deliberately ¶
Measured at four million elements, against CumSum:
float32 1669 us -> 1347 us 1.24x on avx512, and 2.59x on sse2 float64 1745 us -> 1909 us 0.91x SLOWER, so it is not used
Eight doubles fill one AVX-512 register, so the shift steps become cross-lane permutes and there is not enough serial latency to hide behind them. float32 has sixteen lanes and wins. Rather than offer a Fast function that is slower than the plain one on the tier most machines select, FastCumSum on float64 IS CumSum — same bits, same speed, no surprise. The float32 path is the blocked scan and everything above applies to it.
The wider the vector, the less this helps, which is why sse2 is the fastest tier for it. That is unusual enough to be worth stating: the cost of a log-shift scan is the shuffle, and shuffles get more expensive with width while the work per element does not.
func FastCumSumInto ¶
func FastCumSumInto[T Float](dst, a []T)
FastCumSumInto writes the running totals of a into dst. dst may alias a. See FastCumSum for what it trades.
func FastErf ¶
func FastErf[T Float](a []T)
FastErf replaces every element with the error function of that element.
Unlike the rest of this tier it carries an absolute error bound rather than a ULP one, for the reason Erf gives: a rational approximation to erf is accurate in absolute terms and not in relative ones near its zero.
func FastErfInto ¶
func FastErfInto[T Float](dst, a []T)
FastErfInto writes the result into dst. dst may alias a.
func FastExp ¶
func FastExp[T Float](a []T)
FastExp replaces every element with e raised to it, to within 3.5 ULP. See Exp for the accurate form.
func FastExp2 ¶
func FastExp2[T Float](a []T)
FastExp2 replaces every element with 2 raised to it, to within 3.5 ULP. See Exp2 for the accurate form.
func FastExp2Into ¶
func FastExp2Into[T Float](dst, a []T)
FastExp2Into writes the result into dst. dst may alias a.
func FastExpInto ¶
func FastExpInto[T Float](dst, a []T)
FastExpInto writes the result into dst. dst may alias a.
func FastExpm1 ¶
func FastExpm1[T Float](a []T)
FastExpm1 replaces every element with e raised to it, minus one, to within 3.5 ULP. See Expm1 for the accurate form.
func FastExpm1Into ¶
func FastExpm1Into[T Float](dst, a []T)
FastExpm1Into writes the result into dst. dst may alias a.
func FastHypot ¶
func FastHypot[T Float](a, b []T)
FastHypot sets a[i] to the length of the hypotenuse with sides a[i] and b[i], to within 3.5 ULP. See Hypot for the accurate form.
func FastHypotInto ¶
func FastHypotInto[T Float](dst, a, b []T)
FastHypotInto writes the result into dst. dst may alias a or b.
func FastLog ¶
func FastLog[T Float](a []T)
FastLog replaces every element with its natural logarithm, to within 3.5 ULP. See Log for the accurate form.
func FastLog1p ¶
func FastLog1p[T Float](a []T)
FastLog1p replaces every element with the natural logarithm of one plus it, to within 3.5 ULP. See Log1p for the accurate form.
func FastLog1pInto ¶
func FastLog1pInto[T Float](dst, a []T)
FastLog1pInto writes the result into dst. dst may alias a.
func FastLog2 ¶
func FastLog2[T Float](a []T)
FastLog2 replaces every element with its base-2 logarithm, to within 3.5 ULP. See Log2 for the accurate form.
func FastLog2Into ¶
func FastLog2Into[T Float](dst, a []T)
FastLog2Into writes the result into dst. dst may alias a.
func FastLog10 ¶
func FastLog10[T Float](a []T)
FastLog10 replaces every element with its base-10 logarithm, to within 3.5 ULP. See Log10 for the accurate form.
func FastLog10Into ¶
func FastLog10Into[T Float](dst, a []T)
FastLog10Into writes the result into dst. dst may alias a.
func FastLogInto ¶
func FastLogInto[T Float](dst, a []T)
FastLogInto writes the result into dst. dst may alias a.
func FastPow ¶
func FastPow[T Float](a, b []T)
FastPow sets a[i] to a[i] raised to the power b[i], to within 3.5 ULP. See Pow for the accurate form.
func FastPowInto ¶
func FastPowInto[T Float](dst, a, b []T)
FastPowInto writes the result into dst. dst may alias a or b.
func FastSigmoid ¶
func FastSigmoid[T Float](a []T)
FastSigmoid replaces every element with its logistic sigmoid, to within 3.5 ULP. See Sigmoid for the accurate form.
func FastSigmoidInto ¶
func FastSigmoidInto[T Float](dst, a []T)
FastSigmoidInto writes the result into dst. dst may alias a.
func FastSin ¶
func FastSin[T Float](a []T)
FastSin replaces every element with its sine, to within 3.5 ULP. See Sin for the accurate form.
func FastSinInto ¶
func FastSinInto[T Float](dst, a []T)
FastSinInto writes the result into dst. dst may alias a.
func FastSinh ¶
func FastSinh[T Float](a []T)
FastSinh replaces every element with its hyperbolic sine, to within 3.5 ULP. See Sinh for the accurate form.
func FastSinhInto ¶
func FastSinhInto[T Float](dst, a []T)
FastSinhInto writes the result into dst. dst may alias a.
func FastTan ¶
func FastTan[T Float](a []T)
FastTan replaces every element with its tangent, to within 3.5 ULP. See Tan for the accurate form.
func FastTanInto ¶
func FastTanInto[T Float](dst, a []T)
FastTanInto writes the result into dst. dst may alias a.
func FastTanh ¶
func FastTanh[T Float](a []T)
FastTanh replaces every element with its hyperbolic tangent, to within 3.5 ULP. See Tanh for the accurate form.
func FastTanhInto ¶
func FastTanhInto[T Float](dst, a []T)
FastTanhInto writes the result into dst. dst may alias a.
func FilterInto ¶
FilterInto keeps the elements of src for which pred returns true, writing them into dst and returning how many.
The predicate is a Go closure called once per element, so this is the convenient form rather than the fast one — a call per element defeats the vector unit entirely. It is here because a filter with an arbitrary condition is a real thing to want, and writing it out by hand is worse. When the predicate is a comparison against a constant, build the mask with the vectorized comparison in compare.go and call CompressInto instead; that is the path this function exists to be slower than.
Example ¶
FilterInto takes an arbitrary Go predicate, which is convenient and not fast: the call per element cannot be vectorized. Use a comparison plus CompressInto when it matters.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
dst := make([]float64, 5)
n := simd.FilterInto(dst, []float64{1, -2, 3, -4, 5},
func(v float64) bool { return v > 0 })
fmt.Println(dst[:n])
}
Output: [1 3 5]
func Float8E4M3ToFloat32Into ¶
Float8E4M3ToFloat32Into widens OCP e4m3 to float32: 1 sign bit, 4 exponent bits, 3 mantissa bits, bias 7. This is the format weights and activations use.
**e4m3 has no infinity.** Exponent 1111 with mantissa 111 is the only NaN encoding and every other value at that exponent is finite, which is what gives the format its 448 maximum rather than 240. That is the OCP OFP8 definition and NVIDIA's e4m3fn — the "fn" is finite-not-nan — and it is what every shipping implementation does. Compare Float8E5M2ToFloat32Into, which is IEEE-shaped and does have infinities.
It writes min(len(dst), len(a)) elements and allocates nothing.
func Float8E5M2ToFloat32Into ¶
Float8E5M2ToFloat32Into widens e5m2 to float32: 1 sign bit, 5 exponent bits, 2 mantissa bits, bias 15. This is the format gradients use, and it trades e4m3's extra mantissa bit for float16's exponent range.
Unlike e4m3 this one is IEEE-shaped: it has infinities and NaNs where a float16 has them.
func Float16ToFloat32Into ¶
Float16ToFloat32Into widens each float16 in a into a float32 in dst.
Exact, including denormals, which are renormalized rather than flushed.
Example ¶
float16 and bfloat16 are storage formats: half the bytes, and the conversion is what the vector unit is for.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
dst := make([]float32, 2)
simd.Float16ToFloat32Into(dst, []uint16{0x3c00, 0x4000}) // 1.0, 2.0
fmt.Println(dst)
}
Output: [1 2]
func Float32ToBFloat16Into ¶
Float32ToBFloat16Into narrows each float32 in a into a bfloat16 in dst, rounding to nearest even.
Rounding rather than truncating, which costs one add and is the difference between a rounding error and a drift: truncation is biased, and a bias applied to every weight in a network accumulates. A NaN is passed through quieted rather than rounded, because rounding can carry into the exponent and turn a NaN with a low mantissa into an infinity.
func Float32ToFloat8E4M3Into ¶
Float32ToFloat8E4M3Into narrows to OCP e4m3, rounding to nearest even.
Because the format has no infinity, values above 448 in magnitude saturate to ±448 rather than becoming one, and an input infinity saturates too. A NaN stays a NaN. Denormals are produced rather than flushed.
Example ¶
e4m3 trades exponent range for mantissa, which is the trade inference weights want. It has no infinity: the largest value is 448.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
dst := make([]byte, 2)
simd.Float32ToFloat8E4M3Into(dst, []float32{1, 2})
back := make([]float32, 2)
simd.Float8E4M3ToFloat32Into(back, dst)
fmt.Println(back)
}
Output: [1 2]
func Float32ToFloat8E5M2Into ¶
Float32ToFloat8E5M2Into narrows to e5m2, rounding to nearest even.
Values above 57344 in magnitude become infinities, as in float16. Denormals are produced rather than flushed.
func Float32ToFloat16Into ¶
Float32ToFloat16Into narrows each float32 in a into a float16 in dst, rounding to nearest even.
Values above 65520 in magnitude become infinities and values below the smallest float16 denormal become zeros, both with the sign preserved. Denormals are produced rather than flushed.
func FloorInto ¶
func FloorInto[T Float](dst, a []T)
FloorInto sets dst[i] to a[i] rounded down. dst may alias a.
func FormatInts ¶
FormatInts writes vals as decimal text separated by sep — the inverse of ParseInts — and returns how many bytes it wrote, or -1 if dst cannot hold the result.
Size dst at 21 bytes per value — a sign, up to nineteen digits and the separator — and the fast path never needs to measure first:
dst := make([]byte, 21*len(vals)) n := simd.FormatInts(dst, vals, ',') line := dst[:n]
A tighter dst still works when the rendering actually fits; it just runs the exact-fit reference. -1 means not even the exact rendering fits.
Measured over 200,000 values, both sides reusing their buffers: 957µs against 1678µs for a strconv.AppendInt loop — 1.75x. (The C kernel alone probes at 3.3x; the gap is the call and guard overhead a Go caller actually pays, and the honest number is the one that includes it.) The kernel renders two digits per table lookup, halving both the divisions and the stores. No separator follows the last value.
func FromPartsInto ¶
FromPartsInto assembles dst[i] from re[i] and im[i].
func GELU ¶
func GELU[T Float](a []T)
GELU applies the Gaussian error linear unit, using the tanh approximation that transformer implementations conventionally use:
0.5x * (1 + tanh(sqrt(2/pi) * (x + 0.044715x³)))
The exact form uses the error function; this one is within about 1e-3 of it and is what published model weights were trained against, so it is the compatible choice rather than merely the fast one.
func GatherInto ¶
GatherInto reads src at each index in idx: dst[i] = src[idx[i]].
Indices outside src are skipped, leaving dst untouched at that position, rather than panicking. A gather is usually driven by computed indices where a stray value should not take the process down; check the indices yourself if you need strictness.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
dst := make([]float64, 3)
simd.GatherInto(dst, []float64{10, 20, 30, 40}, []int32{3, 0, 2})
fmt.Println(dst)
}
Output: [40 10 30]
func GemmPackLen ¶
GemmPackLen returns the scratch length PackBInto needs for a k-by-n B.
func GemvInto ¶
GemvInto multiplies an m*k row-major matrix by a k-vector into m results: dst[i] = sum over p of a[i*k+p] * x[p].
This is the shape most callers actually want — a matrix applied to a vector, once per row — and it is much cheaper than going through MatMulInto with n=1, which would treat the vector as a one-column matrix and give up the contiguous reads that make the reduction fast.
Row i is bit-identical to Dot of that row against x. That is by construction rather than by coincidence, so a caller can freely mix the two.
It does nothing if the slices are too short for the stated dimensions.
// a 1000x256 matrix applied to a 256-vector simd.GemvInto(dst, a, x, 1000, 256)
Example ¶
GemvInto applies a matrix to a vector, which is the operation most callers actually want and much cheaper than going through MatMulInto with n=1.
Row i of the result is bit-identical to Dot of that row against x — by construction, not by coincidence — so the two can be mixed freely.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
a := []float64{ // 3x2, row-major
1, 2,
3, 4,
5, 6,
}
x := []float64{10, 20}
y := make([]float64, 3)
simd.GemvInto(y, a, x, 3, 2)
fmt.Println(y)
fmt.Println(y[1] == simd.Dot(a[2:4], x))
}
Output: [50 110 170] true
func GemvParallelInto ¶ added in v1.1.0
GemvParallelInto is GemvInto across several goroutines.
dst is length m, a is m×k row-major and x is length k, and the result is bit-identical to GemvInto — the work divides by output row, so no element's summation over k changes order.
The same advice as MatMulParallelInto applies: use it when this product is the whole job, and stay with GemvInto when you are already running work in parallel yourself.
Example ¶
GemvParallelInto is GemvInto across goroutines, for when one matrix-vector product is the whole job. Like MatMulParallelInto it divides by output row, so the result is bit-identical to the serial version, and below a few million multiply-accumulates it runs the serial kernel anyway.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
// [1 2] [1] [ 5]
// [3 4] * [2] = [11]
a := []float64{1, 2, 3, 4} // 2x2, row-major
x := []float64{1, 2}
dst := make([]float64, 2)
simd.GemvParallelInto(dst, a, x, 2, 2)
fmt.Println(dst)
}
Output: [5 11]
func GrayscaleInto ¶
func GrayscaleInto(dst, r, g, b []byte)
GrayscaleInto writes the BT.601 luma of three planar colour channels:
Y = 0.299 R + 0.587 G + 0.114 B
The channels are separate slices — one per component — rather than interleaved RGBRGB. That is the layout a vector unit can use, and it is the same struct-of-arrays advice the tutorial gives for everything else here.
The arithmetic is Q8 fixed point, so the result is exact and identical on every instruction set rather than carrying a floating-point error bound. It rounds to nearest; truncating would bias an image dark by half a level.
It writes min of every argument's length and allocates nothing.
Example ¶
Grayscale uses the libjpeg BT.601 weights in Q16, so it agrees with every other implementation of the same conversion to the bit.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
dst := make([]byte, 3)
simd.GrayscaleInto(dst,
[]byte{255, 0, 0}, // r
[]byte{0, 255, 0}, // g
[]byte{0, 0, 255}) // b
fmt.Println(dst)
}
Output: [76 150 29]
func GreaterEqualInto ¶
GreaterEqualInto writes whether a[i] >= b[i].
func GreaterEqualScalarInto ¶
GreaterEqualScalarInto writes whether a[i] >= v.
func GreaterInto ¶
GreaterInto writes whether a[i] > b[i].
func GreaterScalarInto ¶
GreaterScalarInto writes whether a[i] > v.
func Hamming ¶
func Hamming[T Float](dst []T)
Hamming fills dst with a symmetric Hamming window.
Example ¶
Hamming is the window function. The bit-counting operation is HammingDistance — the two are unrelated and the names collide, which is why one of them is spelled out.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
w := make([]float64, 3)
simd.Hamming(w)
fmt.Printf("%.2f %.2f %.2f\n", w[0], w[1], w[2])
}
Output: 0.08 1.00 0.08
func HammingDistance ¶
HammingDistance returns the number of bit positions at which a and b differ, over the shorter of the two.
The name is spelled out because Hamming is already the Hamming *window*, a different thing from the same person: that one shapes a signal before an FFT, this one compares two bit vectors.
This is the fused popcount(a^b). Both halves are separately available here as XorInto and PopCount, and chaining them is the wrong way to do it: that needs a destination buffer the size of the input and three passes over memory where this makes one. At the sizes Hamming distance is used at — binary embedding search, LSH buckets, SimHash near-duplicate detection — the intermediate is most of the cost.
The result is exact and identical on every instruction set. It allocates nothing.
Example ¶
HammingDistance is the number of differing bits, in one pass over both slices rather than an xor pass and a popcount pass.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
fmt.Println(simd.HammingDistance([]byte{0b1111_0000}, []byte{0b1010_0000}))
}
Output: 2
func HammingDistanceWords ¶
HammingDistanceWords is HammingDistance for a bit vector already stored as []uint64, which is the layout most binary-embedding indexes use. It gives the same answer as HammingDistance over the same bytes and saves the caller an allocating conversion.
func Hann ¶
func Hann[T Float](dst []T)
Hann fills dst with a symmetric Hann window.
Example ¶
A window is generated once and applied to every frame.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
w := make([]float64, 4)
simd.Hann(w)
fmt.Printf("%.2f %.2f %.2f %.2f\n", w[0], w[1], w[2], w[3])
}
Output: 0.00 0.75 0.75 0.00
func HannPeriodic ¶
func HannPeriodic[T Float](dst []T)
HannPeriodic fills dst with a periodic Hann window, which is the symmetric window of length len(dst)+1 with its last sample dropped. This is the form spectral analysis wants; Hann is the form filter design wants.
func HexDecode ¶
HexDecode decodes hexadecimal from src into dst, returning the number of bytes written and whether the whole input was valid. Both upper and lower case digits are accepted.
On a bad digit it stops there and reports false, with the bytes decoded so far already written. An odd-length input also reports false.
func HexEncode ¶
HexEncode writes the lowercase hexadecimal encoding of src into dst and returns the number of bytes written. dst needs room for 2*len(src).
It matches encoding/hex.Encode.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
dst := make([]byte, 8)
n := simd.HexEncode(dst, []byte{0xde, 0xad, 0xbe, 0xef})
fmt.Println(string(dst[:n]))
}
Output: deadbeef
func Hilbert ¶
func Hilbert(src []float64) []complex128
Hilbert returns the analytic signal of src, allocating the plan and the result. len(src) must be a power of two; it returns nil otherwise.
func HilbertInto ¶
func HilbertInto(p *FFTPlan, dst []complex128, src []float64)
HilbertInto writes the analytic signal of the real sequence src to dst: a complex sequence whose real part is src and whose imaginary part is its Hilbert transform.
len(src) must be p.Len(), a power of two, and dst must be at least as long.
The construction is the frequency-domain one, which is why this lives beside the FFT rather than in window.go: transform, discard the negative frequencies and double the positive ones, transform back. The time-domain alternative is convolution with a filter whose ideal impulse response decays as 1/n and so has to be truncated, which is both slower and less accurate.
The direct-current and Nyquist bins are left alone rather than doubled. They have no negative-frequency partner to fold in — bin 0 and bin n/2 are their own conjugates for real input — and doubling them is the classic error, showing up as a constant offset and an alternating ripple.
The envelope of a signal is the magnitude of its analytic signal, which is what most callers want this for:
simd.HilbertInto(p, analytic, samples) simd.AbsComplexInto(env, analytic)
Example ¶
The analytic signal: the Hilbert transform gives a complex signal whose magnitude is the envelope.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
src := []float64{0, 1, 0, -1}
p := simd.NewFFTPlan(len(src))
analytic := make([]complex128, len(src))
simd.HilbertInto(p, analytic, src)
env := make([]float64, len(src))
simd.AbsComplexInto(env, analytic)
fmt.Printf("%.2f %.2f %.2f %.2f\n", env[0], env[1], env[2], env[3])
}
Output: 1.00 1.00 1.00 1.00
func Histogram ¶
Histogram is HistogramInto allocating the counts.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
// Five values over [0,10) in 2 bins.
fmt.Println(simd.Histogram([]float64{1, 2, 3, 8, 9}, 2, 0, 10))
}
Output: [3 2]
func HistogramInto ¶
HistogramInto counts the elements of a falling into len(counts) equal-width bins spanning [lo, hi), adding to counts[i] the number that land in bin i.
Values below lo or at or above hi are skipped, and so is a NaN, which compares false against both bounds. The range is half-open at the top, which is numpy's default and the usual convention, so a value exactly equal to hi is not counted.
counts is not zeroed first, so repeated calls accumulate. It does nothing if hi is not greater than lo.
func Hypot ¶
func Hypot[T Float](a, b []T)
Hypot replaces each element of a with sqrt(a[i]**2 + b[i]**2), computed without the intermediate overflow that squaring would cause.
func HypotInto ¶
func HypotInto[T Float](dst, a, b []T)
HypotInto sets dst[i] = sqrt(a[i]**2 + b[i]**2), avoiding overflow.
func IFFT ¶
func IFFT(a []complex128) []complex128
IFFT returns the inverse transform of a, including the 1/N scaling.
func IFFTInto ¶
func IFFTInto(p *FFTPlan, dst, src []complex128)
IFFTInto writes the inverse discrete Fourier transform of src to dst, including the 1/N scaling, so IFFTInto(FFTInto(x)) recovers x.
func Index ¶
Index returns the index of the first occurrence of needle in haystack, or -1. An empty needle returns 0.
It matches bytes.Index and strings.Index and is a drop-in replacement for either.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
fmt.Println(simd.Index("the quick brown fox", "brown"))
}
Output: 10
func IndexAll ¶
IndexAll writes the offset of every occurrence of c in s into dst, and returns how many it found. It stops early if dst fills up, so a short dst bounds the work rather than being an error.
This is the structural-index step of a vectorized parser: run it once for each delimiter class you care about and you have the shape of the document before you have looked at a single byte twice.
offsets := make([]int32, 1024)
n := simd.IndexAll(offsets, line, ',')
for _, off := range offsets[:n] { ... }
Offsets are int32, so an input longer than 2 GiB cannot be indexed past that point; every path here truncates identically rather than one disagreeing with another. Split such an input and add the base yourself.
Example ¶
IndexAll is the structural-index step of a parser: every delimiter located in one pass, then the offsets walked. Like the rest of the text functions it takes a string or a []byte and copies neither.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
line := "id,name,email,created_at"
commas := make([]int32, 16)
n := simd.IndexAll(commas, line, ',')
fields, prev := []string{}, 0
for _, off := range commas[:n] {
fields = append(fields, line[prev:off])
prev = int(off) + 1
}
fields = append(fields, line[prev:])
fmt.Println(n, fields)
}
Output: 3 [id name email created_at]
func IndexAny ¶
IndexAny returns the index of the first byte of s that is also in chars, or -1.
The set is turned into a 256-bit table once, so the cost is linear in s and independent of how many characters you are looking for.
Note the difference from strings.IndexAny, which searches for Unicode code points: this is a byte scan. For an ASCII set the two agree, because no byte of a multi-byte UTF-8 sequence is below 0x80. For a set containing non-ASCII characters they do not, and this is the wrong function.
Example ¶
IndexAny finds the first byte belonging to a set, which is the shape a tokenizer wants.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
fmt.Println(simd.IndexAny("key=value;next", "=;"))
}
Output: 3
func IndexByte ¶
IndexByte returns the index of the first occurrence of c in s, or -1.
It matches bytes.IndexByte and strings.IndexByte and is a drop-in replacement for either.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
fmt.Println(simd.IndexByte("a,b,c", ','))
}
Output: 1
func IndexFoldASCII ¶
IndexFoldASCII returns the index of the first occurrence of needle in haystack under ASCII case folding, or -1 if it is not present.
scratch must be at least len(haystack); a shorter one is replaced by an allocation. Give it len(haystack)+len(needle) and the call allocates nothing at all — the needle's fold is carved from the same scratch. (A stack buffer for the needle sounds cheaper and is not: it escapes through the dispatch call and heap-allocates every time, which the allocation test caught.)
scratch := make([]byte, len(page)) // once
for _, w := range words {
if simd.IndexFoldASCII(page, w, scratch) >= 0 { ... }
}
The returned index is a position in the original haystack — folding does not move bytes, so offsets in the folded copy are offsets in the input.
Example ¶
Case-insensitive search without allocating lowered copies: the haystack is folded once into caller scratch at ToLowerASCII speed, and offsets in the fold are offsets in the original.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
page := []byte("The QUICK brown fox")
scratch := make([]byte, len(page))
fmt.Println(simd.IndexFoldASCII(page, "quick", scratch))
fmt.Println(simd.ContainsFoldASCII(page, "BROWN FOX", scratch))
}
Output: 4 true
func IndexNotAny ¶
IndexNotAny returns the index of the first byte of s that is *not* in chars, or -1 if every byte is.
This is the primitive under trimming and under skipping a run of whitespace, which is where a tokenizer spends the time it is not spending in IndexAny. An empty set contains nothing, so every byte is outside it and the answer for a non-empty s is 0.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
fmt.Println(simd.IndexNotAny(" indented", " "))
}
Output: 3
func Interp ¶
func Interp[T Float](x, xp, fp []T) []T
Interp is InterpInto allocating the destination.
func InterpInto ¶
func InterpInto[T Float](dst []T, x, xp, fp []T)
InterpInto writes to dst the piecewise-linear interpolation of (xp, fp) evaluated at each x, matching numpy's interp.
xp must be increasing; the result is undefined if it is not, and that is not checked, because checking costs a pass over xp on every call and the caller almost always knows. Values of x below xp[0] give fp[0] and values above the last xp give the last fp, which is numpy's clamping default rather than extrapolation.
The search for each x is a binary search over xp, which is where the time goes and is not vectorisable — the branch depends on the loaded value. What is vectorisable is the interpolation itself, and it is left as a plain expression because the search dominates: at 64 knots the search is six dependent loads against three arithmetic operations.
Example ¶
InterpInto is numpy's interp: piecewise-linear lookup in a table, clamping outside it.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
xp := []float64{0, 10, 20} // table positions
fp := []float64{0, 100, 0} // table values
dst := make([]float64, 3)
simd.InterpInto(dst, []float64{5, 15, 99}, xp, fp)
fmt.Println(dst)
}
Output: [50 50 0]
func IntersectInto ¶
IntersectInto writes the elements present in both a and b to dst, in ascending order, and returns how many there were.
a and b must be sorted ascending with no duplicates. dst must have room for min(len(a), len(b)) elements, which is the most an intersection can produce; it panics otherwise, because the kernel is at the six-argument limit its ABI allows and cannot be told the destination's length.
Example ¶
IntersectInto keeps the elements present in both. Both inputs must be sorted and free of duplicates — the shape a posting list is already in.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
a := []int32{1, 3, 5, 7, 9}
b := []int32{3, 4, 5, 6, 9}
dst := make([]int32, min(len(a), len(b)))
n := simd.IntersectInto(dst, a, b)
fmt.Println(dst[:n])
}
Output: [3 5 9]
func IsASCII ¶
IsASCII reports whether every byte is below 0x80.
It is worth checking before text processing, because the ASCII path of most algorithms is dramatically simpler than the general one.
func IsFiniteInto ¶
IsFiniteInto writes to dst whether each element of a is neither infinite nor NaN.
A NaN fails the comparison against +Inf as unordered rather than as less than, so this cannot be spelled as "not infinite" — the magnitude has to be strictly less than infinity, which excludes both.
func IsInfInto ¶
IsInfInto writes to dst whether each element of a is an infinity of either sign.
The magnitude of an infinity is +Inf and the magnitude of everything else is finite or NaN, so one absolute value and one comparison against +Inf answers it. scratch is working space of at least len(a); if it is shorter this allocates one, so pass it on a hot path and omit it otherwise.
func IsNaNInto ¶
IsNaNInto writes to dst whether each element of a is a NaN.
It is NotEqualInto of a against itself, which is exactly what the IEEE definition of NaN says: the only value not equal to itself. Every NaN payload, quiet and signalling alike, answers true.
dst and a must be the same length; the shorter bounds the work.
Example ¶
package main
import (
"fmt"
"math"
"github.com/sebishogun/simd"
)
func main() {
mask := make([]bool, 3)
simd.IsNaNInto(mask, []float64{1, math.NaN(), 3})
fmt.Println(mask)
}
Output: [false true false]
func L1Norm ¶
func L1Norm[T Number](a []T) T
L1Norm returns the sum of the absolute values of all elements, also called the taxicab or Manhattan norm.
func Lanes ¶
Lanes reports how many elements of type T the widest usable vector holds, and returns 0 here because this build has no vector type. See the amd64 version for what it reports when there is one.
Check for zero rather than dividing by it.
func LastIndex ¶
LastIndex returns the index of the last occurrence of needle in haystack, or -1. An empty needle returns len(haystack).
It matches bytes.LastIndex and strings.LastIndex.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
fmt.Println(simd.LastIndex("a,b,c", ","))
}
Output: 3
func LastIndexByte ¶
LastIndexByte returns the index of the last occurrence of c in s, or -1.
func LastIndexNotAny ¶
LastIndexNotAny returns the index of the last byte of s that is not in chars, or -1 if every byte is.
func LayerNorm ¶
func LayerNorm[T Float](a []T, eps T)
LayerNorm rescales a to zero mean and unit variance, with eps added to the variance before the square root to keep the division stable when the input is nearly constant.
This is Standardize with the epsilon that neural network layers require.
Example ¶
LayerNorm centres and scales a vector to zero mean and unit variance — the normalization a transformer block does between every sublayer. One pass, where Mean followed by StdDev followed by the arithmetic is four.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
a := []float64{1, 2, 3, 4}
simd.LayerNorm(a, 1e-5)
fmt.Printf("%.3f %.3f %.3f %.3f\n", a[0], a[1], a[2], a[3])
}
Output: -1.342 -0.447 0.447 1.342
func LayerNormInto ¶
func LayerNormInto[T Float](dst, a, gamma, beta []T, eps T)
LayerNormInto is LayerNorm with the learned per-element scale and offset a transformer applies after normalizing:
dst[i] = (a[i] - mean) / sqrt(variance + eps) * gamma[i] + beta[i]
LayerNorm normalizes and stops, which is only half of the layer as it is used in practice: gamma and beta are parameters the model learns, and without them the layer cannot represent the identity.
The mean and the variance are the same two-pass ones Variance computes, so this agrees with normalizing and then applying the affine transform by hand. It writes min of every argument's length and allocates nothing.
Example ¶
The Into form applies the learned gamma and beta in the same pass.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
a := []float64{1, 2, 3, 4}
gamma := []float64{2, 2, 2, 2}
beta := []float64{1, 1, 1, 1}
dst := make([]float64, 4)
simd.LayerNormInto(dst, a, gamma, beta, 1e-5)
fmt.Printf("%.3f %.3f\n", dst[0], dst[3])
}
Output: -1.683 3.683
func LeadingZerosInto ¶
func LeadingZerosInto[T Integer](dst, a []T)
LeadingZerosInto writes the number of leading zero bits of each element of a to dst. Zero gives the element width, as in math/bits.LeadingZeros.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
dst := make([]uint8, 1)
simd.LeadingZerosInto(dst, []uint8{0b0001_0000})
fmt.Println(dst[0])
}
Output: 3
func LeakyReLU ¶
func LeakyReLU[T Float](a []T, slope T)
LeakyReLU scales negative elements by slope instead of zeroing them, which keeps a gradient flowing where ReLU would stop it.
func Lerp ¶
func Lerp[T Number](a, b []T, t T)
Lerp interpolates each element of a towards b by t: a[i] += (b[i]-a[i]) * t.
t is not clamped, so values outside [0, 1] extrapolate. The evaluation form is monotonic in t and lands exactly on b at t=1, which the algebraically equal a*(1-t) + b*t does not.
func LerpInto ¶
func LerpInto[T Number](dst, a, b []T, t T)
LerpInto sets dst[i] = a[i] + (b[i]-a[i])*t. dst may alias a or b.
func LessEqualInto ¶
LessEqualInto writes whether a[i] <= b[i].
func LessEqualScalarInto ¶
LessEqualScalarInto writes whether a[i] <= v.
func LessScalarInto ¶
LessScalarInto writes whether a[i] < v.
func LinearRegression ¶
func LinearRegression[T Float](x, y []T) (slope, intercept T)
LinearRegression fits y = slope*x + intercept by ordinary least squares.
It returns zeros if there are fewer than two points or if every x is the same, since no line is determined in either case.
func Log ¶
func Log[T Float](a []T)
Log replaces every element with its natural logarithm.
Zero yields -Inf and a negative input yields NaN, following IEEE 754.
Example ¶
package main
import (
"fmt"
"math"
"github.com/sebishogun/simd"
)
func main() {
a := []float64{1, math.E}
simd.Log(a)
fmt.Printf("%.4f %.4f\n", a[0], a[1])
}
Output: 0.0000 1.0000
func Log1p ¶
func Log1p[T Float](a []T)
Log1p replaces every element x with log(1+x).
Use it instead of adding one and calling Log when x is near zero, where that form loses almost all its significant digits.
func Log1pInto ¶
func Log1pInto[T Float](dst, a []T)
Log1pInto sets dst[i] = log(1+a[i]), accurately near zero. dst may alias a.
func Log2Into ¶
func Log2Into[T Float](dst, a []T)
Log2Into sets dst[i] to the base-2 logarithm of a[i]. dst may alias a.
func Log10Into ¶
func Log10Into[T Float](dst, a []T)
Log10Into sets dst[i] to the base-10 logarithm of a[i]. dst may alias a.
func LogInto ¶
func LogInto[T Float](dst, a []T)
LogInto sets dst[i] to the natural logarithm of a[i]. dst may alias a.
func LogSumExp ¶
func LogSumExp[T Float](a []T) T
LogSumExp returns log(sum(exp(a))) without overflowing, by factoring out the maximum. It is the normalizing constant of Softmax, and appears wherever log-probabilities are combined.
func LowerBoundInto ¶
LowerBoundInto fills dst with, for each element of q, the number of elements of a strictly less than it — the index std::lower_bound and sort.SearchInts return, and the position at which the query would be inserted to keep a sorted.
a must be sorted ascending. dst and q must be the same length; the shorter bounds the work. Nothing is allocated.
Why the batch form and not a single search ¶
One binary search is log2(n) probes and every probe's address comes from the previous comparison, which is a dependency no vector unit helps with — a branchless scalar search is already close to optimal for one query.
Many queries are a different problem. They all walk the same number of steps over the same table, so the loop nest turns inside out: step on the outside, query on the inside. The inner loop is then elementwise over the batch, and the only thing it needs beyond arithmetic is a gather, because each lane probes a different element.
That gather is also the limit. Where the instruction exists — AVX2, AVX-512, SVE2, RVV — this is genuinely vectorized; where it does not, LLVM declines and the portable bisection runs instead. Same wall as the scatter family, recorded in docs/wrong.md entry 59.
Duplicates in a are fine and behave as lower_bound does: the index of the first equal element. For the index after the last, subtract from the count of elements less than or equal, which is LowerBound of the next representable value.
Example ¶
LowerBoundInto is a binary search done for many queries at once. One search is a chain of dependent probes and vectorizes on nothing; a batch turns the loop nest inside out and becomes elementwise over the queries.
Each answer is the number of elements strictly less than the query, which is the index std::lower_bound and sort.SearchInts return — so a query equal to a table entry lands *on* it, not after it. 10 below is at index 1, not 2.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
table := []float64{0, 10, 20, 30}
queries := []float64{5, 25, 10, 99}
pos := make([]int32, len(queries))
simd.LowerBoundInto(pos, table, queries)
fmt.Println(pos)
}
Output: [1 3 1 4]
func ManhattanDistance ¶
func ManhattanDistance[T Number](a, b []T) T
ManhattanDistance returns the sum of the absolute differences between a and b, also called the L1 or taxicab distance.
func MatMulInto ¶
MatMulInto multiplies an m*k matrix by a k*n matrix into an m*n one, all in row-major order. dst is zeroed first.
It does nothing if the slices are too short for the stated dimensions, so check your sizes.
// 2x3 times 3x2 into 2x2 simd.MatMulInto(dst, a, b, 2, 3, 2)
Each output element is one accumulator summed over the shared dimension in ascending order, which is what makes every instruction set here agree bit for bit. Zeros in a are not treated specially: a zero times an infinity is a NaN, as IEEE 754 says and as BLAS and numpy both produce.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
a := []float64{ // 2x3
1, 2, 3,
4, 5, 6,
}
b := []float64{ // 3x2
7, 8,
9, 10,
11, 12,
}
dst := make([]float64, 2*2)
simd.MatMulInto(dst, a, b, 2, 3, 2)
fmt.Println(dst[:2])
fmt.Println(dst[2:])
}
Output: [58 64] [139 154]
func MatMulIntoPacked ¶
MatMulIntoPacked multiplies the m-by-k matrix a against a B previously packed by PackBInto, writing the m-by-n result to dst. dst is zeroed first. It does nothing if any slice is too short for the stated shape.
Bit-identical to MatMulInto of the same operands, by construction.
func MatMulIntoScratch ¶
MatMulIntoScratch is MatMulInto with caller-supplied working space, switching to the packed algorithm when B is large enough that packing wins.
scratch needs GemmPackLen(k, n) elements for the packed path; a shorter one — including nil — falls back to the plain kernel, costing speed and never correctness. Results are bit-identical on either path.
func MatMulParallelInto ¶ added in v1.1.0
MatMulParallelInto is MatMulInto across several goroutines.
dst is m×n, a is m×k and b is k×n, all row-major, and the result is bit-identical to MatMulInto on the same input — the work is divided by output row, so no element's accumulation order changes.
It uses up to GOMAXPROCS goroutines and returns once they are done. Below roughly a million multiply-accumulates, or when GOMAXPROCS is 1, it runs the serial kernel instead.
Use this when the multiply is the whole job. If you are already running work in parallel — one goroutine per matrix in a batch, say — use MatMulInto and keep the parallelism where you can see it.
Example ¶
MatMulParallelInto is MatMulInto spread across goroutines. It is opt-in because a library that fans out on its own steals cores from a caller that may already be using them — reach for it when the multiply is the whole job, and stay with MatMulInto when you are running a batch of them in parallel yourself.
The result is bit-identical to MatMulInto: the work divides by output row, so no element's accumulation order changes. Below a few million multiply-accumulates it runs the serial kernel anyway, which is why this small example prints the same thing either way.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
// [1 2] [5 6] [19 22]
// [3 4] * [7 8] = [43 50]
a := []float64{1, 2, 3, 4} // 2x2, row-major
b := []float64{5, 6, 7, 8} // 2x2
dst := make([]float64, 4)
simd.MatMulParallelInto(dst, a, b, 2, 2, 2)
fmt.Println(dst)
}
Output: [19 22 43 50]
func Max ¶
func Max[T Number](a []T) T
Max returns the largest element.
For floats this is the IEEE 754-2019 maximum: NaN propagates, and +0 is larger than -0. It panics on an empty slice.
For the elementwise maximum of two slices, see Maximum.
func Maximum ¶
func Maximum[T Number](a, b []T)
Maximum keeps the larger of each pair: a[i] = max(a[i], b[i]).
This is the elementwise operation. For the largest element of one slice, see Max.
func MaximumInto ¶
func MaximumInto[T Number](dst, a, b []T)
MaximumInto sets dst[i] = max(a[i], b[i]). dst may alias a or b.
func Mean ¶
func Mean[T Float](a []T) T
Mean returns the arithmetic mean, or zero for an empty slice.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
fmt.Println(simd.Mean([]float64{1, 2, 3, 4}))
}
Output: 2.5
func Median ¶
func Median[T Number](a []T) T
Median returns the median of a, **reordering a in the process**.
The reordering is what keeps this allocation-free; copy the slice first if you need to keep its order. For an even number of elements the two middle values are averaged, so the result need not be an element of the input. NaN sorts to the end and so does not corrupt the middle.
Where the two middle values compare equal but differ in bits — the only case being negative and positive zero — which one is returned may differ between the accelerated and portable paths. See the note on Sort.
Above a threshold it runs a quickselect around the accelerated partition and allocates a scratch slice to do it, for the same reason Sort does: the partition kernel is out of place and needs somewhere to write. Use MedianInto on a hot path to supply that scratch yourself and allocate nothing. Below the threshold, and on architectures with no compress instruction, it stays on the scalar quickselect and allocates nothing.
It panics on an empty slice.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
fmt.Println(simd.Median([]float64{3, 1, 4, 1, 5}))
}
Output: 3
func MedianInto ¶
func MedianInto[T Number](a, scratch []T) T
MedianInto is Median using scratch as working space, allocating nothing.
scratch must be at least as long as a; if it is shorter, this falls back to the scalar quickselect rather than panicking, so a short scratch costs speed and not correctness. Its contents afterwards are unspecified, and a is reordered exactly as Median reorders it.
scratch := make([]T, len(a)) // once
for _, batch := range batches {
m := simd.MedianInto(batch, scratch)
}
Example ¶
MedianInto sorts into the scratch slice instead of allocating one.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
a := []float64{3, 1, 4, 1, 5}
fmt.Println(simd.MedianInto(a, make([]float64, len(a))))
}
Output: 3
func Min ¶
func Min[T Number](a []T) T
Min returns the smallest element.
For floats this is the IEEE 754-2019 minimum: NaN propagates, and -0 is smaller than +0. It panics on an empty slice.
For the elementwise minimum of two slices, see Minimum.
func MinMax ¶
func MinMax[T Number](a []T) (lo, hi T)
MinMax returns the smallest and largest elements in a single pass, which is cheaper than calling Min and Max separately because the data is read once. It panics on an empty slice.
Example ¶
MinMax walks the slice once for both, which matters when the slice does not fit in cache.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
lo, hi := simd.MinMax([]float64{3, 1, 4, 1, 5})
fmt.Println(lo, hi)
}
Output: 1 5
func Minimum ¶
func Minimum[T Number](a, b []T)
Minimum keeps the smaller of each pair: a[i] = min(a[i], b[i]).
This is the elementwise operation. For the smallest element of one slice, see Min.
func MinimumInto ¶
func MinimumInto[T Number](dst, a, b []T)
MinimumInto sets dst[i] = min(a[i], b[i]). dst may alias a or b.
func MovingAverageInto ¶
MovingAverageInto writes the mean of each window of the given width, producing len(a)-width+1 elements.
Each window is computed independently rather than by sliding a running total. That is more arithmetic, but a running total accumulates rounding error without bound over a long series and cannot be vectorized, since every output would depend on the one before it.
func Mul ¶
func Mul[T Number](a, b []T)
Mul multiplies a by b, elementwise: a[i] *= b[i].
Integer multiplication wraps on overflow, matching the hardware.
func MulAll ¶
func MulAll[T Number](dst []T, srcs ...[]T)
MulAll is AddAll with multiplication: dst[i] is the product of the corresponding element of every source, left to right, in one pass.
With no sources dst is filled with ones, which is the identity for the operation being folded.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
dst := make([]float64, 3)
simd.MulAll(dst, []float64{1, 2, 3}, []float64{2, 2, 2}, []float64{5, 5, 5})
fmt.Println(dst)
}
Output: [10 20 30]
func MulComplex ¶
func MulComplex[C Complex](a, b []C)
MulComplex multiplies a by b elementwise: a[i] *= b[i].
func MulComplexInto ¶
func MulComplexInto[C Complex](dst, a, b []C)
MulComplexInto sets dst[i] = a[i] * b[i]. dst may alias a or b.
func MulInto ¶
func MulInto[T Number](dst, a, b []T)
MulInto sets dst[i] = a[i] * b[i]. dst may alias a or b.
func NanMean ¶
NanMean returns the mean of the non-NaN elements of a, and how many there were.
The count is returned rather than discarded because it is the thing a caller needs to know whether the mean means anything: a mean over three surviving points out of a thousand is not a mean. A slice with no non-NaN elements returns NaN and zero rather than dividing by zero.
Example ¶
NanMean returns the mean and how many values it was taken over.
package main
import (
"fmt"
"math"
"github.com/sebishogun/simd"
)
func main() {
a := []float64{1, math.NaN(), 3}
mean, n := simd.NanMean(a, make([]float64, len(a)), make([]bool, len(a)))
fmt.Println(mean, "over", n)
}
Output: 2 over 2
func NanSum ¶
NanSum returns the sum of the non-NaN elements of a, treating NaN as absent rather than as poison.
Sum propagates: one NaN anywhere makes the whole answer NaN, which is what IEEE says and usually what you want. This is the other convention — numpy's nansum, R's sum(na.rm=TRUE) — for data with gaps in it.
It is a select and a sum, both accelerated: the NaN lanes are replaced by zero and the ordinary reduction runs over the result. Adding zero is exact, so the answer is the sum of the surviving elements and nothing has been perturbed by the substitution.
scratch and mask are working space of at least len(a); short ones are replaced by allocations. An empty slice, or one that is entirely NaN, sums to zero — the identity, consistently with Sum of an empty slice.
Example ¶
package main
import (
"fmt"
"math"
"github.com/sebishogun/simd"
)
func main() {
a := []float64{1, math.NaN(), 3}
fmt.Println(simd.NanSum(a, make([]float64, len(a)), make([]bool, len(a))))
}
Output: 4
func NeedsEscapeJSON ¶
NeedsEscapeJSON reports whether s contains any byte that AppendEscapeJSON would change. The common answer on real data is no, and knowing it costs one accelerated scan — a serializer can then write the input directly.
func Neg ¶
func Neg[T Number](a []T)
Neg negates every element: a[i] = -a[i].
For floats this flips the sign bit, which unlike 0-x is correct for ±0 and NaN. For integers it wraps.
func NegComplexInto ¶
func NegComplexInto[C Complex](dst, a []C)
NegComplexInto sets dst[i] = -a[i]. dst may alias a.
func NegInto ¶
func NegInto[T Number](dst, a []T)
NegInto sets dst[i] = -a[i]. See Neg for semantics.
func Norm ¶
func Norm[T Float](a []T) T
Norm returns the Euclidean length of a: the square root of the sum of squares. Also called the L2 norm.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
fmt.Println(simd.Norm([]float64{3, 4}))
}
Output: 5
func Normalize ¶
func Normalize[T Float](a []T)
Normalize scales a to unit Euclidean length. A vector of all zeros is left unchanged, since it has no direction to preserve.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
a := []float64{3, 4}
simd.Normalize(a)
fmt.Println(a)
}
Output: [0.6 0.8]
func NotEqualInto ¶
NotEqualInto writes whether a[i] != b[i]. This is true when either side is NaN, so it is not the negation of EqualInto.
func NotEqualScalarInto ¶
NotEqualScalarInto writes whether a[i] != v.
func NotMaskInto ¶
func NotMaskInto(dst, a []bool)
NotMaskInto sets dst[i] = !a[i]. dst may alias a.
func OnesCountInto ¶
func OnesCountInto[T Integer](dst, a []T)
OnesCountInto writes the number of one bits in each element of a to dst.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
dst := make([]uint8, 1)
simd.OnesCountInto(dst, []uint8{0b1011})
fmt.Println(dst[0])
}
Output: 3
func Or ¶
func Or(a, b []byte)
Or sets in a every bit set in b: a[i] |= b[i].
It processes min(len(a), len(b)) bytes and allocates nothing. Use OrInto to write the result elsewhere.
func OrInto ¶
func OrInto(dst, a, b []byte)
OrInto sets dst[i] = a[i] | b[i].
It processes min(len(dst), len(a), len(b)) bytes. dst may alias a or b.
func OrMaskInto ¶
func OrMaskInto(dst, a, b []bool)
OrMaskInto sets dst[i] = a[i] || b[i]. dst may alias a or b.
func PackBInto ¶
PackBInto packs the k-by-n row-major matrix b into bp for MatMulIntoPacked. bp must be at least GemmPackLen(k, n) long; it does nothing otherwise.
Packing costs one pass over B. A caller multiplying several A matrices against one B — the classifier-weights shape — packs once:
bp := make([]float64, simd.GemmPackLen[float64](k, n))
simd.PackBInto(bp, weights, k, n)
for _, batch := range batches {
simd.MatMulIntoPacked(out, batch, bp, m, k, n)
}
func ParseFloats ¶
ParseFloats converts the fields of src into float64, writing them to dst, and returns how many it converted and whether every one was valid.
It takes the same index slice as ParseInts:
n := simd.IndexAll(idx, line, ',') idx[n] = int32(len(line)) count, ok := simd.ParseFloats(vals, line, idx[:n+1])
Results are identical to strconv.ParseFloat's, bit for bit, including the sign of zero. Fields the fast path cannot round exactly are passed to strconv rather than approximated, so there is no accuracy tradeoff and no Fast* variant. See the package comment above for what that costs on input that is mostly such fields.
It stops at the first field strconv also rejects and returns that field's index, so a caller can report where the input went wrong.
func ParseInts ¶
ParseInts converts the fields of src into signed integers, writing them to dst, and returns how many it converted and whether every one was valid.
idx holds the offset of each field separator, which is exactly what IndexAll produces, plus a final entry at len(src) if the last field is not separator-terminated:
n := simd.IndexAll(idx, line, ',') idx[n] = int32(len(line)) count, ok := simd.ParseInts(vals, line, idx[:n+1])
It stops at the first field that is not a valid integer and returns that field's index, so a caller can report where the input went wrong. A field is invalid if it is empty, contains a non-digit after an optional leading + or -, or names a value outside the int64 range — an over-long field is rejected rather than wrapped.
Why the separator scan is not part of this ¶
It is already fast and it is not where the time goes. On 200,000 short CSV fields IndexAll alone runs at 4.06 GB/s, and the same scan followed by strconv.Atoi at 0.83 — so the scan is a fifth of the work and the conversion is the other four fifths. Splitting them lets a caller reuse a scan, and keeps this kernel to the part that was actually slow.
Example ¶
The two-step CSV integer parse: IndexAll finds every separator in one pass, then ParseInts converts the fields between them.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
line := "10,20,30,40"
idx := make([]int32, len(line)+1)
n := simd.IndexAll(idx, line, ',')
// The last field is not separator-terminated, so it needs a sentinel at
// the end of the input. Without it the final value is silently dropped.
idx[n] = int32(len(line))
dst := make([]int64, n+1)
got, ok := simd.ParseInts(dst, line, idx[:n+1])
fmt.Println(dst[:got], ok)
}
Output: [10 20 30 40] true
func ParseUints ¶
ParseUints is ParseInts over the full uint64 range.
It is a separate kernel rather than a wrapper because the signed one's limit is 2^63: every value above that — half the domain, and the half a caller reaches for uint64 to get — would be rejected by it.
No sign is accepted, not even a leading '+', matching strconv.ParseUint.
func PartitionInto ¶
PartitionInto splits src about a pivot: every element strictly less than the pivot is written to the front of dst, everything else after them, and the number that went to the front is returned.
Both sides keep their relative order, so this is a stable partition.
dst must be at least as long as src and must not overlap it. This is the primitive SortInto is built on, exposed because a partition is useful on its own — selecting a quantile, splitting a batch by threshold, bucketing before a scatter — and because it is the part that is accelerated.
n := simd.PartitionInto(dst, src, 0) negatives, rest := dst[:n], dst[n:]
Example ¶
PartitionInto splits about a pivot and reports where the split landed. Both sides keep their original relative order.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
dst := make([]float64, 6)
n := simd.PartitionInto(dst, []float64{5, 1, 9, 3, 7, 2}, 4)
fmt.Println(dst[:n], dst[n:])
}
Output: [1 3 2] [5 9 7]
func PolyEval ¶
func PolyEval[T Number](x, coeffs []T)
PolyEval evaluates a polynomial in place, replacing each x with p(x). Coefficients are lowest order first.
func PolyEvalInto ¶
func PolyEvalInto[T Number](dst, x, coeffs []T)
PolyEvalInto evaluates a polynomial at every point of x, writing the results to dst. Coefficients are lowest order first, so coeffs[0] is the constant term: coeffs = {1, 2, 3} means 1 + 2x + 3x².
It uses Horner's method and reads x once, rather than the pass per coefficient that chaining Mul and AddScalar would cost.
func PopCount ¶
PopCount returns the total number of set bits across every byte of b.
Example ¶
PopCount counts set bits across a whole byte slice, as one number.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
fmt.Println(simd.PopCount([]byte{0b1011, 0b1000}))
}
Output: 4
func Pow ¶
func Pow[T Float](a, b []T)
Pow raises each element of a to the corresponding power in b: a[i] **= b[i].
func PowInto ¶
func PowInto[T Float](dst, a, b []T)
PowInto sets dst[i] = a[i] ** b[i]. dst may alias a or b.
func Prod ¶
func Prod[T Number](a []T) T
Prod returns the product of all elements, or one for an empty slice.
Unlike Sum this is evaluated left to right rather than across a fixed accumulator tree. Products overflow and underflow far more readily than sums, so splitting them across lanes changes which intermediate blows up rather than merely changing the rounding — a reassociated product is not the same computation.
Integer multiplication wraps on overflow.
func QMatMulInt8Into ¶
QMatMulInt8Into multiplies two row-major int8 matrices into an int32 destination: an m*k matrix by a k*n matrix, giving m*n.
The accumulator is int32 and that is the point of a separate function. MatMulInto is generic over one element type, so instantiating it at int8 would accumulate in int8 and overflow after two or three terms — two full-scale int8 values already multiply to 16129. Here the worst case is k*127*128, which stays inside int32 up to k = 132097, past any layer anyone runs.
This is what QuantizeInt8 produces tensors for. Follow it with RequantizeInt8Into to get back to int8, or keep the int32 result if a bias or a residual connection is added next.
The result is exact and identical on every instruction set: integer addition is associative, so unlike the float matmul there is no accumulation order to preserve. It does nothing if the slices are too short for the stated dimensions, and it allocates nothing.
Example ¶
The int8 matrix multiply accumulates into int32, because the products of two int8 values overflow int8 immediately. Requantize brings it back down.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
// [1 2] * [1 0] = [1 2]
// [3 4] [0 1] [3 4]
a := []int8{1, 2, 3, 4}
b := []int8{1, 0, 0, 1}
acc := make([]int32, 4)
simd.QMatMulInt8Into(acc, a, b, 2, 2, 2)
fmt.Println(acc)
// q = round(acc*scale) + zeroPoint, rounding half to EVEN — so 1*0.5
// becomes 0 and 3*0.5 becomes 2, which is what the runtimes this
// interoperates with do and what round-half-away-from-zero would not.
out := make([]int8, 4)
simd.RequantizeInt8Into(out, acc, 0.5, 0)
fmt.Println(out)
}
Output: [1 2 3 4] [0 1 2 2]
func Quantile ¶
Quantile returns the q-th quantile of a, **reordering a in the process**.
q is clamped to [0, 1]: Quantile(a, 0) is the minimum, 0.5 the median, 1 the maximum. Values between order statistics are interpolated linearly, which is what numpy and R type 7 do, so results are comparable with those tools.
Like Median it reorders rather than copying, which is what keeps it allocation-free. Copy the slice first if you need to keep its order. For integer types the interpolated value truncates toward zero.
Like Median it uses the accelerated partition above a threshold and allocates a scratch slice to do so; QuantileInto takes that scratch from the caller and allocates nothing.
It panics on an empty slice.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
fmt.Println(simd.Quantile([]float64{1, 2, 3, 4, 5}, 0.5))
}
Output: 3
func QuantileInto ¶
QuantileInto is Quantile using scratch as working space, allocating nothing. scratch must be at least as long as a; a shorter one falls back to the scalar quickselect rather than panicking.
func QuantizeInt8 ¶
QuantizeInt8 converts float32 to int8 with an affine scale and zero point, which is the quantization every inference runtime uses:
q = clamp(round(x/scale) + zeroPoint, -128, 127)
Rounding is half to even, matching ONNX, PyTorch and TFLite. That is worth stating because the naive form, int8(x/scale + 0.5), rounds half away from zero and disagrees on every exact .5 — which a symmetric scale produces in quantity rather than rarely, so the two differ in the middle of a typical distribution and not only at its edges.
Values outside the representable range saturate rather than wrapping. It writes min(len(dst), len(a)) elements and allocates nothing.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
// A symmetric per-tensor scale, the common case for weights.
w := []float32{-1.0, -0.5, -0.25, 0, 0.25, 0.5, 1.0}
scale := float32(1.0 / 127)
q := make([]int8, len(w))
simd.QuantizeInt8(q, w, scale, 0)
fmt.Println(q)
}
Output: [-127 -64 -32 0 32 64 127]
func QuantizePerChannelInt8 ¶
func QuantizePerChannelInt8(dst []int8, a []float32, scale []float32, zeroPoint []int32, channels, inner int)
QuantizePerChannelInt8 is QuantizeInt8 with one scale and zero point per output channel rather than one for the whole tensor.
This is what inference actually uses for weights. Output channels are trained independently and their ranges differ by an order of magnitude or more, so a single tensor-wide scale is set by the widest channel and wastes most of the int8 range on every other one — typically one to two bits of effective precision, for no cost beyond storing a scale per channel.
The layout is channels groups of inner consecutive elements: element c*inner+i belongs to channel c. That is how a weight tensor shaped [outChannels][inChannels*kh*kw] already sits in memory, so no rearrangement is needed.
Rounding, saturation and the zero point behave exactly as in QuantizeInt8. It does nothing if the slices are too short for the stated shape, and it allocates nothing.
Example ¶
Quantization to int8 with a scale and zero point: the operation an inference runtime does to every weight and activation.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
// Two channels of two values, each with its own scale.
a := []float32{1, 2, 30, 40}
scale := []float32{0.5, 10}
zero := []int32{0, 0}
dst := make([]int8, len(a))
simd.QuantizePerChannelInt8(dst, a, scale, zero, 2, 2)
fmt.Println(dst)
}
Output: [2 4 3 4]
func QuantizePerChannelUint8 ¶
func QuantizePerChannelUint8(dst []uint8, a []float32, scale []float32, zeroPoint []int32, channels, inner int)
QuantizePerChannelUint8 is QuantizePerChannelInt8 into the unsigned range, clamping to [0, 255].
func QuantizeUint8 ¶
QuantizeUint8 is QuantizeInt8 into the unsigned range, clamping to [0, 255]. This is the form TFLite and most mobile runtimes use.
func RFFT ¶
func RFFT(src []float64) []complex128
RFFT returns the non-redundant half of the transform of the real sequence src, allocating the plan, the scratch and the result. len(src) must be even with len(src)/2 a power of two; it returns nil otherwise.
Example ¶
A real-input FFT returns n/2+1 complex bins, because the rest are conjugates and carry no new information.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
// A constant signal has all its energy in bin 0.
spectrum := simd.RFFT([]float64{1, 1, 1, 1})
fmt.Println(len(spectrum), real(spectrum[0]), real(spectrum[1]))
}
Output: 3 4 0
func RFFTInto ¶
func RFFTInto(p *RFFTPlan, dst []complex128, src []float64, scratch []complex128)
RFFTInto writes the non-redundant half of the Fourier transform of the real sequence src to dst.
src must be at least p.Len() long and dst at least p.OutLen(). scratch must be at least p.Len()/2 long; a shorter one is replaced by an allocation.
dst[k] equals the k-th bin of the full complex transform, for k from 0 to p.Len()/2. The remaining bins are conj(dst[Len()-k]) and are not written.
func RGBToUVInto ¶
func RGBToUVInto(u, v, r, g, b []byte)
RGBToUVInto writes the two full-range (JFIF) chroma planes of three planar colour channels. The luma plane is GrayscaleInto, which computes the same Y — so a full Y'CbCr conversion is the two calls, and a caller who wants luma alone makes one.
They are separate rather than one call because seven arguments is one more than the SysV amd64 ABI passes in registers, and the fused form was declined by the generator on every amd64 tier.
U and V are biased by 128 so they fit a byte, which is what every 8-bit full-range format does. Each chroma row of the matrix sums to zero, so a grey input gives exactly 128 in both planes — which is why a round trip does not tint a greyscale image.
Like GrayscaleInto it is Q8 fixed point, exact, and allocation-free.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
u := make([]byte, 1)
v := make([]byte, 1)
simd.RGBToUVInto(u, v, []byte{255}, []byte{0}, []byte{0})
fmt.Println(u[0], v[0])
}
Output: 85 255
func RK4Step ¶
func RK4Step[T Float](y []T, t, h T, f Derivative[T], w *RK4Workspace[T])
RK4Step advances y by one step of size h using the classical fourth-order Runge-Kutta method, calling f four times.
It is fourth order, so halving the step size cuts the error per step by about sixteen. w must have been made by NewRK4Workspace for a system at least as large as y.
w := simd.NewRK4Workspace[float64](len(y))
for t := 0.0; t < 10; t += h {
simd.RK4Step(y, t, h, deriv, w)
}
func RMSNorm ¶
func RMSNorm[T Float](a []T, eps T)
RMSNorm divides a by the root mean square of its elements, with eps added for stability.
Unlike LayerNorm it does not subtract the mean, which makes it cheaper and is what several recent transformer architectures use.
func Ramp ¶
func Ramp[T Number](a []T, start, step T)
Ramp fills a with an arithmetic progression: a[i] = start + i*step.
Each element is computed from its own index rather than by accumulating, so rounding error does not build up along the slice and the whole thing vectorizes.
func RandomInto ¶
RandomInto fills dst with uniformly distributed values from a counter-based generator seeded by seed.
float32 and float64 get values in [0, 1); uint64 gets the full range. It allocates nothing.
What "counter-based" buys, and why it is not the usual design ¶
Element i depends on the seed and on i, and on nothing else. A conventional generator — xorshift, PCG, Mersenne Twister — threads a state through the loop, so element i+1 cannot begin until element i has finished. That is a serial dependence and it cannot be vectorized at all.
Computing each element from its index instead makes the loop elementwise, and three useful properties fall out of the same change:
simd.RandomInto(buf, 42) // the same bytes on every architecture
simd.RandomInto(buf[1000:], 42) // the same bytes as the tail of the above
- **The same stream everywhere.** This is integer arithmetic with no
accumulation order, so rule 2 applies and there is nothing to negotiate.
A simulation seeded the same way gives the same answer on your laptop and
on an ARM server.
- **No state to carry.** Filling a window gives the same bytes whether or
not the preceding window was filled, which is what makes a checkpointed
run resumable without serialising a generator.
- **Splittable.** Two goroutines filling disjoint halves produce exactly
what one goroutine filling the whole would.
What it is not ¶
Not cryptographic. The mixing is splitmix64's finalizer, which passes BigCrush and is the standard choice for simulation and initialisation, but an attacker who sees output can recover the seed. Use crypto/rand where that matters.
Not a drop-in for math/rand: the stream is different, and deliberately so — math/rand's is neither reproducible across Go versions nor vectorizable.
Example ¶
A counter-based generator: element i depends on i alone, which is what makes it vectorizable, reproducible across architectures, and splittable across goroutines without any shared state.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
a := make([]float64, 4)
simd.RandomInto(a, 42)
b := make([]float64, 4)
simd.RandomInto(b, 42)
fmt.Println(a[0] == b[0], a[0] >= 0 && a[0] < 1)
}
Output: true true
func Rank ¶
Rank returns the number of set bits in v at positions strictly below p, using a table from RankTableInto.
Exclusive, so Rank(v, t, 0) is 0 and Rank(v, t, len(v)*64) is the total. That is the definition Select inverts: Select(v, t, Rank(v, t, p)) is the first set bit at or after p.
A p past the end returns the total rather than panicking, which is what makes the usual Rank(hi) - Rank(lo) idiom safe at both ends.
Example ¶
Rank counts the set bits below a position. It is exclusive — Rank(v, t, 0) is 0 — which is what makes Select its exact inverse.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
v := []uint64{0b1011}
table := make([]uint64, len(v)+1)
simd.RankTableInto(table, v)
// 0b1011 has bits 0, 1 and 3 set. Below position 2 that is two of them.
fmt.Println(simd.Rank(v, table, 0), simd.Rank(v, table, 2), simd.Rank(v, table, 64))
}
Output: 0 2 3
func RankTableInto ¶
func RankTableInto(dst, v []uint64)
RankTableInto fills dst with the exclusive prefix population count of v: dst[i] is the number of set bits in v[:i], so dst[0] is 0 and the last entry is the total.
dst must have len(v)+1 entries. It panics otherwise, because a table one short answers queries at the end of the vector wrongly rather than visibly.
Example ¶
Rank and Select are the pair every succinct structure is built on. The table is built once — a population count per word, then a prefix sum — and every query afterwards is O(1) or O(log n).
The table is an *exclusive* prefix and has one more entry than the vector, which is what makes Rank a single addition with no special case at a word boundary and makes Select its exact inverse.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
v := []uint64{0b1011, 0b1100} // 3 bits set in word 0, 2 in word 1
table := make([]uint64, len(v)+1)
simd.RankTableInto(table, v)
fmt.Println("set bits below position 3:", simd.Rank(v, table, 3))
fmt.Println("position of the 3rd set bit:", simd.Select(v, table, 2))
fmt.Println("total:", table[len(v)])
}
Output: set bits below position 3: 2 position of the 3rd set bit: 3 total: 5
func ReLU ¶
func ReLU[T Float](a []T)
ReLU clamps every negative element to zero, the rectified linear unit.
It is expressed as a clamp against a scalar rather than an elementwise maximum against a zero slice, so it needs no second buffer. NaN propagates.
func Reciprocal ¶
func Reciprocal[T Float](a []T)
Reciprocal replaces every element with 1/x.
This is a correctly rounded division, not a fast approximation.
func ReplaceByte ¶
ReplaceByte replaces every occurrence of old with new, in place.
func ReplaceByteInto ¶
ReplaceByteInto writes s into dst with every old replaced by new. dst may alias s when s is a []byte.
func RequantizeInt8Into ¶
RequantizeInt8Into takes an int32 accumulator back down to int8 with a scale and zero point:
q = clamp(round(acc*scale) + zeroPoint, -128, 127)
Rounding is half to even, matching QuantizeInt8 and the runtimes this interoperates with. Values outside the range saturate rather than wrapping.
It is separate from QMatMulInt8Into rather than fused because a real layer adds a bias to the int32 accumulator first, and because the scale is usually per output channel — see the per-channel note on QuantizeInt8.
It writes min(len(dst), len(a)) elements and allocates nothing.
Example ¶
RequantizeInt8Into brings an int32 accumulator back to int8: q = round(acc*scale) + zeroPoint, rounding half to even and saturating rather than wrapping.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
dst := make([]int8, 4)
simd.RequantizeInt8Into(dst, []int32{100, 200, 300, 100000}, 0.5, 0)
fmt.Println(dst)
}
Output: [50 100 127 127]
func Rescale ¶
func Rescale[T Float](a []T, lo, hi T)
Rescale maps a linearly onto the range [lo, hi], so its smallest element becomes lo and its largest becomes hi. If every element is identical there is no range to map and a is set to lo.
Rescale(a, 0, 1) is the common min-max normalization.
func ReverseBitsInto ¶
func ReverseBitsInto[T Integer](dst, a []T)
ReverseBitsInto writes each element of a with its bits in reverse order to dst.
func ReverseInto ¶
func ReverseInto[T Number](dst, a []T)
ReverseInto writes a into dst in reverse order.
dst may be a itself, but partially overlapping slices are not supported.
func RollingMaxInto ¶
RollingMaxInto writes the maximum of every window of the given size into dst. See RollingMinInto, whose contract it shares.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
a := []float64{5, 1, 4, 2, 8, 3}
dst := make([]float64, len(a)-3+1)
simd.RollingMaxInto(dst, a, 3)
fmt.Println(dst)
}
Output: [5 4 8 8]
func RollingMinInto ¶
RollingMinInto writes the minimum of every window of the given size into dst: dst[i] is the smallest element of a[i : i+window].
There are len(a)-window+1 outputs, so dst must have room for that many. A window that is not positive, or is longer than a, writes nothing.
The extreme is IEEE 754-2019 minimum, the same one Min and Minimum use: a window containing a NaN yields NaN, and -0 is smaller than +0.
dst must not overlap a.
Use this below a window of about 48, and a deque above it ¶
The textbook sliding-window minimum is a monotonic deque: two amortized comparisons per element, whatever the window. This does window-1 elementwise passes, which is more arithmetic — but each pass is a plain contiguous minimum, the shape a vector unit is fastest at, and they are tiled so the block being accumulated stays in L1 across all of them. So it does sixteen windows at a time where the deque does one, and the comparison turns on the *window*, not on n. Measured on a Zen 5 at one million float64:
window this hand-written deque
4 0.65 ms 8.35 ms 12.8x
8 1.35 8.90 6.6x
16 2.79 8.62 3.1x
32 5.65 8.44 1.5x
64 11.2 8.33 0.75x
256 44.7 8.21 0.18x
The crossover is just above 32, which is four times the eight float64 lanes an AVX-512 register holds. Above roughly 48, write the deque.
This function does not switch to a deque itself, and that is a decision rather than an omission. A deque needs an index ring proportional to the window, which would be the only allocating operation in this library; and getting IEEE minimum out of one is subtle in a way that would not show up in testing — "pop the back while it is worse" does nothing when neither operand orders, so a plain deque holds a NaN without ever reporting it. A third implementation of these semantics is a liability, and the honest thing is to say where this one stops paying. See docs/wrong.md entry 64.
Example ¶
RollingMinInto writes the minimum of every window. There are len(a)-window+1 outputs.
The extreme is IEEE 754-2019 minimum, so a window containing a NaN yields NaN. Below a window of about 48 this beats a hand-written monotonic deque several times over; above it, write the deque — the doc comment has the measurements.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
a := []float64{5, 1, 4, 2, 8, 3}
dst := make([]float64, len(a)-3+1)
simd.RollingMinInto(dst, a, 3)
fmt.Println(dst)
}
Output: [1 1 2 2]
func Rotl ¶
Rotl rotates a left by s bits in place.
Example ¶
Rotl rotates rather than shifts, so nothing falls off the end.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
a := []uint8{0b10000001}
simd.Rotl(a, 1)
fmt.Printf("%08b\n", a[0])
}
Output: 00000011
func RotlInto ¶
RotlInto writes each element of a rotated left by s bits to dst.
A rotate has no undefined case: the count is reduced modulo the element width, so every count is meaningful and none needs clamping.
func Round ¶
func Round[T Float](a []T)
Round rounds every element to the nearest integer, with halves going away from zero. This matches math.Round.
func RoundInto ¶
func RoundInto[T Float](dst, a []T)
RoundInto sets dst[i] to a[i] rounded to nearest, halves away from zero.
func RoundToEven ¶
func RoundToEven[T Float](a []T)
RoundToEven rounds every element to the nearest integer, with halves going to the even neighbour. This matches math.RoundToEven and is the default IEEE 754 rounding mode, which makes it the right choice when rounding repeatedly, since it does not accumulate the upward bias that Round does.
func RoundToEvenInto ¶
func RoundToEvenInto[T Float](dst, a []T)
RoundToEvenInto sets dst[i] to a[i] rounded to nearest, halves to even.
func RunLengthDecodeInt32 ¶
RunLengthDecodeInt32 expands runs back into dst, returning how many elements it wrote.
It is a plain Go loop and there is no kernel behind it, which is the honest shape rather than an omission: expansion's output position depends on the running total of the lengths, so it is a serial prefix. docs/tutorial.md makes the same point about ExpandInto — compression's serial half is the store, which an instruction fixes, and expansion's is the load, which none does.
It stops when dst is full and allocates nothing.
func RunLengthEncodeInt32 ¶
RunLengthEncodeInt32 writes the runs of a into values and lengths, returning how many runs there were.
It needs a scratch []bool at least as long as a — passed in rather than allocated, like every other operation here that needs working space. The run-start mask is computed into it by the kernel and then walked once.
scratch := make([]bool, len(col)) vals := make([]int32, len(col)) lens := make([]int32, len(col)) n := simd.RunLengthEncodeInt32(vals, lens, col, scratch) vals, lens = vals[:n], lens[:n]
values and lengths must each have room for the number of runs, which in the worst case — no two adjacent elements equal — is len(a). It stops early if they are shorter, returning the number of runs written, so a caller who knows the data is run-heavy can size them optimistically and check.
It allocates nothing.
Example ¶
RunLengthEncodeInt32 turns a run of equal values into a value and a count. The run boundaries are found with a vectorized pass; the scratch slice holds them so nothing is allocated.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
a := []int32{7, 7, 7, 9, 9, 4}
values := make([]int32, len(a))
lengths := make([]int32, len(a))
n := simd.RunLengthEncodeInt32(values, lengths, a, make([]bool, len(a)))
fmt.Println(values[:n], lengths[:n])
}
Output: [7 9 4] [3 2 1]
func RunStartsBytesInto ¶
RunStartsBytesInto is RunStartsInto for bytes.
func RunStartsInt64Into ¶
RunStartsInt64Into is RunStartsInto for int64.
func RunStartsInto ¶
RunStartsInto marks every element of a that begins a run of equal values: dst[0] is true, and dst[i] is true when a[i] differs from a[i-1].
This is the vector half of RunLengthEncodeInt32 and it is exported on its own because the mask is useful by itself — feed it to CompressInto to keep one representative per run, or count it to find how many distinct runs a column has before deciding whether encoding is worth it.
It writes min(len(dst), len(a)) entries and allocates nothing.
Example ¶
RunStartsInto marks every element that begins a run, which is the vectorizable half of run-length encoding.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
dst := make([]bool, 6)
simd.RunStartsInto(dst, []int32{7, 7, 7, 9, 9, 4})
fmt.Println(dst)
}
Output: [true false false true false true]
func RuneCount ¶
RuneCount returns the number of runes in s, counting each byte of invalid UTF-8 as one rune.
It matches utf8.RuneCountInString and utf8.RuneCount. The ASCII run is counted without decoding, which is where the time goes in ordinary text.
func SampleStdDev ¶
func SampleStdDev[T Float](a []T) T
SampleStdDev returns the sample standard deviation, the square root of SampleVariance.
func SampleVariance ¶
func SampleVariance[T Float](a []T) T
SampleVariance returns the sample variance, dividing by n-1 rather than n. It returns zero for a slice shorter than two elements.
func SatAdd ¶
func SatAdd[T Saturating](a, b []T)
SatAdd adds b into a with saturation: a[i] = clamp(a[i] + b[i]).
A sum past the element type's maximum gives the maximum, and one past its minimum gives the minimum, instead of wrapping.
It processes min(len(a), len(b)) elements and allocates nothing. Use SatAddInto to write the result elsewhere.
func SatAddInto ¶
func SatAddInto[T Saturating](dst, a, b []T)
SatAddInto sets dst[i] to the saturating sum of a[i] and b[i]. dst may alias a or b.
func SatSub ¶
func SatSub[T Saturating](a, b []T)
SatSub subtracts b from a with saturation: a[i] = clamp(a[i] - b[i]).
For an unsigned type this is the useful one of the pair: the difference clamps at zero rather than wrapping to a huge value.
func SatSubInto ¶
func SatSubInto[T Saturating](dst, a, b []T)
SatSubInto sets dst[i] to the saturating difference of a[i] and b[i]. dst may alias a or b.
func Scale ¶
func Scale[T Number](a []T, s T)
Scale multiplies every element by s: a[i] *= s.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
a := []float64{1, 2, 3}
simd.Scale(a, 10)
fmt.Println(a)
}
Output: [10 20 30]
func ScaleComplex ¶
ScaleComplex multiplies a by the real s in place.
func ScaleComplexInto ¶
ScaleComplexInto sets dst[i] = a[i] * s for a real s. dst may alias a.
Scaling by a real is four multiplies cheaper than a full complex product, which is why it is offered separately rather than left to MulComplex with a slice of reals.
func ScaleInto ¶
func ScaleInto[T Number](dst, a []T, s T)
ScaleInto sets dst[i] = a[i] * s. dst may alias a.
func ScatterInto ¶
ScatterInto writes src to the given indices of dst: dst[idx[i]] = src[i].
Indices outside dst are skipped. Where two indices collide the later write wins, matching what the hardware instruction does.
This is accelerated on amd64 and riscv64 and portable on arm64, s390x, loong64 and ppc64le, and that split is hardware and not an omission. NEON has no scatter instruction at all — a scatter there is a loop of scalar stores, which is what the portable path already is. SVE2 does have one, but skipping out-of-range indices makes the store predicated, and LLVM declines to form a predicated scatter from this loop; forcing it with vectorize(assume_safety) or an explicit width produced one or two vector registers of incidental use and no scatter, so there is nothing to gain by pushing harder. GatherInto has no such problem, because a gather is a load and every one of these architectures either has the instruction or can synthesise it without predication.
func Select ¶
Select returns the position of the k-th set bit of v, counting from zero, or -1 if there are fewer than k+1 set bits.
Binary search over the table for the word, then a walk inside it: O(log n) on the words and at most 64 steps within one. There is no vectorized form because a query reads two words and the search is the whole cost.
Example ¶
Select is Rank's inverse: where is the k-th set bit, counting from zero.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
v := []uint64{0b1011}
table := make([]uint64, len(v)+1)
simd.RankTableInto(table, v)
fmt.Println(simd.Select(v, table, 0), simd.Select(v, table, 2), simd.Select(v, table, 9))
}
Output: 0 3 -1
func SelectInto ¶
SelectInto blends two slices under a mask: dst[i] = mask[i] ? yes[i] : no[i].
This is the branch-free way to apply a condition across a slice, and the operation a vector unit calls a blend. dst may alias yes or no.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
dst := make([]float64, 3)
simd.SelectInto(dst, []bool{true, false, true},
[]float64{1, 2, 3}, []float64{10, 20, 30})
fmt.Println(dst)
}
Output: [1 20 3]
func Shl ¶
Shl shifts a left by s in place.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
a := []uint32{1, 2, 3}
simd.Shl(a, 4)
fmt.Println(a)
}
Output: [16 32 48]
func ShlInto ¶
ShlInto writes a << s to dst.
A count at or above the element width gives zero, as in Go. The count is unsigned because Go panics on a negative shift and a kernel cannot panic.
func ShrInto ¶
ShrInto writes a >> s to dst.
The shift is arithmetic for the signed types and logical for the unsigned ones, as in Go. So a count at or above the width gives zero, except for a negative signed value, which gives -1 — sign extension taken to its limit.
func Sigmoid ¶
func Sigmoid[T Float](a []T)
Sigmoid replaces every element x with the logistic function 1/(1+e**-x).
It is evaluated in whichever of the two algebraically equal forms keeps the exponent negative, so it does not overflow to NaN for large negative inputs the way the naive expression does.
Example ¶
A whole activation layer in one call. Sigmoid guarantees a documented ULP bound; FastSigmoid is a drop-in replacement that trades some of it away.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
x := []float32{-2, -1, 0, 1, 2}
simd.Sigmoid(x)
for _, v := range x {
fmt.Printf("%.4f ", v)
}
fmt.Println()
}
Output: 0.1192 0.2689 0.5000 0.7311 0.8808
func SigmoidInto ¶
func SigmoidInto[T Float](dst, a []T)
SigmoidInto sets dst[i] = 1/(1+e**-a[i]). dst may alias a.
func SignInto ¶
SignInto writes the sign of each element of a to dst: -1 for a negative, +1 for a positive, and zero for either zero.
NaN propagates. sign(NaN) is NaN rather than zero, which is the rule the rest of this package keeps and the one numpy chose; the alternative — treating a NaN as unsigned and so as zero — quietly turns missing data into a real value. Both zeros give +0, because zero has no sign to report even when its bit pattern has one.
scratch is working space of at least len(a) and mask of at least len(a); short ones are replaced by allocations.
This costs more passes than a kernel would, and still wins ¶
It is four passes over the data — two comparisons and two selects — where a kernel would be one. Measured against the branch-per-element loop it replaces: 1.9us against 2.5us at 4096, 45us against 211us at 65536, and 1.40ms against 4.16ms at a million. The extra traffic costs least where the data is largest, because both versions are then memory bound and only one of them branches.
A single-pass kernel would still be faster and is worth writing if a measurement asks for it. That measurement has now been taken and it does not ask.
func Simpson ¶
func Simpson[T Float](y []T, h T) T
Simpson approximates the integral of a function sampled at n evenly spaced points h apart, using Simpson's rule.
It requires an odd number of samples, that is an even number of intervals, and returns 0 otherwise. Its error falls as h⁴.
func Sin ¶
func Sin[T Float](a []T)
Sin replaces every element with its sine, in radians.
Example ¶
package main
import (
"fmt"
"math"
"github.com/sebishogun/simd"
)
func main() {
a := []float64{0, math.Pi / 2}
simd.Sin(a)
fmt.Printf("%.4f %.4f\n", a[0], a[1])
}
Output: 0.0000 1.0000
func SinInto ¶
func SinInto[T Float](dst, a []T)
SinInto sets dst[i] to the sine of a[i]. dst may alias a.
func SinhInto ¶
func SinhInto[T Float](dst, a []T)
SinhInto sets dst[i] to the hyperbolic sine of a[i]. dst may alias a.
func Softmax ¶
func Softmax[T Float](a []T)
Softmax converts a to a probability distribution: each element becomes exp(a[i]) divided by the sum of all the exponentials, so the result is non-negative and sums to one.
The maximum is subtracted before exponentiating. That does not change the result mathematically but it is what stops exp overflowing to Inf for large inputs, which is the usual way a naive implementation produces NaN.
func Softplus ¶
func Softplus[T Float](a []T)
Softplus applies log(1+exp(x)), a smooth approximation to ReLU.
It is evaluated as max(x,0) + log1p(exp(-|x|)), which is exact for large inputs of either sign where the direct form overflows or underflows.
func Sort ¶
func Sort[T Number](a []T)
Measured against slices.Sort ¶
float64, Zen 5, this against Go's pdqsort:
n = 1024 random 6.76us 6.81us even n = 16384 random 649us 804us 24% faster n = 262144 random 13.5ms 16.9ms 26% faster n = 2097152 random 127ms 156ms 24% faster n = 262144 few-distinct 1.10ms 1.58ms 43% faster n = 2097152 few-distinct 10.1ms 13.7ms 36% faster n = 16384 few-distinct 28.7us 22.9us 20% SLOWER
The last row is the one case that still loses, and it is worth saying what changed. It used to lose by 34%, because a median-of-three pivot equal to much of the range sent every copy of itself to the high side and the recursion made no progress against them. extractEqual now takes that run out when the split comes back skewed, which turned the two larger few-distinct sizes from 24% and 17% ahead into 43% and 36%, and cut this row from 34% behind to 20%.
What is left at 16384 is the fixed cost: three passes to detect and remove the equal run, against a range small enough that pdqsort's own duplicate-handling finishes before they pay for themselves. Making that back needs the extraction folded into the partition kernel itself rather than run as separate passes over the result.
Sort sorts a in ascending order, in place.
For floating-point slices NaN sorts to the end, consistently with Median and Quantile and with the IEEE-754-2019 ordering the rest of this package uses; note that this differs from slices.Sort, which orders NaN first.
Negative zero ¶
This is the one place in the package where the accelerated and portable paths may produce different bits for the same input, and it is worth being exact about why. The order is defined by `<`, exactly as slices.Sort and cmp.Less define it, and under `<` negative zero and positive zero compare equal. Which of two equal-comparing values ends up in a given position is then a property of the algorithm, and the two paths run different algorithms — a stable out-of-place partition feeding pdqsort against pdqsort alone. On a 4096-element slice containing both zeros they differed in 848 positions.
Every one of those outputs is a correct ascending sort, and every pair of differing elements is == to the other. Making them agree means giving the zeros a total order, which means a comparator function rather than a bare `<` — measured at 2.5x slower, for a distinction that only math.Signbit can observe. Median and Quantile inherit the same caveat.
Sort allocates. That is unusual for this package and unavoidable here: the accelerated partition is out of place, so it needs somewhere to write. If that matters, use SortInto and supply the scratch yourself.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
a := []float64{3, 1, 4, 1, 5}
simd.Sort(a)
fmt.Println(a)
}
Output: [1 1 3 4 5]
func SortInto ¶
func SortInto[T Number](a, scratch []T)
SortInto sorts a in place using scratch as working space, allocating nothing.
scratch must be at least as long as a. Its contents afterwards are unspecified. This is the form to use in a loop or on a hot path:
scratch := make([]T, len(a)) // once
for _, batch := range batches {
simd.SortInto(batch, scratch)
}
Example ¶
SortInto is Sort with the workspace handed in, so a loop over many batches allocates once rather than once per batch. It sorts a in place; scratch is workspace and its contents afterwards are unspecified.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
scratch := make([]float64, 3) // allocate once, reuse
for _, batch := range [][]float64{{3, 1, 4}, {9, 5, 7}} {
simd.SortInto(batch, scratch)
fmt.Println(batch)
}
}
Output: [1 3 4] [5 7 9]
func SortedIndex ¶
SortedIndex reports where v would be inserted into the sorted slice a to keep it sorted, and whether v is already present. It is a plain binary search, here because it is the natural companion to Sort and callers should not have to reach for a second package to use one with the other.
func SpMVInto ¶
SpMVInto computes dst = A*x for a matrix A in compressed sparse row form.
values and colIdx hold the nonzeros in row-major order; rowPtr has one entry per row plus a final total, so row r occupies values[rowPtr[r]:rowPtr[r+1]]. dst must have one entry per row — len(rowPtr)-1 of them.
This is the loop SparseDot is meant for, written out so callers do not have to get the row slicing right. It allocates nothing and it is not parallel: the rows are independent, so a caller who wants goroutines should split rowPtr and call this on each piece.
Example ¶
SpMVInto is the row loop written out, for a matrix in compressed sparse row form. It allocates nothing and is not parallel: the rows are independent, so split rowPtr to use goroutines.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
// [ 1 0 2 ] [1] [ 1*1 + 2*3 ] [7]
// [ 0 3 0 ] * [2] = [ 3*2 ] = [6]
values := []float64{1, 2, 3}
colIdx := []int32{0, 2, 1}
rowPtr := []int32{0, 2, 3}
x := []float64{1, 2, 3}
dst := make([]float64, len(rowPtr)-1)
simd.SpMVInto(dst, values, colIdx, rowPtr, x)
fmt.Println(dst)
}
Output: [7 6]
func SparseDot ¶
SparseDot returns the sum of v[i] * x[idx[i]] — one row of a sparse matrix-vector product.
v and idx are the row's values and column indices and must be the same length; the shorter bounds the work. x is the dense vector.
An index outside x contributes nothing rather than panicking, the same contract GatherInto has: these indices are usually computed, and a stray one should not take the process down. Check them yourself if you need strictness.
Example ¶
SparseDot is one row of a sparse matrix-vector product. An index outside x contributes nothing rather than panicking, the same contract GatherInto has.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
// Row with nonzeros at columns 0 and 3.
values := []float64{2, 5}
colIdx := []int32{0, 3}
x := []float64{10, 0, 0, 4}
fmt.Println(simd.SparseDot(values, colIdx, x)) // 2*10 + 5*4
}
Output: 40
func Sqrt ¶
func Sqrt[T Float](a []T)
Sqrt replaces every element with its square root.
A negative input yields NaN, following IEEE 754.
func SquaredDistance ¶
func SquaredDistance[T Number](a, b []T) T
SquaredDistance returns the squared Euclidean distance between a and b.
Prefer it over Distance when comparing distances to each other, since the square root changes nothing about the ordering and costs time.
func Standardize ¶
func Standardize[T Float](a []T)
Standardize rescales a to zero mean and unit standard deviation, the transform usually called a z-score. If every element is identical the standard deviation is zero and a is set to all zeros.
func StdDev ¶
func StdDev[T Float](a []T) T
StdDev returns the population standard deviation, the square root of Variance.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
fmt.Printf("%.4f\n", simd.StdDev([]float64{2, 4, 4, 4, 5, 5, 7, 9}))
}
Output: 2.0000
func SubComplex ¶
func SubComplex[C Complex](a, b []C)
SubComplex subtracts b from a elementwise: a[i] -= b[i].
func SubComplexInto ¶
func SubComplexInto[C Complex](dst, a, b []C)
SubComplexInto sets dst[i] = a[i] - b[i]. dst may alias a or b.
func SubInto ¶
func SubInto[T Number](dst, a, b []T)
SubInto sets dst[i] = a[i] - b[i]. dst may alias a or b.
func SubScalar ¶
func SubScalar[T Number](a []T, s T)
SubScalar subtracts s from every element: a[i] -= s.
func SubScalarInto ¶
func SubScalarInto[T Number](dst, a []T, s T)
SubScalarInto sets dst[i] = a[i] - s. dst may alias a.
func Sum ¶
func Sum[T Number](a []T) T
Sum returns the sum of all elements, or zero for an empty slice.
Integer summation wraps on overflow.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
fmt.Println(simd.Sum([]float64{1, 2, 3, 4}))
}
Output: 10
func SumComplex ¶
func SumComplex[C Complex](a []C) C
SumComplex returns the sum of a.
Both components accumulate into the same fixed number of lanes the real Sum uses, so the answer does not change with the machine's vector width.
func SumSquares ¶
func SumSquares[T Number](a []T) T
SumSquares returns the sum of the squares of all elements.
This is Dot(a, a), and is the un-rooted form of Norm.
func TanInto ¶
func TanInto[T Float](dst, a []T)
TanInto sets dst[i] to the tangent of a[i]. dst may alias a.
func TanhInto ¶
func TanhInto[T Float](dst, a []T)
TanhInto sets dst[i] to the hyperbolic tangent of a[i]. dst may alias a.
func Tier ¶
func Tier() string
Tier returns the name of the instruction-set tier whose kernels this process is actually running, such as "avx2", "neon" or "scalar".
This is the backend in use, which is not always the best tier the CPU supports: if no kernels have been generated for that tier yet, the next one down is used instead. Describe reports both.
func Tile ¶
func Tile[T Number](a, pattern []T)
Tile fills a by repeating pattern, truncating the last copy if it does not fit evenly. A empty pattern leaves a unchanged.
func ToLowerASCII ¶
func ToLowerASCII(b []byte)
ToLowerASCII maps A-Z to a-z in place, leaving every other byte alone. See ToUpperASCII on why this is UTF-8 safe.
func ToLowerASCIIInto ¶
ToLowerASCIIInto writes the ASCII-lowercased s into dst. dst may alias s when s is a []byte.
func ToUpperASCII ¶
func ToUpperASCII(b []byte)
ToUpperASCII maps a-z to A-Z in place, leaving every other byte alone.
Only ASCII is folded, which makes this safe to run over UTF-8: continuation bytes are all 0x80 or above and are untouched. For full Unicode folding use the strings package; that is not a vectorizable operation.
func ToUpperASCIIInto ¶
ToUpperASCIIInto writes the ASCII-uppercased s into dst. dst may alias s when s is a []byte.
func TopK ¶
TopK is TopKInto allocating both the destination and the scratch.
Example ¶
The k largest without sorting: TopK selects around the k-th order statistic in linear time, so taking the top 3 of a million never sorts the million.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
scores := []float64{12, 99, 7, 45, 68, 99, 3, 81}
fmt.Println(simd.TopK(scores, 3))
}
Output: [99 99 81]
func TopKInto ¶
TopKInto writes the k largest elements of a to dst, in descending order, and returns how many it wrote — min(k, len(a), len(dst)).
It does not sort a, and it does not sort more of a than it has to: the quickselect that Median uses partitions around the k-th largest in linear time, and only the k elements to its right are then sorted. Sorting the whole slice to take the top ten of a million is the thing this exists to avoid.
NaN sorts to the end, as in Sort, so NaNs are the last thing returned and only when k reaches them.
a is reordered. scratch is working space of at least len(a); a shorter one falls back to a sort rather than failing, which costs speed and not correctness.
func TrailingZerosInto ¶
func TrailingZerosInto[T Integer](dst, a []T)
TrailingZerosInto writes the number of trailing zero bits of each element of a to dst. Zero gives the element width, as in math/bits.TrailingZeros.
func Transpose ¶
Transpose is TransposeInto allocating the destination.
func TransposeInto ¶
TransposeInto writes the m*n row-major matrix a as an n*m row-major matrix into dst.
It does nothing if either slice is too short for the stated dimensions, so check your sizes — the same contract MatMulInto has.
dst must not overlap a. An in-place transpose of a non-square matrix is a different algorithm entirely, a permutation into cycles, and it is not what this is.
The kernel walks the matrix in square blocks rather than row by row. Written the obvious way the loop reads a contiguously and writes dst with a stride of m, so every write lands in a different cache line and a matrix wider than the cache evicts each line before its next element arrives. Blocking keeps a block's rows and columns resident together, so each line is filled before it leaves.
Example ¶
TransposeInto is blocked rather than a naive double loop, because the naive one strides the whole row length on every element and misses the cache on each.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
// 2x3 -> 3x2
a := []float64{1, 2, 3, 4, 5, 6}
dst := make([]float64, 6)
simd.TransposeInto(dst, a, 2, 3)
fmt.Println(dst)
}
Output: [1 4 2 5 3 6]
func Trapezoid ¶
func Trapezoid[T Float](y []T, h T) T
Trapezoid approximates the integral of a function sampled at n evenly spaced points h apart, using the trapezoidal rule.
Its error falls as h². For smooth functions Simpson is usually far better for the same samples.
func TrimAny ¶
func TrimAny[S, T Text](s S, cutset T) S
TrimAny returns s with every leading and trailing byte that is in cutset removed.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
fmt.Printf("%q\n", simd.TrimAny("xxhelloxx", "x"))
}
Output: "hello"
func TrimLeftAny ¶
func TrimLeftAny[S, T Text](s S, cutset T) S
TrimLeftAny returns s with every leading byte that is in cutset removed.
As with IndexAny this is a byte-set operation, not a rune-set one, so it matches strings.TrimLeft only for an ASCII cutset.
func TrimRightAny ¶
func TrimRightAny[S, T Text](s S, cutset T) S
TrimRightAny returns s with every trailing byte that is in cutset removed.
func TrimSpaceASCII ¶
func TrimSpaceASCII[S Text](s S) S
TrimSpaceASCII returns s with leading and trailing ASCII whitespace removed.
It is strings.TrimSpace restricted to the six bytes ASCII calls space, which is what a protocol parser wants: the Unicode set adds NEL, NBSP and the whole of the Zs category, none of which appear in a header line and all of which cost a rune decode to recognize.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
fmt.Printf("%q\n", simd.TrimSpaceASCII(" hello\t\n"))
}
Output: "hello"
func Trunc ¶
func Trunc[T Float](a []T)
Trunc rounds every element towards zero, discarding the fractional part.
func TruncInto ¶
func TruncInto[T Float](dst, a []T)
TruncInto sets dst[i] to a[i] rounded towards zero. dst may alias a.
func UTF8Len ¶
UTF8Len returns the number of bytes AppendUTF8 would append for s.
func UTF16Len ¶
UTF16Len returns the number of UTF-16 units AppendUTF16 would append for s, so that a caller can size a buffer exactly.
It is a full pass over the input. Sizing with len(s) instead is always safe and never more than a factor of two too large — the worst case is all-ASCII, one unit per byte — so this is for callers who would rather pay the scan than the memory.
func ValidUTF8 ¶
ValidUTF8 reports whether s is entirely well-formed UTF-8.
It matches utf8.Valid and utf8.ValidString.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
fmt.Println(simd.ValidUTF8("héllo"), simd.ValidUTF8([]byte{0xff, 0xfe}))
}
Output: true false
func Variance ¶
func Variance[T Float](a []T) T
Variance returns the population variance: the mean of the squared deviations from the mean. It returns zero for a slice shorter than two elements.
It uses two passes rather than the single-pass sum-of-squares identity, because that identity loses most of its significant digits when the variance is small relative to the mean.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
fmt.Println(simd.Variance([]float64{2, 4, 4, 4, 5, 5, 7, 9}))
}
Output: 4
func VarintLenInto ¶
func VarintLenInto[T VarintValue](dst []int32, a []T)
VarintLenInto writes the LEB128 width of each element of a into dst: 1 to 5 bytes for uint32, 1 to 10 for uint64.
dst and a must be the same length; the shorter bounds the work.
Prefix-summing the result with CumSumInto gives every value its offset in the encoded stream, which is what makes the writes independent.
Example ¶
VarintLenInto gives the per-value widths. Prefix-summed, they are every value's offset in the stream, which is what makes the writes independent.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
lens := make([]int32, 3)
simd.VarintLenInto(lens, []uint64{1, 300, 70000})
fmt.Println(lens)
}
Output: [1 2 3]
func VarintSize ¶
func VarintSize[T VarintValue](a []T) int
VarintSize returns the total number of bytes the whole slice encodes to.
One vectorized pass, no allocation. This is the number to pass to make when building the destination buffer: sized exactly, it is written once, where an append-and-grow encoder copies what it has already written every time the slice doubles.
Example ¶
VarintSize gives the exact encoded length of a whole slice in one vectorized pass, so an encoder sizes its buffer once instead of growing it.
Writing the bytes is serial and always will be — where value i lands depends on the width of every value before it — but asking how wide each one is vectorizes, and that is the part that lets the allocation happen once.
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
a := []uint64{1, 300, 70000}
fmt.Println(simd.VarintSize(a), "bytes")
}
Output: 6 bytes
func VerletStep ¶
func VerletStep[T Float](pos, vel, acc []T, h T, accel func(pos, out []T))
VerletStep advances position and velocity by one step of size h using velocity Verlet, given the acceleration at the current and next positions.
It is the standard integrator for molecular dynamics and games because it conserves energy over long runs far better than Euler or even RK4 does, and because it needs only one force evaluation per step.
accel is called with the position to evaluate and the buffer to write the acceleration into. acc holds the acceleration at the current position on entry and at the new position on return, so pass the same slice back on the next step rather than recomputing it.
func Xor ¶
func Xor(a, b []byte)
Xor flips in a every bit set in b: a[i] ^= b[i].
It processes min(len(a), len(b)) bytes and allocates nothing. Use XorInto to write the result elsewhere.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
a := []uint8{0b1100, 0b1010}
simd.Xor(a, []uint8{0b1010, 0b1010})
fmt.Println(a)
}
Output: [6 0]
func XorInto ¶
func XorInto(dst, a, b []byte)
XorInto sets dst[i] = a[i] ^ b[i].
It processes min(len(dst), len(a), len(b)) bytes. dst may alias a or b.
func XorMaskInto ¶
func XorMaskInto(dst, a, b []bool)
XorMaskInto sets dst[i] = a[i] != b[i]. dst may alias a or b.
func ZigzagDecodeInt8Into ¶
ZigzagDecodeInt8Into is the inverse of ZigzagEncodeInt8Into.
func ZigzagDecodeInt16Into ¶
ZigzagDecodeInt16Into is the inverse of ZigzagEncodeInt16Into.
func ZigzagDecodeInt32Into ¶
ZigzagDecodeInt32Into is the inverse of ZigzagEncodeInt32Into.
func ZigzagDecodeInt64Into ¶
ZigzagDecodeInt64Into is the inverse of ZigzagEncodeInt64Into.
func ZigzagEncodeInt8Into ¶
ZigzagEncodeInt8Into is ZigzagEncodeInt32Into for 8-bit values.
func ZigzagEncodeInt16Into ¶
ZigzagEncodeInt16Into is ZigzagEncodeInt32Into for 16-bit values.
func ZigzagEncodeInt32Into ¶
ZigzagEncodeInt32Into maps signed integers onto unsigned ones so that a small magnitude of either sign becomes a small unsigned value:
0, -1, 1, -2, 2 -> 0, 1, 2, 3, 4
This is the transform that makes a varint of a negative number short, and it is what protobuf, Avro and delta-encoded column stores apply before varint encoding. Without it, -1 as a two's complement 32-bit value has every high bit set and costs the full five bytes.
The mapping is a bijection: every value round-trips through ZigzagDecodeInt32Into, including math.MinInt32, which is the case a naive negate-and-double formulation overflows on.
It writes min(len(dst), len(a)) elements and allocates nothing.
Example ¶
package main
import (
"fmt"
"github.com/sebishogun/simd"
)
func main() {
deltas := []int32{0, -1, 1, -2, 2}
enc := make([]uint32, len(deltas))
simd.ZigzagEncodeInt32Into(enc, deltas)
// Small magnitudes of either sign became small unsigned values, which is
// what makes the varint of each one a single byte.
fmt.Println(enc)
}
Output: [0 1 2 3 4]
func ZigzagEncodeInt64Into ¶
ZigzagEncodeInt64Into is ZigzagEncodeInt32Into for 64-bit values, which is the width protobuf's sint64 uses.
Types ¶
type Accumulator ¶
type Accumulator[T Float] struct { // contains filtered or unexported fields }
Accumulator is a resumable sum over float32 or float64.
The zero value is ready to use. Add it chunks in order; Accumulator.Sum, Accumulator.Mean and Accumulator.Count can be read at any point and do not disturb the accumulation.
var acc simd.Accumulator[float64]
for {
n, err := read(buf)
acc.Add(buf[:n])
if err != nil {
break
}
}
fmt.Println(acc.Sum(), acc.Mean())
The result equals Sum over the concatenation, bit for bit, however the chunks fell. It allocates nothing.
func (*Accumulator[T]) Add ¶
func (s *Accumulator[T]) Add(a []T)
Add folds a chunk into the accumulator.
It processes the chunk in three parts, and the split is what preserves the lane assignment: a head of elements up to the next multiple of SumLanes, then whole blocks through the vectorized kernel, then a tail. Only the middle part is accelerated, which is the right trade — the head and tail are at most fifteen elements each per call.
func (*Accumulator[T]) Count ¶
func (s *Accumulator[T]) Count() int
Count returns how many elements have been added.
func (*Accumulator[T]) Mean ¶
func (s *Accumulator[T]) Mean() T
Mean returns the arithmetic mean of everything added so far, or zero if nothing has been.
func (*Accumulator[T]) Reset ¶
func (s *Accumulator[T]) Reset()
Reset returns the accumulator to its zero state, keeping any allocation. There is none to keep, which is the point — it is here so a caller can reuse one in a loop without thinking about it.
func (*Accumulator[T]) Sum ¶
func (s *Accumulator[T]) Sum() T
Sum returns the sum of everything added so far.
It folds a copy of the accumulators, so it can be called at any point without disturbing the accumulation.
type Complex ¶
type Complex interface{ ~complex64 | ~complex128 }
Complex is the constraint for the complex element types.
type Derivative ¶
type Derivative[T Float] func(t T, y, dydt []T)
Derivative computes dy/dt at time t for state y, writing into dydt.
It must not resize or reallocate dydt, and must write every element.
type FFTPlan ¶
type FFTPlan struct {
// contains filtered or unexported fields
}
FFTPlan holds the tables for transforms of one length. It is safe for concurrent use by multiple goroutines, since transforming does not modify it.
func NewFFTPlan ¶
NewFFTPlan builds a plan for transforms of length n, which must be a power of two and at least 1. It returns nil for any other n.
type IntAccumulator ¶
type IntAccumulator[T Integer] struct { // contains filtered or unexported fields }
IntAccumulator is a resumable integer sum.
Separate from Accumulator because integer addition is associative, so there is no lane discipline to preserve and the state is one value. It wraps on overflow, like Sum and like the hardware.
func (*IntAccumulator[T]) Count ¶
func (s *IntAccumulator[T]) Count() int
Count returns how many elements have been added.
func (*IntAccumulator[T]) Reset ¶
func (s *IntAccumulator[T]) Reset()
Reset returns the accumulator to its zero state.
func (*IntAccumulator[T]) Sum ¶
func (s *IntAccumulator[T]) Sum() T
Sum returns the sum of everything added so far.
type Integer ¶
Integer is the element type of operations that only make sense for integers: the bitwise ones, and the two saturating ones.
type MinMaxAccumulator ¶
type MinMaxAccumulator[T Float] struct { // contains filtered or unexported fields }
MinMaxAccumulator is a resumable minimum and maximum.
The zero value is ready to use. Unlike Accumulator this needs no lane discipline: minimum and maximum are associative and commutative, so no grouping is observable and the state is just the two values.
NaN propagates the way Min and Max define it: a NaN anywhere in the input makes both results NaN, and it cannot be un-seen by a later chunk.
func (*MinMaxAccumulator[T]) MinMax ¶
func (s *MinMaxAccumulator[T]) MinMax() (min, max T, ok bool)
MinMax returns the minimum and maximum seen so far, and whether anything has been added. Both are zero when nothing has.
func (*MinMaxAccumulator[T]) Reset ¶
func (s *MinMaxAccumulator[T]) Reset()
Reset returns the accumulator to its zero state.
type MultiSearcher ¶
type MultiSearcher struct {
// contains filtered or unexported fields
}
MultiSearcher searches a haystack for any of a fixed set of needles.
Compile the set once and search many haystacks with it:
m := simd.NewMultiSearcher(keywords)
for _, line := range lines {
if pos, which := m.Index(line); pos >= 0 {
...
}
}
A MultiSearcher is immutable after construction and safe for concurrent use.
func NewMultiSearcher ¶
func NewMultiSearcher[T Text](needles []T) *MultiSearcher
NewMultiSearcher compiles a needle set. An empty needle matches at position 0, as Index does; duplicate needles are kept, and the earliest-listed one wins a tie.
func (*MultiSearcher) Contains ¶
func (m *MultiSearcher) Contains(haystack []byte) bool
Contains reports whether any needle occurs in haystack.
func (*MultiSearcher) ContainsString ¶
func (m *MultiSearcher) ContainsString(haystack string) bool
ContainsString is MultiSearcher.Contains over a string.
func (*MultiSearcher) Index ¶
func (m *MultiSearcher) Index(haystack []byte) (pos, which int)
Index returns the position of the earliest match of any needle in haystack and which needle matched, or (-1, -1) if none does.
Earliest is by position first and by needle order second, so the result does not depend on which needle the scan happened to find first.
A method cannot take a type parameter in Go, so unlike the free functions in this package these come in []byte and string pairs, as the standard library's own bytes and strings packages do.
func (*MultiSearcher) IndexString ¶
func (m *MultiSearcher) IndexString(haystack string) (pos, which int)
IndexString is MultiSearcher.Index over a string, without copying it.
type Number ¶
type Number interface {
float32 | float64 |
int8 | int16 | int32 | int64 |
uint8 | uint16 | uint32 | uint64
}
Number is the element type of the arithmetic operations.
The types are listed exactly rather than as approximations, so a defined type such as `type Celsius float32` is not accepted. Supporting those would require reinterpreting the slice, which this package does not do.
type RFFTPlan ¶
type RFFTPlan struct {
// contains filtered or unexported fields
}
RFFTPlan transforms real sequences of a fixed even length.
A real signal's spectrum is conjugate-symmetric, so half of it is redundant and half the work is wasted computing it. This does the standard trick: pack the n real samples into n/2 complex ones by putting the even-indexed samples in the real parts and the odd-indexed in the imaginary parts, run a complex transform of half the length, then untangle. The result is the n/2+1 non-redundant bins, from DC to Nyquist.
Measured against transforming the same real signal as complex, float64, median of five:
n = 1024 5.85us vs 6.19us 6% faster n = 65536 532us vs 818us 54% faster
Short of the factor of two the operation count promises, because the untangling pass is real work and is not free — at 1024 it very nearly eats the whole saving. It also halves the output: n/2+1 bins instead of n, which at 65536 is 512 KiB rather than 1 MiB, and that matters more than the time for anything that keeps spectra around.
func NewRFFTPlan ¶
NewRFFTPlan builds a plan for real transforms of length n, which must be even and n/2 must be a power of two — so n is 2, 4, 8, 16 and so on. It returns nil otherwise.
type RK4Workspace ¶
type RK4Workspace[T Float] struct { // contains filtered or unexported fields }
RK4Workspace holds the scratch that RK4Step needs, so that stepping a system forward allocates nothing however many steps you take.
Allocate it once with NewRK4Workspace and reuse it for the whole integration.
func NewRK4Workspace ¶
func NewRK4Workspace[T Float](n int) *RK4Workspace[T]
NewRK4Workspace allocates the scratch for integrating a system of n equations. This is the only function in the package that allocates, and it is called once per system rather than once per step.
type Saturating ¶
Saturating is the element type of SatAdd and SatSub.
The 64-bit types are absent, and it is a limit of the implementation rather than of the idea. A saturating add is written as a widening add followed by a clamp, which is the form that compiles to the single instruction every vector unit has for it — and there is no integer wider than 64 bits to widen into. Clamping a 64-bit sum needs an overflow test, which does not vectorize into anything worth crossing the call boundary for.
type Text ¶
Text is the constraint for input a scanning function only reads.
The two types are listed exactly rather than as approximations, the same choice Number makes: a defined type such as `type Token string` is not accepted, because supporting it would mean reinterpreting the value and this package does not do that behind a caller's back.
type VarintValue ¶
VarintValue is the pair of widths LEB128 is defined for here. Signed values are encoded by zigzagging first — see ZigzagEncodeInt32Into — which is what Protocol Buffers calls sint32 and sint64, and what keeps a small negative number one byte rather than ten.
Source Files
¶
- backend_asm.go
- bits.go
- bitvector.go
- bytes.go
- compare.go
- complex.go
- compress.go
- convert.go
- convert_fp.go
- convolve.go
- dispatch.go
- escape.go
- fastmath.go
- fft.go
- fold.go
- gemmpack.go
- histogram.go
- import_amd64.go
- math.go
- multi.go
- nary.go
- numeric.go
- parallel.go
- parsefloat.go
- predicate.go
- random.go
- reduce.go
- rle.go
- sets.go
- simd.go
- sort.go
- sparse.go
- stats.go
- stream.go
- text.go
- utf16.go
- varint.go
- vec_stub.go
- window.go
- wrangle.go
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
simdinfo
command
Command simdinfo reports which instruction-set tier the simd package selected on this machine, and which ones it could have used.
|
Command simdinfo reports which instruction-set tier the simd package selected on this machine, and which ones it could have used. |
|
site
command
Command site serves a page that runs this library's benchmarks on the machine visiting it, beside the code each one measures.
|
Command site serves a page that runs this library's benchmarks on the machine visiting it, beside the code each one measures. |
|
docs
|
|
|
examples/csvscan
command
Command csvscan is a complete program: it parses a CSV column of numbers and reports statistics on it, doing both halves with this library.
|
Command csvscan is a complete program: it parses a CSV column of numbers and reports statistics on it, doing both halves with this library. |
|
examples/standardise
command
Command standardise is the worked example from docs/tutorial.md, kept here as a program so that `go build ./...` and `go vet ./...` check it on every commit.
|
Command standardise is the worked example from docs/tutorial.md, kept here as a program so that `go build ./...` and `go vet ./...` check it on every commit. |
|
internal
|
|
|
backend
Package backend is the registry that connects generated assembly to the dispatcher.
|
Package backend is the registry that connects generated assembly to the dispatcher. |
|
benchmarks
Package benchmarks holds every benchmark in this repository.
|
Package benchmarks holds every benchmark in this repository. |
|
cpu
Package cpu detects which SIMD instruction-set tier the running CPU supports.
|
Package cpu detects which SIMD instruction-set tier the running CPU supports. |
|
kernel
Package kernel defines the contract every backend implements.
|
Package kernel defines the contract every backend implements. |
|
perf
Package perf is a repetition tester in the style Casey Muratori uses in Performance-Aware Programming.
|
Package perf is a repetition tester in the style Casey Muratori uses in Performance-Aware Programming. |
|
ref
Package ref is the portable Go reference implementation of every kernel.
|
Package ref is the portable Go reference implementation of every kernel. |
|
tests/arrays
Package arrays holds the arrays tests for github.com/sebishogun/simd.
|
Package arrays holds the arrays tests for github.com/sebishogun/simd. |
|
tests/docs
Package docs checks the repository's own documentation against the tree: the counts in the README, the identifiers it names, the tiers CONTRIBUTING asks for runs on, and that every operation in the README's index has a runnable example.
|
Package docs checks the repository's own documentation against the tree: the counts in the README, the identifiers it names, the tiers CONTRIBUTING asks for runs on, and that every operation in the README's index has a runnable example. |
|
tests/dsp
Package dsp holds the dsp tests for github.com/sebishogun/simd.
|
Package dsp holds the dsp tests for github.com/sebishogun/simd. |
|
tests/encode
Package encode holds the encode tests for github.com/sebishogun/simd.
|
Package encode holds the encode tests for github.com/sebishogun/simd. |
|
tests/matrix
Package matrix holds the matrix tests for github.com/sebishogun/simd.
|
Package matrix holds the matrix tests for github.com/sebishogun/simd. |
|
tests/reduce
Package reduce holds the reduce tests for github.com/sebishogun/simd.
|
Package reduce holds the reduce tests for github.com/sebishogun/simd. |
|
tests/search
Package search holds the search tests for github.com/sebishogun/simd.
|
Package search holds the search tests for github.com/sebishogun/simd. |
|
tests/text
Package text holds the text tests for github.com/sebishogun/simd.
|
Package text holds the text tests for github.com/sebishogun/simd. |