zig-simd - 背景
歴史的経緯
SIMDの発展
- Intel Pentium MMXで導入
- 64ビットレジスタ、整数演算のみ
- マルチメディア処理向け - 128ビットXMMレジスタ
- 浮動小数点SIMD
- 4つのf32を同時処理 - 256ビットYMMレジスタ
- 8つのf32を同時処理
- 3オペランド命令 - 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);