Skip to content
zig 版本:0.14.0

向量

向量(Vector)提供了一种对一组同类型(布尔、整型、浮点、指针)值进行并行操作的方法,它会尽可能利用 SIMD(单指令多数据)指令集。

向量类型通过内置函数 @Vector 创建。

基本使用

向量支持与底层基本类型相同的内置运算符。这些操作都是按元素执行的,并返回一个与输入向量长度相同的向量。支持的运算符包括:

  • 算术运算符 (+, -, /, *, @divFloor, @sqrt, @ceil, @log, ... )
  • 位操作符 (>>, <<, &, |,~, ... )
  • 比较运算符 (<, >, ==, ...)

禁止混合使用标量(单个数字)和向量进行数学运算。Zig 提供了 @splat 内建函数,可以方便地将标量转换为向量。同时,可以使用 @reduce 和数组索引语法将向量转换为标量。向量还支持直接赋值给已知长度的固定长度数组。如果需要重新排列元素,可以使用 @shuffle@select 函数。

zig
const std = @import("std");
const print = std.debug.print;

pub fn main() void {
    const ele_4 = @Vector(4, i32);

    // 向量必须拥有编译期已知的长度和类型
    const a = ele_4{ 1, 2, 3, 4 };
    const b = ele_4{ 5, 6, 7, 8 };

    // 执行相加的操作
    const c = a + b;

    print("Vector c is {any}\n", .{c});
    // 以数组索引的语法来访问向量的元素
    print("the third element of Vector c is {}\n", .{c[2]});

    // 定义一个数组,注意我们这里使用的是浮点类型
    var arr1: [4]f32 = [_]f32{ 1.1, 3.2, 4.5, 5.6 };
    // 直接转换成为一个向量
    const vec: @Vector(4, f32) = arr1;

    print("Vector vec is {any}\n", .{vec});

    // 将一个切片转换为向量
    const vec2: @Vector(2, f32) = arr1[1..3].*;
    print("Vector vec2 is {any}\n", .{vec2});
}

🅿️ 提示

可以使用 @as 将向量强制转换为数组。

如果向量的长度小于目标机器的 SIMD 寄存器宽度,操作通常会编译为单个 SIMD 指令;如果向量长度更长,则会编译为多个 SIMD 指令。

如果目标体系结构不支持 SIMD,编译器将默认依次对每个向量元素进行操作。

Zig 支持最大 2^32 - 1 的向量长度。请注意,过长的向量长度(例如 2^20)可能会导致当前版本的 Zig 编译器崩溃。

解构向量

与数组类似,向量也可以被解构:

zig
const print = @import("std").debug.print;

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 };
}

pub fn main() void {
    const x: @Vector(4, f32) = .{ 1.0, 2.0, 3.0, 4.0 };
    const y: @Vector(4, f32) = .{ 5.0, 6.0, 7.0, 8.0 };
    print("{}", .{unpack(x, y)});
}

@splat

@splat(scalar: anytype) anytype

生成一个向量,其所有元素都与传入的 scalar 参数相同。向量的类型和长度由编译器推断。

zig
const scalar: u32 = 5;
const result: @Vector(4, u32) = @splat(scalar);

@reduce

@reduce(comptime op: std.builtin.ReduceOp, value: anytype) E

使用传入的运算符对向量进行水平归约(sequential horizontal reduction),最终得到一个标量。

zig
const V = @Vector(4, i32);
const value = V{ 1, -1, 1, -1 };

const result = value > @as(V, @splat(0));
// result 是 { true, false, true, false };

const is_all_true = @reduce(.And, result);
// is_all_true 是 false

🅿️ 提示

  1. 所有运算符均可用于整型。
  2. .And, .Or, .Xor 也可用于布尔类型。
  3. .Min, .Max, .Add, .Mul 还可用于浮点型。

注意:.Add.Mul 在整型上的操作是**环绕(wrapping)**的。

@shuffle

zig
@shuffle(
    comptime E: type,
    a: @Vector(a_len, E),
    b: @Vector(b_len, E),
    comptime mask: @Vector(mask_len, i32)
) @Vector(mask_len, E)

根据掩码 mask(一个向量),从向量 a 或向量 b 中选择元素,组成一个新的向量。mask 的长度决定了返回向量的长度。mask 中的每个值逐个指定从 ab 中选择哪个元素:正数表示从 a 中选择指定索引的元素(索引从 0 开始递增),负数表示从 b 中选择指定索引的元素(索引从 -1 开始递减)。

🅿️ 提示

  • 建议对 b 中的索引使用 ~ 运算符,这样两个向量的索引都可以从 0 开始(例如,~@as(i32, 0) 结果为 -1)。
  • 如果 mask 中选择的元素在 ab 中是 undefined,则结果向量中对应的元素也将是 undefined
  • mask 中的元素索引越界会导致编译错误。
  • 如果 abundefined,则其长度被视为与另一个非 undefined 向量的长度相同。如果 ab 都是 undefined@shuffle 将返回一个所有元素都是 undefined 的向量。
zig
const a = @Vector(7, u8){ 'o', 'l', 'h', 'e', 'r', 'z', 'w' };
const b = @Vector(4, u8){ 'w', 'd', '!', 'x' };

const mask1 = @Vector(5, i32){ 2, 3, 1, 1, 0 };
const res1: @Vector(5, u8) = @shuffle(u8, a, undefined, mask1);
// res1 的值是 hello

// Combining two vectors
const mask2 = @Vector(6, i32){ -1, 0, 4, 1, -2, -3 };
const res2: @Vector(6, u8) = @shuffle(u8, a, b, mask2);
// res2 的值是 world!

@select

zig
@select(
    comptime T: type,
    pred: @Vector(len, bool),
    a: @Vector(len, T),
    b: @Vector(len, T)
) @Vector(len, T)

根据 pred(一个布尔类型的向量),按元素从 ab 中选择值。如果 pred[i]true,则结果向量中对应的元素为 a[i];否则为 b[i]

zig
const ele_4 = @Vector(4, i32);

// 向量必须拥有编译期已知的长度和类型
const a = ele_4{ 1, 2, 3, 4 };
const b = ele_4{ 5, 6, 7, 8 };

const pred = @Vector(4, bool){
    true,
    false,
    false,
    true,
};

const c = @select(i32, pred, a, b);
// c 是 { 1, 6, 7, 4 }