Title: Vectorized Axis-Aligned Rect Ops Brief: Small detour in making rect type and operations on it in SIMD semantics. Date: 1695570693 Tags: Programming, Zig, Optimization CSS: /style.css ### Code ### Zig's `@shuffle` makes it rather arcane to look at, so be prepared. ```zig pub fn RectSIMD(comptime T: type) type { return struct { xyxy: @Vector(4, T), pub fn isPointWithin(self: @This(), p: @Vector(2, T)) bool { const q = @shuffle(T, p, self.xyxy, [4]i32{ -1, -2, 0, 1 }); const w = @shuffle(T, p, self.xyxy, [4]i32{ 0, 1, -3, -4 }); return @reduce(.And, q <= w); } pub fn isRectWithin(self: @This(), a: @This()) bool { const q = @shuffle(T, a.xyxy, self.xyxy, [8]i32{ 0, 1, 2, 3, -1, -2, -1, -2 }); const w = @shuffle(T, a.xyxy, self.xyxy, [8]i32{ -3, -4, -3, -4, 0, 1, 2, 3 }); return @reduce(.And, q <= w); } // todo: Handle zero area cases? pub fn isRectIntersecting(self: @This(), a: @This()) bool { const q = @shuffle(T, a.xyxy, self.xyxy, [4]i32{ 0, 1, -1, -2 }); const w = @shuffle(T, a.xyxy, self.xyxy, [4]i32{ -3, -4, 2, 3 }); return @reduce(.And, q <= w); } }; } ``` ### Assembly ### This is produced by godbolt, which apparently has AVX512 extensions, so, it's extremely compact. Note: Calling prelude and outro are omitted, with inlining you can expect it looking similarly. Zig calling convention is used, which is roughly equal to C's static marked procedure. For 32bit floating point: ```asm "example.RectSIMD(f32).isPointWithin": vmovaps xmm2, xmm0 vmovapd xmm1, xmmword ptr [rdi] vunpcklpd xmm0, xmm1, xmm2 vblendpd xmm1, xmm1, xmm2, 1 vcmpleps k0, xmm0, xmm1 kmovd eax, k0 sub al, 15 sete al "example.RectSIMD(f32).isRectWithin": vmovaps xmm2, xmmword ptr [rsi] vmovaps xmm0, xmm2 vmovddup xmm1, qword ptr [rdi] vinsertf128 ymm0, ymm0, xmm1, 1 vmovddup xmm3, qword ptr [rdi + 8] vmovaps xmm1, xmm3 vinsertf128 ymm1, ymm1, xmm2, 1 vcmpleps k0, ymm0, ymm1 kortestb k0, k0 setb al "example.RectSIMD(f32).isRectIntersecting": vmovapd xmm2, xmmword ptr [rsi] vmovapd xmm1, xmmword ptr [rdi] vunpcklpd xmm0, xmm2, xmm1 vunpckhpd xmm1, xmm1, xmm2 vcmpleps k0, xmm0, xmm1 kmovd eax, k0 sub al, 15 sete al ``` For 32bit signed integers it fares amazing too: ```asm "example.RectSIMD(i32).isPointWithin": vmovaps xmm1, xmm0 vmovdqa xmm2, xmmword ptr [rdi] vpunpcklqdq xmm0, xmm2, xmm1 vpblendd xmm1, xmm1, xmm2, 12 vpcmpled k0, xmm0, xmm1 kmovd eax, k0 sub al, 15 sete al "example.RectSIMD(i32).isRectWithin": vmovdqa xmm2, xmmword ptr [rsi] vmovaps xmm0, xmm2 vpbroadcastq xmm1, qword ptr [rdi] vinserti128 ymm0, ymm0, xmm1, 1 vpbroadcastq xmm3, qword ptr [rdi + 8] vmovaps xmm1, xmm3 vinserti128 ymm1, ymm1, xmm2, 1 vpcmpled k0, ymm0, ymm1 kortestb k0, k0 setb al "example.RectSIMD(i32).isRectIntersecting": vmovdqa xmm2, xmmword ptr [rsi] vmovdqa xmm1, xmmword ptr [rdi] vpunpcklqdq xmm0, xmm2, xmm1 vpunpckhqdq xmm1, xmm1, xmm2 vpcmpled k0, xmm0, xmm1 kmovd eax, k0 sub al, 15 sete al ``` 64bit floating point: ```asm "example.RectSIMD(f64).isPointWithin": vmovaps xmm3, xmm0 vmovapd ymm1, ymmword ptr [rdi] vinsertf128 ymm0, ymm1, xmm3, 1 vmovaps xmm2, xmm3 vblendpd ymm1, ymm1, ymm2, 3 vcmplepd k0, ymm0, ymm1 kmovd eax, k0 sub al, 15 sete al "example.RectSIMD(f64).isRectWithin": vmovapd ymm2, ymmword ptr [rsi] vmovapd ymm1, ymmword ptr [rdi] vmovaps ymm0, ymm2 vpermpd ymm3, ymm1, 68 vinsertf64x4 zmm0, zmm0, ymm3, 1 vpermpd ymm3, ymm1, 238 vmovaps ymm1, ymm3 vinsertf64x4 zmm1, zmm1, ymm2, 1 vcmplepd k0, zmm0, zmm1 kortestb k0, k0 setb al "example.RectSIMD(f64).isRectIntersecting": vmovapd ymm2, ymmword ptr [rsi] vmovapd ymm1, ymmword ptr [rdi] vperm2f128 ymm0, ymm2, ymm1, 32 vperm2f128 ymm1, ymm1, ymm2, 49 vcmplepd k0, ymm0, ymm1 kmovd eax, k0 sub al, 15 sete al ``` AVX512 makes it so that there's no big penalty for double precision types, which is nice. ### Edits ### - Reordered to use packed vectors without swizzling when possible. - Eliminated redundant computations. - Calling convention notice.