587 lines
17 KiB
Markdown
587 lines
17 KiB
Markdown
# std.simd
|
|
|
|
SIMD (Single Instruction, Multiple Data) utilities for parallel processing of multiple elements at once. Provides convenience functions for vector manipulation, pattern generation, searching, and parallel computation.
|
|
|
|
## Quick Reference
|
|
|
|
| Category | Functions |
|
|
|----------|-----------|
|
|
| Vector Length | `suggestVectorLength`, `suggestVectorLengthForCpu`, `VectorIndex`, `VectorCount` |
|
|
| Pattern Generation | `iota`, `repeat`, `join`, `interlace`, `deinterlace` |
|
|
| Extraction/Shifting | `extract`, `mergeShift`, `shiftElementsLeft`, `shiftElementsRight` |
|
|
| Rotation/Reversal | `rotateElementsLeft`, `rotateElementsRight`, `reverseOrder` |
|
|
| Searching | `firstTrue`, `lastTrue`, `countTrues`, `firstIndexOfValue`, `lastIndexOfValue`, `countElementsWithValue` |
|
|
| Parallel Scans | `prefixScan`, `prefixScanWithFunc` |
|
|
|
|
## Core Concepts
|
|
|
|
### Vector Types
|
|
|
|
Zig vectors are first-class types declared with `@Vector(len, T)`. Element types can be booleans, integers, floats, or pointers:
|
|
|
|
```zig
|
|
const Vec4 = @Vector(4, f64); // 4 f64 values
|
|
const Vec8i = @Vector(8, i32); // 8 i32 values
|
|
const Vec16b = @Vector(16, bool); // 16 booleans
|
|
const Vec4p = @Vector(4, *u8); // 4 pointers
|
|
```
|
|
|
|
**Vector length limits:** Zig supports lengths up to 2^32-1, but powers of two from 2-64 are typical. Excessively long vectors (e.g., 2^20) may crash the compiler.
|
|
|
|
**Compilation behavior:** Vectors shorter than the native SIMD size compile to single instructions. Longer vectors compile to multiple SIMD instructions. Without SIMD support, operations fall back to element-by-element execution.
|
|
|
|
### Built-in Operations
|
|
|
|
Vectors support arithmetic, comparisons, and builtins directly (all element-wise):
|
|
|
|
```zig
|
|
const a: @Vector(4, f32) = .{ 1.0, 2.0, 3.0, 4.0 };
|
|
const b: @Vector(4, f32) = .{ 5.0, 6.0, 7.0, 8.0 };
|
|
|
|
// Arithmetic (element-wise)
|
|
const sum = a + b; // { 6.0, 8.0, 10.0, 12.0 }
|
|
const prod = a * b; // { 5.0, 12.0, 21.0, 32.0 }
|
|
|
|
// Comparison (returns bool vector)
|
|
const mask = a < b; // { true, true, true, true }
|
|
|
|
// Broadcast scalar to all lanes
|
|
const twos: @Vector(4, f32) = @splat(2.0); // { 2.0, 2.0, 2.0, 2.0 }
|
|
|
|
// Math builtins (hardware-accelerated when available)
|
|
const sines = @sin(a);
|
|
const sqrts = @sqrt(a);
|
|
|
|
// Horizontal reduction
|
|
const total = @reduce(.Add, a); // 10.0
|
|
const max_val = @reduce(.Max, a); // 4.0
|
|
```
|
|
|
|
**Important:** `and` and `or` keywords do NOT work on bool vectors (they affect control flow). Use `&` and `|` bitwise operators, or `@select` instead.
|
|
|
|
### Vector-Compatible Builtins
|
|
|
|
These builtins work element-wise on vectors:
|
|
|
|
| Category | Builtins |
|
|
|----------|----------|
|
|
| Math | `@sqrt`, `@sin`, `@cos`, `@exp`, `@exp2`, `@log`, `@log2`, `@log10` |
|
|
| Rounding | `@floor`, `@ceil`, `@trunc`, `@round` |
|
|
| Arithmetic | `@abs`, `@min`, `@max`, `@mulAdd`, `@divFloor`, `@divTrunc`, `@mod`, `@rem` |
|
|
| Bit ops | `@clz`, `@ctz`, `@popCount`, `@byteSwap`, `@bitReverse` |
|
|
| Overflow | `@addWithOverflow`, `@subWithOverflow`, `@mulWithOverflow`, `@shlWithOverflow` |
|
|
|
|
### Array/Slice Conversion
|
|
|
|
```zig
|
|
// Array to vector (automatic)
|
|
const arr: [4]f32 = .{ 1.1, 3.2, 4.5, 5.6 };
|
|
const vec: @Vector(4, f32) = arr;
|
|
|
|
// Vector to array (automatic)
|
|
const arr2: [4]f32 = vec;
|
|
|
|
// Slice with comptime-known length to vector
|
|
const vec2: @Vector(2, f32) = arr[1..3].*;
|
|
|
|
// Runtime offset with comptime length
|
|
const slice: []const f32 = &arr;
|
|
var offset: usize = 1;
|
|
const vec3: @Vector(2, f32) = slice[offset..][0..2].*;
|
|
```
|
|
|
|
### Vector Destructuring
|
|
|
|
Vectors can be destructured like tuples:
|
|
|
|
```zig
|
|
const vec: @Vector(4, f32) = .{ 1.0, 2.0, 3.0, 4.0 };
|
|
const a, const b, _, _ = vec; // a=1.0, b=2.0, ignore rest
|
|
|
|
// Useful for SIMD unpacking (emulating punpckldq)
|
|
pub fn unpack(x: @Vector(4, f32), y: @Vector(4, f32)) @Vector(4, f32) {
|
|
const a, const c, _, _ = x;
|
|
const b, const d, _, _ = y;
|
|
return .{ a, b, c, d };
|
|
}
|
|
```
|
|
|
|
### @shuffle - Rearrange Elements
|
|
|
|
Rearrange elements from one or two vectors using an index mask:
|
|
|
|
```zig
|
|
const a: @Vector(7, u8) = .{ 'o', 'l', 'h', 'e', 'r', 'z', 'w' };
|
|
const b: @Vector(4, u8) = .{ 'w', 'd', '!', 'x' };
|
|
|
|
// Shuffle within single vector (pass undefined as second)
|
|
const mask1: @Vector(5, i32) = .{ 2, 3, 1, 1, 0 };
|
|
const hello: @Vector(5, u8) = @shuffle(u8, a, undefined, mask1);
|
|
// "hello"
|
|
|
|
// Combine two vectors (negative indices select from b: -1=b[0], -2=b[1], etc.)
|
|
const mask2: @Vector(6, i32) = .{ -1, 0, 4, 1, -2, -3 };
|
|
const world: @Vector(6, u8) = @shuffle(u8, a, b, mask2);
|
|
// "world!"
|
|
```
|
|
|
|
### @select - Conditional Selection
|
|
|
|
Select elements from two vectors based on a bool mask:
|
|
|
|
```zig
|
|
const a: @Vector(4, f32) = .{ 1.0, 2.0, 3.0, 4.0 };
|
|
const b: @Vector(4, f32) = .{ 5.0, 6.0, 7.0, 8.0 };
|
|
const mask: @Vector(4, bool) = .{ true, false, true, false };
|
|
|
|
const result = @select(f32, mask, a, b);
|
|
// { 1.0, 6.0, 3.0, 8.0 } (a where true, b where false)
|
|
```
|
|
|
|
### @reduce - Horizontal Reduction
|
|
|
|
Reduce a vector to a scalar using an operation:
|
|
|
|
```zig
|
|
const vec: @Vector(4, i32) = .{ 1, 2, 3, 4 };
|
|
|
|
// Arithmetic reductions
|
|
const sum = @reduce(.Add, vec); // 10
|
|
const prod = @reduce(.Mul, vec); // 24
|
|
const min_val = @reduce(.Min, vec); // 1
|
|
const max_val = @reduce(.Max, vec); // 4
|
|
|
|
// Bitwise reductions
|
|
const and_val = @reduce(.And, vec); // 0
|
|
const or_val = @reduce(.Or, vec); // 7
|
|
const xor_val = @reduce(.Xor, vec); // 4
|
|
|
|
// Boolean reductions (for bool vectors)
|
|
const mask: @Vector(4, bool) = .{ true, true, false, true };
|
|
const all_true = @reduce(.And, mask); // false
|
|
const any_true = @reduce(.Or, mask); // true
|
|
```
|
|
|
|
**Available operations by type:**
|
|
- **Integers:** All operations (Add, Mul, Min, Max, And, Or, Xor)
|
|
- **Floats:** Add, Mul, Min, Max
|
|
- **Booleans:** And, Or, Xor
|
|
|
|
## Optimal Vector Length
|
|
|
|
### suggestVectorLength
|
|
|
|
Query the suggested vector length for the current target. This is a comptime heuristic: its element type must be comptime-known.
|
|
|
|
```zig
|
|
const std = @import("std");
|
|
|
|
// Get optimal lane count for this type on current hardware
|
|
const len = std.simd.suggestVectorLength(f32) orelse 4;
|
|
|
|
// Use comptime to create vector type
|
|
const Vec = @Vector(len, f32);
|
|
```
|
|
|
|
Returns `null` if scalars are recommended (no SIMD benefit).
|
|
|
|
### suggestVectorLengthForCpu
|
|
|
|
Query the suggested length for a specific CPU target. Both the element type and `std.Target.Cpu` argument are comptime parameters:
|
|
|
|
```zig
|
|
const len = std.simd.suggestVectorLengthForCpu(f64, target_cpu) orelse 2;
|
|
```
|
|
|
|
**Architecture support:**
|
|
- **x86**: SSE (128-bit), AVX2 (256-bit), AVX-512 (512-bit)
|
|
- **ARM**: NEON (128-bit)
|
|
- **AArch64**: SVE (256-bit heuristic), otherwise NEON (128-bit)
|
|
- **RISC-V**: V extension (32-bit to 65536-bit via zvl* features)
|
|
- **WebAssembly**: simd128 (128-bit)
|
|
- **PowerPC**: AltiVec (128-bit)
|
|
|
|
### Vector Index/Count Types
|
|
|
|
Get the smallest integer type for indexing or counting:
|
|
|
|
```zig
|
|
const Vec8 = @Vector(8, u32);
|
|
|
|
// Type that can index any element (0-7)
|
|
const Idx = std.simd.VectorIndex(Vec8); // u3
|
|
|
|
// Type that can hold the count (0-8)
|
|
const Cnt = std.simd.VectorCount(Vec8); // u4
|
|
```
|
|
|
|
## Pattern Generation
|
|
|
|
### iota - Sequential Values
|
|
|
|
Generate a vector of sequential values starting from 0:
|
|
|
|
```zig
|
|
const indices = std.simd.iota(i32, 8);
|
|
// { 0, 1, 2, 3, 4, 5, 6, 7 }
|
|
|
|
const floats = std.simd.iota(f32, 4);
|
|
// { 0.0, 1.0, 2.0, 3.0 }
|
|
```
|
|
|
|
### repeat - Repeating Pattern
|
|
|
|
Repeat a smaller vector/array to fill a larger one:
|
|
|
|
```zig
|
|
const pattern = [_]u32{ 1, 2, 3 };
|
|
const repeated = std.simd.repeat(8, pattern);
|
|
// { 1, 2, 3, 1, 2, 3, 1, 2 }
|
|
|
|
const vec: @Vector(2, f32) = .{ 10.0, 20.0 };
|
|
const tiled = std.simd.repeat(6, vec);
|
|
// { 10.0, 20.0, 10.0, 20.0, 10.0, 20.0 }
|
|
```
|
|
|
|
### join - Concatenate Vectors
|
|
|
|
Concatenate two vectors end-to-end:
|
|
|
|
```zig
|
|
const a: @Vector(4, u32) = .{ 10, 20, 30, 40 };
|
|
const b: @Vector(4, u32) = .{ 55, 66, 77, 88 };
|
|
const joined = std.simd.join(a, b);
|
|
// { 10, 20, 30, 40, 55, 66, 77, 88 }
|
|
```
|
|
|
|
### interlace - Interleave Multiple Vectors
|
|
|
|
Alternate elements from multiple vectors:
|
|
|
|
```zig
|
|
const a: @Vector(4, u32) = .{ 10, 20, 30, 40 };
|
|
const b: @Vector(4, u32) = .{ 55, 66, 77, 88 };
|
|
const interleaved = std.simd.interlace(.{ a, b });
|
|
// { 10, 55, 20, 66, 30, 77, 40, 88 }
|
|
|
|
// Works with more than 2 vectors
|
|
const v1: @Vector(2, u8) = .{ 0, 1 };
|
|
const v2: @Vector(2, u8) = .{ 2, 3 };
|
|
const v3: @Vector(2, u8) = .{ 4, 5 };
|
|
const result = std.simd.interlace(.{ v1, v2, v3 });
|
|
// { 0, 2, 4, 1, 3, 5 }
|
|
```
|
|
|
|
**Note:** Does not work on MIPS (compile error).
|
|
|
|
### deinterlace - Split Interleaved Data
|
|
|
|
Reverse of interlace - split into separate vectors:
|
|
|
|
```zig
|
|
const interleaved: @Vector(8, u32) = .{ 10, 55, 20, 66, 30, 77, 40, 88 };
|
|
const result = std.simd.deinterlace(2, interleaved);
|
|
// result[0] = { 10, 20, 30, 40 }
|
|
// result[1] = { 55, 66, 77, 88 }
|
|
```
|
|
|
|
## Element Extraction and Shifting
|
|
|
|
### extract - Get Subvector
|
|
|
|
Extract a contiguous slice of elements. `first` and `count` are comptime-known:
|
|
|
|
```zig
|
|
const vec: @Vector(8, u32) = .{ 0, 1, 2, 3, 4, 5, 6, 7 };
|
|
const slice = std.simd.extract(vec, 2, 3);
|
|
// { 2, 3, 4 }
|
|
```
|
|
|
|
### shiftElementsLeft / shiftElementsRight
|
|
|
|
Shift elements, filling with a value. The shift amount is comptime-known:
|
|
|
|
```zig
|
|
const vec: @Vector(4, u32) = .{ 10, 20, 30, 40 };
|
|
|
|
// Shift left (toward lower indices), fill from right
|
|
const left = std.simd.shiftElementsLeft(vec, 2, 999);
|
|
// { 30, 40, 999, 999 }
|
|
|
|
// Shift right (toward higher indices), fill from left
|
|
const right = std.simd.shiftElementsRight(vec, 2, 999);
|
|
// { 999, 999, 10, 20 }
|
|
```
|
|
|
|
### rotateElementsLeft / rotateElementsRight
|
|
|
|
Circular rotation (elements wrap around). The rotation amount is comptime-known:
|
|
|
|
```zig
|
|
const vec: @Vector(4, u32) = .{ 10, 20, 30, 40 };
|
|
|
|
const rotl = std.simd.rotateElementsLeft(vec, 1);
|
|
// { 20, 30, 40, 10 }
|
|
|
|
const rotr = std.simd.rotateElementsRight(vec, 1);
|
|
// { 40, 10, 20, 30 }
|
|
```
|
|
|
|
### reverseOrder
|
|
|
|
Reverse element order:
|
|
|
|
```zig
|
|
const vec: @Vector(4, u32) = .{ 10, 20, 30, 40 };
|
|
const reversed = std.simd.reverseOrder(vec);
|
|
// { 40, 30, 20, 10 }
|
|
```
|
|
|
|
### mergeShift
|
|
|
|
Combine two vectors and extract a shifted window. The shift amount is comptime-known:
|
|
|
|
```zig
|
|
const a: @Vector(4, u32) = .{ 1, 2, 3, 4 };
|
|
const b: @Vector(4, u32) = .{ 5, 6, 7, 8 };
|
|
const merged = std.simd.mergeShift(a, b, 2);
|
|
// Joins to { 1, 2, 3, 4, 5, 6, 7, 8 }, extracts starting at index 2
|
|
// { 3, 4, 5, 6 }
|
|
```
|
|
|
|
## Searching
|
|
|
|
### firstTrue / lastTrue
|
|
|
|
Find first/last true element in a boolean vector:
|
|
|
|
```zig
|
|
const mask: @Vector(8, bool) = .{ false, false, true, false, true, false, false, false };
|
|
|
|
const first = std.simd.firstTrue(mask); // 2
|
|
const last = std.simd.lastTrue(mask); // 4
|
|
|
|
// Returns null if no true values
|
|
const all_false: @Vector(4, bool) = .{ false, false, false, false };
|
|
const none = std.simd.firstTrue(all_false); // null
|
|
```
|
|
|
|
### countTrues
|
|
|
|
Count true elements:
|
|
|
|
```zig
|
|
const mask: @Vector(8, bool) = .{ true, false, true, false, true, false, true, false };
|
|
const count = std.simd.countTrues(mask); // 4
|
|
```
|
|
|
|
### firstIndexOfValue / lastIndexOfValue
|
|
|
|
Find first/last occurrence of a value:
|
|
|
|
```zig
|
|
const vec: @Vector(8, u32) = .{ 6, 4, 7, 4, 4, 2, 3, 7 };
|
|
|
|
const first_4 = std.simd.firstIndexOfValue(vec, 4); // 1
|
|
const last_4 = std.simd.lastIndexOfValue(vec, 4); // 4
|
|
const not_found = std.simd.lastIndexOfValue(vec, 99); // null
|
|
```
|
|
|
|
### countElementsWithValue
|
|
|
|
Count occurrences of a value:
|
|
|
|
```zig
|
|
const vec: @Vector(8, u32) = .{ 6, 4, 7, 4, 4, 2, 3, 7 };
|
|
const count = std.simd.countElementsWithValue(vec, 4); // 3
|
|
```
|
|
|
|
## Parallel Prefix Scans
|
|
|
|
### prefixScan
|
|
|
|
Compute cumulative operations across vector lanes. This is an O(log N), non-linear associative scan; floating-point results can differ from a scalar left-to-right scan because the grouping and rounding differ.
|
|
|
|
```zig
|
|
const vec: @Vector(4, i32) = .{ 11, 23, 9, -21 };
|
|
|
|
// Running sum
|
|
const sums = std.simd.prefixScan(.Add, 1, vec);
|
|
// { 11, 34, 43, 22 }
|
|
|
|
// Running product
|
|
const prods = std.simd.prefixScan(.Mul, 1, vec);
|
|
// { 11, 253, 2277, -47817 }
|
|
|
|
// Running min
|
|
const mins = std.simd.prefixScan(.Min, 1, vec);
|
|
// { 11, 11, 9, -21 }
|
|
|
|
// Running max
|
|
const maxs = std.simd.prefixScan(.Max, 1, vec);
|
|
// { 11, 23, 23, 23 }
|
|
|
|
// Bitwise operations
|
|
const ands = std.simd.prefixScan(.And, 1, vec);
|
|
const ors = std.simd.prefixScan(.Or, 1, vec);
|
|
const xors = std.simd.prefixScan(.Xor, 1, vec);
|
|
```
|
|
|
|
**Hop parameter:** Controls which elements combine. `hop=2` combines every other element.
|
|
|
|
```zig
|
|
const vec: @Vector(4, i32) = .{ 11, 23, 9, -21 };
|
|
const skip = std.simd.prefixScan(.Add, 2, vec);
|
|
// { 11, 23, 20, 2 } (11+9=20, 23+(-21)=2)
|
|
|
|
// Negative hop scans in reverse
|
|
const rev = std.simd.prefixScan(.Add, -1, vec);
|
|
// { 22, 11, -12, -21 }
|
|
```
|
|
|
|
**Note:** Does not work on MIPS (compile error).
|
|
|
|
### prefixScanWithFunc
|
|
|
|
Use a custom associative function. The callback may return an error union; choose `ErrorType` accordingly and use `try` on `prefixScanWithFunc` to propagate callback errors. Passing `void` selects the non-erroring form:
|
|
|
|
```zig
|
|
fn myMax(a: @Vector(4, f32), b: @Vector(4, f32)) @Vector(4, f32) {
|
|
return @max(a, b);
|
|
}
|
|
|
|
const vec: @Vector(4, f32) = .{ 1.0, 5.0, 2.0, 8.0 };
|
|
const result = std.simd.prefixScanWithFunc(1, vec, void, myMax, -std.math.inf(f32));
|
|
// { 1.0, 5.0, 5.0, 8.0 }
|
|
```
|
|
|
|
The identity value must satisfy: `func(x, identity) == x`.
|
|
|
|
## Practical Patterns
|
|
|
|
### Branchless Selection
|
|
|
|
Replace `if` statements with vector selection:
|
|
|
|
```zig
|
|
// Scalar (branching)
|
|
fn clampScalar(x: f32, lo: f32, hi: f32) f32 {
|
|
if (x < lo) return lo;
|
|
if (x > hi) return hi;
|
|
return x;
|
|
}
|
|
|
|
// Vector (branchless)
|
|
fn clampSimd(x: @Vector(4, f32), lo: f32, hi: f32) @Vector(4, f32) {
|
|
const lo_vec: @Vector(4, f32) = @splat(lo);
|
|
const hi_vec: @Vector(4, f32) = @splat(hi);
|
|
return @min(@max(x, lo_vec), hi_vec);
|
|
}
|
|
```
|
|
|
|
### Convergence Loops
|
|
|
|
Process lanes that converge at different rates:
|
|
|
|
```zig
|
|
fn iterateUntilConverged(vec: @Vector(4, f64), tolerance: f64) @Vector(4, f64) {
|
|
const tol_vec: @Vector(4, f64) = @splat(tolerance);
|
|
var current = vec;
|
|
var converged: @Vector(4, bool) = @splat(false);
|
|
|
|
while (!@reduce(.And, converged)) {
|
|
const next = computeNext(current);
|
|
const delta = @abs(next - current);
|
|
converged = delta <= tol_vec;
|
|
current = next;
|
|
}
|
|
return current;
|
|
}
|
|
```
|
|
|
|
### Time-Batched Processing
|
|
|
|
Process multiple time points for one object:
|
|
|
|
```zig
|
|
const Vec4 = @Vector(4, f64);
|
|
|
|
fn propagateV4(state: *const State, times: [4]f64) [4]Result {
|
|
const time_vec: Vec4 = times;
|
|
// Process all 4 times simultaneously
|
|
const positions = computePositions(state, time_vec);
|
|
const velocities = computeVelocities(state, time_vec);
|
|
// ...
|
|
}
|
|
```
|
|
|
|
### Object-Batched Processing (Struct of Arrays)
|
|
|
|
Process multiple objects at the same time point:
|
|
|
|
```zig
|
|
// Struct of Arrays layout for 4 objects
|
|
const ObjectsV4 = struct {
|
|
x: @Vector(4, f64),
|
|
y: @Vector(4, f64),
|
|
vx: @Vector(4, f64),
|
|
vy: @Vector(4, f64),
|
|
};
|
|
|
|
fn updatePositions(objs: *ObjectsV4, dt: f64) void {
|
|
const dt_vec: @Vector(4, f64) = @splat(dt);
|
|
objs.x += objs.vx * dt_vec;
|
|
objs.y += objs.vy * dt_vec;
|
|
}
|
|
```
|
|
|
|
### Custom atan2 Approximation
|
|
|
|
LLVM lacks vectorized `atan2`. Implement polynomial approximation:
|
|
|
|
```zig
|
|
fn atan2Simd(y: @Vector(4, f64), x: @Vector(4, f64)) @Vector(4, f64) {
|
|
const abs_x = @abs(x);
|
|
const abs_y = @abs(y);
|
|
const max_xy = @max(abs_x, abs_y);
|
|
const min_xy = @min(abs_x, abs_y);
|
|
const epsilon: @Vector(4, f64) = @splat(1.0e-30);
|
|
const t = min_xy / @max(max_xy, epsilon);
|
|
|
|
// Polynomial approximation (Horner's method)
|
|
const t2 = t * t;
|
|
var atan_t = @as(@Vector(4, f64), @splat(0.0028662257));
|
|
atan_t = atan_t * t2 + @as(@Vector(4, f64), @splat(-0.0161657367));
|
|
atan_t = atan_t * t2 + @as(@Vector(4, f64), @splat(0.0429096138));
|
|
atan_t = atan_t * t2 + @as(@Vector(4, f64), @splat(-0.0752896400));
|
|
atan_t = atan_t * t2 + @as(@Vector(4, f64), @splat(0.1065626393));
|
|
atan_t = atan_t * t2 + @as(@Vector(4, f64), @splat(-0.1420889944));
|
|
atan_t = atan_t * t2 + @as(@Vector(4, f64), @splat(0.1999355085));
|
|
atan_t = atan_t * t2 + @as(@Vector(4, f64), @splat(-0.3333314528));
|
|
atan_t = atan_t * t2 + @as(@Vector(4, f64), @splat(1.0));
|
|
atan_t = atan_t * t;
|
|
|
|
// Quadrant correction
|
|
const half_pi: @Vector(4, f64) = @splat(std.math.pi / 2.0);
|
|
const pi: @Vector(4, f64) = @splat(std.math.pi);
|
|
const swap_mask = abs_y > abs_x;
|
|
atan_t = @select(f64, swap_mask, half_pi - atan_t, atan_t);
|
|
const x_neg = x < @as(@Vector(4, f64), @splat(0.0));
|
|
atan_t = @select(f64, x_neg, pi - atan_t, atan_t);
|
|
const y_neg = y < @as(@Vector(4, f64), @splat(0.0));
|
|
return @select(f64, y_neg, -atan_t, atan_t);
|
|
}
|
|
```
|
|
|
|
## Performance Notes
|
|
|
|
- **Optimal vector size:** Use `suggestVectorLength` for portable code; don't hardcode lane counts. Powers of two (2-64) are most efficient
|
|
- **Compilation:** Lowering depends on the target, vector width, operation, and optimizer; inspect generated code for performance-critical paths
|
|
- **Alignment:** Vectors are automatically aligned; use `@alignCast` when loading from byte pointers
|
|
- **Branching:** Replace scalar branches with `@select` for branchless SIMD code. `and`/`or` keywords don't work on bool vectors
|
|
- **Reductions:** `@reduce` operations break SIMD parallelism; minimize their use in hot paths
|
|
- **Memory layout:** Prefer Struct-of-Arrays over Array-of-Structs for better vectorization
|
|
- **Cache tiling:** For large datasets, process in cache-sized chunks (e.g., 64 elements) to maintain data locality
|
|
- **Fused operations:** For floating-point `T`, use `@mulAdd(T, a, b, c)` for a fused `(a * b) + c` with one final rounding
|
|
- **MIPS limitation:** `interlace` and `prefixScan` don't work on MIPS architecture
|