4.1.24 · Coding › Computer Architecture (Deep)
Intuition Ek-sentence idea
SIMD = Single Instruction, Multiple Data . Ek ADD jo sirf ek number handle karta hai, uski jagah ek ADD use karo jo 8, 16, ya 32 numbers ek saath handle kare, kyunki woh sab ek wide register mein side-by-side packed hote hain aur hardware mein parallel ALU lanes hoti hain.
Real programs ka ek bada hissa ek hi operation ek array par karta hai: pixels scale karna, do vectors add karna, audio samples sum karna. Scalar code mein aap instruction-fetch/decode ka cost har element ke liye pay karte ho. Yeh wasteful hai — data change hota hai lekin instruction identical hota hai.
SIMD data-level parallelism (DLP) exploit karta hai: ek decoded instruction kaafi saari ALU "lanes" ko simultaneously drive karta hai. Aap fetch/decode ko kai elements par amortize karte ho aur ~N× throughput milta hai relatively saste silicon ke saath (lanes replicate karna sasta hota hai).
YEH PROBLEM SOLVE KARTA HAI: har element par instruction overhead aur under-used wide datapaths .
HARDWARE KAISE KARTA HAI: register file wider ho jaati hai (128/256/512 bits). addps jaisi instruction register ko independent lanes mein cut karti hai aur saare adders parallel mein fire hote hain — koi carry lane boundaries cross nahi karta.
Definition Core vocabulary
Vector register — ek wide register jo kai packed elements hold karta hai. Width:
SSE : 128-bit (xmm0–xmm15)
AVX/AVX2 : 256-bit (ymm0–ymm15)
AVX-512 : 512-bit (zmm0–zmm31)
Lane — ek vector register ke andar ek independent element-slot.
Packed vs Scalar — addps (packed, saari lanes) vs addss (scalar, sirf lane 0).
Element type suffix : ps = packed single (f32), pd = packed double (f64), b/w/d/q = byte/word/dword/qword integers.
Worked example 8 floats ke liye AVX ke saath
c[i] = a[i] + b[i]
// intrinsics (compiler-portable SIMD)
__m256 va = _mm256_loadu_ps ( & a [i]); // load 8 floats (unaligned ok)
__m256 vb = _mm256_loadu_ps ( & b [i]); // load 8 floats
__m256 vc = _mm256_add_ps (va, vb); // 8 adds in ONE instruction
_mm256_storeu_ps ( & c [i], vc); // store 8 floats
loadu kyun, load kyun nahi? _mm256_load_ps ko 32-byte-aligned address chahiye warna fault hota hai; loadu koi bhi address tolerate karta hai (historically thoda slower, modern CPUs par equal).
8 ek saath kyun process karte hain? 256/32 = 8 lanes — yahi natural stride hai.
Remainder loop kyun chahiye: agar array length 8 ka multiple nahi hai, toh last len % 8 elements ek scalar tail loop se kiye jaate hain.
Worked example Poora vectorized loop (tail ke saath)
size_t i = 0 ;
for (; i + 8 <= n; i += 8 ) { // Why: only step while a full vector fits
__m256 va = _mm256_loadu_ps (a + i);
__m256 vb = _mm256_loadu_ps (b + i);
_mm256_storeu_ps (c + i, _mm256_add_ps (va, vb));
}
for (; i < n; ++ i) c [i] = a [i] + b [i]; // Why: scalar tail for leftover elements
Worked example Horizontal reduction — array ko sum karna
Lane-wise ops aasaan hain; lanes ke across combine karna ("horizontal") tricky part hai.
__m256 acc = _mm256_setzero_ps ();
for ( size_t i = 0 ; i + 8 <= n; i += 8 )
acc = _mm256_add_ps (acc, _mm256_loadu_ps (a + i)); // Why: keep 8 partial sums
// Ab 8 partial sums -> 1 fold karo
__m128 lo = _mm256_castps256_ps128 (acc); // lanes 0..3
__m128 hi = _mm256_extractf128_ps (acc, 1 ); // lanes 4..7
__m128 s = _mm_add_ps (lo, hi); // 4 partial sums
s = _mm_hadd_ps (s, s); // pairwise add
s = _mm_hadd_ps (s, s); // phir -> total in lane 0
float total = _mm_cvtss_f32 (s);
Loop mein har lane sum karne ki jagah fold kyun? 8 independent accumulators rakhne se ek long dependency chain avoid hoti hai → CPU additions pipeline karta hai; expensive cross-lane fold sirf ek baar end mein pay karte hain.
Common mistake "AVX hamesha ~8× faster hota hai scalar se"
Kyun sahi lagta hai: 8 lanes ⇒ 8× arithmetic throughput, obvious hai.
Fix: Zyaadatar array loops memory-bandwidth bound hote hain, compute bound nahi. RAM se saari bytes load toh karni hi padengi. Agar memory bottleneck hai, SIMD kam deta hai. Plus Amdahl: ( 1 − f ) + f /8 1 . SIMD ko compute-dense, cache-resident data par use karo.
Common mistake "Bade registers (AVX-512) hamesha better hote hain"
Kyun sahi lagta hai: 16 lanes > 8 lanes, zyada = faster.
Fix: Heavy AVX-512 use kaafi CPUs par frequency downclocking trigger kar sakta hai (wide units zyada power draw karte hain), toh ek "faster" loop lower clock par chalta hai aur short/mixed workloads ke liye net-loss ho jaata hai. Benchmark karo, assume mat karo.
Common mistake AVX (256-bit, VEX-encoded) ko legacy SSE instructions ke saath mix karna
Kyun sahi lagta hai: dono xmm touch karte hain, interchangeable hone chahiye.
Fix: Non-VEX SSE aur VEX AVX ke beech transition ek expensive state-save penalty incur karta hai jab tak vzeroupper issue na karo. Compilers yeh handle karte hain; hand-written asm mein dhyan rakhna padta hai.
Common mistake SIMD float reduction ko scalar ke saath bit-identical maanna
Kyun sahi lagta hai: addition to addition hai.
Fix: Float addition associative nahi hai ; lane-groups mein sum karna additions ka order change karta hai, thoda alag rounding deta hai. Graphics/ML ke liye theek hai; strict reproducibility ke liye matter karta hai (-ffast-math compiler ko reductions auto-vectorize karne ke liye required hai).
Recall Lanes ki number kaise compute karte hain? (cover karo aur answer do)
lanes = element size in bits register width in bits . E.g. AVX (256) with f32 (32) → 8 lanes.
Recall Real speedup lane count ke barabar kyun nahi hota?
Amdahl's law (non-vectorizable fraction) + memory bandwidth ceiling + setup/tail overhead + possible downclocking.
Recall
addps aur addss mein kya difference hai?
addps = packed , saari lanes add karta hai; addss = scalar , sirf lane 0.
Recall SIMD sum mein multiple accumulators kyun rakhte hain?
Dependency chain todne ke liye taaki additions pipeline hon; cross-lane fold sirf ek baar end mein karo.
Recall Feynman: SIMD ko ek 12-saal ke bachche ko explain karo
Socho ek teacher papers grade kar rahi hai. Normally woh ek ek paper grade karti hai. SIMD ek magic stamp ki tarah hai jo 8 stamp wide hai — woh 8 papers line mein lagaati hai aur ek press mein sab par "+5" stamp kar deti hai. Action (stamp +5) same hai; sirf papers alag hain. Toh woh 8× faster finish karti hai — jab tak woh pile se 8 papers fast enough pakad sake (wahi memory limit hai!).
Mnemonic Yaad rakhne ka tarika
"SIMD = Same Instruction, Many Doers." Aur widths ke liye: S SE 128 , A VX 256 , AVX-512 — numbers literally double hote hain: 128 → 256 → 512.
Worked example Compute karne se pehle predict karo
Q: Ek 256-bit register jo int16 hold kare. Kitni lanes? Forecast , phir check karo: 256/16 = 16 lanes. ✅
Q: Ideal speedup agar ek loop 90% vectorizable hai AVX f32 par (8 lanes)? Forecast ~5×, phir: 0.1 + 0.9/8 1 = 0.2125 1 ≈ 4.7 × . ✅ (8× nahi!)
SIMD ka full form kya hai? Single Instruction, Multiple Data — ek instruction kai packed data elements par operate karti hai ek saath.
SSE / AVX / AVX-512 ke register widths? 128-bit / 256-bit / 512-bit.
Har ek ke liye register file names? xmm (SSE, 128), ymm (AVX, 256), zmm (AVX-512, 512).
Lanes ki number ka formula? lanes = register_width_bits / element_size_bits.
AVX ke saath f32 mein kitni lanes? 256/32 = 8.
AVX-512 ke saath int8 mein kitni lanes? 512/8 = 64.
addps aur addss mein difference? packed (saari lanes) vs scalar (sirf lane 0).
Real speedup lane count se kam kyun hota hai? Amdahl's law, memory bandwidth limit, setup/tail overhead, possible downclocking.
Amdahl speedup formula? 1 / ((1-f) + f/lanes), f = vectorizable fraction.
Scalar tail loop kyun chahiye? array length lane count ka multiple nahi ho sakti; leftover elements ek ek karke done hote hain.
'Horizontal' operation kya hota hai? ek operation jo lanes ke across values combine kare (jaise saari lanes sum karna), lane-wise vertical ops ke opposite.
Reduction mein multiple SIMD accumulators kyun rakhte hain? dependency chain todne ke liye taaki additions pipeline hon; fold sirf ek baar end mein karo.
vzeroupper kis kaam aata hai? costly SSE/AVX (non-VEX/VEX) state-transition penalty se bachne ke liye.
SIMD float reductions scalar se alag kyun ho sakte hain? float addition associative nahi hai; lane-grouping adds ka order change karti hai → alag rounding.
loadu vs load (aligned)? load ko alignment chahiye (warna fault); loadu koi bhi address allow karta hai.
Data-Level Parallelism — SIMD in-core form of DLP hai.
Amdahl's Law — achievable speedup cap karta hai.
Memory Bandwidth and the Roofline Model — compute-bound vs memory-bound decide karta hai ki SIMD kitna payoff dega.
Cache and Alignment — aligned loads, cache lines vs vector width.
Auto-vectorization and the Compiler — -O3 -march=native, -ffast-math.
GPU SIMT — GPU cousin (Single Instruction, Multiple Threads).
Floating Point Representation — FP addition ki non-associativity.
SIMD one instr, many data
Per-element fetch/decode cost
lanes = width / element size
SSE 128 / AVX 256 / AVX-512 512