আপনার laptop-এ কি একটাই memory আছে?
স্বাভাবিক উত্তর — হ্যাঁ, RAM। ১৬ GB বা ৩২ GB, যা-ই হোক।
কিন্তু আসলে laptop-এ এই মুহূর্তে কয়েক রকমের storage একসাথে কাজ করছে। কিছু এত ছোট যে সবমিলিয়ে সামান্য কয়েকটা value ধরে, কিন্তু CPU-র execution logic-এর একদম গায়ে লাগানো। কিছু এত বড় যে টেরাবাইট পর্যন্ত ধরে, কিন্তু সেখান থেকে data আনতে CPU-কে তুলনায় অনেক বেশি অপেক্ষা করতে হয়।
কেন এই ব্যবস্থা? আগের আর্টিকেলে দেখেছি একটা instruction কীভাবে fetch, decode আর execute হয়। কিন্তু সেই instruction আর data-গুলো থাকে কোথায়, আর একটাই memory দিয়ে কেন কাজ চলে না? এটাই আজকের গল্প।
কেন একটাই memory দিয়ে হয় না?
সোজা প্রশ্ন:
একটাই বিশাল memory বানিয়ে সেটাকে CPU-র সবচেয়ে কাছের storage-এর মতো দ্রুত বানানো যায় না?
সমস্যাটা engineering trade-off-এর।
Memory design-এ আমরা একসাথে চাই:
- কম latency — data খুব দ্রুত পাওয়া যাবে
- বেশি capacity — অনেক data রাখা যাবে
- কম cost — একই capacity-র জন্য খরচ নাগালের মধ্যে থাকবে
কোনো একটা storage technology একসাথে এই সব দিকেই সেরা হতে পারে না।
Very fast + very large → very expensive Very fast + inexpensive → small capacity Very large + inexpensive → higher latency
তাই computer engineer-রা একটাই memory technology বেছে নেয়নি। বরং বিভিন্ন trade-off-এর storage-কে স্তরে স্তরে সাজানো হয়েছে — সবচেয়ে ছোট আর দ্রুত storage CPU-র সবচেয়ে কাছে, বড় আর ধীর storage আরও নিচে।
এই layered বিন্যাসটাই Memory Hierarchy।
এটা কোনো কঠিন rule নয় যে প্রতিটা computer-এ ঠিক একই সংখ্যক layer থাকবে। Architecture অনুযায়ী স্তরের সংখ্যা, মাপ, কোনটা shared আর কোনটা নয় — সব বদলাতে পারে। আমরা এখানে একটা simplified hierarchy নিয়ে কাজ করছি, যাতে মূল ধারণাটা পরিষ্কার হয়।
Layer-গুলো
একটা simplified laptop বা desktop-এর memory hierarchy-কে এভাবে ভাবা যায়:
- Register — CPU-র execution logic-এর সবচেয়ে কাছের storage। চলমান computation-এর জন্য দরকারি কিছু value, address বা state এখানে থাকে। Capacity খুব ছোট, কিন্তু access latency সবচেয়ে কম।
- L1 Cache — প্রতিটা core-এর খুব কাছের ছোট cache। প্রায়ই instruction আর data-র জন্য আলাদা ভাগে থাকে — L1i আর L1d।
- L2 Cache — L1-এর চেয়ে বড়, সাধারণত একটু বেশি latency-র। অনেক architecture-এ এটা core-এর সঙ্গে ঘনিষ্ঠভাবে যুক্ত থাকে, যদিও exact design processor ভেদে আলাদা।
- L3 Cache — আরও বড় cache layer। অনেক modern multi-core processor-এ এটা একাধিক core-এর মধ্যে shared হতে পারে, তবে exact organization architecture অনুযায়ী বদলায়।
- RAM — system-এর main memory। চলমান program আর তাদের data-র বড় অংশ এখানে থাকে। Cache বা register-এর তুলনায় ধারণক্ষমতা অনেক বেশি, latency-ও অনেক বেশি।
- SSD / HDD — persistent storage। ধারণক্ষমতা অনেক বড়, আর power বন্ধ হলেও data থেকে যায়। কিন্তু CPU-র জন্য এদের access RAM-এর তুলনায়ও অনেক ধীর।
Registers ↓ L1 Cache ↓ L2 Cache ↓ L3 Cache ↓ RAM ↓ SSD / HDD
উপরের দিকে গেলে সাধারণত:
ছোট → দ্রুত → প্রতি unit capacity-তে বেশি খরচসাপেক্ষ
নিচের দিকে গেলে সাধারণত:
বড় → ধীর → প্রতি unit capacity-তে সস্তা
এটা একটা conceptual hierarchy। বাস্তব processor-এ কিছু layer আলাদা হতে পারে, কিছু shared হতে পারে, আবার কোনো architecture-এ নির্দিষ্ট কোনো layer না-ও থাকতে পারে। নিচের যন্ত্রে একেকটা layer-এ চাপ দিয়ে তার ভূমিকা দেখুন:
Cache কি শুধু "আরও দ্রুত RAM"?
না।
Cache আর RAM — দুটোই memory, কিন্তু এরা একই technology-র storage নয়, একই ভূমিকারও নয়। CPU cache সাধারণত অনেক ছোট, কম latency-র, আর processor-এর execution logic-এর সঙ্গে ঘনিষ্ঠভাবে যুক্ত। Main memory অনেক বড়, আর system-এর অনেক বড় working set ধরে রাখে।
Cache-এর উদ্দেশ্য RAM-এর বিকল্প হওয়া নয়। উদ্দেশ্য হলো বারবার কাজে লাগা data-কে CPU-র কাছাকাছি রাখা, যাতে প্রতিবার ধীর main memory পর্যন্ত যেতে না হয়।
Latency-র পার্থক্য কেন গুরুত্বপূর্ণ?
"Register দ্রুত, RAM ধীর" — বলা সহজ।
আসল কথাটা হলো, এই latency-গুলো একই scale-এর নয়। Hierarchy-র নিচের দিকে নামলে access time অনেক গুণ বেড়ে যেতে পারে।
আর clock cycle-এর সংখ্যাও কোনো memory technology-র স্থায়ী বৈশিষ্ট্য নয়। একটা নির্দিষ্ট access কত cycle নেবে, সেটা processor-এর clock frequency, architecture আর সেই particular access-এর ওপর নির্ভর করে।
তাই নির্দিষ্ট সংখ্যা মুখস্থ করার চেয়ে এই mental model-টা কাজে লাগবে:
Closer to the CPU → generally lower latency Farther down the hierarchy → generally higher latency
CPU-কে যদি প্রতিটা data access-এর জন্য বারবার অনেক ধীর layer পর্যন্ত অপেক্ষা করতে হতো, তাহলে execution প্রায়ই data-র জন্য বসে থাকত। এই অবস্থাকে বলা হয় stall।
তাই hierarchy-র মূল লক্ষ্যটা এক লাইনে:
যে data এখন বা খুব শিগগির দরকার হতে পারে, তার অন্তত কিছু অংশ CPU-র কাছের দ্রুত layer-এ রাখা।
কিন্তু cache তো ছোট। পুরো program-এর data সেখানে ধরবে না। তাহলে hardware বুঝবে কীভাবে কোন data কাছে রাখলে কাজে লাগবে? এখানেই আসে locality।
Locality: কেন hierarchy কাজ করে
এত ছোট cache দিয়ে লাভটা হয়, কারণ অনেক program memory access করার সময় locality দেখায়।
সব program-এর access pattern একরকম নয়, সব access predictable-ও নয়। কিন্তু অনেক common workload-এ দুটো pattern খুব বেশি দেখা যায়।
Temporal locality
এই মুহূর্তে যে data access হয়েছে, অল্প সময়ের মধ্যে সেটাই আবার access হওয়ার সম্ভাবনা থাকে — কোনো loop-এর counter, বারবার ব্যবহার করা variable, বা বারবার কল হওয়া function-এর data।
Recently used data আবার লাগতে পারে।
Spatial locality
এখন যে memory address access হয়েছে, তার আশেপাশের address-গুলোও শিগগির লাগতে পারে। যেমন একটা array sequentially traverse করলে:
arr[0] arr[1] arr[2] arr[3] ...
একটা element-এর পরে সাধারণত তার কাছের element-ই আসে।
কাছের data-ও শিগগির দরকার হতে পারে।
Cache এই দুই ধরনের locality-ই কাজে লাগায়। এই কারণেই CPU যে byte চেয়েছে শুধু সেটা cache-এ আনা সবচেয়ে ভালো কৌশল নাও হতে পারে — আশেপাশের data সহ একটা ছোট block আনা লাভজনক হতে পারে।
Cache line: cache আসলে কীভাবে data আনে?
Cache সাধারণত একেকটা byte আলাদা করে নয়, বরং cache line নামের fixed-size block ধরে data manage করে। অনেক modern CPU-তে একটা cache line 64 byte, যদিও exact size architecture অনুযায়ী বদলায়।
ধরুন CPU arr[0] access করল, আর সেই access-এ cache miss হলো। তখন সেই address-কে ধারণ করা cache line-টা hierarchy-র নিচের level থেকে আনা হতে পারে। সেই line-এর ভেতরে arr[1], arr[2] আর আরও কাছের element-ও থাকতে পারে — ফলে পরের access-গুলো cache থেকেই মিটে যেতে পারে।
Cache line হলো cache-এর transfer বা storage-এর একক। "CPU সবসময় RAM থেকে ঠিক 64 byte চায়" — এমন কোনো universal নিয়ম এটা নয়।
নিচের যন্ত্রে একটা address-এ চাপ দিয়ে দেখুন — line-এর বাকি অংশও কীভাবে সঙ্গে চলে আসে:
কোড লেভেলে প্রভাব: row-major vs column-major
Memory hierarchy শুধু hardware engineer-দের মাথাব্যথা নয় — সাধারণ code-এর performance-এও এর সরাসরি প্রভাব পড়ে।
C-র built-in multidimensional array memory-তে row-major order-এ contiguousভাবে সাজানো থাকে — এক row-এর সব element পাশাপাশি, তারপরেই পরের row।
নিচের দুটো loop একই matrix-এর সব element যোগ করে, কিন্তু access pattern আলাদা:
#define SIZE 2048
int arr[SIZE][SIZE];
// Approach A: row-major traversal
long long sumA = 0;
for (int i = 0; i < SIZE; i++) {
for (int j = 0; j < SIZE; j++) {
sumA += arr[i][j]; // neighbouring addresses
}
}
// Approach B: column-major traversal
long long sumB = 0;
for (int j = 0; j < SIZE; j++) {
for (int i = 0; i < SIZE; i++) {
sumB += arr[i][j]; // large-stride jumps
}
}Approach A-র inner loop-এ পাশাপাশি address পড়া হয়:
arr[0][0] arr[0][1] arr[0][2] arr[0][3] ...
একটা cache line একবার চলে এলে সেই line-এর আরও অনেক element কাজে লাগানো যায়। Cache hit-এর সম্ভাবনা বাড়ে।
Approach B-তে inner loop-এ i বাড়ে, তাই access-গুলো হয়:
arr[0][0] arr[1][0] arr[2][0] arr[3][0] ...
SIZE = 2048 আর sizeof(int) = 4 হলে প্রতিটা access-এর মাঝে দূরত্ব:
2048 × 4 = 8192 bytes
অর্থাৎ পরের element পেতে memory-তে প্রায় ৮ KB লাফ দিতে হচ্ছে। এতে spatial locality খারাপ হয় — একই cache line আবার কাজে লাগার সুযোগ কমে যায়, cache miss-এর হার অনেক বেড়ে যেতে পারে।
তাই Approach A সাধারণত Approach B-র চেয়ে অনেক বেশি cache-friendly। তবে ঠিক কত গুণ দ্রুত হবে, সেটা hardware, compiler আর workload-এর ওপর নির্ভর করে।
একই algorithmic result-এর জন্যও memory access pattern performance বদলে দিতে পারে।
এই একই locality principle-এর কারণেই data structure বা algorithm বাছার সময় শুধু Big-O complexity নয়, memory access pattern-ও গুরুত্বপূর্ণ হতে পারে। নিচের যন্ত্রে দুই mode toggle করে hit/miss গুনে দেখুন:
SRAM আর DRAM: ভেতরে পার্থক্য কী?
Cache আর main RAM — দুটোই semiconductor memory, কিন্তু সাধারণত এদের storage cell একরকম নয়।
SRAM — cache-এর দিকে
SRAM (Static RAM) সাধারণত একাধিক transistor দিয়ে তৈরি একটা bistable cell ব্যবহার করে bit ধরে রাখে; common design-এ ৬-transistor cell দেখা যায়।
সুবিধা: খুব দ্রুত access, আলাদা refresh cycle লাগে না, আর CPU-র কাছাকাছি ঘন ঘন access করা data-র জন্য উপযুক্ত।
অসুবিধা: প্রতি bit-এ বেশি hardware লাগে, তাই storage density কম — একই capacity বানাতে বেশি silicon area লাগে।
এই কারণেই SRAM তুলনামূলক ছোট আর দ্রুত cache-এর জন্য উপযোগী।
DRAM — main memory-র দিকে
DRAM (Dynamic RAM) সাধারণত একটা transistor আর একটা capacitor-ভিত্তিক cell দিয়ে bit রাখে। Capacitor-এ জমা charge সময়ের সঙ্গে leak করে, তাই DRAM-কে নিয়মিত refresh করতে হয়।
এর বড় সুবিধা density — অনেক বেশি bit খুব ছোট physical area-তে রাখা যায়। তাই large-capacity main memory-র জন্য DRAM উপযোগী।
কিন্তু এর cell structure আর read/write process SRAM-এর তুলনায় বেশি জটিল, তাই latency-ও বেশি।
SRAM → faster, larger per-bit footprint, more expensive per bit DRAM → slower, denser, cheaper per bit
এই trade-off-টাই বুঝিয়ে দেয় কেন cache আর main memory আলাদা technology-তে বানানো হয়।
Volatility: power গেলে কী হয়?
Memory-কে আরেকভাবেও ভাগ করা যায়: power বন্ধ হওয়ার পর data কি নির্ভরযোগ্যভাবে থেকে যায়? এটাই volatile আর non-volatile-এর পার্থক্য।
Register, cache আর DRAM-based main memory volatile। মানে এগুলো power ছাড়া stored state অনির্দিষ্টকাল ধরে রাখার জন্য designed নয় — power চলে গেলে আগের data আর নির্ভরযোগ্যভাবে থাকে না।
SSD আর HDD non-volatile। power ছাড়াও এরা data ধরে রাখতে পারে, আর এদের physical technology আলাদা:
- HDD: magnetic storage ব্যবহার করে।
- SSD: NAND flash memory ব্যবহার করে, যেখানে electrical charge দিয়ে stored state বহুদিন ধরে রাখা যায়।
Volatile → requires power to retain state Non-volatile → can retain state without continuous power
এখানে এই distinction-টুকুই যথেষ্ট। physical implementation-এর গভীর detail পরে দরকার হলে দেখা যাবে।
পুরো ছবিটা একবার
ধরুন CPU-কে এমন একটা instruction execute করতে হবে, যার জন্য memory থেকে একটা value load করা দরকার। Simplifiedভাবে গল্পটা এমন:
- CPU memory access-এর প্রয়োজন বুঝল। Instruction-এর control information থেকে CPU জানে যে একটা memory value লাগবে, আর কোন address থেকে সেটা নিতে হবে।
- কাছের cache-এ খোঁজা হয়। L1 data cache-এ value থাকলে সেটা cache hit — CPU কম latency-তেই data পেয়ে যায়।
- না থাকলে নিচের level-এ। Processor-এর design অনুযায়ী request L2, তারপর L3-এ যেতে পারে। যেখানে data পাওয়া যায়, সেখান থেকে সেটা CPU-র দিকে ফেরে, আর প্রয়োজন অনুযায়ী cache-এ রাখা হতে পারে। কোনো cache-এই না থাকলে request main memory পর্যন্ত যায়।
- Data পেলে execution এগোয়। Data execution machinery-তে ফিরে এলে instruction-এর বাকি অংশ চলতে পারে, আর result শেষ পর্যন্ত কোনো register-এ বসতে পারে।
CPU ↓ L1 ↓ L2 ↓ L3 ↓ RAM
উপরের layer-এ data পাওয়া গেলে নিচ পর্যন্ত যেতেই হয় না। এই কারণেই cache hit মূল্যবান, আর cache miss expensive হতে পারে। নিচের যন্ত্রে data কোথায় পাওয়া গেল সেটা বেছে নিয়ে পুরো cascade দেখুন:
Page fault কোথায় আসে?
এটা আলাদা একটা concept, যেটা আমরা এখানে বিস্তারিত করছি না। Virtual memory-তে CPU যে address ব্যবহার করছে, তার required page যদি RAM-এ এই মুহূর্তে না থাকে, তখন page fault ঘটতে পারে, আর operating system-কে জড়াতে হয়।
Cache miss মানে relevant cache-এ data পাওয়া যায়নি। Page fault হলো virtual memory system-এর একটা আলাদা ঘটনা। দুটোকে গুলিয়ে না ফেলাই ভালো।
বাস্তবতার কোণা: একাধিক core হলে?
একাধিক CPU core থাকলে আরেকটা সমস্যা আসে।
একই memory data-র copy একাধিক core-এর private cache-এ থাকতে পারে। কোনো একটা core সেই data বদলালে অন্য core যেন অনির্দিষ্টকাল পুরোনো, inconsistent copy ব্যবহার করতে না থাকে — তার জন্য processor-এ cache coherence mechanism থাকে।
একাধিক core-এর private cache থাকলে cached copy-গুলোর মধ্যে consistency রাখতে hardware-এর বাড়তি coordination দরকার হয়।
এখানে অনেক protocol আর hardware mechanism জড়িত। MESI-র মতো protocol-এর বিস্তারিত পরে আলাদা করে দেখা যেতে পারে।
এই আর্টিকেলে কী শিখলাম
- একটা memory technology সবদিক থেকে সেরা হতে পারে না — latency, capacity আর cost-এর মধ্যে trade-off আছে, তাই computer একাধিক storage layer ব্যবহার করে।
- Memory hierarchy CPU-কে দরকারি data দ্রুত পাওয়ার সুযোগ দেয় — register আর cache ছোট কিন্তু দ্রুত, RAM অনেক বড়, storage আরও বড় কিন্তু অনেক ধীর।
- Cache দাঁড়িয়ে আছে locality-র ওপর — recently used data আবার লাগতে পারে (temporal), আর কাছের address-ও শিগগির লাগতে পারে (spatial)।
- Cache line কাছের data-কে কাজে লাগায় — একটা miss-এর সময় আশেপাশের data সহ একটা block আসতে পারে, ফলে পরের access-গুলো hit হতে পারে।
- SRAM আর DRAM আলাদা trade-off দেয় — SRAM দ্রুত কিন্তু প্রতি bit-এ জায়গা বেশি; DRAM dense আর সস্তা, কিন্তু latency বেশি ও refresh লাগে।
- Volatile আর non-volatile-এর পার্থক্য state retention-এর — volatile storage power ছাড়া state নির্ভরযোগ্যভাবে রাখতে পারে না, non-volatile পারে।