
Simd Intrinsics
- 366 installs
- 155 repo stars
- Updated June 27, 2026
- mohitmishra786/low-level-dev-skills
simd-intrinsics is a Claude Code skill that guides vectorizing hot loops with platform SIMD intrinsics including SSE, AVX, and NEON for developers who need measurable throughput and latency wins in native services, codec
About
simd-intrinsics is a low-level performance skill from mohitmishra786/low-level-dev-skills for replacing scalar hot loops with platform-specific SIMD instructions. It covers SSE and AVX on x86 and NEON on ARM, targeting native services, codecs, and data pipelines where CPU-bound work limits throughput. The skill walks through identifying vectorizable loops, selecting the right intrinsic sets, and applying patterns that yield measurable latency and throughput improvements. Developers reach for simd-intrinsics when profiling shows tight loops dominating flame graphs and compiler auto-vectorization is insufficient. It assumes comfort with C, C++, or Rust native code and CPU architecture basics.
- Platform-specific intrinsic selection and fallbacks
- Loop vectorization patterns for hot paths
- Alignment, packing, and memory-access discipline
- Benchmarking before/after intrinsic refactors
- Safe scalar fallback when SIMD unavailable
Simd Intrinsics by the numbers
- 366 all-time installs (skills.sh)
- +24 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #1,158 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/mohitmishra786/low-level-dev-skills --skill simd-intrinsicsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 366 |
|---|---|
| repo stars | ★ 155 |
| Last updated | June 27, 2026 |
| Repository | mohitmishra786/low-level-dev-skills ↗ |
How do you vectorize hot loops with SIMD intrinsics?
Vectorize hot loops in native services, codecs, or data pipelines using platform SIMD intrinsics (SSE/AVX/NEON) for measurable throughput and latency wins.
Who is it for?
Backend engineers optimizing CPU-bound native code in services, codecs, or data pipelines after profiling identifies scalar hot loops.
Skip if: Developers working only in interpreted Python/JavaScript or projects where I/O latency—not CPU vectorization—is the bottleneck.
When should I use this skill?
Profiling shows a tight scalar loop in native code and auto-vectorization or higher-level optimizations cannot deliver required throughput or latency targets.
What you get
SIMD-optimized loop implementations using SSE, AVX, or NEON intrinsics with improved throughput
- SIMD-vectorized loop code
- platform-specific intrinsic implementations
Files
SIMD Intrinsics
Purpose
Guide agents through SIMD: reading auto-vectorization output, writing SSE2/AVX2/NEON intrinsics, runtime CPU feature detection, and choosing between compiler auto-vectorization and manual intrinsics.
Triggers
- "How do I check if my loop is being auto-vectorized?"
- "How do I write SSE2/AVX2 intrinsics?"
- "Auto-vectorization failed — how do I fix it?"
- "How do I check for CPU features at runtime?"
- "Should I use intrinsics or let the compiler vectorize?"
- "How do I write NEON intrinsics for ARM?"
Workflow
1. Check auto-vectorization
# 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]Common auto-vectorization blockers:
| Blocker | Fix |
|---|---|
| Loop-carried dependency | Restructure to remove dependency |
| Data-dependent exit (early return) | Move exit after loop |
| Non-contiguous memory | Use gather/scatter or restructure |
| Aliasing (pointer may alias) | Add __restrict__ or restrict |
| Unknown trip count | Add __builtin_expect or hint |
| Function call in loop body | Inline the function |
// 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. Runtime CPU feature detection
// 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 intrinsics (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 intrinsics (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
}
}
#endifCompile with: gcc -O2 -mavx2 -mfma src/simd.c
5. NEON intrinsics (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
}
}Compile with: gcc -O2 -march=armv8-a+simd src/simd.c
6. Choose auto-vectorization vs intrinsics
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. Alignment and performance
// 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 CPUsFor Intel Intrinsics Guide reference and NEON lookup tables, see references/intel-intrinsics-guide.md.
Related skills
- Use
skills/compilers/gccfor-march,-msse4.2,-mavx2flags - Use
skills/compilers/clangfor vectorization remarks and auto-vectorization control - Use
skills/profilers/linux-perfto measure SIMD impact with perf stat counters - Use
skills/low-level-programming/assembly-x86for reading SIMD assembly output
SIMD Intrinsics Quick Reference
x86 Header Files
#include <immintrin.h> // All x86 SIMD (recommended single include)
#include <xmmintrin.h> // SSE
#include <emmintrin.h> // SSE2
#include <pmmintrin.h> // SSE3
#include <tmmintrin.h> // SSSE3
#include <smmintrin.h> // SSE4.1
#include <nmmintrin.h> // SSE4.2
#include <avxintrin.h> // AVX
#include <avx2intrin.h> // AVX2
#include <avx512fintrin.h> // AVX-512
#include <arm_neon.h> // ARM NEONx86 Vector Types
| Type | Width | Elements | Description |
|---|---|---|---|
__m64 | 64-bit | varies | MMX (legacy) |
__m128 | 128-bit | 4x f32 | SSE float |
__m128d | 128-bit | 2x f64 | SSE double |
__m128i | 128-bit | int variants | SSE integer |
__m256 | 256-bit | 8x f32 | AVX float |
__m256d | 256-bit | 4x f64 | AVX double |
__m256i | 256-bit | int variants | AVX2 integer |
__m512 | 512-bit | 16x f32 | AVX-512 float |
__m512d | 512-bit | 8x f64 | AVX-512 double |
__m512i | 512-bit | int variants | AVX-512 integer |
SSE2 / AVX2 Float Intrinsics
| Operation | SSE2 (4xf32) | AVX2 (8xf32) |
|---|---|---|
| Load (aligned) | _mm_load_ps | _mm256_load_ps |
| Load (unaligned) | _mm_loadu_ps | _mm256_loadu_ps |
| Store (aligned) | _mm_store_ps | _mm256_store_ps |
| Store (unaligned) | _mm_storeu_ps | _mm256_storeu_ps |
| Add | _mm_add_ps | _mm256_add_ps |
| Sub | _mm_sub_ps | _mm256_sub_ps |
| Mul | _mm_mul_ps | _mm256_mul_ps |
| Div | _mm_div_ps | _mm256_div_ps |
| FMA (a*b+c) | _mm_fmadd_ps (FMA) | _mm256_fmadd_ps |
| FMA (a*b-c) | _mm_fmsub_ps | _mm256_fmsub_ps |
| Min | _mm_min_ps | _mm256_min_ps |
| Max | _mm_max_ps | _mm256_max_ps |
| Sqrt | _mm_sqrt_ps | _mm256_sqrt_ps |
| Abs (mask) | _mm_andnot_ps(sign, v) | _mm256_andnot_ps |
| Set all | _mm_set1_ps(x) | _mm256_set1_ps(x) |
| Set elements | _mm_set_ps(d,c,b,a) | _mm256_set_ps(...) |
| Zero | _mm_setzero_ps() | _mm256_setzero_ps() |
| Compare eq | _mm_cmpeq_ps | _mm256_cmp_ps(..., _CMP_EQ_OQ) |
| Blend | _mm_blend_ps | _mm256_blend_ps |
| Shuffle | _mm_shuffle_ps | _mm256_shuffle_ps |
| Horizontal add | _mm_hadd_ps (SSE3) | _mm256_hadd_ps |
AVX2 Integer Intrinsics (Common)
| Operation | 32-bit int (8x32) |
|---|---|
| Load | _mm256_loadu_si256 |
| Store | _mm256_storeu_si256 |
| Add | _mm256_add_epi32 |
| Sub | _mm256_sub_epi32 |
| Mul low | _mm256_mullo_epi32 |
| And | _mm256_and_si256 |
| Or | _mm256_or_si256 |
| Xor | _mm256_xor_si256 |
| Shift left | _mm256_slli_epi32(v, n) |
| Shift right | _mm256_srli_epi32(v, n) |
| Gather (32-bit idx) | _mm256_i32gather_epi32(base, idx, scale) |
| Set1 | _mm256_set1_epi32(x) |
| Compare eq | _mm256_cmpeq_epi32 |
| Blend | _mm256_blendv_epi8 |
ARM NEON Types
| Type | Elements | Width |
|---|---|---|
uint8x8_t | 8x u8 | 64-bit |
uint8x16_t | 16x u8 | 128-bit |
uint16x4_t | 4x u16 | 64-bit |
uint16x8_t | 8x u16 | 128-bit |
uint32x2_t | 2x u32 | 64-bit |
uint32x4_t | 4x u32 | 128-bit |
uint64x1_t | 1x u64 | 64-bit |
uint64x2_t | 2x u64 | 128-bit |
float32x2_t | 2x f32 | 64-bit |
float32x4_t | 4x f32 | 128-bit |
float64x2_t | 2x f64 | 128-bit |
NEON Load/Store/Arithmetic
| Operation | NEON (4xf32) |
|---|---|
| Load | vld1q_f32(ptr) |
| Store | vst1q_f32(ptr, v) |
| Add | vaddq_f32(a, b) |
| Sub | vsubq_f32(a, b) |
| Mul | vmulq_f32(a, b) |
| FMA | vfmaq_f32(acc, a, b) — acc + a*b |
| Min | vminq_f32(a, b) |
| Max | vmaxq_f32(a, b) |
| Abs | vabsq_f32(v) |
| Sqrt | vsqrtq_f32(v) |
| Set1 | vdupq_n_f32(x) |
| Zero | vdupq_n_f32(0.0f) |
| Reduce add | vaddvq_f32(v) — horizontal sum |
Compiler Feature Guards
// Compile specific functions with target attribute (GCC/Clang)
__attribute__((target("avx2,fma")))
void process_avx2(float *dst, const float *src, int n) {
// Can use AVX2 and FMA intrinsics here
// Even if not compiling with -mavx2
}
__attribute__((target("sse4.2")))
uint32_t checksum_sse42(const char *data, size_t len) {
// Use _mm_crc32_u8() etc.
}
// Dispatch based on CPU features
__attribute__((ifunc("resolve_process")))
void process(float *dst, const float *src, int n);
static typeof(process) *resolve_process(void) {
if (__builtin_cpu_supports("avx2")) return process_avx2;
if (__builtin_cpu_supports("sse4.2")) return process_sse42;
return process_scalar;
}Online Resources
- Intel Intrinsics Guide: <https://www.intel.com/content/www/us/en/docs/intrinsics-guide/>
- ARM NEON Reference: <https://developer.arm.com/architectures/instruction-sets/intrinsics/>
- Compiler Explorer (Godbolt): <https://godbolt.org/> — see assembly output
- uops.info: instruction latency/throughput data
Related skills
FAQ
Which SIMD platforms does simd-intrinsics cover?
simd-intrinsics addresses x86 SSE and AVX intrinsics plus ARM NEON for vectorizing hot loops in native services, codecs, and data pipelines where platform-specific instructions deliver throughput gains scalar code cannot match.
When should developers apply SIMD instead of compiler auto-vectorization?
simd-intrinsics fits when profiling shows CPU-bound scalar loops in native code and compiler auto-vectorization fails to reach latency or throughput targets in services, codecs, or data pipelines requiring explicit intrinsic control.