Zig 学习笔记:向量 (@Vector) 与 SIMD 编程
Ziglings 109: 向量 (Vectors)
在之前的练习中,我们习惯用 for 循环来处理数组数据。但在高性能计算领域,现代 CPU 提供了一种名为 SIMD (Single Instruction, Multiple Data) 的能力,允许我们在一条指令中同时处理多个数据。
通常在 C 或 C++ 中使用 SIMD 需要调用复杂的 CPU 指令集(Intrinsics),但 Zig 将其抽象为了一个原生类型:@Vector。
任务:优化成对差值计算
我们要解决的问题是:给定两个包含 4 个浮点数的列表,找出它们对应位置差值的绝对值中最大的那个。
传统方法 (For Loop)
传统的写法是遍历数组,逐个计算并更新最大值。虽然逻辑清晰,但它是串行执行的。
fn calcMaxPairwiseDiffOld(list1: [4]f32, list2: [4]f32) f32 {
var max_diff: f32 = 0;
for (list1, list2) |n1, n2| {
const abs_diff = @abs(n1 - n2);
if (abs_diff > max_diff) {
max_diff = abs_diff;
}
}
return max_diff;
}
Zig 的向量化方法 (SIMD)
在 Zig 中,我们可以使用 @Vector(len, type) 定义向量。向量支持直接的算术运算,这会被编译器自动优化为并行的 SIMD 指令。
这是我在练习中实现的优化版本:
const Vec4 = @Vector(4, f32);
fn calcMaxPairwiseDiffNew(a: Vec4, b: Vec4) f32 {
// 1. 并行减法与绝对值计算
// a - b 会同时计算 4 对数字的差
// @abs 会同时计算这 4 个结果的绝对值
const abs_diff_vec = @abs(a - b);
// 2. 归约 (Reduce)
// @reduce 会高效地将向量“坍缩”为一个标量值
// 这里使用 .Max 操作符找出向量中的最大元素
const max_diff = @reduce(.Max, abs_diff_vec);
return max_diff;
}
知识点总结
通过这个练习,我学到了 Zig 处理 SIMD 的几个核心概念:
1. 定义向量 (@Vector)
不需要导入任何库,直接使用内置函数:
const v = @Vector(4, i32){1, 2, 3, 4};
2. 逐元素运算 (Element-wise Operations)
标准运算符(+, -, *, /)以及许多标准库函数(如 @abs, @sqrt)可以直接作用于向量。
v1 + v2:对应位置相加。v1 * 5:向量每个元素都乘以 5。
3. 数组与向量的互换
Zig 允许固定长度的数组 ([N]T) 和相同长度的向量 (@Vector(N, T)) 之间进行隐式或显式的转换,这让代码编写非常灵活。
4. 归约 (@reduce)
这是 SIMD 编程中非常强大的工具。它接受一个操作符(如 .Add, .Min, .Max, .And, .Or)和一个向量,将其缩减为单个值。
@reduce(.Add, v):计算向量所有元素的和。@reduce(.Max, v):找出向量中的最大值。
5. 广播 (@splat)
如果想把一个标量变成充满该值的向量,可以使用 @splat:
const v: @Vector(4, u32) = @splat(10); // {10, 10, 10, 10}
总结
Zig 的向量语法让编写高性能代码变得异常简单。原本需要十几行循环代码逻辑,现在只需要两行,而且这背后的机器码效率可能高出数倍。这就是 Zig “没有隐藏控制流” 但拥有 “强大编译期能力” 的体现。
Happy Zigling! 🦎