zig-simd - 背景

歴史的経緯

SIMDの発展

  • MMX(1996年)
- Intel Pentium MMXで導入 - 64ビットレジスタ、整数演算のみ - マルチメディア処理向け

  • SSE(1999年)
- 128ビットXMMレジスタ - 浮動小数点SIMD - 4つのf32を同時処理

  • AVX(2011年)
- 256ビットYMMレジスタ - 8つのf32を同時処理 - 3オペランド命令

  • AVX-512(2016年)
- 512ビットZMMレジスタ - 16のf32を同時処理 - マスク操作

ARM NEON

ARMアーキテクチャのSIMD拡張
- 128ビットレジスタ
- Apple M1/M2で重要
- Zigは自動的に対応

コンピュータサイエンス的な意味

Flynn分類

分類 説明
SISD 単一命令、単一データ 従来のCPU
SIMD 単一命令、複数データ ベクトル演算
MISD 複数命令、単一データ 耐障害システム
MIMD 複数命令、複数データ マルチコア

データ並列処理

スカラー処理:
a[0] + b[0] → c[0]
a[1] + b[1] → c[1]
a[2] + b[2] → c[2]
a[3] + b[3] → c[3]
(4回の演算)

SIMD処理:
[a[0], a[1], a[2], a[3]]
        +
[b[0], b[1], b[2], b[3]]
        ↓
[c[0], c[1], c[2], c[3]]
(1回の演算)

レジスタサイズと処理能力

レジスタ サイズ f32数 f64数
XMM 128bit 4 2
YMM 256bit 8 4
ZMM 512bit 16 8

実践での活用

画像処理

// グレースケール変換(SIMD)
// RGB → Y = 0.299R + 0.587G + 0.114B
pub fn toGrayscaleSimd(pixels: []Pixel) void {
    const r_weight: @Vector(4, f32) = @splat(0.299);
    const g_weight: @Vector(4, f32) = @splat(0.587);
    const b_weight: @Vector(4, f32) = @splat(0.114);

    var i: usize = 0;
    while (i + 4 <= pixels.len) : (i += 4) {
        // 4ピクセルを同時処理
        const r: @Vector(4, f32) = .{ pixels[i].r, pixels[i+1].r, pixels[i+2].r, pixels[i+3].r };
        const g: @Vector(4, f32) = .{ pixels[i].g, pixels[i+1].g, pixels[i+2].g, pixels[i+3].g };
        const b: @Vector(4, f32) = .{ pixels[i].b, pixels[i+1].b, pixels[i+2].b, pixels[i+3].b };

        const gray = r * r_weight + g * g_weight + b * b_weight;
        // 結果を書き戻し...
    }
}

機械学習

ニューラルネットワークの行列演算
- 重み行列との乗算
- 活性化関数の適用
- バッチ処理

SIMDにより大幅な高速化が可能

ゲーム物理

// 複数の物体の位置更新
pub fn updatePositions(
    positions: []Vec3,
    velocities: []const Vec3,
    dt: f32
) void {
    // p = p + v * dt をSIMDで並列処理
}

実世界とのギャップ

自動ベクトル化 vs 手動SIMD

アプローチ 利点 欠点
自動(コンパイラ) 簡単、ポータブル 最適化が不完全なことも
手動(intrinsics) 最大性能 複雑、非ポータブル
Zig @Vector バランスが良い 一部の最適化が難しい

アライメント要件

// SIMDはアライメントされたメモリを好む
// 未アライメントアクセスは遅い場合がある

const aligned_array = try allocator.alignedAlloc(f32, 32, count);
// 32バイトアライメント(AVX用)

分岐の問題

// 分岐はSIMDの敵
// 条件によって異なる処理が必要な場合

// 悪い例(分岐あり):
for (a) |val| {
    if (val > 0) result += val;
}

// 良い例(分岐なし):
const mask = @select(f32, a > zeros, a, zeros);
result = @reduce(.Add, mask);