f64 SIMD vector fast math·s library for Zig
- Zig 100%
| src | Fixed minor compilation errors 2 | |
| .gitignore | Initialized repo with fully formed code, who needs git during development? | |
| build.zig | Removed redundant code and simplified repo structure. Enabled operations on arbitrary comptime-known vector lengths. | |
| build.zig.zon | Initialized repo with fully formed code, who needs git during development? | |
| LICENSE | Initial commit | |
| README.md | Added some raw timings for benchmarked functions | |
fvml
f64 vector math·s library
A decently fast f64 SIMD vector math library for Zig.
NOTE: This is not a linear algebra vector library. It is a library for SIMD operations.
Features:
- SIMD-friendly implementations of functions implemented for scalars but not for SIMD vectors in the Zig standard library.
- Fast approximations for slow existing SIMD functions (e.g.
exp,log,mod). - A guaranteed relative error under
1e-10for all functions implemented in the Zig standard library (see Parity with Zig standard library) - N-Ary operators (sums, products, means)
- Extra functions for statistics, biology, and AI/ML.
- Out-of-the-box support for 256-bit (AVX/AVX2), 512-bit (AVX-512) vectors as well as automatically detected width vectors on supported architectures.
- Straightforward support for arbitrary-length vectors, as long as the length is known at compile-time.
Getting started
Adding fvml to a project
fvml can be added to any Zig project's build.zig.zon via:
zig fetch --save=fvml git+https://codeberg.org/13091101/fvml
Using fvml
build.zig (an example, modify as you see fit)
conststd=@import("std");pubfnbuild(b:*std.Build)void{consttarget=b.standardTargetOptions(.{});constoptimize=b.standardOptimizeOption(.{});...// Normal build.zig contentsconstfvml_dep=b.dependency("fvml",.{.target=target,.optimize=optimize,});constmod=b.addModule(.{.root_source_file=b.path("src/main.zig"),.optimize=optimize,.target=target,});mod.addImport("fvml",fvml_dep.module("fvml"));constexe=b.addExecutable(.{.name="project-name",.root_module=mod,});b.installArtifact(exe);...// Normal build.zig contents}main.zig
constfvml=@import("fvml");// AVX-2, AVX-512 can be accessed directlyconstf64x4:type=fvml.f64x4;constf64x8:type=fvml.f64x8;constavx2=fvml.avx2;constavx512=fvml.avx512;// ... Or by their namespaceconstf64x4_separated:type=avx2.f64xN;// Automatic detection of supported vectors is possible on supported hardwareconstautodetected=fvml.auto;constVecN:type=autodetected.f64xN;constvec_len:usize=fvml.auto_vec_len;// Custom vector lengths are also accessible, as they are an alias to Zig's inbuilt `@Vector(type, len)`constcustom=fvml.common.GenerateOps(3);constVec3:type=custom.f64xN;// Operations & SIMD constants are accessible from their namespaceconstcbrt_sin_3:f64x4=avx2.cbrt(avx2.sin(avx2.One+avx2.Two));constsomething_else:f64x8=avx512.sigmoid(avx512.erf(avx512.Pi));// `f64xN` is a type alias, so normal SIMD vector operations workconstthree:f64x4=@splat(3.0);List of non-Zig-stdlib functions
Unary operators
erf: Error functionerfinv: Inverse of the error functionsigmoid: Sigmoid functionrelu: Rectified linear unit (ReLU)gelu: Gaussian-error linear unit (GELU)swish: Sigmoid linear unit or swish functionmish: Mish functionsoftplus: Softplus function, useful for ML/AIsoftsign: Softsign functionlogsigmoid: LogSigmoid function
Binary operators
leakyrelu: Leaky ReLU variant of the ReLU functionelu: Exponential linear units (ELU)geometric_arithmetic_mean: Arithmetic-geometric meanand,or: Logical operators checking whether values are not zero.bitand,bitor,bitxor: Bitwise operations operating directly on f64. Usage not recommended.
N-ary operators
sum: Sum / summation operation. Has an ILP variant.product: Repeated product operation. Has an ILP variant.arithmetic_mean: Arithmetic mean. Has an ILP variant.geometric_mean_unstable: Naive geometric mean implementation that may be numerically unstable for large or small inputs. Has an ILP variant.geometric_mean: Numerically stable geometric mean using alog2/exp2identity. Slower than the unstable variant (4x in personal benchmarks).harmonic_mean: Harmonic mean operation.hypot: Euclidean / L2 Norm operation. Has an ILP variant.
Parity with Zig standard library
We tested the relative error against the Zig standard library implementation.
- Tests are run in
Debug,ReleaseSafe, andReleaseFastmodes to ensure there is no unexpected behaviour. - Test results are the combined results for 256-bit (
f64x4), 512-bit (f64x8) and autodetected width (f64xN) vector implementations.
Test details can be found in src/tests.zig, but as a general overview:
- Ranges tested:
-1..1,-50..50,-1e4..1e4,-1e27..1e27 - Test types: reproducible (RNG with set seed), non-reproducible (RNG seeded from current time)
- Samples: 10k per test type per range (i.e. 80k values tested per function, per test run)
Unary Functions
Inverse hyperbolic trigonometric functions (atanh, acosh, asinh) are tested against a modified version of the standard library baseline, as the stdlib implementations can provide incorrect values for out-of-domain inputs.
| Function | 1e-10 | 1e-11 | 1e-13 |
|---|---|---|---|
| ln / @log | ✅ | ✅ | ❌ |
| log2 | ✅ | ❌ | ❌ |
| log10 | ✅ | ✅ | ❌ |
| exp2 | ✅ | ✅ | ❌ |
| exp | ✅ | ✅ | ❌ |
| cbrt | ✅ | ✅ | ❌ |
| atan | ✅ | ❌ | ❌ |
| asin | ✅ | ❌ | ❌ |
| acos | ✅ | ✅ | ❌ |
| tanh | ✅ | ❌ | ❌ |
| sinh | ✅ | ❌ | ❌ |
| cosh | ✅ | ✅ | ❌ |
| atanh | ✅ | ✅ | ❌ |
| asinh | ✅ | ❌ | ❌ |
| acosh | ✅ | ✅ | ❌ |
| sign (full) | ✅ | ✅ | ✅ |
| sign (partial) | ✅ | ✅ | ✅ |
Binary Functions
| Function | 1e-10 | 1e-11 | 1e-13 |
|---|---|---|---|
| log | ✅ | ❌ | ❌ |
| pow | ✅ | ❌ | ❌ |
| atan2 | ✅ | ❌ | ❌ |
| copysign | ✅ | ✅ | ✅ |
Performance comparison
Benchmarking conditions:
- CPU: Intel Core i7-12700H
- Arch: x86_64-linux
- OS: NixOS Yarara (26.05.20260316)
- Zig version: 0.16.0
ReleaseSafeandReleaseFastbenchmarks were compiled with-Dtarget=native -Dcpu-native- Benchmarks pinned to a single CPU core with
taskset --cpu-list 0(Alderlake P-core, average benchmark frequency: 4.6 GHz) - Dataset: array of 10k random
f64x4items (~320kB per pass, likely residing in L2 cache), timings averaged over 512 passes
Only the f64x4 implementations have been benchmarked so far.
SIMD operators in the Zig standard library
Input range: [0,1)
| Function | Debug Speedup | ReleaseSafe Speedup | ReleaseFast Speedup |
|---|---|---|---|
Ln, @log |
0.25x | 4.71x | 4.71x |
Log2 |
0.32x | 7.35x | 7.34x |
Log10 |
0.34x | 7.51x | 7.49x |
Mod |
22.55x | 55.38x | 54.40x |
Exp2 |
0.22x | 3.09x | 3.57x |
Exp |
0.35x | 4.64x | 5.28x |
Input range: [-5,5)
| Function | Debug Speedup | ReleaseSafe Speedup | ReleaseFast Speedup | ReleaseFast Time Zig | ReleaseFast Time fvml |
|---|---|---|---|---|---|
Ln, @log |
0.32x | 7.74x | 7.69x | 21.9 ns | 2.87 ns |
Log2 |
0.31x | 8.09x | 8.07x | 22.2 ns | 2.75 ns |
Log10 |
0.32x | 7.74x | 7.75x | 22.6 ns | 2.85 ns |
Mod |
32.61x | 50.25x | 50.78x | 88.0 ns | 1.75 ns |
Exp2 |
0.32x | 4.59x | 5.26x | 26.2 ns | 4.90 ns |
Exp |
0.48x | 6.36x | 7.05x | 37.2 ns | 5.23 ns |
From this point onwards the performance comparison is consistent except for the exp function, which doubles in performance from ranges of [-5e3,5e3) to ranges of [-5e26,5e26), after which point the performance regresses to half of its [-5,5) speedup.
Notes on the speedup
- By targeting a guaranteed relative error of
1e-10rather than full IEEE-754 compliance,fvmlcan eliminate a lot of the branches that are used in Zig's standard library. - By profiling with
perf, we noticed the standard library is prone to branch misses and high CPU cycle counts: over the entire benchmark, the standard library functions accounts for 99.83% of branch misses, and 83.95% of CPU cycles.
N-Ary Operators with ILP Variants
ILP: Instruction-level parallelism.
ILP-variants do not guarantee a performance increase, as it is very much hardware-specific. However, on many modern CPUs, they should be beneficial, as seen in these benchmarks. In a worst-case scenario, they should not be any slower than the non-ilp variants.
| Function | Naive Variant Time | ILP Variant Time | ILP Speedup |
|---|---|---|---|
sum |
0.439 ns / f64x4 | 0.177 ns / f64x4 | 2.49x |
product |
0.874 ns / f64x4 | 0.221 ns / f64x4 | 3.96x |
arithmetic_mean |
0.439 ns / f64x4 | 0.177 ns / f64x4 | 2.49x |
geometric_mean_unstable |
0.879 ns / f64x4 | 0.224 ns / f64x4 | 3.92x |
hypot |
0.876 ns / f64x4 | 0.227 ns / f64x4 | 3.86x |
Minimum Supported Zig Version (MSZV)
The minimum supported version of Zig for fvml is 0.16.0. It is also currently the only tested version.
License
This project is licensed under the EUPL v1.2 license
Attribution
- Remez.jl: This Julia library was used to generate the polynomial coefficients for minimax function approximations.
Miscellaneous
About the dot in "math·s"
Just for some fun, I decided to borrow the dot from new(ish) changes to the French language, where it can be used to include both versions of a gendered noun. As I prefer to use "maths", but many people refer to it as "math", this felt like a reasonable compromise.