Cache-aware ও Cache-oblivious — Big-O যখন মিথ্যা বলে
Cache-Aware and Cache-Oblivious Algorithms
Big-O একই থেকে গতি ৪-১০× বদলায় — cache line, prefetcher, locality আর layout-এর হিসাব না জানলে RAM model আপনাকে ধোঁকা দেবে।
আগে এটা বুঝি
Asymptotic-analysis লেসনে আমরা Big-O-কে শৃঙ্খলা শিখিয়েছি: constant factor উপেক্ষা করো, growth rate-ই সত্য। পুরো module ধরে সেটা ভালো compass ছিল। কিন্তু আজ module-এর শেষ লেসনে একটা confession লাগবে: RAM model মিথ্যা বলে।
Model বলে: memory access O(1), সমান দাম। বাস্তব: L1 hit ~4 cycles, DRAM ~200+ cycles — ৫০× পার্থক্য। একই Θ(n)-algorithm দুইভাবে লিখলে wall-clock-এ ৪× পার্থক্য; একই Θ(n³) matmul loop-order বদলে ৮×। Big-O blind spot-টা ঠিক এখানেই: access count মাপে, access cost-এর variance নয়।
আজ দেখব silicon-এর চুক্তিপত্র — cache line, locality, prefetcher, layout — আর দুইটা design philosophy: cache-aware (hardware parameters জেনে tune) আর cache-oblivious (parameter না জেনেও সব hierarchy level-এ optimal)। Module finale: এই জ্ঞান Level 7-এর networking-এ packet processing-এ যাবে।
মূল ধারণা
Latency ladder — মাইক্রোসেকেন্ডের রাজনীতি
আধুনিক x86-এর typical numbers (order-of-magnitude, machine-ভেদে):
| Tier | Latency | Relative | Size |
|---|---|---|---|
| Register | 0–1 cycle | 1× | bytes |
| L1 cache | ~4 cycles | 4× | 32–64 KB |
| L2 cache | ~14 cycles | 14× | 256 KB–2 MB |
| L3 cache | ~40–60 cycles | 50× | 8–64 MB |
| DRAM | ~100–300 cycles | 200× | GBs |
| NVMe SSD | ~25 µs | 100,000× | TBs |
Cache line — সর্বনিম্ন বিনিময় একক
Cache line = 64 byte (industry standard)। এক byte চাইলেও পুরো 64 byte আসে:
64 B line = 16 × 32-bit int
= 8 × 64-bit double/pointer
a[0] touch করলে a[0..15] সব L1-এ!
পরের ১৫টা access — free (hit)দুই ধরনের miss: cold (প্রথমবার), capacity (working set > cache), conflict (mapping collision)। Algorithm designer-এর হাতিয়ার miss কমানো: spatial locality (pase-pase data একসাথে ছোঁয়া) + temporal locality (একই data আবার আবার, eviction-এর আগে)।
Linear vs binary search — miss-এর চশমায় আবার
Linear search sequential scan: প্রতি line-এ ১৬ comparison — n element-এ n/16 misses। Prefetcher-friendly: stride predictable, hardware পরের lines আগে আনে। Binary search: log₂n comparisons কিন্তু প্রতিটা jump random-ish address — প্রতি step-এ প্রায় ১ miss, no prefetch help।
n = 16M ints (64 MB):
linear : misses ≈ 1M lines comparisons ≈ 16M (but cheap hits!)
binary : misses ≈ 24 × 1 comparisons ≈ 24 (expensive each)
RAM-model verdict: binary wins massively
Cache-reality verdict: gap much narrower — sometimes linear wins for
small patterns inside cache-resident blocks (SIMD vectorized scans)!Branch misprediction যোগ হলে (~15-20 cycles প্রতি flush) binary search-এর random branch pattern আরেকটা tax। Eytzinger layout (BFS-ordered array) binary search-কে prefetchable বানায় — advanced but real technique databases-এ।
Pointer structures — line-এর অপচয়
Linked list node = 8-byte pointer + 4-byte data → এক node এক line occupy করে 64 byte-এর মধ্যে 12 ব্যবহার করে — 81% waste per line, আর allocation order random বলে next-node প্রায়ই অন্য region-এ: traversal = প্রতি step-এ cold miss। Array-based alternative (implicit index as ‘pointer’) sequential layout পায়। Sorting লেসনের heapsort-vs-quicksort gap এখানেই ব্যাখ্যা হয়েছিল।
ভেতরে কী ঘটছে
AoS vs SoA — layout-ই architecture
Array of Structs vs Struct of Arrays:
import random
rng = random.Random(42)
N = 1000
# AoS: particle-দের সারি — object-oriented instinct
particles_aos = [tuple(rng.random() for _ in range(7)) for _ in range(N)]
# update velocity only? vx,vy,vz দরকার — কিন্তু line-এ x,y,z,mass-ও এসেছে
# SoA: field-গুলোর সারি — data-oriented design
xs = [rng.random() for _ in range(N)]; ys = [rng.random() for _ in range(N)]
zs = [rng.random() for _ in range(N)]; vxs = [rng.random() for _ in range(N)]
vys = [rng.random() for _ in range(N)]; vzs = [rng.random() for _ in range(N)]
masses = [rng.random() for _ in range(N)]
# velocity update শুধু vxs/vys/vzs arrays touch — pure streamingVelocity-only workload-এ AoS প্রতি line-এ 3 useful fields / 7 — SoA 7/7। Bandwidth utilization আর SIMD vectorization (contiguous same-type data) দুটোই SoA-তে জেতে। Game engines, physics sims, databases columnar storage (Parquet!) — সব SoA philosophy। Experiment-এ 2–4× gap মাপব।
Trade-off honesty: entity-wise full-record access বেশি হলে AoS ভালো (সব field এক line-এ)। Layout decision = query pattern analysis, dogma নয়।
ijk vs ikj — matmul-এর মায়া
C[i][j] += A[i][k] · B[k][j] — triple loop, order মনে হয় cosmetic:
row-major storage (C-style):
ijk order: B[k][j] — k বাড়ে ⇒ row-stride-n jump! প্রতি inner step
নতুন cache line (n=512: B-এর প্রতি access নতুন line)
misses ≈ n³ lines touched... disaster
ikj order: C[i][j] fixed inner? না — inner k: A[i][k] sequential ✓
B[k][j]... wait, reorder properly:
ikj: for i: for k: for j: C[i][j] += A[i][k]*B[k][j]
A[i][k]: k-loop sequential (row walk) ✓
B[k][j]: j-loop sequential (row walk) ✓
C[i][j]: j-loop sequential ✓ ← তিনটাই streaming!
same FLOPs (2n³), same Θ(n³) — but misses collapse from ~n³/line to
~n²·(n/tile) — orders fewer.Experiment-এ GFLOPS measurement দেখব — theory বদলায়নি, locality বদলেছে। Hager-Wellein book-এর এই classic demo production compilers auto-vectorize করার সময়ও কাজে লাগে।
Tiling/blocking — working set-কে cache-এ আটকাও
Matmul-এ n=1024: B-এর এক row 4 KB — L1 (32 KB) সহজেই ধরে না পুরো B (4 MB)। Solution: matrix-কে tile-এ ভাগ করো যেন তিনটা tile একসাথে L1/L2-তে resident থাকে:
for ii in range(0, n, T):
for kk in range(0, n, T):
for jj in range(0, n, T):
# mini-matmul on tiles — 3·T²·8 bytes working set
# T=32: 24 KB — L1-তে আঁটে!Tile size parameter: ছোট হলে overhead (loop machinery), বড় হলে capacity miss — sweet spot machine-specific (L1 size ÷ 3 ÷ 8 rough guide)। Build section-এ sweep {32, 64, 128} করে curve আঁকব — cache-aware philosophy-র definition এটাই: parameters hardware-এর কাছে tune করা।
Cache-oblivious — parameter ছাড়া optimality
Aware approach-এর অস্বস্তি: tile size প্রতি machine-এ আলাদা — portable performance কঠিন। Frigo et al. (1999)-এর answer: cache-oblivious algorithm — কোনো hardware parameter assume না করে, recursive subdivision এমনভাবে যেন প্রতিটা level-এ subproblem ছোট হয়ে base-case-এ line-fit হয়।
Recursively halved matmul: বড় matrix ৪ quadrant-এ, recursion নিচে নামতে নামতে কোনো এক level-এ working set L1-এ fit করে (level টা hardware decide করে implicitly!), আরও নিচে L2, L3-ও নিজে নিজে covered। Analysis tool: I/O model — cache size M, line size B; oblivious algorithms prove করে optimal miss count across ALL M,B simultaneously।
Van Emde Boas layout: tree/binary search array-কে recursively halve করে store করলে search path-এর nodes contiguous থাকে every level-এ — binary search miss count O(log_B n) instead of log n। Production examples: FFT (six-step recursive), funsort libraries, sparse matrix kernels।
matrix M (n×n) working set at depth d: n²/4^d
┌───────┬───────┐
│ M11 │ M12 │ depth 0 full — maybe DRAM
├───────┼───────┤
│ M21 │ M22 │
└───────┴───────┘
┌───┬───┐ ┌───┬───┐
│…│…│ │…│…│ depth 1 quarter — maybe L3
└───┴───┘ └───┴───┘
depth 2 sixteenth — maybe L2
depth d fits L1 eventually — automatic!
oblivious promise: optimal misses for EVERY (M,B) simultaneously —
no tuning constants anywhere in code.উদাহরণ
Full worked micro-analysis: sum over matrix, two traversals
n=2048 int matrix (16 MB — L2/L3 boundary):
Version A — row-major scan (i outer, j inner):
access order: [0][0],[0][1],...,[0][2047],[1][0]...
per line: 16 consecutive ints → 1 miss serves 16 accesses
total misses ≈ 2048 rows × 128 lines = 262,144
time ≈ misses × ~150 cyc + 4M × ~4 cyc ≈ dominated by compute
Version B — column-major scan (j outer, i inner):
access order: [0][0],[1][0],[2][0],...
stride = 2048 ints = 8192 B = 128 lines apart!
every single access = new line = cold miss (no reuse until wrap)
total misses ≈ 4,194,304 — 16× more
time ≈ misses × 200 cyc ≈ 838M cycles ≈ 280 ms @3GHz vs ~55 ms
Same Θ(n²). Same instructions. 5× wall-clock difference.এই hand-analysis-ই experiment-এর prediction — মেপে verify করব। Analysis discipline: (১) access sequence লেখো, (২) line-boundary map, (৩) reuse distance হিসাব, (৪) tier-latency multiply।
ছবিতে
Memory-conscious design stack
- Algorithm complexityΘ(n log n) vs Θ(n²) — module-wide compass, still first
- Access patternsequential vs strided vs random — prefetcher contract
- Data layoutAoS/SoA, array vs pointers — line utilization %
- Blocking/tilingworking-set shaping to fit tiers — aware tuning
- Oblivious structurerecursive layouts optimal everywhere — portability play
AoS record (7×4B=28B fields, want 3):
line: |f1 f2 f3|f4 f5 f6 f7|f1 f2 f3|f4 f5 f6 f7|
useful: ███░░░░░ ███ (37.5%) → 2.67× bandwidth tax
SoA (three parallel arrays):
line(vx): |v v v v v v v v v v v v v v v v| 100% useful ✓
Pointer nodes (12B used / 64B line, scattered):
line: |ptr dat .........(52B dead)| 19% — worst case
bandwidth tax = 1/utilization — direct multiplier on memory-bound loopsনিজে চালিয়ে দেখুন
তিনটা benchmark: AoS vs SoA, ijk vs ikj GFLOPS, array vs pointer-chain
import numpy as np
import time
def bench(fn, *args, reps=5):
best = min(
(lambda t0: (fn(*args), time.perf_counter() - t0)[1])(time.perf_counter())
for _ in range(reps))
return best
# ---------- part ১: AoS vs SoA structured-sum ----------
N = 10_000_000
rng = np.random.default_rng(42)
# AoS: one interleaved (N, 7) array; sum columns 4,5,6 (velocities)
aos = rng.random((N, 7))
t_aos = bench(lambda: aos[:, 4].sum() + aos[:, 5].sum() + aos[:, 6].sum())
# SoA: three separate contiguous velocity arrays
va = rng.random(N); vb = rng.random(N); vc = rng.random(N)
t_soa = bench(lambda: va.sum() + vb.sum() + vc.sum())
print(f'AoS interleaved sum : {t_aos*1000:8.1f} ms')
print(f'SoA contiguous sum : {t_soa*1000:8.1f} ms')
print(f'gap : {t_aos/t_soa:8.2f}x')
# ---------- part ২: matmul ijk vs ikj (pure python lists, small n) ----------
def matmul_ijk(A, B, n):
C = [[0.0] * n for _ in range(n)]
for i in range(n):
for j in range(n):
s = 0.0
for k in range(n):
s += A[i][k] * B[k][j] # B column-walk: stride-n
C[i][j] = s
return C
def matmul_ikj(A, B, n):
C = [[0.0] * n for _ in range(n)]
for i in range(n):
Ci = C[i]; Ai = A[i]
for k in range(n):
a = Ai[k]; Bk = B[k]
for j in range(n):
Ci[j] += a * Bk[j] # all walks row-sequential
return C
n = 220
Arng = [[float((i * j) % 7) for j in range(n)] for i in range(n)]
Brng = [[float((i + j) % 5) for j in range(n)] for i in range(n)]
R1 = matmul_ijk(Arng, Brng, n)
t_ijk = bench(matmul_ijk, Arng, Brng, n, reps=1)
R2 = matmul_ikj(Arng, Brng, n)
t_ikj = bench(matmul_ikj, Arng, Brng, n, reps=1)
assert R1 == R2
gflops = lambda t: 2 * n**3 / t / 1e9
print(f'\nmatmul n={n} (pure python):')
print(f' ijk : {t_ijk*1000:9.0f} ms ({gflops(t_ijk):5.3f} GFLOPS)')
print(f' ikj : {t_ikj*1000:9.0f} ms ({gflops(t_ikj):5.3f} GFLOPS)')
print(f' speedup: {t_ijk/t_ikj:.2f}x')
# numpy reference — BLAS blocked+tuned reality check
Anp = rng.random((512, 512)); Bnp = rng.random((512, 512))
t_blas = bench(lambda: Anp @ Bnp, reps=3)
print(f' numpy 512² BLAS: {t_blas*1000:.1f} ms '
f'({2*512**3/t_blas/1e9:.1f} GFLOPS — tuned blocking+SIMD)')
# ---------- part ৩: array vs pointer-chain traversal proxy ----------
import sys
M = 400_000
vals_arr = list(range(M)) # contiguous index space
# pointer-chain: shuffled linked-list simulation via index-hopping
perm = list(range(M)); rng.shuffle(perm)
next_idx = perm[:] # random successor mapping
def traverse_array():
s = 0
for i in range(M):
s += vals_arr[i] # sequential
return s
def traverse_chain():
s, i = 0, 0
for _ in range(M):
s += vals_arr[i]
i = next_idx[i] # random hop = cache miss proxy
return s
ta = bench(traverse_array)
tc = bench(traverse_chain)
print(f'\ntraversal {M:,} nodes:')
print(f' sequential array : {ta*1000:8.1f} ms')
print(f' random chain : {tc*1000:8.1f} ms ratio {tc/ta:5.2f}x')সাধারণ ফলাফল (M-series laptop):
AoS interleaved sum : 129.2 ms
SoA contiguous sum : 11.7 ms
gap : 11.04x
matmul n=220 (pure python):
ijk : 1384 ms (0.015 GFLOPS)
ikj : 1040 ms (0.020 GFLOPS)
speedup: 1.33x
numpy 512² BLAS: 2.0 ms (135.0 GFLOPS — tuned blocking+SIMD)
traversal 400,000 nodes:
sequential array : 30.7 ms
random chain : 162.1 ms ratio 5.28xপড়ার বিষয়:
১. AoS→SoA gap ~১১× — numpy strided view (aos[:,4]) প্রতিটা row-এ ৬টা অবাঞ্ছিত field skip করে; contiguous SoA pure streaming। Native C-তে gap সাধারণত ২-৪× — layout-এর bandwidth tax universal।
২. Pure-Python matmul-এ ikj মাত্র 1.33× — interpreter overhead locality gain-এর বেশিরভাগ mask করে। আসল গল্পটা BLAS-এর সংখ্যায়: numpy 135 GFLOPS বনাম pure-Python 0.02 — ~৭০০০× gap টাই compiled loop + tiling + SIMD-এর combined verdict; C/C++-এ শুধু reorder-ই সাধারণত ৪-৮× দেয়।
৩. Random-chain 5.3× ধীর — pointer-chasing প্রতি hop-এ cache-miss wait; Python object heap scattered হওয়ায় এটা DRAM-latency proxy হিসেবে নিখুঁত।
Identical asymptotics-এর ভেতরে layout আর access pattern wall-clock বহুগুণ বদলায়: AoS/SoA sum-এ ~১১× gap, random pointer-chain traversal-এ ~৫× cost — আর BLAS-এর ১৩৫ GFLOPS pure-Python-এর ০.০২-এর সাথে tiling+compiled loop-এর সীমানা দেখায়।
নিজে বানান
Tiled matmul sweep + AoS/SoA pair — awareness tools হাতে
- naive ijk baseline লিখুন
- tiled version লিখুন — T parameterized
- Sweep T ∈ `{32, 64, 128}` timing print করে curve দেখান
- AoS ও SoA pair লিখুন — একই aggregate query, দুই layout
- GFLOPS normalize করে তুলনাযোগ্য করুন
- Correctness assert: সব version একই result
import time
N = 512
def make_matrices():
A = [[float((i * 3 + j) % 11) for j in range(N)] for i in range(N)]
B = [[float((i + 2 * j) % 7) for j in range(N)] for i in range(N)]
return A, B
def matmul_tiled(A, B, T):
"""blocked matmul — 3·T² floats working set per mini-product"""
C = [[0.0] * N for _ in range(N)]
for ii in range(0, N, T):
for kk in range(0, N, T):
for jj in range(0, N, T):
for i in range(ii, min(ii + T, N)):
Ai, Ci = A[i], C[i]
for k in range(kk, min(kk + T, N)):
a = Ai[k]
Bk = B[k]
for j in range(jj, min(jj + T, N)):
Ci[j] += a * Bk[j]
return C
def gflops(seconds):
return 2 * N**3 / seconds / 1e9
if __name__ == '__main__':
A, B = make_matrices()
def timed(fn, arg):
t0 = time.perf_counter()
r = fn(A, B, arg)
return r, time.perf_counter() - t0
ref, _ = timed(matmul_tiled, N if False else 512) # T=N = untiled ikj
print(f'tiled matmul sweep (n={N}):')
print(f'{"tile":>6} {"ms":>9} {"GFLOPS":>8}')
for T in (32, 64, 128, 512):
out, dt = timed(matmul_tiled, T)
assert out == ref
print(f'{T:>6} {dt*1000:>9.0f} {gflops(dt):>8.3f}')
# ---------- AoS vs SoA pair ----------
import random
rnd = random.Random(9)
class Particle:
__slots__ = ('x', 'y', 'vx', 'vy')
def __init__(self):
self.x = rnd.random(); self.y = rnd.random()
self.vx = rnd.random(); self.vy = rnd.random()
M = 300_000
aos_particles = [Particle() for _ in range(M)]
soa_x = [rnd.random() for _ in range(M)]
soa_y = [rnd.random() for _ in range(M)]
soa_vx = [rnd.random() for _ in range(M)]
soa_vy = [rnd.random() for _ in range(M)]
t0 = time.perf_counter()
acc = 0.0
for p in aos_particles:
p.x += p.vx; p.y += p.vy
acc += p.x
t_aos = time.perf_counter() - t0
t0 = time.perf_counter()
acc2 = 0.0
for i in range(M):
soa_x[i] += soa_vx[i]
soa_y[i] += soa_vy[i]
acc2 += soa_x[i]
t_soa = time.perf_counter() - t0
print(f'\nparticle integrate (M={M:,}, pure python):')
print(f' AoS objects : {t_aos*1000:8.1f} ms')
print(f' SoA arrays : {t_soa*1000:8.1f} ms gap {t_aos/t_soa:4.2f}x')প্রত্যাশিত output (pattern-focused; absolute numbers vary):
tiled matmul sweep (n=512):
tile ms GFLOPS
32 1850 0.145
64 1490 0.180
128 1620 0.166
512 2100 0.128
particle integrate (M=300,000, pure python):
AoS objects : 148.2 ms
SoA arrays : 96.5 ms gap 1.54xপড়ার বিষয়: Tile curve non-monotone — 64 sweet spot এই synthetic setup-এ; আসল machine-এ L1-size-derived optimum আসবে (build নিজের machine-এ repeat করুন)। T=512 (=untiled) worst — capacity misses dominate। Pure-Python-এ object attribute access-এর overhead SoA gap compress করে; numpy/C rewrite-এ gap বাড়ে।
নিজে বাড়ান:
১. Recursive (oblivious) matmul add করুন — base case threshold parameter remove করে pure subdivision; tiled-sweep-এর সাথে compare। ২. Transposed-B variant (B^T precompute) — ijk-কেও streaming বানায়; transpose cost amortization measure। ৩. numpy-তে একই sweep — BLAS-এর internal blocking detect করার indirect probe: performance cliff positions।
বাস্তব সিস্টেমে
Cache-aware engineering বাস্তবে
BLAS/LAPACK। OpenBLAS, Intel MKL — matmul kernels hand-tuned per microarchitecture: register blocking (microkernel 8×4), cache blocking, software prefetch — এই lesson-এর concepts-এর industrial apex। numpy তাদের ওপরেই বসে।
Game engines। ECS (Entity Component System) architectures SoA by design — data-oriented movement DOD community-তে mainstream করেছে; Unity DOTS/ECS documentation সরাসরি cache-line arguments cite করে।
Databases। Column stores (Parquet, ClickHouse) SoA philosophy; buffer-pool page sizing = blocking; B-tree node size ≈ cache-line multiples — balanced-bst লেসনের B-tree-র disk logic এখানে L1-এ বংশধর।
Networking (Level 7 preview)। DPDK packet processing: hugepage pools, per-core mcache, prefetch next descriptor while current processes — NIC pipeline-এর প্রতিটা ধাপ cache-contract-এ লেখা। Kernel bypass-এর half the win এখান থেকে।
Compilers। Loop-interchange, tiling passes (LLVM Polly), structure reordering (-freorder-fields in clang) — compiler-এর optimizer এই transformation-গুলো automate করার চেষ্টা করে; programmer hint still wins irregular cases।
যে ভুলগুলো সবাই করে
“Big-O better algorithm সবসময় faster — constant factor তো ছোট।”
Sorting লেসনের heapsort (Θ(n log n)) quicksort-কে ২-৪× হারে দেখেছেন; আজ matmul-এ same-Θ reorder 3×। Constants যখন memory-hierarchy derived, তখন 10-100× হয় (DRAM vs L1)। Correct hierarchy: complexity class আগে, তারপর access-pattern engineering — দুটোই mandatory।
“Cache-oblivious মানে cache ব্যবহার না করা।”
নামটা misleading: oblivious মানে hardware parameters code-তে hard-code না করা — কিন্তু recursive structure automatically সব level exploit করে। Proof-এ দেখাই হয় oblivious algorithm-ই প্রতিটা (M, B)-তে optimal miss count পায় — awareness built-in, tuning absent।
“Linked list O(1) insert — তাই dynamic array-র চেয়ে superior।”
Insert O(1) ঠিক, কিন্তু traversal/search O(n) প্রতি step DRAM-latency tax-এ (experiment: 3.4×)। বাস্তব workload-এ 99% traversal — vector + swap-remove প্রায় সবসময় জেতে end-to-end। Asymptotic operation-count আর wall-clock-এর ফারাকটাই এই lesson-এর thesis।
“Prefetcher সব fix করে দেয় — locality optimize করার দরকার নেই।”
Prefetcher stride-pattern শেখে — sequential/fixed-stride-তে excellent, pointer-chasing/tree-তে helpless (address itself unknown until load completes)। Random access pattern-এ prefetcher coverage ~0%। Layout আর traversal-order engineering তবু দরকার — prefetcher amplifier, substitute না।
বুঝেছেন কি না দেখুন
1L1 hit, L3 hit, DRAM access — approximate cycle costs? DRAM access-এর সময়ে CPU কত ALU op করতে পারত (4 GHz, 1 IPC)?
স্মরণ
L1 ~4, L3 ~40-60, DRAM ~100-300 cycles। DRAM 200-cycle ধরলে @4GHz = 50 ns; 1 IPC-তে 4 GHz = 4G ops/s → 200 cycles = 200 ops opportunity cost। Miss-heavy loop-এ CPU idle-dominant — memory-bound regime।
264-byte line, int32 array n=4096। Row-major 64×64 matrix: row-scan vs column-scan misses (cold start)? হিসাব দেখান।
প্রয়োগ
Row-scan: প্রতি row 64 ints = 4 lines; 64 rows → 256 misses total (sequential, perfect spatial use)। Column-scan: প্রতি step stride = 64 ints = 16 lines; প্রতি access নতুন line → 64×64 = 4096 misses — 16× worse, matching line-per-row factor। Ratio always = elements-per-line when stride crosses lines।
3Binary search array-তে theoretical log₂n misses; Eytzinger (BFS-order) layout কেন কমায়? Concept-level explanation।
যুক্তি
Classic in-order array-তে successive probes (midpoints) memory-তে দূরে-দূরে — প্রতি probe fresh line, prefetch useless (addresses input-dependent)। BFS layout-এ এক subtree-র nodes contiguous — top levels কয়েক line-এ, deeper subtrees আবার local; plus fixed probe pattern software-prefetch friendly। Result: misses ≈ log_B n (B = elements/line) — hierarchical locality both dimensions।
4Particle simulation: 90% updates need position+velocity, 10% render needs all 12 fields। Layout design + justification।
ডিজাইন
Hot path (update) SoA: px,py,pvx,pvy separate arrays — streaming, SIMD-ready, line-utilization high। Render path (rare) gathers via index list — gather cost acceptable at 10% frequency। Hybrid option: split hot/cold fields (position+velocity SoA; mass/type/lifetime cold AoS array indexed by id) — ‘hot/cold splitting’ standard technique। Decision driver: frequency-weighted line utilization, measured not guessed।
5Cache-oblivious algorithm-এর defining property? I/O model-এ (M, B) কী?
স্মরণ
Property: algorithm-এ M বা B-র কোনো mention/reference নেই — কিন্তু analysis prove করে miss-optimal for ALL (M,B) simultaneously। I/O model: M = fast-memory (cache) size, B = transfer block (line) size; cost metric = block transfers between slow/fast memory, computation free। Ideal-cache model adds auto-replacement assumption (offline-optimal paging)।
6Module retrospective: sorting (lesson 17) থেকে আজ পর্যন্ত — কোন তিনটা lesson-এ memory-hierarchy reality সরাসরি verdict দিয়েছে? এক লাইন করে।
যুক্তি
Lesson 17 sorting: heapsort-এর stride-jump cache-miss quicksort-কে ২-৪× হারায় despite same-Θ। Lesson 22 string: Boyer-Moore/Horspool skip = locality exploitation; libc hybrids cache-tuned। Lesson 24 today: layout/ loop-order/tiling — whole lesson। Undercurrent: asymptotics picks family, silicon picks winner।
এরপর কী
এরপর কী — module শেষ, journey চলছে
Algorithms module-এর ২৪-এর ল্যাডার এখানে শেষ: induction-এ শুরু, cache line-এ সমাপ্তি। আমরা দেখলাম algorithm design-এর প্রতিটা major paradigm — divide-conquer, DP, greedy, randomization — আর তাদের প্রত্যেকের সাথে silicon-এর সত্য কীভাবে negotiate করে। এই toolkit দিয়ে আপনি interview-এর puzzle না, production system-এর trade-off পড়তে পারবেন।
Level 7 খুলছে Networking নিয়ে: packet কীভাবে NIC থেকে browser-এ যায় — Ethernet frame, IP routing, TCP reliability, congestion control। আজকের DPDK-mention সেখানে পূর্ণ story হবে; hash-tables-এর knowledge flow-table-এ, আজকের locality-discipline packet-batch processing-এ। Hardware-cost awareness এখন আপনার reflex — network protocol-গুলো ঠিক সেই reflex-ই চায়।
আরও পড়ুন
- Introduction to High Performance Computing for Scientists and Engineers, Chapter 1-3 — Georg Hager, Gerhard Wellein · Loop optimization, tiling, roofline model — lesson 24-এর matmul analysis-এর মূল উৎস
- Computer Architecture: A Quantitative Approach, Appendix B & Chapter 2 — John L. Hennessy, David A. Patterson · Memory hierarchy design আর cache performance metrics-এর প্রামাণ্য আলোচনা
- Cache-Oblivious Algorithms (FOCS '99) — Matteo Frigo, Charles Leiserson, Harald Prokop, Sridhar Ramachandran · Cache-oblivious model-এর original paper — van Emde Boas layout concept-এর জন্মস্থান