Foundationপ্রথম নীতি থেকে
LEVEL 11লেসন ২/১৭কঠিন১ ঘণ্টা ৫ মিনিট

Cache Hierarchy — Inclusive, Exclusive, আর মাঝামাঝি

Cache Hierarchy: Inclusive vs Exclusive Design

একাধিক cache level (L1 private, L2 প্রায়ই private, L3 shared) একসাথে কাজ করার সময় একটা design সিদ্ধান্ত নিতে হয় — ইনক্লুসিভ (প্রতিটা outer level inner level-এর সব ধরে রাখে, coherence checking সহজ কিন্তু capacity নষ্ট), এক্সক্লুসিভ (প্রতিটা line ঠিক এক level-এ, বেশি effective capacity কিন্তু eviction জটিল), আর নন-ইনক্লুসিভ (বাস্তবসম্মত মাঝামাঝি) — এই সিদ্ধান্তই পরের লেসনের cache coherence protocol-এর কার্যকারিতাকে সরাসরি প্রভাবিত করে।

এই লেসন শেষে আপনি পারবেন

  • একটা বাস্তব multi-level cache hierarchy-র (L1/L2/L3) typical আকার ও latency সংখ্যায় বলতে পারবেন, শুধু একক-level cache mapping না — পুরো hierarchy-র structure বুঝবেন
  • Inclusive, exclusive, আর non-inclusive cache design-এর সংজ্ঞা দিতে পারবেন এবং একে অপরের থেকে আলাদা করতে পারবেন
  • কেন inclusive design capacity নষ্ট করে অথচ coherence checking সহজ করে — এই trade-off concrete সংখ্যায় ব্যাখ্যা করতে পারবেন
  • কেন exclusive design বেশি effective capacity দেয় অথচ eviction-এ বেশি জটিলতা তৈরি করে তা ব্যাখ্যা করতে পারবেন
  • Inclusive L3 কীভাবে coherence protocol-এর জন্য একটা natural 'filter' হিসেবে কাজ করে তা যুক্তি দিয়ে দেখাতে পারবেন
  • Victim cache-এর ধারণা ও উদ্দেশ্য ব্যাখ্যা করতে পারবেন

আগে যা বোঝা থাকা দরকার

আগে এটা বুঝি

computer-architecture/cache-organization লেসন একটা একক cache level কীভাবে কাজ করে তা সম্পূর্ণভাবে ব্যাখ্যা করেছিল — একটা address কীভাবে tag/index/offset-এ ভাগ হয়, direct-mapped বনাম set-associative বনাম fully-associative। computer-architecture/memory-hierarchy লেসন তার আগে একটা latency table দিয়েছিল — register থেকে HDD পর্যন্ত প্রতিটা স্তরের আনুমানিক গতি। কিন্তু দুইটা লেসনই একটা প্রশ্ন পুরোপুরি এড়িয়ে গিয়েছিল, যেটা advanced-architecture/microarchitecture-overview-র শেষে আমরা ইচ্ছাকৃতভাবে খোলা রেখেছিলাম:

L1, L2, L3 — এই তিনটা level যখন একসাথে, একই CPU-তে, একই সময়ে কাজ করে, তাদের ভেতরের সম্পর্কটা ঠিক কী? একটা memory line যদি L1-এ থাকে, সেটা কি নিশ্চিতভাবে L2-তেও আছে? L3-এ? নাকি প্রতিটা level সম্পূর্ণ স্বাধীনভাবে নিজের জিনিস রাখে, একে অপরের থেকে আলাদা ডেটা নিয়ে?

এই প্রশ্নের উত্তরকে বলে inclusion policy (অন্তর্ভুক্তি নীতি) — আর এই লেসনের মূল দাবি: এই একটা design সিদ্ধান্ত শুধু “capacity efficiency”-র প্রশ্ন না। এটা সরাসরি নির্ধারণ করে দেয় পরের লেসনের বিষয় — cache coherence — কতটা সহজ বা কঠিন হবে বাস্তবায়ন করা, যখন একাধিক core একই memory শেয়ার করে। এই লেসনটাই সেই সেতু।

মূল ধারণা

বাস্তব multi-level hierarchy — সংখ্যায়

memory-hierarchy লেসনের latency table আমাদের একটা প্রথম ধারণা দিয়েছিল। এখন সেই সংখ্যাগুলোকে আরও বাস্তবসম্মত, নির্দিষ্ট করে দেখি — একটা আধুনিক multi-core CPU-র typical L1/L2/L3 বিন্যাস:

Levelসাধারণতtypical আকার (per core, L3 ছাড়া)typical latencyকার সাথে shared
L1split I-cache + D-cache৩২-৬৪ KB (প্রতিটা, তাই মোট ৬৪-১২৮ KB)~৪-৫ cycleশুধু নিজের core
L2unified (instruction+data একসাথে)২৫৬ KB - ১ MB~১২-২০ cycleসাধারণত শুধু নিজের core (কিছু ডিজাইনে cluster-shared)
L3unifiedকয়েক থেকে কয়েক-দশ MB (মোট চিপে)~৪০-৭০ cycleসব core-এর মধ্যে shared

এই টেবিল memory-hierarchy লেসনের সংখ্যার সাথে সামঞ্জস্যপূর্ণ (L1 ~৪ cycle/~১.৩ ns, L2 ~১২ cycle/~৪ ns, L3 ~৪০ cycle/~১৩ ns), শুধু এখানে আকার-পরিসরটা আরও specific — আর সবচেয়ে গুরুত্বপূর্ণ নতুন তথ্য: L3 shared, L1/L2 সাধারণত private। এই একটা পার্থক্যই পরের লেসনের পুরো সমস্যার জন্ম দেবে — private cache মানে একই memory address-এর ভিন্ন ভিন্ন core-এর L1-এ ভিন্ন ভিন্ন copy থাকতে পারে, একই সময়ে।

বাস্তব চিপের নির্দিষ্ট সংখ্যা

CPUL1D (per core)L2 (per core)L3 (shared, মোট)
Intel Golden Cove~৪৮ KB~১.২৫-২ MB~৩০ MB (মডেলভেদে)
AMD Zen 4৩২ KB১ MB৩২ MB (প্রতি CCD)
Apple M1~১২৮ KB (অস্বাভাবিক বড়)~১২ MB (per cluster, shared)কোনো traditional L3 নেই — বদলে একটা System Level Cache (SLC)

Apple M1-এর গঠন বিশেষভাবে লক্ষণীয় — এটা L1/L2/L3-এর traditional তিন-স্তর মডেল হুবহু মানে না, বরং একটা ভিন্ন hierarchy shape ব্যবহার করে (realworld অংশে বিস্তারিত)। এটা এই লেসনের প্রথম ইঙ্গিত যে “L1/L2/L3” কোনো সার্বজনীন সত্য না, এটা একটা common pattern, যা থেকে বাস্তব ডিজাইনাররা প্রয়োজনমতো সরে যান।

Inclusion Policy — তিনটা সম্ভাব্য নকশা

এখন মূল প্রশ্নে আসি। একটা memory line যখন L1-এ আনা হয়, সেটা কি L2/L3-এও রাখা হয় (duplicate), নাকি সরিয়ে নেওয়া হয় (move), নাকি কোনো নিয়মই নেই?

১. Inclusive — বাইরের level সবসময় ভেতরেরটার superset

Inclusive hierarchy-তে একটা কড়া গ্যারান্টি থাকে: L1-এ যা কিছু আছে, তা অবশ্যই L2-তেও আছে; L2-তে যা কিছু আছে, তা অবশ্যই L3-তেও আছে।

L1⊆L2⊆L3L1 \subseteq L2 \subseteq L3

অর্থাৎ একটা line L1-এ থাকলে, সেই একই ডেটার একটা কপি L2-তেও বসে আছে, আরেকটা কপি L3-তেও। ডেটা duplicate হচ্ছে একাধিক level জুড়ে।

সুবিধা: নিচের অংশে (hood) বিস্তারিত, কিন্তু সংক্ষেপে — কোনো একটা line কোনো core-এর private cache-এ আছে কি না তা জানতে পুরো hierarchy খোঁজার দরকার নেই, শুধু সবচেয়ে বাইরের (L3) level দেখলেই যথেষ্ট, কারণ inclusion গ্যারান্টি করে সেখানে না থাকলে ভেতরে কোথাও থাকতেই পারে না।

দাম: capacity নষ্ট। যদি L2 ১ MB আর L3 ৩২ MB হয়, L2-এর পুরো ১ MB-ই L3-তে আবার জায়গা নিচ্ছে — L3-এর কার্যকর, অনন্য (unique) capacity কমে যায় প্রতিটা core-এর L2-এর সমান পরিমাণে (একাধিক core থাকলে, প্রতিটার L2-এর সমষ্টি)।

২. Exclusive — প্রতিটা line ঠিক এক জায়গায়

Exclusive hierarchy-তে বিপরীত নিয়ম: একটা line ঠিক একটা level-এ থাকে, কখনোই একাধিক level-এ একসাথে না।

L1∩L2=∅,L2∩L3=∅L1 \cap L2 = \emptyset, \quad L2 \cap L3 = \emptyset

L1-এ একটা line থাকলে, সেই ডেটা L2/L3-তে নেই — তারা একে অপরের সম্পূর্ণ আলাদা, অ-পুনরাবৃত্ত অংশ ধরে রাখে।

সুবিধা: কোনো অপচয় নেই। L1+L2+L3-এর মোট capacity-ই কার্যকর, অনন্য ডেটার জন্য ব্যবহৃত হয় — উপরের উদাহরণে ১ MB + ৩২ MB = ৩৩ MB সবটাই ভিন্ন ডেটা ধরে রাখতে পারে।

দাম: eviction জটিল। L1 থেকে একটা line miss করে L2-তে পাওয়া গেলে (L1 miss, L2 hit), সেই line-টাকে শুধু কপি করলেই চলবে না (তাহলে দুই জায়গায় একসাথে থাকবে, exclusive নিয়ম ভেঙে যাবে) — সেটাকে L2 থেকে সরিয়ে (move) L1-এ আনতে হয়, L2-তে আর থাকবে না। এই “সরানো” প্রক্রিয়া (swap) বাস্তবায়ন করতে বাড়তি bus transaction আর নিয়ন্ত্রণ-যুক্তি (control logic) লাগে — inclusive-এ যেখানে “শুধু কপি রেখে দাও, বাদ দিয়ো না” যথেষ্ট ছিল।

৩. Non-inclusive — বাস্তবসম্মত মাঝামাঝি

Non-inclusive (পুরো নাম প্রায়ই “NINE” — Non-Inclusive Non-Exclusive) hierarchy-তে কোনো গ্যারান্টিই নেই — একটা line L1-এ থাকতে পারে, L2-তেও থাকতে পারে, দুইটাতেই থাকতে পারে, অথবা শুধু L2-তে থাকতে পারে — hardware কোনো কড়া নিয়ম বজায় রাখে না।

সুবিধা: inclusive-এর মতো জোর করে duplicate রাখতে হয় না (তাই কিছুটা capacity বাঁচে), আবার exclusive-এর মতো প্রতিটা miss-এ বাধ্যতামূলক move করতে হয় না (সরল replacement — miss হলে শুধু নতুন লাইন আনো, পুরনোটা থাকুক বা না থাকুক ভেতরের level-এ, কেউ পরোয়া করে না)।

দাম: coherence checking-এর জন্য inclusive-এর “শুধু L3 দেখলেই যথেষ্ট” সুবিধা হারিয়ে যায় (নিচের hood অংশে বিস্তারিত) — একটা আলাদা, explicit tracking কাঠামো (snoop filter/directory) লাগে সেই সুবিধা ফিরে পেতে।

INCLUSIVE                    EXCLUSIVE                    NON-INCLUSIVE
┌─────────────────┐          ┌─────────────────┐          ┌─────────────────┐
│ L3: A B C D E F  │          │ L3: C D E F      │          │ L3: A C D E F     │
│ L2: A B C        │◄─কপি     │ L2: A B          │◄─move     │ L2: A B          │  কোনো
│ L1: A            │◄─কপি     │ L1: A            │◄─move     │ L1: A            │  গ্যারান্টি নেই
└─────────────────┘          └─────────────────┘          └─────────────────┘
A সব জায়গায় duplicate         A শুধু L1-এ, অন্য কোথাও না      A দুই জায়গায়, কিন্তু
L1⊆L2⊆L3 কড়াভাবে সত্য         প্রতিটা line ঠিক এক জায়গায়      সেটা নিয়মের অংশ না, কাকতাল
তিন ধরনের inclusion policy — একই তিন-level hierarchy, ভিন্ন সম্পর্ক।

ভেতরে কী ঘটছে

কেন inclusion policy coherence-এর জন্য এত গুরুত্বপূর্ণ

এই অংশটাই এই লেসনের কেন্দ্রীয় প্রকৌশলগত যুক্তি, আর পরের লেসনের (cache-coherence-mesi) ভিত্তি।

কল্পনা করুন একটা ৮-core চিপ, প্রতিটা core-এর নিজস্ব private L1+L2, একটা shared L3। Core A যখন memory address X-এ লিখতে চায়, coherence protocol-কে (পরের লেসনের বিষয়) নিশ্চিত করতে হবে অন্য কোনো core-এর private cache-এ X-এর পুরনো (stale) কপি নেই — থাকলে সেটা invalidate করতে হবে।

নিষ্পাপ (naive) সমাধান — সবাইকে জিজ্ঞেস করা

সবচেয়ে সরল উপায়: A প্রতিটা অন্য core-কে (B, C, D, E, F, G, H) জিজ্ঞেস করবে “তোমার L1/L2-তে X আছে কি?” — এটাকে বলে broadcast snooping। কিন্তু এটা core সংখ্যার সাথে সরাসরি স্কেল করে — ৮ core মানে ৭টা প্রশ্ন প্রতিটা লেখার জন্য, ৬৪ core মানে ৬৩টা প্রশ্ন। বাস্তব বড় multi-core/multi-socket সিস্টেমে এই broadcast ট্রাফিক দ্রুত অসহনীয় হয়ে ওঠে।

Inclusive L3-এর সমাধান — “L3 দেখলেই যথেষ্ট”

যদি L3 inclusive হয় (L1⊆L2⊆L3L1 \subseteq L2 \subseteq L3 সব core জুড়ে), তাহলে একটা চমৎকার logical শর্টকাট পাওয়া যায়:

যদি X L3-তে না থাকে, X নিশ্চিতভাবে কোনো core-এর L1/L2-তেও নেই।

কারণ — inclusion গ্যারান্টি করে L1/L2-তে যা কিছু আছে তার সবই L3-তে থাকতেই হবে; তাই বিপরীতক্রমে, L3-তে না থাকলে ভেতরে কোথাও থাকা যুক্তিগতভাবে অসম্ভব। Core A-কে তাই আর সবাইকে জিজ্ঞেস করতে হয় না — শুধু একবার L3 চেক করলেই যথেষ্ট।

আর যদি X L3-তে থাকে? তখনও পুরো broadcast লাগে না — বাস্তব inclusive ডিজাইনে প্রতিটা L3 line-এর সাথে অতিরিক্ত কিছু বিট রাখা হয়, যাকে বলে presence bits (বা core-valid bits) — প্রতিটা core-এর জন্য ১ বিট, বলে দেয় “এই core-এর private cache-এ এই line-এর কপি থাকতে পারে কি না”। A শুধু সেই presence bits পড়ে বলতে পারে ঠিক কোন কোন core-কে invalidate message পাঠাতে হবে — বাকিদের বিরক্ত করার দরকার নেই।

একটা লেখা (write) request-এর coherence path — inclusive L3-এর সুবিধা
  1. Core A লিখতে চায় ঠিকানা X-এনিশ্চিত হতে হবে অন্য কোনো core-এর কাছে stale কপি নেই
  2. L3-এ X-এর entry চেকinclusion গ্যারান্টি: এখানে না থাকলে কোথাও নেই
  3. যদি L3-এ না থাকেসরাসরি নিশ্চিত — কোনো broadcast লাগবে না, A নিরাপদে লিখতে পারে
  4. যদি L3-এ থাকে — presence bits পড়াধরুন bits বলছে শুধু core C আর F-এর কাছে সম্ভাব্য কপি আছে
  5. শুধু C আর F-কে targeted invalidateবাকি ৫টা core-কে (B, D, E, G, H) কোনো বার্তাই পাঠানো হয় না

দামের আরেকটা দিক — Back-invalidation

Inclusion বজায় রাখতে গেলে একটা কম-আলোচিত কিন্তু বাস্তব দাম আছে। যদি L3 (capacity সীমিত থাকায়) একটা line evict করতে বাধ্য হয়, inclusion নিয়ম রক্ষা করতে সেই একই line-এর কপি যদি কোনো core-এর private L1/L2-তে থেকে থাকে, সেটাকেও জোর করে invalidate করতে হয় — এমনকি যদি সেই core এখনো সক্রিয়ভাবে line-টা ব্যবহার করছে! এটাকে বলে back-invalidation।

L3 evict করে X  ⟹  সব core-এর L1/L2-তে X-ও invalidate (ব্যবহার করছে কি না তা না দেখেই)\text{L3 evict করে X} \implies \text{সব core-এর L1/L2-তে X-ও invalidate (ব্যবহার করছে কি না তা না দেখেই)}

এটা একটা প্রকৃত পারফরম্যান্স-দাম — একটা core-এর জন্য এখনো “গরম” (hot, ঘন ঘন ব্যবহৃত) একটা L1 line শুধু এই কারণে হারিয়ে যেতে পারে যে L3 (যেটা অনেক বেশি core-এর ডেটা একসাথে ধরে রাখে, তাই বেশি চাপে থাকে) জায়গার প্রয়োজনে সেটা বাদ দিয়েছে। এটাই একটা বড় কারণ কেন কিছু আধুনিক server-grade ডিজাইন (realworld অংশে বিস্তারিত) কড়া inclusive থেকে সরে non-inclusive-এ চলে গেছে।

Victim Cache — একটা ছোট, চতুর মধ্যপন্থা

cache-organization লেসনের realworld অংশে সংক্ষেপে উল্লেখ করা হয়েছিল একটা victim cache-এর কথা — এখানে একটু বিস্তারিত করি। এটা একটা ছোট (সাধারণত মাত্র ৪-১৬ entry), fully-associative বাফার, যেটা L1 থেকে সদ্য evict হওয়া line-গুলো সাময়িকভাবে ধরে রাখে, তারা L2-তে পৌঁছানোর আগে বা পাশাপাশি।

উদ্দেশ্য: যদি L1 (প্রায়ই low-associativity বা এমনকি direct-mapped, latency budget-এর কারণে, cache-organization-এর আলোচনা মনে করুন) একটা conflict miss-এ কোনো line evict করে ফেলে, কিন্তু সেই line অচিরেই আবার দরকার পড়ে (যা conflict miss-এ প্রায়ই ঘটে, কারণ সমস্যাটা capacity না, শুধু index-সংঘর্ষ), victim cache-এ সেটা এখনো পাওয়া যেতে পারে — পুরো L2/L3 পর্যন্ত না গিয়েই। এটা exclusive hierarchy-র কিছুটা সুবিধা (recently-evicted ডেটা হারিয়ে না যাওয়া) দেয়, fully exclusive ডিজাইনের পূর্ণ জটিলতা ছাড়াই — শুধু একটা ছোট, সস্তা বাফারের মাধ্যমে।

Snoop Filter — inclusion ছাড়াই coherence-শর্টকাট ফিরে পাওয়া

Non-inclusive hierarchy inclusion-এর “L3 দেখলেই যথেষ্ট” সুবিধা হারায় (পরের লেসনের বিষয়, কিন্তু এখানেই ভিত্তিটা বোঝা দরকার)। কিন্তু coherence-checking সহজ রাখার চাহিদা তো থেকেই যায় — সমাধান একটা আলাদা, dedicated snoop filter (কখনো “directory” নামেও পরিচিত) কাঠামো।

Snoop filter মূলত inclusive L3-এর presence-bits ধারণাটাকে cache থেকে আলাদা করে ফেলে — এটা নিজে কোনো ডেটা রাখে না, শুধু “কোন address কোন core-এর private cache-এ থাকতে পারে” এই তথ্যটা একটা compact টেবিলে রাখে। ফলাফল: inclusion-এর capacity-দাম (ডেটা duplicate রাখা) ছাড়াই, coherence-checking-এর গতির সুবিধা (broadcast এড়ানো) ধরে রাখা যায় — দামটা এখন শুধু এই ছোট directory-র জন্য বাড়তি silicon area, পুরো L3-এর একটা অংশ উৎসর্গ করার বদলে।

Inclusive L3: presence bits⊂L3 নিজেই (বিনামূল্যে, কিন্তু capacity-দামসহ)\text{Inclusive L3: presence bits} \subset \text{L3 নিজেই (বিনামূল্যে, কিন্তু capacity-দামসহ)} Snoop filter: presence tracking⊥L3 (আলাদা structure, capacity-দাম নেই, কিন্তু নতুন silicon area লাগে)\text{Snoop filter: presence tracking} \perp \text{L3 (আলাদা structure, capacity-দাম নেই, কিন্তু নতুন silicon area লাগে)}

উদাহরণ

সংখ্যায় — effective capacity-র পার্থক্য

ধরুন একটা ৮-core চিপ, প্রতিটা core-এর L2 = ১ MB, shared L3 = ৩২ MB।

Inclusive hierarchy-তে (worst-case ধরে, প্রতিটা core-এর পুরো L2 ভরা এবং সবই L3-তেও duplicate থাকতে বাধ্য):

L3-তে দখলকৃত (duplicate) জায়গা=8×1 MB=8 MB\text{L3-তে দখলকৃত (duplicate) জায়গা} = 8 \times 1\text{ MB} = 8\text{ MB} L3-এর কার্যকর, অনন্য capacity=32 MB−8 MB=24 MB\text{L3-এর কার্যকর, অনন্য capacity} = 32\text{ MB} - 8\text{ MB} = 24\text{ MB} মোট usable (L2+L3 unique)=8 MB (L2)+24 MB (L3-এর বাকি)=32 MB\text{মোট usable (L2+L3 unique)} = 8\text{ MB (L2)} + 24\text{ MB (L3-এর বাকি)} = 32\text{ MB}

লক্ষ্য করুন — L2-এর ডেটা L3-তেও আছে বলে, মোট unique capacity আসলে L3-এর আকারের সমানই থেকে যায় (৩২ MB), L2-এর ৮ MB নতুন কিছু যোগ করে না worst-case-এ, কারণ সেটা শুধু L3-এর একটা অংশের প্রতিফলন।

Exclusive hierarchy-তে (একই আকার ধরে, কোনো duplication নেই):

মোট usable (L2+L3 unique)=8 MB (L2)+32 MB (L3)=40 MB\text{মোট usable (L2+L3 unique)} = 8\text{ MB (L2)} + 32\text{ MB (L3)} = 40\text{ MB}

পার্থক্য: 40−32=8 MB40 - 32 = 8\text{ MB} বাড়তি কার্যকর capacity, exclusive ডিজাইনে, ঠিক একই hardware আকারে — কোনো বাড়তি transistor ছাড়াই, শুধু নীতি পরিবর্তন করে। এটাই ঠিক সেই যুক্তি যা AMD তাদের Zen-গোত্রের L2/L3 সম্পর্ককে (মূলত exclusive/victim-ধাঁচের, realworld অংশে বিস্তারিত) ডিজাইন করার সময় ব্যবহার করেছে — বিশেষত 3D V-Cache-এর মতো ডিজাইনে, যেখানে L3-এর প্রতিটা অতিরিক্ত MB প্রকৃতপক্ষে নতুন, অনন্য ডেটা ধরে রাখা জরুরি, শুধু L2-এর প্রতিধ্বনি না।

একটা L1 miss-এর সম্পূর্ণ ট্রেস, inclusion policy-সহ

L1 miss → L2 hit, দুই ভিন্ন inclusion policy-তে ভিন্ন আচরণ
  1. CPU address X চায়L1 miss — X নেই L1-এ
  2. L2 lookupX পাওয়া গেল L2-তে (hit)
  3. INCLUSIVE হলেX-এর একটা কপি L1-এ বসানো হয়; L2-এর কপি থেকেই যায় — কোনো removal নেই
  4. EXCLUSIVE হলেX L2 থেকে সরিয়ে (move) L1-এ আনা হয় — L2-তে X আর থাকবে না, একটা বাড়তি bus/control transaction লাগে
  5. NON-INCLUSIVE হলেবাস্তবায়ন-নির্ভর — কপি রাখতেও পারে, নাও পারে; কোনো hardware গ্যারান্টি নেই কোনটা হবে

নিজে চালিয়ে দেখুন

EXPERIMENT

নিজের মেশিনে L1/L2/L3-এর আসল সীমানা মাপুন — cache latency ladder

Python 3 বা C (C-তে পার্থক্য আরও পরিষ্কার)· ২০ মিনিট

ধারণা: ক্রমান্বয়ে বড় হতে থাকা array-তে বারবার sequential access করে, প্রতিটা আকারে গড় per-access সময় মাপা — যেখানে array cache boundary পার হবে, সেখানেই latency-তে একটা স্পষ্ট “লাফ” দেখা যাবে।

import time
import array

def measure(size_kb, iterations=2_000_000):
    n = (size_kb * 1024) // 8
    buf = array.array('q', [0] * n)
    stride = 8  # প্রতিটা int64 (৮ বাইট), একটানা sequential access
    idx = 0
    total = 0
    t0 = time.perf_counter()
    for _ in range(iterations):
        total += buf[idx % n]
        idx += 1
    dt = time.perf_counter() - t0
    return dt / iterations


sizes_kb = [8, 16, 32, 48, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768]
print(f"{'আকার (KB)':\<12}{'প্রতি-access সময় (ns)':\<20}")
for s in sizes_kb:
    t = measure(s)
    print(f"{s:\<12}{t * 1e9:.2f}")

প্রত্যাশিত ফলাফলের ধরন (হাইপোথেটিক্যাল, একটা ৪৮KB L1D / ১.২৫MB L2 / ৩০MB shared L3 মেশিনে):

আকার (KB)   প্রতি-access সময় (ns)
8           0.35
16          0.36
32          0.38
48          0.40      ← L1 সীমার কাছাকাছি
64          1.20      ← প্রথম লাফ: L1 → L2
128         1.35
512         1.40
1024        1.45      ← L2 সীমার কাছাকাছি
2048        4.10      ← দ্বিতীয় লাফ: L2 → L3
16384       4.50
32768       5.20      ← L3 সীমার কাছাকাছি
65536       18.00     ← তৃতীয় লাফ: L3 → DRAM

দুইটা স্পষ্ট “লাফ” পাওয়া উচিত — একটা L1→L2 সীমানায়, একটা L2→L3 সীমানায়, আর (যথেষ্ট বড় array দিলে) একটা তৃতীয়টা L3→DRAM সীমানায় (memory-hierarchy লেসনের experiment-এর সরাসরি সম্প্রসারণ, এবার নির্দিষ্টভাবে তিনটা on-chip level আলাদা করে চিহ্নিত করার জন্য)। নিজের মেশিনে lscpu বা (Linux-এ) cat /sys/devices/system/cpu/cpu0/cache/index*/size দিয়ে প্রকৃত আকার বের করে মিলিয়ে দেখুন — লাফগুলো ঠিক আপনার CPU-র নির্দিষ্ট L1/L2/L3 আকারের কাছাকাছি ঘটা উচিত।

Python-এর overhead precise cycle-level পরিমাপ ঢেকে দিতে পারে; C-তে (clock_gettime দিয়ে, memory-hierarchy লেসনের experiment-এর C কোডের ধাঁচে) পার্থক্য আরও তীক্ষ্ণভাবে দেখা যাবে।

এটা কী প্রমাণ করে

একটা বাস্তব CPU-তে ঠিক তিনটা (বা চারটা, DRAM-সহ) latency 'ধাপ' পাওয়া যায়, যেগুলোর আকার আপনার নিজের CPU-র প্রকৃত L1/L2/L3 আকারের সাথে মেলে — এই লেসনের সংখ্যাগুলো তত্ত্ব না, পরিমাপযোগ্য বাস্তবতা।

নিজে বানান

BUILD IT

Inclusive বনাম Exclusive — একটা toy দুই-level cache simulator

Python · ●●●●○
  1. একটা L1 (ছোট, ৪ entry) আর একটা L2 (বড়, ১৬ entry) dict-ভিত্তিক cache বানান — উভয়ই LRU replacement ব্যবহার করবে
  2. একটা INCLUSIVE mode লিখুন — L1-এ কোনো line আনলে সেটা L2-তেও রাখুন (কপি); L1 evict করলে L2 অপরিবর্তিত থাকুক; L2 evict করলে L1 থেকেও সেই line জোরপূর্বক সরান (back-invalidation)
  3. একটা EXCLUSIVE mode লিখুন — L1-এ কোনো line আনলে সেটা L2 থেকে সরান (যদি সেখানে থাকে); L1 evict করলে সেটা L2-তে ফিরিয়ে দিন (move-back)
  4. একই memory access trace (একটা address sequence) দুই mode-এ চালান, প্রতিটাতে গণনা করুন মোট hit/miss সংখ্যা আর "duplicate storage" (inclusive-এ) বনাম "move সংখ্যা" (exclusive-এ)
  5. একটা working-set আকারের trace দিয়ে দেখান exclusive mode একই hardware আকারে বেশি hit rate দেয়, বিনিময়ে বেশি অভ্যন্তরীণ move-অপারেশন লাগে

এই simulator ছোট আকারে ঠিক এই লেসনের central trade-off সংখ্যায় দেখাবে — inclusive mode-এ কম hit rate (কম কার্যকর capacity) কিন্তু সরল eviction, exclusive mode-এ বেশি hit rate কিন্তু প্রতিটা L1↔L2 movement-এ বাড়তি বুককিপিং।

from collections import OrderedDict

class TinyCache:
    def __init__(self, capacity):
        self.capacity = capacity
        self.store = OrderedDict()  # address -> True, LRU order বজায় রাখতে

    def has(self, addr):
        return addr in self.store

    def touch(self, addr):
        if addr in self.store:
            self.store.move_to_end(addr)

    def insert(self, addr):
        self.store[addr] = True
        self.store.move_to_end(addr)
        evicted = None
        if len(self.store) > self.capacity:
            evicted, _ = self.store.popitem(last=False)
        return evicted

    def remove(self, addr):
        if addr in self.store:
            del self.store[addr]


def run_inclusive(trace, l1_cap=4, l2_cap=16):
    l1, l2 = TinyCache(l1_cap), TinyCache(l2_cap)
    hits = misses = duplicates = 0
    for addr in trace:
        if l1.has(addr):
            l1.touch(addr); l2.touch(addr); hits += 1
        elif l2.has(addr):
            hits += 1
            l2.touch(addr)
            evicted_from_l1 = l1.insert(addr)   # L1-এ কপি, L2-তে থেকেই যায়
            duplicates += 1
        else:
            misses += 1
            l2.insert(addr)
            evicted_from_l1 = l1.insert(addr)   # নতুন line দুই জায়গাতেই
            duplicates += 1
    return hits, misses, duplicates


def run_exclusive(trace, l1_cap=4, l2_cap=16):
    l1, l2 = TinyCache(l1_cap), TinyCache(l2_cap)
    hits = misses = moves = 0
    for addr in trace:
        if l1.has(addr):
            l1.touch(addr); hits += 1
        elif l2.has(addr):
            hits += 1
            l2.remove(addr)                     # L2 থেকে সরানো (move)
            evicted_from_l1 = l1.insert(addr)
            if evicted_from_l1 is not None:
                l2.insert(evicted_from_l1)       # L1 থেকে বাদ পড়া line L2-তে ফিরে যায়
            moves += 1
        else:
            misses += 1
            evicted_from_l1 = l1.insert(addr)
            if evicted_from_l1 is not None:
                l2.insert(evicted_from_l1)
    return hits, misses, moves

hits/misses-এর পার্থক্য ছোট trace-এ নাও দেখা যেতে পারে (dataset যথেষ্ট বড় আর working-set L1+L2 সীমার কাছাকাছি না হলে) — একটা ইচ্ছাকৃতভাবে “L1+L2-এর সমান working set, বারবার পুনরাবৃত্ত” trace বানিয়ে (যেমন ২০টা distinct address, বারবার cycle করে) পার্থক্যটা স্পষ্ট করে তুলুন।

বাস্তব সিস্টেমে

বাস্তব CPU-তে inclusion policy-র সিদ্ধান্ত

  • Intel Sandy Bridge থেকে Skylake client (২০১১-২০১৭ ঘরানা) — কড়াভাবে inclusive L3, প্রতি-line presence bits-সহ, ঠিক এই লেসনের hood অংশে বর্ণিত কৌশল ব্যবহার করে coherence snoop ট্র্যাফিক কমানোর জন্য। একটা দীর্ঘ-স্থায়ী, প্রমাণিত ডিজাইন সিদ্ধান্ত।

  • Intel Skylake-SP / Ice Lake-SP (server, ~২০১৭ থেকে) — সরে গেছে non-inclusive L3-তে, সাথে একটা আলাদা, explicit snoop filter কাঠামো (presence-tracking-এর কাজ এখন inclusion-এর বদলে একটা dedicated structure করে)। কারণ: server চিপে core সংখ্যা অনেক বেশি (৩০+ পর্যন্ত), তাই প্রতিটা core-এর L2 (যথেষ্ট বড়, ~১ MB+) L3-তে বাধ্যতামূলক duplicate রাখলে capacity নষ্টের পরিমাণ আর সহনীয় থাকে না — এই লেসনের example অংশের ৮ MB নষ্টের হিসাব আরও অনেক core-এ আরও বড় আকারে ঘটত।

  • AMD Zen পরিবার (Zen থেকে Zen 4) — L2 আর L3-এর সম্পর্ক মূলত victim/mostly-exclusive ধাঁচের — একটা line সাধারণত প্রথমে L2-তে থাকে, L2 থেকে evict হলেই L3-তে যায় (victim cache-এর একটা বড় আকারের প্রয়োগ, যেমনটা hood অংশে আলোচিত)। এটা AMD-কে তুলনামূলক ছোট L2 রেখেও বড়, কার্যকর, প্রায়-সম্পূর্ণ-unique L3 capacity দিতে সাহায্য করে।

  • AMD 3D V-Cache (Zen 3/4 X3D চিপ) — L3-এর উপর সরাসরি আরেকটা সিলিকন স্তর stack করে বিশাল L3 (যেমন ৯৬ MB পর্যন্ত) যোগ করা — এই বিনিয়োগ সবচেয়ে বেশি লাভজনক হয় ঠিক তখনই যখন সেই বাড়তি L3 capacity প্রকৃতপক্ষে নতুন, অনন্য ডেটা ধরে রাখে (mostly-exclusive ডিজাইনের সরাসরি সুবিধা) — যদি এটা inclusive হতো আর শুধু L2-এর প্রতিধ্বনি বহন করত, এই বিশাল বিনিয়োগের একটা বড় অংশ নষ্ট হয়ে যেত।

  • Apple M-সিরিজ — traditional L1/L2/L3 কাঠামো থেকে সরে গিয়ে একটা per-cluster shared L2 (CPU core-দের একটা গুচ্ছ একটাই বড় L2 শেয়ার করে) আর একটা চিপ-ব্যাপী System Level Cache (SLC) ব্যবহার করে, যা CPU, GPU, Neural Engine সবার মধ্যে shared — একটা সম্পূর্ণ ভিন্ন hierarchy shape, প্রমাণ করে “L1/L2/L3” কোনো বাধ্যতামূলক টেমপ্লেট না।

  • ARM DynamIQ/big.LITTLE cluster — সাধারণত প্রতিটা cluster-এর নিজস্ব private L2 (per-core) আর একটা cluster-shared L3, বেশিরভাগ ডিজাইনে non-inclusive বা exclusive-ধাঁচের সম্পর্ক, ARM নিজে vendor-দের সুনির্দিষ্ট নীতি বেছে নেওয়ার স্বাধীনতা দেয় (ISA-র মতো coherence policy-ও ARM কড়াভাবে নির্দিষ্ট করে না, বরং একটা framework দেয়)।

  • TLB, আবারও fully-associative-এর উদাহরণ — cache-organization লেসনে TLB-কে fully-associative-এর একটা বাস্তব উদাহরণ হিসেবে দেখানো হয়েছিল; একইভাবে multi-level TLB hierarchy-তেও (L1 TLB, L2 TLB) inclusion policy-র প্রশ্ন ওঠে, একই যুক্তিতে — যদিও TLB miss-এর দাম (page table walk) এত বেশি যে বেশিরভাগ ডিজাইন এখানে সরলতা (non-inclusive) বেছে নেয়।

যে ভুলগুলো সবাই করে

“Inclusive cache মানে মোট usable capacity সবসময় L1+L2+L3-এর সরল যোগফল।”

উল্টো — inclusive design-এ মোট কার্যকর, অনন্য (unique) capacity প্রায়ই শুধু সবচেয়ে বড় (বাইরের) level-এর সমান, কারণ ভেতরের level-গুলোর ডেটা বাইরের level-এরই একটা subset/প্রতিফলন, নতুন কিছু যোগ করে না worst-case-এ। এই লেসনের example অংশের হিসাব (৩২ MB, যোগফল ৪০ MB না) এটা সরাসরি দেখায়। Exclusive design-এই বরং প্রকৃত যোগফল (L1+L2+L3) কার্যকর capacity হিসেবে পাওয়া যায়।

“Exclusive cache স্ট্রিক্টলি বেশি ভালো, তাই সব আধুনিক CPU exclusive ব্যবহার করা উচিত।”

Exclusive design বেশি effective capacity দেয়, ঠিক — কিন্তু বিনিময়ে প্রতিটা L1↔L2 miss/hit-এ বাড়তি “move” অপারেশন (আর তার নিয়ন্ত্রণ-যুক্তি জটিলতা) লাগে, আর সবচেয়ে গুরুত্বপূর্ণ — inclusive-এর “শুধু L3 দেখলেই যথেষ্ট” coherence-শর্টকাট হারিয়ে যায়। Intel দীর্ঘদিন client CPU-তে inclusive বেছে নিয়েছিল ঠিক এই coherence-সরলতার জন্য, capacity-অপচয় জেনেও। এটা একটা প্রকৃত trade-off, কোনো একমুখী “ভালো/খারাপ” রায় না — বাস্তবে core সংখ্যা, workload ধরন, আর coherence ট্র্যাফিকের গুরুত্ব অনুযায়ী সিদ্ধান্ত ভিন্ন হয় (Intel-এর নিজেরই client বনাম server ডিজাইনে ভিন্ন সিদ্ধান্ত এর প্রমাণ)।

“Inclusion policy শুধু capacity-efficiency-র প্রশ্ন, coherence protocol-এর সাথে সরাসরি সম্পর্ক নেই — সেগুলো আলাদা বিষয়।”

এই লেসনের সবচেয়ে গুরুত্বপূর্ণ সংশোধন। Inclusion policy-র মূল ঐতিহাসিক ও প্রকৌশলগত কারণই হলো coherence checking সহজ করা — hood অংশের presence-bits যুক্তি দেখায় ঠিক কীভাবে inclusive L3 broadcast snoop এড়িয়ে targeted invalidate সম্ভব করে। Capacity-efficiency একটা গুরুত্বপূর্ণ পাল্টা-বিবেচনা, কিন্তু coherence-সরলতাই মূল চালিকাশক্তি ছিল বহু বছরের inclusive-design সিদ্ধান্তের পেছনে — পরের লেসন (cache-coherence-mesi) এই সংযোগ সম্পূর্ণভাবে ব্যাখ্যা করবে।

বুঝেছেন কি না দেখুন

1

তিনটা inclusion policy-র নাম বলুন এবং প্রতিটার একটা এক-লাইনের সংজ্ঞা দিন।

স্মরণ

Inclusive — একটা outer level (L2, L3) সবসময় তার সব inner level-এর (L1, L2) সব line-এর কপি ধরে রাখে: L1⊆L2⊆L3L1 \subseteq L2 \subseteq L3।

Exclusive — একটা line ঠিক একটা level-এ থাকে, কখনো একাধিক level-এ একসাথে না; L1↔L2-এর মধ্যে ডেটা move হয়, কপি না।

Non-inclusive — কোনো কড়া গ্যারান্টি নেই; একটা line দুই জায়গাতেও থাকতে পারে, এক জায়গাতেও থাকতে পারে — hardware কোনো নিয়ম বজায় রাখে না।

2

একটা inclusive L3-তে একটা line X evict হয়ে গেল capacity চাপের কারণে। Core ৩-এর L1-এ তখনও X সক্রিয়ভাবে ব্যবহৃত হচ্ছিল। এর পরে ঠিক কী ঘটবে, আর একে কী বলা হয়?

প্রয়োগ

Inclusion গ্যারান্টি (L1⊆L2⊆L3L1 \subseteq L2 \subseteq L3) বজায় রাখতে, L3 থেকে X চলে যাওয়ার সাথে সাথেই Core ৩-এর L1 (এবং L2, যদি সেখানেও থাকে)-এ থাকা X-এর কপিও জোর করে invalidate করতে হবে — এমনকি Core ৩ এখনো সক্রিয়ভাবে ব্যবহার করছিল সত্ত্বেও। এই ঘটনাকে বলা হয় back-invalidation। এর ফলে Core ৩ পরবর্তী access-এ একটা অপ্রয়োজনীয় miss পাবে, যা শুধুমাত্র L3-এর capacity চাপের কারণে ঘটল, Core ৩-এর নিজের access pattern-এর কোনো দোষ ছাড়াই — এটাই strict inclusion-এর একটা প্রকৃত, লুকানো পারফরম্যান্স-দাম।

3

যুক্তি দিয়ে ব্যাখ্যা করুন — কেন “যদি line X shared L3-তে না থাকে, তাহলে X কোনো core-এর private L1/L2-তেও থাকতে পারে না” — এই সিদ্ধান্তটা শুধু inclusive hierarchy-তেই বৈধ, exclusive বা non-inclusive-এ না।

যুক্তি

এই সিদ্ধান্তটা সরাসরি inclusion-এর সংজ্ঞা থেকে আসে: inclusive মানে L1⊆L2⊆L3L1 \subseteq L2 \subseteq L3 — অর্থাৎ L1/L2-এ যা কিছু থাকতে পারে তার সব সম্ভাব্য সদস্যই L3-এর একটা subset। যুক্তি হলো contrapositive: যদি x∉L3x \notin L3 হয়, আর L1⊆L3L1 \subseteq L3 সত্য হয়, তাহলে x∉L1x \notin L1 হতেই হবে (নাহলে subset সম্পর্ক ভেঙে যেত)।

Exclusive-এ এই সম্পর্কটাই উল্টো — L1-এ যা আছে তা ইচ্ছাকৃতভাবে L3-তে নেই (L1∩L3=∅L1 \cap L3 = \emptyset), তাই “L3-তে নেই” থেকে “L1-এও নেই” বলা যায় না — বরং প্রায়ই উল্টোটাই সত্য (L3-তে না থাকা মানে হয়তো L1-এই আছে)।

Non-inclusive-এ কোনো গ্যারান্টিই নেই, তাই “L3-তে নেই” থেকে L1/L2 সম্পর্কে কিছুই logically deduce করা যায় না — precisely এই কারণেই non-inclusive ডিজাইনে coherence-এর জন্য L3-নির্ভর শর্টকাট কাজ করে না, একটা আলাদা explicit snoop filter লাগে।

4

একটা ৬৪-core server চিপ ডিজাইন করছেন, প্রতিটা core-এর L2 = ২ MB, shared L3 = ১২৮ MB। Inclusive না non-inclusive বেছে নেবেন, আর কেন? সংখ্যা দিয়ে যুক্তি দিন।

ডিজাইন

Non-inclusive (বা exclusive-ধাঁচের) — ৬৪ core-এ inclusion-এর capacity-দাম বিশাল আকার ধারণ করে।

যদি সব core-এর L2 একসাথে ভরা থাকে (worst-case, কিন্তু বড় server workload-এ বাস্তবসম্মত):

L3-তে duplicate জায়গা=64×2 MB=128 MB\text{L3-তে duplicate জায়গা} = 64 \times 2\text{ MB} = 128\text{ MB}

এটা পুরো L3 (১২৮ MB)-র সমান — অর্থাৎ worst-case-এ inclusive ডিজাইনে L3-এর কার্যকর, অনন্য capacity প্রায় শূন্যের কাছাকাছি নেমে যেতে পারে, কারণ পুরো L3 শুধু L2-গুলোর প্রতিধ্বনি বহন করছে! এটা সম্পূর্ণ অগ্রহণযোগ্য।

এই কারণেই বাস্তব উচ্চ-core-count server চিপ (Intel Skylake-SP-উত্তর প্রজন্ম) ঠিক এই যুক্তিতে non-inclusive-এ সরে গেছে, coherence-এর জন্য inclusion-এর বদলে একটা আলাদা, dedicated snoop filter structure ব্যবহার করে — coherence-সরলতার সুবিধা (কিছুটা ভিন্নভাবে হলেও) ধরে রেখে capacity-দাম এড়ানো।

5

একটা ৪-core চিপ, প্রতিটার L2 = ৫১২ KB, shared L3 = ৮ MB। Inclusive আর exclusive — দুই ক্ষেত্রেই মোট কার্যকর (unique) capacity হিসাব করুন (worst-case ধরে সব L2 ভরা)।

প্রয়োগ

Inclusive: L3-তে duplicate=4×512 KB=2048 KB=2 MB\text{L3-তে duplicate} = 4 \times 512\text{ KB} = 2048\text{ KB} = 2\text{ MB} L3-এর অনন্য অংশ=8 MB−2 MB=6 MB\text{L3-এর অনন্য অংশ} = 8\text{ MB} - 2\text{ MB} = 6\text{ MB} মোট usable=2 MB (L2, সবই L3-এ duplicate)+6 MB (L3-এর বাকি)=8 MB (worst-case-এ শুধু L3-এর সমান)\text{মোট usable} = 2\text{ MB (L2, সবই L3-এ duplicate)} + 6\text{ MB (L3-এর বাকি)} = 8\text{ MB (worst-case-এ শুধু L3-এর সমান)}

Exclusive: মোট usable=2 MB (L2)+8 MB (L3)=10 MB\text{মোট usable} = 2\text{ MB (L2)} + 8\text{ MB (L3)} = 10\text{ MB}

পার্থক্য: 10−8=2 MB10 - 8 = 2\text{ MB} বাড়তি কার্যকর capacity exclusive-এ, ঠিক একই মোট hardware (L2+L3) আকারে — এই লেসনের মূল সংখ্যাগত দাবির আরেকটা concrete উদাহরণ।

6

Snoop filter আর inclusive L3 — দুইটাই coherence checking-এ “শুধু একটা জায়গা দেখলেই যথেষ্ট” সুবিধা দেয়। তাহলে snoop filter থাকলে কেন সব CPU inclusive L3 বাদ দিয়ে সবসময় non-inclusive + snoop filter বেছে নেয় না?

যুক্তি

কারণ snoop filter কোনো “বিনামূল্যে উন্নতি” না — এটা inclusion-এর capacity-দামকে একটা ভিন্ন দামে (dedicated hardware area, design জটিলতা, আর filter নিজেই ভুল হতে পারা — filter miss হলে conservative-ভাবে broadcast-এ ফিরে যেতে হয়) রূপান্তরিত করে, দাম শূন্য করে না। ছোট core-সংখ্যার চিপে (৪-৮ core) inclusion-এর capacity-অপচয় তুলনামূলক ছোট (এই লেসনের example অংশের হিসাবে ২ MB, সামান্য) — সেখানে inclusive L3-এর সরলতা (আলাদা কোনো নতুন hardware কাঠামো ডিজাইন/verify করতে হয় না) prefereable। বড় core-সংখ্যায় (৩০+) সেই একই অপচয় বিশাল হয়ে ওঠে (৬৪-core উদাহরণে প্রায় পুরো L3), তখনই snoop filter-এর বাড়তি জটিলতা গ্রহণযোগ্য হয়ে ওঠে। এটা তাই কোনো এক-দিকের “উন্নতি” না, বরং core-সংখ্যা অনুযায়ী trade-off-এর ভারসাম্য বিন্দু সরে যাওয়া — ঠিক Intel client (কম core, inclusive) বনাম server (বেশি core, non-inclusive+filter)-এর বাস্তব সিদ্ধান্তের পার্থক্য যা ব্যাখ্যা করে।

এরপর কী

আমরা এখন জানি একাধিক cache level কীভাবে একে অপরের সাথে সম্পর্কিত হতে পারে (inclusive, exclusive, non-inclusive), আর কেন সেই সম্পর্কটা coherence checking-এর কঠিনতা সরাসরি নির্ধারণ করে — inclusive L3 একটা “ফ্রি” filter দেয়, বাকিদের একটা আলাদা snoop filter বানাতে হয়।

কিন্তু আমরা এখনো “coherence checking” জিনিসটা কী সেটাই বিস্তারিত বলিনি — শুধু বলেছি এটা দরকার কারণ private L1/L2-তে একই address-এর একাধিক copy থাকতে পারে। পরের লেসনে (cache-coherence-mesi) আমরা ঠিক সেই প্রশ্নে যাব — MESI protocol, চারটা state (Modified, Exclusive, Shared, Invalid), আর একটা সম্পূর্ণ worked example যেখানে দুইটা core একই address পড়ে, তারপর একটা core লেখে, আর প্রতিটা ধাপে কী inter-core message পাঠানো হয় তা দেখব — ঠিক এই লেসনের presence-bits আর targeted-invalidate ধারণার উপর দাঁড়িয়ে।

আরও পড়ুন

  • Computer Architecture: A Quantitative Approach, Appendix B.3 — Hennessy & Patterson · Multi-level cache inclusion policy-র প্রামাণ্য আলোচনা
  • An Analysis of Cache Inclusion Policies for Multi-Level Cache Hierarchies — Jaleel, Eden, Steely (Intel/AMD research context) · Inclusive/exclusive/non-inclusive trade-off-এর গবেষণাধর্মী বিশ্লেষণ
  • Software Optimization Guide for AMD Family 19h Processors — AMD · Zen-গোত্রের বাস্তব L2/L3 exclusion নীতির বিবরণ