Zig Optimization Patterns
An optimization pattern is a small change that makes code do less work for the same result. The best patterns remove repeated work instead of adding clever tricks.
Hoist the factor
The factor never changes inside the loop, so it is set once before the loop. The loop then only multiplies and adds.
const std = @import("std");
pub fn main() !void {
const out = std.io.getStdOut().writer();
const data = [_]u32{ 1, 2, 3, 4 };
const factor: u32 = 3;
var sum: u32 = 0;
for (data) |v| {
sum += v * factor;
}
try out.print("{d}\n", .{sum});
}Prefetch the next element
@prefetch hints that the next element will be needed soon. The loop still adds every element, so the sum is unchanged.
const std = @import("std");
pub fn main() !void {
const out = std.io.getStdOut().writer();
var data = [_]u32{ 1, 2, 3, 4, 5, 6, 7, 8 };
var sum: u32 = 0;
var i: usize = 0;
while (i < data.len) : (i += 1) {
if (i + 1 < data.len) @prefetch(&data[i + 1], .{});
sum += data[i];
}
try out.print("{d}\n", .{sum});
}Iterate directly
Iterating a slice directly avoids index bookkeeping in the loop. The sum is the same as with indexes.
const std = @import("std");
pub fn main() !void {
const out = std.io.getStdOut().writer();
const data = [_]u32{ 4, 8, 12, 16 };
var sum: u32 = 0;
for (data) |v| {
sum += v;
}
try out.print("{d}\n", .{sum});
}Example
const std = @import("std");
pub fn main() !void {
const out = std.io.getStdOut().writer();
var data = [_]u32{ 1, 2, 3, 4, 5, 6, 7, 8 };
const factor: u32 = 2;
var sum: u32 = 0;
var i: usize = 0;
while (i < data.len) : (i += 1) {
if (i + 1 < data.len) @prefetch(&data[i + 1], .{});
sum += data[i] * factor;
}
try out.print("{d}\n", .{sum});
}Key points
- Optimization pattern: an optimization pattern is a repeatable change that makes code faster without changing its result.
- Loop invariant: a loop invariant is a value that stays the same on every pass through a loop.
- Hoisting: hoisting is moving a loop invariant out of a loop so it is computed once.
- Prefetch: a prefetch is an early hint that data will be needed soon, so it can be loaded before use.
- Cache: a cache is fast memory that holds data the processor has used recently.
- Repeated work: repeated work is the same computation done again when its result is already known.
The factor is set once before the loop. Each pass hints at the next element before adding the current one. The result is 72, the same as without either pattern.
Try it Yourself
Edit the code, then press Run.
Running is simulated
Exercise
What does @prefetch do?