mcpField
S
simd-intrinsics

simd-intrinsics

github.com/mohitmishra786開発269656 回インストール18 週間前に更新
リポジトリ

simd-intrinsics について

SIMD 開発を支援するエージェント向けガイドです。コンパイラーのベクトル化確認、SSE2・AVX2・NEON の組み込み関数、実行時の CPU 機能検出を扱います。

主な機能

  1. 01コンパイラーの自動ベクトル化確認
  2. 02SSE2・AVX2・NEON の組み込み関数
  3. 03実行時の CPU 機能検出
  4. 04自動ベクトル化を妨げる要因への対処
  5. 05自動化と手動ベクトル化の比較
  6. 06アライメントと性能のヒント

活用シーン

  1. 01ループがベクトル化されているか確認する
  2. 02x86 または ARM 向け SIMD コードを書く
  3. 03CPU 機能に応じて処理を選ぶ
  4. 04組み込み関数を使うか判断する

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/mohitmishra786/low-level-dev-skills --skill simd-intrinsics

インストール後、Claude Code で/simd-intrinsicsを入力して読み込まれたか確認します。

同梱ファイルreferences/intel-intrinsics-guide.md

詳細

SIMD Intrinsics

目的

エージェントが SIMD を活用するためのガイドです。自動ベクトル化の出力の読み方、SSE2/AVX2/NEON の intrinsic の記述方法、実行時の CPU 機能検出、自動ベクトル化と手動 intrinsic の使い分けを説明します。

トリガー

  • 「ループが自動ベクトル化されているかどうかを確認するには?」
  • 「SSE2/AVX2 の intrinsic を記述するには?」
  • 「自動ベクトル化に失敗しました。修正するには?」
  • 「実行時に CPU 機能を確認するには?」
  • 「intrinsic を使うべきですか、それともコンパイラーにベクトル化を任せるべきですか?」
  • 「ARM 向けに NEON の intrinsic を記述するには?」

ワークフロー

1. 自動ベクトル化を確認する
# GCC: show vectorization info
gcc -O2 -march=native -fopt-info-vec src/hot.c -o hot

# Verbose: show missed + successful
gcc -O2 -march=native -fopt-info-vec-missed -fopt-info-vec-optimized src/hot.c

# Clang: vectorization remarks
clang -O2 -march=native \
    -Rpass=loop-vectorize \
    -Rpass-missed=loop-vectorize \
    -Rpass-analysis=loop-vectorize \
    src/hot.c -o hot

# Example missed message:
# hot.c:15:5: remark: loop not vectorized: value that could not be identified as
# reduction is used outside the loop [-Rpass-missed=loop-vectorize]

自動ベクトル化を妨げる一般的な要因:

要因対処法
ループ反復間の依存関係依存関係を取り除くように再構成する
データ依存の終了(早期 return)終了処理をループの後に移動する
非連続なメモリーアクセスgather/scatter を使用するか、再構成する
エイリアシング(ポインターがエイリアスする可能性がある)__restrict__ または restrict を追加する
反復回数が不明__builtin_expect またはヒントを追加する
ループ本体内の関数呼び出し関数をインライン化する
// Help the compiler by adding restrict
void add_arrays(float * __restrict__ dst,
                const float * __restrict__ a,
                const float * __restrict__ b,
                size_t n) {
    for (size_t i = 0; i < n; i++)
        dst[i] = a[i] + b[i];  // Now vectorizable
}
2. 実行時の CPU 機能検出
// Linux: use __builtin_cpu_supports (GCC/Clang)
if (__builtin_cpu_supports("avx2")) {
    process_avx2(data, len);
} else if (__builtin_cpu_supports("sse4.2")) {
    process_sse42(data, len);
} else {
    process_scalar(data, len);
}

// Check specific features:
__builtin_cpu_supports("sse2")
__builtin_cpu_supports("sse4.1")
__builtin_cpu_supports("sse4.2")
__builtin_cpu_supports("avx")
__builtin_cpu_supports("avx2")
__builtin_cpu_supports("avx512f")
__builtin_cpu_supports("bmi")
__builtin_cpu_supports("bmi2")
__builtin_cpu_supports("fma")
// Portable: use CPUID directly
#include <cpuid.h>

static int has_avx2(void) {
    unsigned int eax, ebx, ecx, edx;
    // CPUID leaf 7, subleaf 0
    __cpuid_count(7, 0, eax, ebx, ecx, edx);
    return (ebx >> 5) & 1;  // bit 5 = AVX2
}
3. SSE2/SSE4.2 の intrinsic(x86)
#include <immintrin.h>  // All x86 intrinsics

// SSE2: 128-bit vectors
// __m128  = 4 floats
// __m128d = 2 doubles
// __m128i = integers (8x16, 4x32, 2x64, 16x8)

void sum_floats_sse2(float *dst, const float *a, const float *b, int n) {
    int i = 0;
    for (; i <= n - 4; i += 4) {
        __m128 va = _mm_loadu_ps(a + i);  // unaligned load
        __m128 vb = _mm_loadu_ps(b + i);
        __m128 vc = _mm_add_ps(va, vb);
        _mm_storeu_ps(dst + i, vc);       // unaligned store
    }
    // Handle remainder
    for (; i < n; i++) dst[i] = a[i] + b[i];
}
4. AVX2 の intrinsic(x86)
#ifdef __AVX2__
#include <immintrin.h>

// __m256  = 8 floats, __m256d = 4 doubles, __m256i = integers

void sum_floats_avx2(float *dst, const float *a, const float *b, int n) {
    int i = 0;
    for (; i <= n - 8; i += 8) {
        __m256 va = _mm256_loadu_ps(a + i);
        __m256 vb = _mm256_loadu_ps(b + i);
        __m256 vc = _mm256_add_ps(va, vb);
        _mm256_storeu_ps(dst + i, vc);
    }
    // SSE2 tail (4 elements)
    for (; i <= n - 4; i += 4) {
        __m128 va = _mm_loadu_ps(a + i);
        __m128 vb = _mm_loadu_ps(b + i);
        _mm_storeu_ps(dst + i, _mm_add_ps(va, vb));
    }
    // Scalar tail
    for (; i < n; i++) dst[i] = a[i] + b[i];
}

// Fused multiply-add (FMA) — 1 instruction for a*b+c
void fma_avx2(float *dst, const float *a, const float *b, const float *c, int n) {
    for (int i = 0; i <= n - 8; i += 8) {
        __m256 va = _mm256_loadu_ps(a + i);
        __m256 vb = _mm256_loadu_ps(b + i);
        __m256 vc = _mm256_loadu_ps(c + i);
        _mm256_storeu_ps(dst + i, _mm256_fmadd_ps(va, vb, vc)); // dst = a*b + c
    }
}
#endif

コンパイル方法:gcc -O2 -mavx2 -mfma src/simd.c

5. NEON の intrinsic(ARM/AArch64)
#include <arm_neon.h>

// float32x4_t = 4 floats (128-bit)
// float32x8_t = 8 floats (ARM SVE — scalable)
// uint8x16_t  = 16 bytes
// int32x4_t   = 4 int32

void sum_floats_neon(float *dst, const float *a, const float *b, int n) {
    int i = 0;
    for (; i <= n - 4; i += 4) {
        float32x4_t va = vld1q_f32(a + i);  // load 4 floats
        float32x4_t vb = vld1q_f32(b + i);
        float32x4_t vc = vaddq_f32(va, vb);  // add
        vst1q_f32(dst + i, vc);               // store 4 floats
    }
    for (; i < n; i++) dst[i] = a[i] + b[i];
}

// AArch64 FMA
void fma_neon(float *dst, const float *a, const float *b, const float *c, int n) {
    for (int i = 0; i <= n - 4; i += 4) {
        float32x4_t va = vld1q_f32(a + i);
        float32x4_t vb = vld1q_f32(b + i);
        float32x4_t vc = vld1q_f32(c + i);
        vst1q_f32(dst + i, vfmaq_f32(vc, va, vb));  // vc + va*vb
    }
}

コンパイル方法:gcc -O2 -march=armv8-a+simd src/simd.c

6. 自動ベクトル化と intrinsic の使い分け
Can the compiler auto-vectorize?
  → Try first: add __restrict__, remove complex control flow, align data
  → Check with -fopt-info-vec or -Rpass=loop-vectorize
  → If vectorized: verify correctness and performance

Still need intrinsics?
  → Prefer compiler builtins: __builtin_popcount, __builtin_ctz
  → Use SIMD intrinsics for: hand-tuned shuffles, gather/scatter, horizontal ops
  → Avoid intrinsics for: simple element-wise ops (let compiler do it)
7. アラインメントとパフォーマンス
// Aligned allocation (required for _mm256_load_ps, optional for _mm256_loadu_ps)
float *buf = (float *)aligned_alloc(32, n * sizeof(float));
// 32-byte alignment for AVX2, 64 for AVX-512

// Hint alignment to compiler
float *__attribute__((aligned(32))) buf = ...;

// Use aligned loads when data is aligned (faster)
__m256 v = _mm256_load_ps(aligned_ptr);    // requires 32-byte alignment
__m256 v = _mm256_loadu_ps(unaligned_ptr); // any alignment, slightly slower on old CPUs

Intel Intrinsics Guide のリファレンスと NEON のルックアップテーブルについては、references/intel-intrinsics-guide.md を参照してください。

関連スキル

  • -march、-msse4.2、-mavx2 フラグについては skills/compilers/gcc を使用してください
  • ベクトル化に関する警告と自動ベクトル化の制御については skills/compilers/clang を使用してください
  • perf stat カウンターで SIMD の影響を測定するには skills/profilers/linux-perf を使用してください
  • SIMD のアセンブリー出力を読むには skills/low-level-programming/assembly-x86 を使用してください

コメント