Microarchitecture Overview — একই ISA, ভিন্ন ভিন্ন ভেতরের গল্প
Microarchitecture Overview: Frontend, Backend, Retirement
Microarchitecture হলো একটা ISA-র নির্দিষ্ট হার্ডওয়্যার বাস্তবায়ন — frontend (fetch, decode, branch prediction), backend (out-of-order execution engine), আর retirement (ROB-এর program-order commit), এই তিনটা নামকরণকৃত অংশ, যাদের ভেতরের ডিজাইন সিদ্ধান্তই ঠিক করে দেয় একই কোড কেন একেক CPU-তে একেক গতিতে চলে।
আগে এটা বুঝি
এই module-এর (Level ১১, Advanced Architecture ও Performance) driving question একটাই বাক্যে ধরা যায়:
একই algorithm, একই complexity — তবু একটা ১০ গুণ দ্রুত কেন?
লক্ষ্য করুন প্রশ্নটা কী জিজ্ঞেস করছে না। এটা জিজ্ঞেস করছে না “কোড কি সঠিক” (algorithms module-এর প্রশ্ন), আর জিজ্ঞেস করছে না “এর asymptotic complexity কী” (mathematics/asymptotic-notation-এর প্রশ্ন)। দুইটা প্রোগ্রামের ধরে নেওয়া হচ্ছে দুইটাই সঠিক, দুইটারই একই , তবু বাস্তবে একটা মেশিনের ঘড়িতে দশগুণ পার্থক্য। computer-architecture module পুরোটাই উত্তর দিয়েছে “কীভাবে একটা CPU কাজ করে” — fetch-decode-execute, pipelining, hazard, branch prediction, superscalar, out-of-order, cache organization। এই সবকিছুই সত্যি, কিন্তু এই module-এর curriculum entry নিজেই যা বলছে তার সারমর্ম:
“এখানে আমরা ‘কাজ করে’ থেকে ‘কত দ্রুত এবং কেন’ -তে যাই। Cache coherence protocol, memory ordering, false sharing, SIMD, GPU-র SIMT model — আর সবচেয়ে গুরুত্বপূর্ণ: সঠিকভাবে measure করতে শেখা।”
অর্থাৎ, computer-architecture module একটা single-core CPU-র ভেতরের যুক্তি ব্যাখ্যা করেছে যেন সেটা একা, বিচ্ছিন্নভাবে কাজ করছে। বাস্তব CPU-তে কয়েকটা জিনিস সেই সরল ছবিকে জটিল করে তোলে — একাধিক core একই memory শেয়ার করে (cache coherence-এর জন্ম, লেসন ৩), cache-এর multiple level-এর মধ্যে সম্পর্ক (লেসন ২), memory-এর মধ্যে ordering-এর প্রশ্ন, আর হার্ডওয়্যার parallelism-এর একাধিক রূপ (SIMD, GPU)। এই পুরো module সেই জটিলতাগুলো একে একে খুলবে।
কিন্তু শুরুর আগে একটা মৌলিক শব্দ পরিষ্কার করা দরকার, যেটা এই পুরো module-এর নাম হয়ে আছে — microarchitecture। আজকের লেসন এই শব্দটার সংজ্ঞা দেবে, আর সবচেয়ে গুরুত্বপূর্ণভাবে, একটা মানচিত্র আঁকবে — একটা আধুনিক CPU-র ভেতরটাকে তিনটা নামকরণকৃত অংশে ভাগ করে, যে ভাগটা এই পুরো module জুড়ে বারবার ফিরে আসবে।
মূল ধারণা
Microarchitecture বনাম ISA — একটা চুক্তি বনাম তার হাজারো সম্ভাব্য বাস্তবায়ন
computer-architecture/what-is-an-isa লেসনে আমরা দেখেছি একটা ISA (Instruction Set Architecture) হলো একটা চুক্তি — CPU আর সফটওয়্যারের মধ্যে একটা সম্মত ইন্টারফেস, যা বলে দেয় কোন instruction-এর অর্থ কী, register-গুলো কী, memory model কেমন। ISA কখনো বলে না CPU-র ভেতরে কীভাবে এই চুক্তি বাস্তবায়িত হবে — কয়টা ALU থাকবে, pipeline কত গভীর, branch predictor কেমন, cache কত বড়।
সেই “কীভাবে বাস্তবায়িত হবে”-র নামই microarchitecture (সংক্ষেপে প্রায়ই লেখা হয় “µarch”)।
একইভাবে Apple M-সিরিজ (ARM64 ISA) আর একটা ARM Cortex-A55 (একই ARM64 ISA, ভিন্ন microarchitecture, phone-এর “little” core) — একই instruction set, সম্পূর্ণ ভিন্ন performance profile। ISA স্থির থাকে বছরের পর বছর (backward compatibility-র জন্য), কিন্তু microarchitecture প্রতি ১-২ বছরে নতুন সংস্করণ আসে (Skylake → Ice Lake → Golden Cove → Redwood Cove…) — প্রতিবার একই পুরনো চুক্তি মেনে, ভেতরের বাস্তবায়ন নতুন করে ডিজাইন করে।
computer-architecture module-এ যা শিখেছেন — pipelining, hazard, branch prediction, superscalar, out-of-order execution, cache organization — এই প্রতিটাই আসলে microarchitecture-এর একেকটা কৌশল। কোনোটাই ISA বাধ্য করে না; একটা ইচ্ছা করলে in-order, non-pipelined, cache-বিহীন একটা x86-64 CPU-ও বানাতে পারত (ভয়ংকর ধীর হতো, কিন্তু ISA-র প্রতি সৎ থাকত)। Microarchitecture হলো ঠিক সেই স্বাধীনতার জায়গা যেখানে ইঞ্জিনিয়াররা প্রতিযোগিতা করেন — কে একই কাজ দ্রুততর হার্ডওয়্যার দিয়ে করতে পারে।
Codename — microarchitecture-এর নিজস্ব “সংস্করণ নম্বর”
যেহেতু microarchitecture ISA-র চেয়ে অনেক দ্রুত বদলায়, ইন্ডাস্ট্রি প্রতিটা নতুন microarchitecture-কে একটা আলাদা codename দেয় — ISA-র নাম (x86-64, ARM64) বছরের পর বছর একই থেকে যায়, কিন্তু codename প্রতিবার বদলায়। এই পার্থক্যটা concrete দেখার একটা সহজ উপায়:
| কোম্পানি | ISA (স্থির) | সাম্প্রতিক কয়েকটা microarchitecture codename (বদলায়) |
|---|---|---|
| Intel | x86-64 | Skylake → Ice Lake → Alder Lake (Golden Cove + Gracemont) → Raptor Cove → Redwood Cove |
| AMD | x86-64 | Zen → Zen 2 → Zen 3 → Zen 4 → Zen 5 |
| Apple | ARM64 | Firestorm/Icestorm (M1) → Avalanche/Blizzard (M2) → Everest/Sawtooth (M3) |
| ARM (licensable core) | ARM64 | Cortex-A76 → Cortex-X1 → Cortex-X4 |
তিনটা নাম যা এই পুরো module জুড়ে ফিরে আসবে
আধুনিক যেকোনো উচ্চ-performance, superscalar, out-of-order CPU-র ভেতরটাকে — Intel, AMD, Apple, বা আধুনিক out-of-order RISC-V ডিজাইন, প্রায় সবাই — তিনটা বড়, নামকরণকৃত অংশে ভাগ করে বর্ণনা করা হয়। এই তিনটা নাম শুধু এই লেসনের না — Intel-এর নিজস্ব optimization manual, AMD-র software optimization guide, আর industry-র profiling টুল (একটু পরেই দেখব) সবাই ঠিক এই তিনটা নামই ব্যবহার করে।
১. Frontend — instruction “জোগাড়” করা
Frontend-এর দায়িত্ব: memory থেকে instruction আনা, বুঝে নেওয়া (decode), আর পরবর্তী কোন instruction আনতে হবে তার সিদ্ধান্ত নেওয়া (branch prediction)। এটা ঠিক computer-architecture/pipelining-and-hazards লেসনের IF (Instruction Fetch) আর ID (Instruction Decode) স্টেজের সরাসরি সম্প্রসারণ, প্লাস computer-architecture/branch-prediction লেসনের পুরো যন্ত্র — কোন instruction পরে আসবে তার আগাম অনুমান, যাতে fetch থেমে না থাকে।
Frontend-এর কাজ superscalar CPU-তে জটিল হয়ে ওঠে কারণ একই cycle-এ একাধিক instruction fetch/decode করতে হয় (computer-architecture/superscalar-and-out-of-order লেসনের 2-wide উদাহরণের সরাসরি সম্প্রসারণ, আধুনিক CPU-তে ৪-৮ wide পর্যন্ত)। CISC ISA-তে (x86-64-এর মতো, computer-architecture/risc-vs-cisc লেসনের আলোচনা মনে করুন) instruction-গুলোর দৈর্ঘ্য বিভিন্ন — decode স্টেজকে তাই আগে বুঝতে হয় প্রতিটা instruction ঠিক কত বাইট লম্বা, তারপর তাকে ভেঙে এক বা একাধিক সরল, স্থির-দৈর্ঘ্যের µop (micro-operation, প্রায় RISC-এর মতো একটা অভ্যন্তরীণ ফরম্যাট)-এ রূপান্তর করতে হয় — backend সেই µop-গুলো নিয়েই কাজ করে, আসল CISC instruction নিয়ে না।
২. Backend — আসল কাজ, যেখানে-খুশি-ক্রমে
Backend (একে “execution engine”ও বলা হয়) হলো সেই অংশ যেখানে µop-গুলো আসলে execute হয় — computer-architecture/superscalar-and-out-of-order লেসনের পুরো আলোচনা (dependency-ভিত্তিক readiness, out-of-order issue) আর computer-architecture/register-renaming-reorder-buffer লেসনের register renaming (WAR/WAW ভাঙা) সরাসরি এখানেই ঘটে। Backend-এর ভেতরে থাকে একাধিক execution port — প্রতিটা port একটা নির্দিষ্ট ধরনের functional unit-এর (ALU, load unit, store unit, floating-point unit) সাথে যুক্ত, আর একই cycle-এ বিভিন্ন port-এ বিভিন্ন µop সমান্তরালে execute হতে পারে, dependency-অনুমোদিত যেকোনো ক্রমে।
Backend-এর একটা কেন্দ্রীয় সম্পদ হলো instruction window (বা out-of-order window) — কতগুলো µop একসাথে “in-flight” (fetch হয়ে গেছে, এখনও commit হয়নি) থাকতে পারে, সেই সীমা। এই সীমাটাই বলে দেয় backend কত দূর ভবিষ্যতে তাকিয়ে independent কাজ খুঁজে বের করতে পারে, বিশেষত একটা ধীর operation (cache miss) সামনে পড়লে।
৩. Retirement — কড়া নিয়মে, লেখা ক্রমেই ফিরে আসা
Retirement (একে “commit”ও বলা হয়, computer-architecture/register-renaming-reorder-buffer লেসনে এই নামেই আলোচিত) হলো সেই stage যেখানে backend-এ যা-ইচ্ছা-ক্রমে সম্পন্ন হওয়া কাজ আবার program order-এ সাজিয়ে দৃশ্যমান করা হয়। Reorder Buffer (ROB) — একটা circular buffer, প্রতিটা µop-এর জন্য program-order-এ একটা entry — এই কাজের কেন্দ্রীয় হার্ডওয়্যার। প্রতি cycle-এ ROB-এর মাথা (head) থেকে যদি একটা µop সম্পন্ন (completed) থাকে, সেটা “retire” হয় — architectural state (register file, memory) আপডেট হয়, ROB থেকে বিদায় নেয়। মাথা যদি এখনও অসম্পূর্ণ থাকে, কেউ retire করতে পারে না, backend যতই বাকি সব µop আগেই শেষ করে ফেলুক না কেন।
এই কড়া, in-order retirement-ই superscalar-and-out-of-order লেসনের সবচেয়ে গুরুত্বপূর্ণ misconception-খণ্ডনের ভিত্তি — out-of-order execution ভেতরে যতই এলোমেলো হোক, বাইরে থেকে সব সময় program-order-এর মতোই “দেখায়”। Retirement stage-ই সেই দৃশ্যমান গ্যারান্টির বাস্তবায়ন।
┌─────────────────────┐ ┌──────────────────────────────┐ ┌─────────────────────┐
│ FRONTEND │ │ BACKEND │ │ RETIREMENT │
│ ─────────────────────│ │ ──────────────────────────── │ │ ─────────────────── │
│ fetch (একাধিক/cycle) │──►│ rename, dispatch, issue queue │──►│ ROB head → tail │
│ decode → µop │ │ execution ports (ALU/load/ │ │ program-order commit │
│ branch prediction │ │ store/FP), out-of-order │ │ precise exception │
│ │ │ issue (readiness-ভিত্তিক) │ │ │
└─────────────────────┘ └──────────────────────────────┘ └─────────────────────┘
pipelining-and-hazards superscalar-and-out-of-order register-renaming-
branch-prediction register-renaming-reorder-buffer reorder-buffer
↑ ↑ ↑
এই module-এর branch prediction এই module-এর cache hierarchy, coherence-এর লেখা/
ও speculative execution লেসন coherence, false sharing, পড়া এখানেই final হয়
এখানে আরও গভীরে যাবে NUMA, SIMD সব এখানে বসেপুরো module-এর রোডম্যাপ — প্রতিটা topic কোন অংশে বসে
এই module-এর curriculum-এ ১৭টা topic তালিকাভুক্ত আছে। এখনই, প্রথম লেসনে, প্রতিটা topic-কে frontend/backend/retirement মানচিত্রে বসিয়ে দিলে বাকি module জুড়ে “আমরা এখন কোথায় আছি” প্রশ্নের উত্তর সবসময় হাতের কাছে থাকবে।
| Topic | মূলত কোন অংশে | সংক্ষিপ্ত কারণ |
|---|---|---|
| Microarchitecture — frontend/backend/retirement | (এই লেসন) | পুরো মানচিত্র |
| Cache hierarchy, inclusive/exclusive | Backend | memory subsystem, load/store unit যা ব্যবহার করে |
| Cache coherence — MESI, MOESI | Backend | multi-core memory subsystem-এর সমন্বয় |
| False sharing | Backend (cross-cutting bug) | coherence-এর দাম যখন বাগে পরিণত হয় |
| NUMA topology ও memory affinity | Backend | multi-socket memory subsystem-এর দূরত্ব-নির্ভরতা |
| Memory consistency models ও memory barriers | Backend + Retirement | একাধিক address জুড়ে অর্ডারিং গ্যারান্টি |
| Atomics ও lock-free programming | Backend | synchronization primitive, coherence-এর উপর নির্মিত |
| Branch prediction ও speculative execution | Frontend | পরের instruction অনুমান, branch-prediction-এর গভীরতর সংস্করণ |
| Prefetching | Frontend/Backend সীমানা | memory access আগাম অনুমান |
| SIMD programming | Backend | execution unit-এর width বাড়ানো |
| GPU architecture — SM, warp, SIMT | Backend (ভিন্ন প্যারাডাইম) | সম্পূর্ণ ভিন্ন backend গঠন |
| CUDA/compute programming model | Backend (software মডেল) | GPU backend-এর জন্য প্রোগ্রামিং interface |
| Parallel patterns — map, reduce, scan | Cross-cutting | backend parallelism ব্যবহারের সফটওয়্যার প্যাটার্ন |
| Amdahl ও Gustafson | Cross-cutting (তত্ত্ব) | parallelism-এর তাত্ত্বিক সীমা |
| Roofline model | Cross-cutting (analysis) | compute বনাম memory bandwidth সীমা একসাথে |
| Profiling — perf, flamegraph, PMU counters | Cross-cutting (measurement) | TMA-এর মতো, তিন অংশ জুড়েই পরিমাপ |
| Benchmarking methodology ও statistical rigor | Cross-cutting (discipline) | driving question-এর “কীভাবে সঠিকভাবে measure করব” |
লক্ষ্য করুন — বেশিরভাগ topic backend-এ কেন্দ্রীভূত (cache, coherence, NUMA, SIMD, GPU) — এটা কাকতালীয় না। Frontend (fetch/decode/branch-prediction) computer-architecture module-এই বিস্তারিত আলোচিত হয়েছে; এই module মূলত সেই ভিত্তির উপর দাঁড়িয়ে backend-এর memory ও parallelism-সম্পর্কিত জটিলতা নিয়ে গভীরে যায় — ঠিক driving question-এর “একই algorithm, একই complexity, তবু ১০ গুণ দ্রুত কেন” প্রশ্নের বেশিরভাগ বাস্তব উত্তরই backend-এ (memory hierarchy, cache coherence, parallelism দক্ষতা) লুকিয়ে থাকে, শুধু raw compute speed-এ না।
ভেতরে কী ঘটছে
ভেতরে কী ঘটছে — একটা বাস্তব pipeline-এ তিন অংশের পূর্ণ পথ
pipelining-and-hazards লেসনে আমরা একটা সরল ৫-স্টেজ (IF-ID-EX-MEM-WB) pipeline দেখেছিলাম — একটা classic teaching মডেল (MIPS/DLX-ধাঁচের)। বাস্তব আধুনিক CPU-র pipeline অনেক গভীর, কারণ প্রতিটা কাজ (fetch, decode, rename, issue, execute, writeback, retire) নিজেই একাধিক sub-stage-এ ভাগ হয়ে যায় যাতে প্রতিটা stage-এর কাজ যথেষ্ট ছোট থাকে উচ্চ clock speed বজায় রাখতে। একটা আনুমানিক, বাস্তবসম্মত ধারণা (নির্দিষ্ট সংখ্যা প্রজন্মভেদে বদলায়, তাই hedge করা জরুরি):
| আমাদের তিন ভাগ | আনুমানিক বাস্তব sub-stage | মূল কাজ |
|---|---|---|
| Frontend | branch predict → fetch (I-cache থেকে) → pre-decode (instruction-এর দৈর্ঘ্য বের করা, CISC-এ জরুরি) → decode → µop cache/queue | instruction “জোগাড়” ও µop-এ রূপান্তর |
| Backend | rename (register renaming) → dispatch (reservation station/issue queue-তে বসানো) → issue (readiness অনুযায়ী, out-of-order) → execute (execution port-এ) → writeback | আসল গণনা, dependency মেনে যেকোনো ক্রমে |
| Retirement | complete (ROB entry-তে ফলাফল চিহ্নিত) → commit (ROB head থেকে, কড়াভাবে in-order) → architectural state আপডেট | ফলাফলকে program-order-এ “সত্য” করে তোলা |
একটা আধুনিক high-performance x86 core-এ (যেমন Intel Skylake-গোত্রের ডিজাইন) মোট pipeline গভীরতা আনুমানিক ১৪-১৯ stage — classic ৫-stage মডেলের প্রায় ৩-৪ গুণ, ঠিক এই কারণেই যে প্রতিটা বড় ধাপ (fetch, decode, rename, ইত্যাদি) নিজেই একাধিক sub-stage-এ বিভক্ত।
একটা instruction-এর সম্পূর্ণ যাত্রা
ধরুন ADD R1, R2, R3 instruction-টা আসছে। ধাপে ধাপে তিন অংশের মধ্য দিয়ে তার যাত্রা:
- FRONTEND: fetchI-cache থেকে instruction bytes আনা হয়, branch predictor পরবর্তী fetch address ঠিক করে
- FRONTEND: decodeADD R1, R2, R3 → একটা সরল µop-এ রূপান্তরিত (x86-এ প্রায়ই ১:১, জটিল CISC instruction-এ একাধিক µop হতে পারে)
- BACKEND: renameR1, R2, R3 — architectural register — physical register-এ rename হয় (register-renaming-reorder-buffer লেসনের মূল প্রক্রিয়া)
- BACKEND: dispatch + issue queueROB-এ program-order entry তৈরি হয়; issue queue-তে operand ready হওয়ার অপেক্ষা (superscalar-and-out-of-order-এর scoreboard-যুক্তি)
- BACKEND: executeR2, R3-এর মান ready হলে, একটা ALU port-এ যোগফল গণনা হয় — dependency মেনে, যেকোনো cycle-এ
- BACKEND: writebackফলাফল physical register file-এ লেখা হয়, ROB entry "সম্পন্ন" চিহ্নিত হয়
- RETIREMENT: commitROB-এর মাথায় পৌঁছালে (এই instruction-এর আগের সব commit হলে), architectural register R1 আপডেট হয় — program order-এ
লক্ষ্য করুন — frontend আর retirement সবসময় program order মেনে চলে (fetch যে ক্রমে instruction পড়ে, retire ঠিক সেই ক্রমেই ফলাফল প্রকাশ করে) — শুধু মাঝখানের backend-এ ক্রম ভাঙা হয়, dependency-এর সীমার মধ্যে। এই “sandwich” গঠন — in-order বাইরে, out-of-order ভেতরে — প্রতিটা আধুনিক high-performance CPU-র কেন্দ্রীয় নকশা-নীতি।
Frontend আর backend-এর মধ্যে buffer — কেন দরকার
Frontend আর backend আলাদা গতিতে চলতে পারে — frontend হয়তো এক cycle-এ ৬টা µop decode করল, কিন্তু backend সেই cycle-এ dependency-এর কারণে মাত্র ২টা issue করতে পারল। এই গতির পার্থক্য শোষণ করতে থাকে একটা µop queue (বা issue queue-এর সামনের বাফার) — frontend দ্রুত এগোলে এখানে জমা হয়, backend ধীর হলে এখান থেকে টেনে নেয়। এই বাফার পূর্ণ হয়ে গেলে frontend stall করে — এটাই পরে “frontend bound বনাম backend bound” আলোচনার ভিত্তি (experiment অংশে বিস্তারিত)।
µop cache — frontend-এর নিজের একটা অপ্টিমাইজেশন
CISC decode (আগের অংশে উল্লেখিত, variable-length instruction ভেঙে µop বানানো) নিজেই একটা ব্যয়বহুল কাজ — প্রতিটা instruction-এর দৈর্ঘ্য বের করা, তারপর ভাঙা, শক্তি ও cycle দুটোই খরচ করে। একটা tight loop (যেমন একটা ছোট, বারবার চলা for loop) হাজার হাজার বার একই কয়েকটা instruction re-fetch, re-decode করতে পারে — একই কাজ বারবার করা, সম্পূর্ণ অপচয়।
আধুনিক Intel/AMD ডিজাইনে এর সমাধান একটা µop cache (Intel-এর পরিভাষায় “Decoded Stream Buffer” বা DSB) — একটা ছোট, দ্রুত cache যা ইতিমধ্যে decode হওয়া µop ধরে রাখে। যদি একটা loop-এর instruction-গুলো এই cache-এ থাকে (hit), frontend সম্পূর্ণ heavy decode ধাপ বাইপাস করে সরাসরি µop cache থেকে backend-এ পাঠাতে পারে — কম latency, কম power, আর গুরুত্বপূর্ণভাবে branch predictor-এর ভুলের সম্ভাবনাও কম (একটা hot loop-এর ভেতরের branch pattern অনেক বেশি predictable, branch-prediction লেসনের ধারণার সরাসরি সংযোগ)।
এটা computer-architecture/risc-vs-cisc লেসনের একটা পুরনো সমালোচনার (CISC decode জটিল ও ব্যয়বহুল, RISC-এর তুলনায়) বিরুদ্ধে আধুনিক x86 ডিজাইনের একটা বাস্তব পাল্টা-উত্তর — decode-এর দাম একবার দিয়ে, ফলাফল cache করে বারবার ব্যবহার করে সেই দামটাকে amortize করা।
উদাহরণ
সংখ্যায় — বাস্তব CPU-র width আর window কত বড়
তিনটা অংশের “আকার” আসলে কী বোঝায় সেটা concrete সংখ্যা ছাড়া অস্পষ্ট থেকে যায়। নিচের সংখ্যাগুলো আনুমানিক ও প্রকাশ্য প্রযুক্তিগত ডকুমেন্টেশন/প্রকৌশল-বিশ্লেষণ থেকে সাধারণীকৃত — নির্দিষ্ট চিপ revision-এ কিছুটা ভিন্ন হতে পারে, তাই “প্রায়” (approximately) শব্দটা সবসময় ধরে নিন।
| CPU (microarchitecture) | Frontend decode/fetch width (আনুমানিক) | Backend execution port সংখ্যা (আনুমানিক) | Retirement/ROB আকার (আনুমানিক) |
|---|---|---|---|
| Intel Skylake (2015) | ~৪-wide decode | ৮ port | ২২৪ entry |
| Intel Golden Cove (2021) | ~৬-wide decode | ১২ port | ৫১২ entry |
| AMD Zen 4 (2022) | ~৪-wide decode (+ µop cache path) | ১০ port | ৩২০ entry |
| Apple M1 (2020) | ~৮-wide decode | ~১৩ port | ৬৩০ entry |
(ROB সংখ্যাগুলো register-renaming-reorder-buffer লেসনের টেবিলের সাথে সামঞ্জস্যপূর্ণ — সেই লেসনে দেখানো হয়েছিল কীভাবে বড় ROB মানে CPU একটা ধীর load-কে “পাশ কাটিয়ে” আরও দূরে independent কাজ খুঁজে বের করতে পারে।)
পর্যবেক্ষণ: Apple M1-এর decode width (৮) সবচেয়ে বেশি হলেও, এটা একা কিছু বলে না — যদি backend-এ যথেষ্ট port না থাকত, বা ROB যথেষ্ট বড় না হতো, ৮-wide frontend প্রায়ই backend-এ আটকে থাকত (buffer পূর্ণ, frontend stall)। M1-এর ডিজাইন তিনটা সংখ্যাই — frontend, backend, retirement — একসাথে বড় রেখেছে, যা এর তখনকার অস্বাভাবিক ভালো single-thread performance-এর একটা বড় প্রকৌশলগত কারণ ছিল। তিনটা অংশ একসাথে সুষম (balanced) না হলে, একটা অংশ একা বড় করা অপচয় — এটাই এই লেসনের সবচেয়ে গুরুত্বপূর্ণ ডিজাইন-নীতি, যা misconception অংশে আবার আসবে।
একটা numeric হিসাব — pipeline গভীরতা বনাম misprediction penalty
Frontend-এর গভীরতা (কতগুলো sub-stage fetch থেকে execute পর্যন্ত) সরাসরি প্রভাব ফেলে branch misprediction-এর দাম-এ (branch-prediction লেসনের সরাসরি প্রাসঙ্গিকতা) — misprediction ধরা পড়লে pipeline-এ থাকা ভুল পথের সব instruction বাতিল (flush) করতে হয়, আর ঠিক পথ থেকে নতুন করে fetch শুরু করতে হয়:
একটা ঐতিহাসিক চরম উদাহরণ (realworld অংশে বিস্তারিত) — Pentium 4 (NetBurst microarchitecture, ২০০০)-এর frontend এত গভীর ছিল (২০-৩১ stage) যে একটা misprediction-এর দাম হতে পারত ২০+ cycle — অথচ একটা ছোট, ৫-stage classic pipeline-এ সেই দাম মাত্র ১-২ cycle। এটাই দেখায় কেন “গভীর pipeline = বেশি clock speed = ভালো” এই সরল যুক্তি সবসময় সত্যি না; গভীরতার একটা লুকানো দাম আছে, যেটা branch prediction-এর মান কতটা ভালো তার উপর নির্ভর করে।
নিজে চালিয়ে দেখুন
নিজের CPU-তে Top-Down Microarchitecture Analysis (TMA) চালিয়ে frontend/backend/retirement সরাসরি দেখুন
Intel-এর Top-Down Microarchitecture Analysis (TMA) পদ্ধতি (Ahmad Yasin, ২০১৪) প্রতিটা CPU cycle-কে ঠিক চারটা বিভাগের একটাতে ফেলে — আর প্রথম তিনটা এই লেসনের তিন অংশের সাথে হুবহু মেলে:
- Frontend Bound — frontend যথেষ্ট দ্রুত µop সরবরাহ করতে পারছে না (I-cache miss, decode জটিলতা)
- Bad Speculation — branch misprediction-এর কারণে বাতিল হওয়া কাজ (frontend-এর branch prediction ব্যর্থতার backend-ভাগে প্রভাব)
- Backend Bound — backend-এর execution port বা মেমরি অপেক্ষায় আটকে থাকা (পরের দুই লেসনের cache/coherence সমস্যা ঠিক এখানে ধরা পড়ে)
- Retiring — প্রকৃত, কার্যকর কাজ — cycle-গুলো আসলে useful কাজে ব্যয় হচ্ছে
perf stat -a -e task-clock,cycles,instructions,branches,branch-misses ./your_programদ্রুত IPC (Instructions Per Cycle) হিসাব:
IPC = instructions / cyclesযদি আপনার perf-এ --topdown অপশন থাকে (আধুনিক Linux perf, Intel CPU-তে):
perf stat --topdown ./your_programএই একটা কমান্ড সরাসরি আপনার প্রোগ্রামের cycle-গুলো Frontend Bound / Bad Speculation / Backend Bound / Retiring-এ ভেঙে দেখাবে — এই লেসনের তিন-অংশের মডেল একটা teaching abstraction না, এটা বাস্তব ইঞ্জিনিয়ারিং ভাষা।
প্রত্যাশিত ফলাফলের ধরন (একটা সাধারণ CPU-bound লুপ প্রোগ্রামে):
IPC: 1.85
Frontend Bound: 8.2%
Bad Speculation: 3.1%
Backend Bound: 22.4%
Retiring: 66.3%যদি Backend Bound উচ্চ হয় (যেমন ২০%+), সন্দেহ করুন cache miss বা execution port contention — ঠিক এই module-এর পরের দুই লেসনের বিষয়। যদি Frontend Bound উচ্চ হয়, সন্দেহ করুন I-cache miss বা খুব জটিল decode pattern। যদি Bad Speculation উচ্চ হয়, সন্দেহ করুন unpredictable branch — পরের branch-prediction-সম্পর্কিত লেসনগুলোর বিষয়।
Intel-এর নিজস্ব, শিল্প-প্রমিত profiling পদ্ধতি (TMA) ঠিক এই লেসনের frontend/backend/retirement বিভাজনের উপর দাঁড়িয়ে — এটা কোনো teaching-এর জন্য বানানো সরলীকরণ না, বাস্তব ইঞ্জিনিয়ারদের রোজকার টুল।
নিজে বানান
একটা তিন-অংশের (Frontend/Backend/Retirement) Pipeline Simulator
- superscalar-and-out-of-order লেসনের dependency-finder ও latency মডেল পুনর্ব্যবহার করুন
- একটা FRONTEND ধাপ বানান — প্রতি cycle-এ সর্বোচ্চ N-টা instruction fetch+decode করে একটা limited-size µop queue-তে রাখে; queue পূর্ণ হলে frontend stall করে
- একটা BACKEND ধাপ বানান — µop queue থেকে টেনে, dependency-readiness অনুযায়ী M-টা execution port-এ out-of-order issue করে (superscalar-and-out-of-order-এর simulator-এর সরাসরি সম্প্রসারণ)
- একটা RETIREMENT ধাপ বানান — একটা fixed-size ROB-এ program-order-এ entry রাখুন, প্রতি cycle-এ ROB head থেকে যতগুলো টানা সম্পন্ন আছে ততগুলো (সর্বোচ্চ R সংখ্যা পর্যন্ত) commit করুন
- প্রতি cycle গণনা করুন CPU কোন অবস্থায় ছিল — frontend-stalled (queue পূর্ণ), backend-busy, বা retiring — আর শেষে একটা সরল টপ-ডাউন শতাংশ ছাপান
- ROB আকার ছোট (যেমন ৮) বনাম বড় (যেমন ১২৮) করে একই instruction sequence-এ মোট cycle-এর পার্থক্য পরিমাপ করুন
এই simulator-টা আগের লেসনের out-of-order simulator-কে ঠিক এই লেসনের তিন-অংশের কাঠামোতে সংগঠিত করে — একটা limited-size queue frontend আর backend-এর মধ্যে, একটা fixed-size ROB backend আর retirement-এর মধ্যে। ROB আকার ছোট করলে দেখবেন CPU দ্রুত “ROB পূর্ণ” অবস্থায় আটকে যায়, এমনকি backend-এ প্রচুর independent কাজ থাকলেও — ঠিক এই কারণেই বাস্তব CPU-গুলো ROB আকার প্রজন্মে প্রজন্মে বাড়িয়ে চলেছে (উপরের টেবিলের ২২৪ → ৫১২ → ৬৩০ প্রবণতা)।
class MicroOp:
def __init__(self, idx, srcs, dst, latency):
self.idx = idx
self.srcs = srcs
self.dst = dst
self.latency = latency
self.issued_at = None
self.done_at = None
self.retired_at = None
def simulate(uops, frontend_width=4, backend_ports=4, rob_size=32, retire_width=4):
cycle = 0
fetch_ptr = 0
uop_queue = [] # frontend থেকে backend-এর দিকে
rob = [] # program-order, backend থেকে retirement-এর দিকে
ready_values = {} # register -> সম্পন্ন হওয়ার cycle
in_flight = {} # port ব্যস্ত থাকার হিসাব
stats = {"frontend_stall": 0, "backend_busy": 0, "retiring": 0}
while any(u.retired_at is None for u in uops):
cycle += 1
# FRONTEND: fetch+decode, queue পূর্ণ না হলে
fetched_this_cycle = 0
while (fetch_ptr \< len(uops) and fetched_this_cycle \< frontend_width
and len(uop_queue) - len(rob) \< rob_size):
uop_queue.append(uops[fetch_ptr])
rob.append(uops[fetch_ptr])
fetch_ptr += 1
fetched_this_cycle += 1
if fetched_this_cycle == 0 and fetch_ptr \< len(uops):
stats["frontend_stall"] += 1
# BACKEND: readiness অনুযায়ী issue, port সীমার মধ্যে
issued_this_cycle = 0
for u in uop_queue:
if u.issued_at is not None or issued_this_cycle \>= backend_ports:
continue
if all(ready_values.get(s, -1) \< cycle for s in u.srcs if s is not None):
u.issued_at = cycle
u.done_at = cycle + u.latency
ready_values[u.dst] = u.done_at
issued_this_cycle += 1
if issued_this_cycle > 0:
stats["backend_busy"] += 1
# RETIREMENT: ROB head থেকে, program order, সম্পন্ন হলে
retired_this_cycle = 0
while (rob and rob[0].done_at is not None and rob[0].done_at \<= cycle
and retired_this_cycle \< retire_width):
rob[0].retired_at = cycle
rob.pop(0)
retired_this_cycle += 1
if retired_this_cycle > 0:
stats["retiring"] += 1
return cycle, statsছোট ROB (৮) আর বড় ROB (১২৮) দিয়ে একটা ভারী LOAD-সহ sequence চালিয়ে মোট cycle তুলনা করুন — এই লেসনের “তিনটা অংশ একসাথে সুষম না হলে অপচয়” দাবিটা এখানে নিজের চোখে সংখ্যায় দেখবেন।
বাস্তব সিস্টেমে
Microarchitecture বাস্তব ইতিহাসে ও বাস্তব চিপে
-
Intel Golden Cove (Alder Lake/Sapphire Rapids, ২০২১-২২) — ~৬-wide decode frontend, ১২টা execution port, ৫১২-entry ROB। Intel-এর “hybrid” ডিজাইনে (Alder Lake) এই বড়, শক্তিশালী “Performance core” (P-core)-এর পাশে ছোট, সরল “Efficiency core” (E-core, Gracemont microarchitecture) থাকে — একই ISA, দুই সম্পূর্ণ ভিন্ন microarchitecture, একই চিপে, ভিন্ন workload-এর জন্য।
-
AMD Zen 4 (২০২২) — ~১০ execution port, ৩২০-entry ROB, একটা বড় µop cache (decode-করা instruction পুনর্ব্যবহারের জন্য, বারবার decode এড়িয়ে frontend-এর কাজ কমায়)।
-
Apple M-সিরিজ (M1, ২০২০ থেকে) — ARM64 ISA, কিন্তু অস্বাভাবিক চওড়া frontend (~৮-wide decode) আর বড় ROB (~৬৩০) — Apple-এর একটা সচেতন সিদ্ধান্ত যে তিনটা অংশই একসাথে বড় হবে, শুধু একটা সংখ্যায় বিনিয়োগ না করে।
-
Pentium 4 / NetBurst (২০০০-২০০৮) — একটা বিখ্যাত ব্যর্থ নকশা-পরীক্ষা: অত্যন্ত গভীর frontend (২০-৩১ stage) উচ্চ clock speed (৩+ GHz, তখনকার জন্য বিশাল) পাওয়ার জন্য, কিন্তু misprediction penalty এত বড় হয়ে যায় যে বাস্তব workload-এ প্রায়ই তার উত্তরসূরি (Core microarchitecture, অনেক অগভীর pipeline) থেকে ধীর ছিল। Intel পরে ইচ্ছাকৃতভাবে গভীরতা কমিয়ে আনে — গভীরতা একাই performance-এর সূচক না, এই lesson-এর টেবিলের সবকয়টা সংখ্যা একসাথে গুরুত্বপূর্ণ।
-
ARM big.LITTLE / DynamIQ — একই ARM64 ISA-তে সচেতনভাবে দুই ভিন্ন microarchitecture একসাথে রাখা: “big” core (out-of-order, চওড়া frontend/backend, বেশি power) আর “LITTLE” core (in-order বা সংকীর্ণ, কম power) —
superscalar-and-out-of-orderলেসনের IoT/battery আলোচনার সরাসরি সম্প্রসারণ, এখন পুরো একটা phone SoC-র নকশা-নীতি হিসেবে। -
Top-Down Microarchitecture Analysis (TMA) — Intel-এর নিজস্ব profiling পদ্ধতি, প্রতিটা cycle-কে Frontend Bound/Bad Speculation/Backend Bound/Retiring-এ ভাগ করে — এই লেসনের তিন-অংশের মডেলের সবচেয়ে সরাসরি বাস্তব-জগতের প্রয়োগ,
perf-এর মতো টুল-এ built-in। -
GPU-র সম্পূর্ণ ভিন্ন frontend/backend গঠন — একটা GPU-তেও “frontend” (instruction fetch/schedule) আর “backend” (execution unit, ALU) ধারণা আছে, কিন্তু গঠন মৌলিকভাবে ভিন্ন — একটা instruction stream শত শত thread-এ (SIMT model) একসাথে broadcast হয়, এই module-এর পরের দিকে (GPU architecture, SM, warp) বিস্তারিত আসবে।
-
RISC-V-ভিত্তিক আধুনিক out-of-order core (যেমন SiFive P650-গোত্রের ডিজাইন) — দেখায় এই frontend/backend/retirement মডেল কোনো x86/ARM-নির্দিষ্ট ধারণা না, বরং যেকোনো ISA-র উপর superscalar, out-of-order বাস্তবায়ন করতে গেলে যে সার্বজনীন হার্ডওয়্যার-প্যাটার্ন প্রয়োজন হয়, তারই প্রতিফলন।
-
Intel Pentium Pro / P6 microarchitecture (১৯৯৫) — আধুনিক out-of-order x86-এর প্রকৃত উৎস।
register-renaming-reorder-bufferলেসনের ROB টেবিলে এটাই ছিল প্রথম, সবচেয়ে ছোট এন্ট্রি (মাত্র ৪০-entry ROB) — সেখান থেকে ৫১২-entry Golden Cove পর্যন্ত প্রায় ৩০ বছরের বিবর্তন, ঠিক frontend/backend/retirement-এর প্রতিটা অংশ ধাপে ধাপে বড় ও পরিশীলিত হওয়ার একটা জীবন্ত রেকর্ড।
যে ভুলগুলো সবাই করে
“Microarchitecture আর ISA (Architecture) মূলত একই জিনিস, শুধু ভিন্ন নাম।”
সম্পূর্ণ ভুল, আর এই ভুল-ধারণা প্রায়ই “x86 architecture” আর “Intel-এর architecture” গুলিয়ে ফেলা থেকে আসে। ISA একটা স্থিতিশীল চুক্তি (x86-64, ARM64, RISC-V) — সফটওয়্যার এর উপর ভরসা করে, বছরের পর বছর প্রায় অপরিবর্তিত থাকে। Microarchitecture সেই চুক্তির একটা নির্দিষ্ট, প্রতিযোগিতামূলক বাস্তবায়ন (Golden Cove, Zen 4, Firestorm) — প্রতি ১-২ বছরে বদলায়, কোম্পানিভেদে সম্পূর্ণ ভিন্ন। একই ISA-র উপর দাঁড়িয়ে ভিন্ন কোম্পানি (Intel বনাম AMD) সম্পূর্ণ ভিন্ন microarchitecture বানাতে পারে — সেটাই বাজারে প্রতিযোগিতার আসল জায়গা, ISA-তে না।
“Frontend/backend/retirement মানে instruction-গুলো এক-এক করে, ক্রমান্বয়ে এই তিন ধাপ পার হয় — একবারে একটামাত্র instruction পুরো pipeline-এ থাকে।”
pipelining-and-hazards লেসনের মূল শিক্ষা এখানেও প্রযোজ্য, বরং আরও তীব্রভাবে — যেকোনো মুহূর্তে শত শত µop একসাথে পাইপলাইনের বিভিন্ন ধাপে থাকে (আধুনিক CPU-র ROB আকার অনুযায়ী, উপরের টেবিলে ২২৪-৬৩০ পর্যন্ত)। Frontend প্রতি cycle-এ নতুন instruction ঢালছে, backend তাদের মধ্যে বহু একসাথে execute করছে dependency অনুযায়ী, retirement আরেকটা দল একসাথে commit করছে — সব একই cycle-এ, সমান্তরালে। “তিন ধাপ” মানে তিনটা কার্যকরী এলাকা, তিনটা ক্রমিক ঘটনা না।
“Backend-এ বেশি execution port বা frontend-এ বেশি decode width যোগ করলেই performance বাড়বে — যত বেশি তত ভালো।”
superscalar-and-out-of-order লেসনের misconception-এর সরাসরি সম্প্রসারণ। যদি backend-এর port সংখ্যা বাড়ানো হয় কিন্তু ROB (retirement) ছোট থেকে যায়, backend দ্রুত সব independent কাজ শেষ করে ফেলে, তারপর retirement-এর জন্য অপেক্ষা করে বসে থাকে — বাড়তি port অলস। উল্টোভাবে, যদি frontend চওড়া করা হয় কিন্তু backend-এর port কম থাকে, µop queue দ্রুত পূর্ণ হয়ে যায়, frontend stall করে — বাড়তি decode width অলস। তিনটা অংশ একসাথে, সুষমভাবে বড় করতে হয়; একটা সংখ্যা একা বাড়ানো প্রায়ই diminishing বা শূন্য লাভ দেয়, যা এই লেসনের build simulator-এ সরাসরি পরিমাপযোগ্য।
বুঝেছেন কি না দেখুন
1তিনটা অংশের নাম বলুন, আর প্রতিটার জন্য computer-architecture module-এর একটা করে সরাসরি সম্পর্কিত লেসনের নাম দিন।
স্মরণ
computer-architecture module-এর একটা করে সরাসরি সম্পর্কিত লেসনের নাম দিন।Frontend — instruction fetch, decode, branch prediction। সরাসরি সম্পর্কিত: pipelining-and-hazards (fetch/decode স্টেজ) আর branch-prediction।
Backend — out-of-order execution engine, execution port। সরাসরি সম্পর্কিত: superscalar-and-out-of-order (dependency-ভিত্তিক readiness, out-of-order issue)।
Retirement — ROB-ভিত্তিক, কড়া in-order commit। সরাসরি সম্পর্কিত: register-renaming-reorder-buffer (ROB, precise exception, commit)।
2একটা প্রোগ্রামে একটা LOAD instruction বারবার L3 cache-ও miss করে DRAM পর্যন্ত যাচ্ছে (~২০০ cycle)। ROB-এ সেই LOAD-এর entry মাথায় বসে আছে, এখনও সম্পন্ন হয়নি। এই সময়ে frontend, backend, আর retirement — প্রতিটা কী অবস্থায় থাকবে?
প্রয়োগ
Backend — LOAD-এর উপর নির্ভরশীল না এমন instruction independent হলে এখনও execute হতে পারে (out-of-order), কিন্তু ROB পূর্ণ হয়ে গেলে (নতুন entry রাখার জায়গা না থাকলে) আর নতুন কাজ নিতে পারবে না।
Retirement — সম্পূর্ণ থেমে থাকবে। ROB-এর মাথায় সেই অসম্পূর্ণ LOAD আছে বলে, তার পরে যত instruction-ই সম্পন্ন হোক (এমনকি সবাই সম্পন্ন হলেও), কেউ retire করতে পারবে না — কড়া in-order commit নিয়মের কারণে। এটাই ROB-এর “head-of-line blocking”।
Frontend — যতক্ষণ ROB-এ নতুন entry-র জায়গা আছে ততক্ষণ fetch/decode চালিয়ে যাবে, কিন্তু retirement থেমে থাকায় ROB দ্রুত পূর্ণ হয়ে যাবে (কেউ বের হচ্ছে না, শুধু ঢুকছে) — একবার পূর্ণ হলে frontend-ও stall করবে, নতুন কিছু fetch করার জায়গা না থাকায়।
এই পুরো পরিস্থিতিটাই TMA-এর ভাষায় “Backend Bound” (মূল কারণ memory latency) হিসেবে প্রকাশ পাবে, যদিও frontend-ও পরোক্ষভাবে থেমে যায় — এটাই দেখায় কেন তিনটা অংশ একে অপরের থেকে সম্পূর্ণ স্বাধীন না, একটা backpressure তৈরি করে বাকিদের প্রভাবিত করে।
3একজন সহকর্মী বলছেন, “Backend যেহেতু out-of-order, retirement-ও out-of-order হওয়া উচিত — যেই instruction আগে সম্পন্ন হয়, সেটাই আগে commit হওয়া উচিত, দ্রুততর হবে।” এই যুক্তিতে কী ভুল আছে?
যুক্তি
এই যুক্তি দুইটা ভিন্ন জিনিস গুলিয়ে ফেলছে — কখন কাজ শেষ হয় বনাম কখন সেই কাজ “সরকারিভাবে সত্য” হিসেবে ঘোষিত হয়। যদি retirement-ও out-of-order হতো:
১. Precise exception অসম্ভব হয়ে যেত (register-renaming-reorder-buffer লেসনের মূল যুক্তি) — যদি instruction ৫ instruction ৩-এর আগে commit হয়ে যায়, আর তারপর instruction ৩-এ একটা exception ধরা পড়ে, architectural state-এ ইতিমধ্যে instruction ৫-এর প্রভাব বসে গেছে যা program order অনুযায়ী “হওয়ার কথা ছিল না” — exception handler একটা অসামঞ্জস্যপূর্ণ, অসংজ্ঞায়িত অবস্থা দেখবে।
২. Branch misprediction recovery ভেঙে পড়ত — একটা ভুল-predict করা branch-এর পরের instruction যদি আগেই out-of-order commit হয়ে যায় (backend-এ দ্রুত সম্পন্ন হওয়ায়), সেই ভুল কাজ বাতিল (flush) করার কোনো উপায় থাকত না — architectural state-এ ইতিমধ্যে স্থায়ীভাবে লেখা হয়ে গেছে।
তাই retirement-এর in-order নিয়মটা কোনো performance-এর অভাব না, এটা একটা সচেতন, প্রয়োজনীয় সিদ্ধান্ত — backend-এর গতি (out-of-order) আর প্রোগ্রামের দৃশ্যমান সঠিকতা (in-order semantics) দুইটাই একসাথে রাখার একমাত্র ব্যবহারিক উপায়।
4একটা নতুন CPU ডিজাইন প্রস্তাব: frontend-এর decode width দ্বিগুণ করা হবে (৪ থেকে ৮-wide), কিন্তু ROB আকার আর execution port সংখ্যা অপরিবর্তিত রাখা হবে, খরচ বাঁচানোর জন্য। এই ডিজাইনে কী সমস্যা প্রত্যাশিত, আর বাস্তব ডেটা (উপরের টেবিল) কীভাবে এই আশঙ্কা সমর্থন করে?
ডিজাইন
সমস্যা: ৮-wide frontend প্রতি cycle-এ দ্বিগুণ µop তৈরি করবে, কিন্তু সেগুলো রাখার জায়গা (ROB) আর execute করার ক্ষমতা (port সংখ্যা) আগের মতোই থাকলে, µop queue আর ROB দ্রুত পূর্ণ হয়ে যাবে — frontend তখন প্রায়ই stall করবে, কারণ তার তৈরি করা কাজ backend/retirement হজম করতে পারছে না তত দ্রুত। ফলাফল: বাস্তব IPC-তে দ্বিগুণ লাভ আসবে না, হয়তো সামান্যই লাভ হবে, অথচ decode hardware-এর খরচ (transistor, power, area) পুরোপুরি দ্বিগুণ হয়ে গেছে।
বাস্তব ডেটা এই আশঙ্কাকে সমর্থন করে: Apple M1-এর ৮-wide frontend-এর সাথে জোড়া লেগে আছে একটা অস্বাভাবিক বড় ROB (৬৩০, টেবিলের সবচেয়ে বড়) আর ~১৩টা execution port — অর্থাৎ Apple-ও জানত যে চওড়া frontend একা যথেষ্ট না, backend আর retirement-কেও সমান অনুপাতে বড় করতে হয়েছে। এটাই প্রমাণ করে বাস্তব ডিজাইনাররাও এই সুষমতার নীতি মেনেই সিদ্ধান্ত নেন, শুধু একটা সংখ্যায় বিনিয়োগ করেন না।
5perf stat --topdown চালিয়ে দেখলেন আপনার প্রোগ্রামে Backend Bound: 45%, বাকি তিনটা বিভাগ মিলিয়ে ছোট। পরবর্তী optimization প্রচেষ্টা কোন দিকে কেন্দ্রীভূত করা উচিত, আর কেন এই সিদ্ধান্ত এই module-এর পরের দুই লেসনের সাথে সরাসরি সম্পর্কিত?
প্রয়োগ
perf stat --topdown চালিয়ে দেখলেন আপনার প্রোগ্রামে Backend Bound: 45%, বাকি তিনটা বিভাগ মিলিয়ে ছোট। পরবর্তী optimization প্রচেষ্টা কোন দিকে কেন্দ্রীভূত করা উচিত, আর কেন এই সিদ্ধান্ত এই module-এর পরের দুই লেসনের সাথে সরাসরি সম্পর্কিত?Backend Bound উচ্চ মানে backend-এর execution port বা memory-এর জন্য অপেক্ষা করে সময় নষ্ট হচ্ছে, decode বা branch prediction-এ সমস্যা না (সেগুলো Frontend Bound/Bad Speculation-এ ধরা পড়ত)। সন্দেহের প্রথম জায়গা: cache miss (L1/L2/L3-এ hit না পাওয়া, পরের লেসন cache-hierarchy-inclusive-exclusive-এর বিষয়) অথবা, যদি প্রোগ্রামটা multi-threaded হয়, cache coherence traffic (একাধিক core একই cache line-এ বারবার লেখা/পড়া করছে, লেসন cache-coherence-mesi-এর বিষয়)। পরবর্তী পদক্ষেপ হবে perf stat -e cache-misses,cache-references বা perf c2c দিয়ে ঠিক কোন উৎস (memory hierarchy বনাম coherence traffic) দায়ী তা আলাদা করা — যা এই module-এর পরের দুই লেসনের সরাসরি ব্যবহারিক প্রয়োগ।
6Intel-এর µop cache (Decoded Stream Buffer)-এ একটা hot loop-এর instruction hit করলে ঠিক কোন pipeline sub-stage-গুলো বাইপাস হয়, আর কেন এটা শুধু “গতি” না, “power”-ও বাঁচায়?
যুক্তি
µop cache hit-এ frontend-এর সবচেয়ে ব্যয়বহুল অংশগুলো — instruction-length নির্ণয় (pre-decode), byte-ভাঙা, আর মূল CISC-থেকে-µop decode যুক্তি — সম্পূর্ণ বাইপাস হয়ে যায়; শুধু µop cache থেকে ইতিমধ্যে-তৈরি µop সরাসরি backend-এর rename/dispatch ধাপে পাঠানো হয়।
Power বাঁচার কারণ সরল: decode logic (বিশেষত CISC-এর variable-length instruction ভাঙা) নিজেই একটা জটিল, বহু-transistor সমান্তরাল সার্কিট, যা প্রতিবার সক্রিয় হলে গতিশীল power খরচ করে (transistor switching-এর ফলে, digital-logic module-এর CMOS power আলোচনার সরাসরি প্রাসঙ্গিকতা)। µop cache hit মানে সেই সার্কিট এই cycle-এ সম্পূর্ণ নিষ্ক্রিয় থাকতে পারে — শুধু একটা ছোট, সরল cache lookup যথেষ্ট। এই কারণেই ল্যাপটপ/phone-এর মতো power-সংবেদনশীল ডিভাইসে হট-লুপ-ভারী কোড (যেমন ভিডিও ডিকোডিং-এর inner loop) থেকে µop cache একটা বাস্তব ব্যাটারি-লাইফ সুবিধা দেয়, শুধু benchmark-সংখ্যা না।
এরপর কী
আমরা এখন একটা মানচিত্র পেয়েছি — frontend, backend, retirement — যার উপর এই পুরো module দাঁড়াবে। কিন্তু একটা বড় প্রশ্ন ইচ্ছাকৃতভাবে এড়িয়ে গেছি: backend-এর load/store unit যখন memory-তে যায়, ঠিক কোন cache-এ যায়, আর যদি একাধিক core থাকে (আজকের প্রায় সব CPU), সেই cache-গুলোর মধ্যে সম্পর্কটা কেমন?
পরের লেসনে (cache-hierarchy-inclusive-exclusive) আমরা computer-architecture/cache-organization-এর একক-level cache mapping (direct-mapped/set-associative) থেকে এগিয়ে গিয়ে একাধিক cache level-এর মধ্যে সম্পর্ক দেখব — inclusive, exclusive, আর non-inclusive ডিজাইন, বাস্তব সংখ্যাসহ। আর তারপর, লেসন ৩-এ (cache-coherence-mesi), যখন সেই backend একাধিক core-এ ছড়িয়ে যায়, তখন কীভাবে সব core একই memory-র একটা সামঞ্জস্যপূর্ণ ছবি দেখে তা নিশ্চিত করা হয় — MESI protocol।
আরও পড়ুন
- Computer Architecture: A Quantitative Approach, Chapter 3 — Hennessy & Patterson · Modern superscalar microarchitecture-এর প্রামাণ্য কাঠামো
- A Top-Down Method for Performance Analysis and Its Application to Intel Skylake — Ahmad Yasin (Intel, 2014) · TMA পদ্ধতির মূল পেপার — frontend/backend/retirement বিভাজনের বাস্তব শিল্প-প্রয়োগ
- Intel 64 and IA-32 Architectures Optimization Reference Manual — Intel Corporation · Golden Cove-সহ আধুনিক Intel core-এর frontend/backend স্পেসিফিকেশন