পাঠ ৪২ · ৫৭-এর মধ্যে · মডিউল ৮
Home / Courses / Computer Architecture & Digital Logic / মাল্টি-লেভেল ক্যাশ

মাল্টি-লেভেল ক্যাশ (L1/L2/L3)

Multi-level cache — L1, L2, L3
৮ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • L1, L2, L3 ক্যাশের মধ্যে আকার, গতি ও শেয়ারিং-এর পার্থক্য
  • রিকার্সিভ মাল্টি-লেভেল AMAT সূত্র — কীভাবে একটি স্তরের AMAT পরের স্তরের "মিস পেনাল্টি" হয়ে যায়
  • একটি সম্পূর্ণ যাচাইকৃত উদাহরণে দ্বিতীয় স্তরের বাস্তব পারফরম্যান্স সুবিধা
  • কেন L3 সাধারণত একাধিক কোরের মধ্যে শেয়ার করা হয়, এবং এর ভবিষ্যৎ প্রভাব (M11/M52)

১ · কেন একাধিক স্তরের ক্যাশ দরকার

L38-L41-এ আমরা একটি একক ক্যাশ স্তর ধরে নিয়ে কাজ করেছি — CPU হিট বা মিস করে, মিস হলে সরাসরি মূল মেমরিতে যায়। কিন্তু বাস্তব CPU-তে একটি সমস্যা আছে: একটি ক্যাশকে দ্রুত রাখতে হলে সেটাকে ছোট রাখতে হয় (ছোট ক্যাশে ঠিকানা খোঁজা দ্রুত হয়), কিন্তু ছোট ক্যাশে হিট রেট কম থাকে। এই দ্বন্দ্ব সমাধানের বাস্তব উপায় হলো একাধিক স্তর ব্যবহার করা — প্রতিটি স্তর আগের স্তরের চেয়ে বড় কিন্তু কিছুটা ধীর।

L1 ক্যাশ — CPU কোরের একদম কাছে, সবচেয়ে ছোট (সাধারণত কয়েক দশ কিলোবাইট) ও সবচেয়ে দ্রুত — প্রায়ই ইনস্ট্রাকশন-ক্যাশ ও ডেটা-ক্যাশে ভাগ করা থাকে (L33-এর স্ট্রাকচারাল হ্যাজার্ড সমাধানের সরাসরি প্রয়োগ — আলাদা মেমরি মানেই ফেচ ও মেমরি-অ্যাক্সেস স্টেজ একসাথে চলতে পারে, একে অপরের সাথে বিরোধ না করে)। L2 ক্যাশ — L1-এর চেয়ে বড় (কয়েকশ কিলোবাইট থেকে কয়েক মেগাবাইট), কিছুটা ধীর, L1 মিস করলে দ্বিতীয় সুযোগ দেয়। L3 ক্যাশ (যখন থাকে) — আরও বড় (কয়েক মেগাবাইট থেকে কয়েক ডজন মেগাবাইট), আরও ধীর, এবং সাধারণত একাধিক CPU কোরের মধ্যে শেয়ার করা হয় — এই শেয়ারিং-ই M11-এর মাল্টিকোর আর্কিটেকচার ও L52-এর ক্যাশ কোহেরেন্স সমস্যার সরাসরি ভিত্তি।

CPU কোর L1-I ক্যাশ (ইনস্ট্রাকশন) L1-D ক্যাশ (ডেটা) L2 ক্যাশ (বড়, ধীর, প্রতি-কোর) L3 ক্যাশ (আরও বড়, শেয়ার্ড — একাধিক কোর) মূল মেমরি (DRAM)
প্রতিটি পরের স্তর আকারে বড়, কিন্তু গতিতে ধীর — মিস হলেই পরের স্তরে যাওয়া হয়, একেবারে মূল মেমরি পর্যন্ত।
L1 ক্যাশ
~কয়েক দশ KB · সবচেয়ে দ্রুত (১-২ সাইকেল) · প্রতি-কোর, প্রায়ই I/D আলাদা।
L2 ক্যাশ
~কয়েকশ KB - কয়েক MB · মাঝারি গতি (~১০ সাইকেল) · সাধারণত প্রতি-কোর।
L3 ক্যাশ
~কয়েক MB - কয়েক ডজন MB · ধীরতম ক্যাশ স্তর · সাধারণত সব কোরের মধ্যে শেয়ার্ড।

২ · রিকার্সিভ মাল্টি-লেভেল AMAT সূত্র

L41-এ আমরা একটি একক ক্যাশের AMAT বের করেছিলাম: $AMAT = HitTime + MissRate \times MissPenalty$। মাল্টি-লেভেল ক্যাশে এই একই ধারণা রিকার্সিভভাবে প্রয়োগ হয় — L1-এর "মিস পেনাল্টি" এখন আর সরাসরি মূল মেমরির সময় নয়, বরং L2-এর নিজস্ব AMAT-ই সেই ভূমিকা নেয়:

$$AMAT_{total} = HitTime_{L1} + MissRate_{L1} \times (HitTime_{L2} + MissRate_{L2} \times MissPenalty_{L2\_to\_mem})$$

অর্থাৎ ভেতরের অংশ — $HitTime_{L2} + MissRate_{L2} \times MissPenalty_{L2\_to\_mem}$ — আসলে L2-এর নিজের AMAT-ই, যা L1-এর দৃষ্টিকোণ থেকে শুধু "L1 মিস হলে কত সময় লাগবে" হিসেবে কাজ করছে। তিন স্তর থাকলে এই একই প্যাটার্ন আরও একবার ভেতরে প্রয়োগ হতো (L3-এর AMAT হয়ে যেত L2-এর মিস পেনাল্টি)।

উদাহরণ · Worked example

ধরা যাক L1: HitTime = ১ সাইকেল, MissRate = ০.১০ (১০%); L2: HitTime = ১০ সাইকেল, MissRate = ০.০৫ (L1-মিসের ৫% আবার L2-তেও মিস করে); মূল মেমরি পেনাল্টি = ১০০ সাইকেল। প্রথমে L2-এর কার্যকর খরচ বের করি: ১০ + ০.০৫×১০০ = ১৫ সাইকেল। এরপর সম্পূর্ণ AMAT: ১ + ০.১০×১৫ = ১ + ১.৫ = ২.৫ সাইকেল। নিচের কোড সেলে এটি প্রকৃতপক্ষে হিসাব করে যাচাই করা হয়েছে, পাশাপাশি L2 ছাড়া (সরাসরি মূল মেমরিতে যাওয়া) হাইপোথেটিক্যাল সিঙ্গেল-লেভেল AMAT-ও (১ + ০.১০×১০০ = ১১ সাইকেল) দেখানো হয়েছে — পার্থক্যটা দেখলেই বোঝা যায় L2 কতটা কার্যকর।

Python
# মাল্টি-লেভেল AMAT -- রিকার্সিভ সূত্র বাস্তবে গণনা করে যাচাই
def multi_level_amat(l1_hit_time, l1_miss_rate, l2_hit_time, l2_miss_rate, mem_penalty):
    """L2-এর নিজস্ব AMAT-ই L1-এর মিস পেনাল্টি হিসেবে কাজ করে -- এখানে সেই রিকার্সিভ সম্পর্ক"""
    l2_effective_cost = l2_hit_time + l2_miss_rate * mem_penalty
    amat_total = l1_hit_time + l1_miss_rate * l2_effective_cost
    return l2_effective_cost, amat_total

def single_level_amat(hit_time, miss_rate, mem_penalty):
    """L2 না থাকলে -- L1 মিস করলেই সরাসরি মূল মেমরিতে"""
    return hit_time + miss_rate * mem_penalty

l1_hit_time, l1_miss_rate = 1, 0.10
l2_hit_time, l2_miss_rate = 10, 0.05
mem_penalty = 100

l2_cost, amat_two_level = multi_level_amat(
    l1_hit_time, l1_miss_rate, l2_hit_time, l2_miss_rate, mem_penalty
)
amat_single = single_level_amat(l1_hit_time, l1_miss_rate, mem_penalty)

print(f"L2-এর কার্যকর খরচ = {l2_hit_time} + {l2_miss_rate} × {mem_penalty} = {l2_cost} সাইকেল")
print(f"দুই-স্তরের AMAT   = {l1_hit_time} + {l1_miss_rate} × {l2_cost}  = {amat_two_level} সাইকেল")
print(f"L2 ছাড়া AMAT     = {l1_hit_time} + {l1_miss_rate} × {mem_penalty} = {amat_single} সাইকেল")
print()
speedup_cycles = amat_single - amat_two_level
speedup_pct = (1 - amat_two_level / amat_single) * 100
print(f"L2 যোগ করায় AMAT কমেছে {speedup_cycles} সাইকেল ({speedup_pct:.1f}% দ্রুত)")

    
লক্ষ্য করুন — L2-এর MissRate (০.০৫) সরাসরি সামগ্রিক AMAT-কে গুণ করে না, বরং প্রথমে L1-এর MissRate (০.১০) দিয়ে "ওজন" করা হয়, কারণ L2 শুধু তখনই অ্যাক্সেস হয় যখন L1 ইতিমধ্যে মিস করেছে। এই "শর্তসাপেক্ষ" (conditional) সম্ভাবনার ধারণাটিই রিকার্সিভ AMAT সূত্রের মূল ভিত্তি — প্রতিটি পরের স্তর শুধু আগের স্তরের মিসের ভেতরেই "সক্রিয়" হয়।

৩ · L3 শেয়ারিং ও ভবিষ্যৎ প্রভাব

মাল্টিকোর CPU-তে (M11) প্রতিটি কোরের সাধারণত নিজস্ব প্রাইভেট L1 ও L2 থাকে, কিন্তু L3 সব কোরের মধ্যে শেয়ার্ড — এর একটি বাস্তব সুবিধা আছে: একটি কোর যদি কোনো ডেটা ইতিমধ্যে L3-এ এনে ফেলে, অন্য কোর সেটা আবার মূল মেমরি থেকে না এনেই ব্যবহার করতে পারে। কিন্তু এই একই শেয়ারিং একটি গুরুতর সমস্যারও জন্ম দেয় — যদি একাধিক কোরের প্রাইভেট L1/L2-এ একই ঠিকানার আলাদা কপি থাকে, আর একটি কোর সেটা পরিবর্তন করে, অন্য কোরগুলো কীভাবে জানবে তাদের কপি পুরনো হয়ে গেছে? এটিই L52-এর ক্যাশ কোহেরেন্স সমস্যা — এই পাঠের বিষয় নয়, তবে এর মূল কারণ এখানেই তৈরি হয়।

মূল কথা · Key takeaway

মাল্টি-লেভেল ক্যাশ "ছোট-কিন্তু-দ্রুত বনাম বড়-কিন্তু-ধীর" দ্বন্দ্বকে সমাধান করে একাধিক স্তর সাজিয়ে — প্রতিটি স্তর আগেরটার মিসের ভেতরে "সক্রিয়" হয়। রিকার্সিভ AMAT সূত্র এই পুরো ধারণাকে একটি নির্ভুল সংখ্যায় রূপান্তর করে, এবং একটি বাস্তব উদাহরণে দেখা গেছে মাত্র একটি L2 স্তর যোগ করলেই AMAT ১১ থেকে কমে ২.৫ সাইকেলে নেমে আসতে পারে।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ যদি একটি একক, অনেক বড় L1 ক্যাশ বানানো যায় যেটা L2-এর সমান বড়, তাহলে কি আলাদা L2 স্তরের আর দরকার নেই?

বাস্তবে না, কারণ ক্যাশের আকার বাড়লে তার অ্যাক্সেস টাইমও বাড়ে (বড় ক্যাশে সঠিক লাইন খুঁজে বের করতে বেশি তুলনা/সার্কিট লাগে)। যদি L1-কে L2-এর সমান বড় করা হয়, তখন সেটার HitTime-ও L2-এর কাছাকাছি ধীর হয়ে যাবে — অর্থাৎ প্রতিটি অ্যাক্সেসে (হিট হোক বা মিস) সেই ধীর সময়টাই দিতে হবে। ছোট-দ্রুত L1 + বড়-ধীর L2 আলাদা রাখাই ভালো, কারণ বেশিরভাগ অ্যাক্সেস (locality অনুযায়ী) L1-এই হিট করে দ্রুত সময়ে সারা হয়ে যায়, শুধু কম সংখ্যক মিসই ধীর L2-তে যায়।

প্র ০২ উপরের সূত্রে L2-এর MissRate (০.০৫) কেন সরাসরি সামগ্রিক AMAT-তে গুণ হয় না, বরং L1-এর MissRate (০.১০) দিয়ে প্রথমে "ওজন" করা হয়?

কারণ L2 আসলে সব অ্যাক্সেসে সক্রিয় হয় না — শুধু তখনই সক্রিয় হয় যখন L1 আগে থেকেই মিস করেছে। ১০টা অ্যাক্সেসের মধ্যে গড়ে মাত্র ১টা (MissRate ০.১০) L2 পর্যন্ত পৌঁছায়। তাই L2-এর নিজস্ব খরচ (১৫ সাইকেল) শুধু সেই ১০% ক্ষেত্রেই প্রযোজ্য — বাকি ৯০% ক্ষেত্রে L1 হিটেই কাজ শেষ। এই "শর্তসাপেক্ষ ওজন" ছাড়া হিসাব করলে AMAT-কে অন্যায্যভাবে বড় দেখাবে, যা বাস্তব CPU পারফরম্যান্সের সাথে মিলবে না।

প্র ০৩ L3 ক্যাশ সাধারণত একাধিক কোরের মধ্যে শেয়ার করা হয় কেন — প্রতিটি কোরের জন্য আলাদা প্রাইভেট L3 রাখলে সমস্যা কী হতো?

L3 ইতিমধ্যেই তুলনামূলক বড় ও ব্যয়বহুল (চিপের অনেকটা জায়গা নেয়)। প্রতিটি কোরের জন্য আলাদা প্রাইভেট L3 রাখলে একই ডেটা (যেমন শেয়ার্ড লাইব্রেরি কোড বা কমন ডেটা স্ট্রাকচার) একাধিক কপিতে ডুপ্লিকেট হয়ে থাকত, যা চিপের মূল্যবান জায়গার অপচয়। শেয়ার্ড L3 রাখলে একটি কোরের আনা ডেটা অন্য কোরও ব্যবহার করতে পারে, কার্যকর ক্যাশ স্পেস বেড়ে যায় — যদিও, যেমন উপরে উল্লেখ করা হয়েছে, এই শেয়ারিংই L52-এর কোহেরেন্স সমস্যার জন্ম দেয়।

অনুশীলন

  1. হিসাব করুন: যদি L2-এর MissRate কমিয়ে ০.০৫ থেকে ০.০২ করা যায় (বাকি সব মান অপরিবর্তিত: L1 HitTime=1, L1 MissRate=0.10, L2 HitTime=10, mem_penalty=100), নতুন AMAT কত হবে?

    নতুন L2 কার্যকর খরচ = ১০ + ০.০২×১০০ = ১০ + ২ = ১২ সাইকেল। নতুন AMAT = ১ + ০.১০×১২ = ১ + ১.২ = ২.২ সাইকেল — আগের ২.৫ সাইকেলের চেয়ে সামান্য দ্রুত। L2-এর মিস-রেট কমানো (বড় বা বেশি সেট-অ্যাসোসিয়েটিভ L2 দিয়ে) সরাসরি সামগ্রিক AMAT-কে উন্নত করে, যদিও L1-এর MissRate-এর প্রভাবই এখানে সবচেয়ে বড় (যেহেতু L1 সবচেয়ে বেশি অ্যাক্সেস পায়)।

  2. চিন্তা করুন: কোড সেলে multi_level_amat ফাংশনটি মাত্র দুই স্তর (L1, L2) হ্যান্ডেল করে। যদি একটি তৃতীয় স্তর L3 যোগ করতে হয়, ফাংশনের সিগনেচার ও ভেতরের গণনা কীভাবে বদলাতে হবে (এখনই কোড পরিবর্তন করবেন না)?

    নতুন প্যারামিটার l3_hit_time ও l3_miss_rate যোগ করতে হবে। প্রথমে L3-এর কার্যকর খরচ বের করতে হবে: l3_cost = l3_hit_time + l3_miss_rate * mem_penalty — এটাই এখন L2-এর "মিস পেনাল্টি" হয়ে যাবে (আগে যেখানে সরাসরি mem_penalty ব্যবহার হতো)। তারপর l2_cost = l2_hit_time + l2_miss_rate * l3_cost, এবং সবশেষে amat_total = l1_hit_time + l1_miss_rate * l2_cost — একই রিকার্সিভ প্যাটার্ন, শুধু একধাপ আরও গভীরে প্রয়োগ করা।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

আগের পাঠ
ক্যাশ পারফরম্যান্স — হিট/মিস রেট ও AMAT