পাঠ ৩৫ · ৫৭-এর মধ্যে · মডিউল ৭
Home / Courses / Computer Architecture & Digital Logic / কন্ট্রোল হ্যাজার্ড

কন্ট্রোল হ্যাজার্ড ও ব্রাঞ্চ প্রেডিকশন

Control hazards & branch prediction
৯ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

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

  • কন্ট্রোল হ্যাজার্ড কী এবং কেন এটি বিশেষভাবে ব্রাঞ্চ ইনস্ট্রাকশনের সাথে যুক্ত
  • মিসপ্রেডিকশন ফ্লাশ পেনাল্টি ঠিক কী এবং কেন এটি ঘটে
  • স্ট্যাটিক বনাম ডায়নামিক ব্রাঞ্চ প্রেডিকশন কৌশল, এবং কেন প্রেডিকশন কখনও ক্ষতি করে না
  • কোড দিয়ে একটি ব্রাঞ্চ সিকোয়েন্সে মোট অপচয়িত সাইকেল গণনা করা ও পারফেক্ট প্রেডিক্টরের সাথে তুলনা করা

১ · কন্ট্রোল হ্যাজার্ড কী

L33 ও L34-এ দেখা হ্যাজার্ডগুলো হার্ডওয়্যার রিসোর্স ও ডেটা নির্ভরতার সমস্যা ছিল। কন্ট্রোল হ্যাজার্ড সম্পূর্ণ ভিন্ন প্রশ্ন তোলে — পরে কোন ইনস্ট্রাকশনটা ফেচ করব? সাধারণ (নন-ব্রাঞ্চ) ইনস্ট্রাকশনের ক্ষেত্রে উত্তর সহজ — পরের মেমরি ঠিকানার ইনস্ট্রাকশনটাই। কিন্তু একটি ব্রাঞ্চL27-এ পরিচিত BEQ-এর মতো কন্ডিশনাল জাম্প ইনস্ট্রাকশন — শর্ত সত্য হলে প্রোগ্রাম-প্রবাহ ভিন্ন ঠিকানায় লাফ দেয়। (কন্ডিশনাল জাম্প, L27-এর BEQ) ইনস্ট্রাকশনে, এই উত্তর শর্তের ফলাফলের উপর নির্ভরশীল — আর সেই ফলাফল সাধারণত Execute স্টেজে (স্টেজ ৩) নির্ধারিত হয়, তারও আগে IF স্টেজ ইতিমধ্যে পরের ইনস্ট্রাকশন ফেচ করে ফেলেছে (স্বাভাবিক, sequential ধরে নিয়ে)।

যদি ব্রাঞ্চ আসলে নেওয়া (taken) হয়, তাহলে সেই ইতিমধ্যে-ফেচ-করা ইনস্ট্রাকশনগুলো ভুল — সেগুলো বাদ (ফ্লাশ) দিতে হবে, আর সঠিক টার্গেট থেকে নতুন করে ফেচ শুরু করতে হবে। বাস্তব প্রোগ্রামে ব্রাঞ্চ অস্বাভাবিক নয় — মোটামুটি প্রতি ৫-৬টি ইনস্ট্রাকশনের একটি ব্রাঞ্চ হয়, তাই এই হ্যাজার্ড উপেক্ষা করার মতো ছোট নয়।

২ · ব্রাঞ্চ প্রেডিকশন

ব্রাঞ্চ প্রেডিকশনBranch Predictionব্রাঞ্চের ফলাফল বাস্তবে জানার আগেই অনুমান করে স্পেকুলেটিভভাবে সেই পথ ধরে ফেচ চালিয়ে যাওয়ার কৌশল। মূল সমাধান — ফলাফল সত্যিকারে জানার আগেই একটি অনুমান করে, সেই অনুমানকৃত পথ ধরে স্পেকুলেটিভভাবে ফেচ চালিয়ে যাওয়া হয়। অনুমান সঠিক হলে কোনো পেনাল্টি নেই (পাইপলাইন কখনও থামেনি)। ভুল হলে, একই ফ্লাশ-পেনাল্টি লাগে যা প্রেডিকশন ছাড়াই লাগত — অর্থাৎ প্রেডিকশন কখনও ক্ষতি করে না, শুধু সাহায্য করার সম্ভাবনা তৈরি করে।

স্ট্যাটিক প্রেডিকশন
কোনো রানটাইম হিস্ট্রি ছাড়াই একটি ফিক্সড নিয়ম — যেমন "সবসময় Not-Taken প্রেডিক্ট করো", বা "পেছনের দিকের ব্রাঞ্চ Taken, সামনের দিকের Not-Taken" (লুপ-ক্লোজিং ব্রাঞ্চের জন্য যুক্তিসঙ্গত)।
ডায়নামিক প্রেডিকশন
একটি নির্দিষ্ট ব্রাঞ্চের অতীত রানটাইম আচরণের হিস্ট্রি ব্যবহার করে ভবিষ্যতের ফলাফল অনুমান করে — ফিক্সড নিয়মের চেয়ে সাধারণত অনেক বেশি নির্ভুল।
সাইকেল → ১ ২ ৩ ৪ ৫ Branch I(স্পেক)+1 I(স্পেক)+2 IF ID EX ফল MEM WB IF ID ✗ IF ✗
ব্রাঞ্চের ফলাফল EX স্টেজে (সাইকেল ৩) নির্ধারিত হওয়ার আগেই ২টি ইনস্ট্রাকশন স্পেকুলেটিভভাবে ফেচ/ডিকোড হয়ে যায় — ভুল অনুমান হলে দুটোই ফ্লাশ (লাল), অর্থাৎ ২ সাইকেল অপচয়।

৩ · কোড দিয়ে যাচাই — একটি ব্রাঞ্চ সিকোয়েন্সে মোট অপচয়

নিচের কোড সেলে K=5 স্টেজের একটি পাইপলাইনে ব্রাঞ্চ EX স্টেজে (৩ নম্বর স্টেজ) সমাধান হয় ধরে নিয়ে, মিসপ্রেডিকশন পেনাল্টি গণনা করা হয়েছে, তারপর ৮টি ব্রাঞ্চের একটি বাস্তবসম্মত সিকোয়েন্সে "সবসময় Not-Taken" স্ট্যাটিক স্ট্র্যাটেজি প্রয়োগ করে মোট অপচয়িত সাইকেল হিসাব করা হয়েছে।

Python
# কন্ট্রোল হ্যাজার্ড -- মিসপ্রেডিকশন পেনাল্টি ও মোট অপচয়িত সাইকেল
def simulate_branch(actual_taken, predicted_taken, misprediction_penalty):
    """একটি ব্রাঞ্চের প্রকৃত ফলাফল বনাম প্রেডিকশন মিলিয়ে অপচয়িত সাইকেল সংখ্যা রিটার্ন করে"""
    if actual_taken == predicted_taken:
        return 0  # সঠিক প্রেডিকশন -- কোনো অপচয় নেই
    return misprediction_penalty  # ভুল প্রেডিকশন -- স্পেকুলেটিভ ইনস্ট্রাকশনগুলো ফ্লাশ করতে হবে

BRANCH_RESOLVE_STAGE = 3  # EX স্টেজ (৩ নম্বর) -- এখানে ব্রাঞ্চের প্রকৃত ফলাফল জানা যায়
MISPREDICTION_PENALTY = BRANCH_RESOLVE_STAGE - 1  # ততক্ষণে IF ও ID স্টেজে ২টি ইনস্ট্রাকশন স্পেকুলেটিভভাবে ঢুকে গেছে

# একটি বাস্তব প্রোগ্রামে ঘটা ৮টি ব্রাঞ্চের প্রকৃত (Taken/Not-Taken) ফলাফলের ধারা
actual_outcomes = [False, True, True, False, True, False, True, True]

# স্ট্যাটিক স্ট্র্যাটেজি: "সবসময় Not-Taken প্রেডিক্ট করো"
predicted_outcomes = [False] * len(actual_outcomes)

print(f"মিসপ্রেডিকশন পেনাল্টি (EX-এ সমাধান ধরে): {MISPREDICTION_PENALTY} সাইকেল\n")
print(f"{'#':>3} | {'প্রকৃত':>8} | {'প্রেডিকশন':>10} | {'অপচয়িত সাইকেল':>15}")
print("-" * 50)
total_wasted = 0
for i, (actual, predicted) in enumerate(zip(actual_outcomes, predicted_outcomes), start=1):
    wasted = simulate_branch(actual, predicted, MISPREDICTION_PENALTY)
    total_wasted += wasted
    print(f"{i:>3} | {str(actual):>8} | {str(predicted):>10} | {wasted:>15}")

print(f"\nমোট অপচয়িত সাইকেল (Always-Not-Taken প্রেডিকশন): {total_wasted}")

# হাইপোথেটিক্যাল পারফেক্ট প্রেডিক্টর -- সবসময় সঠিক অনুমান করে
perfect_wasted = sum(simulate_branch(a, a, MISPREDICTION_PENALTY) for a in actual_outcomes)
print(f"হাইপোথেটিক্যাল পারফেক্ট প্রেডিক্টরের অপচয়িত সাইকেল: {perfect_wasted}")
print(f"পার্থক্য: {total_wasted - perfect_wasted} সাইকেল বাস্তব প্রেডিকশন-ভুলের কারণে অপচয় হলো")

taken_count = sum(actual_outcomes)
print(f"\n৮টি ব্রাঞ্চের মধ্যে {taken_count}টি আসলে Taken ছিল -- 'সবসময় Not-Taken' প্রতিটিতেই ভুল করেছে")
assert total_wasted == taken_count * MISPREDICTION_PENALTY
assert perfect_wasted == 0

    
লক্ষ্য করুন — "সবসময় Not-Taken" স্ট্র্যাটেজি প্রতিটি Taken ব্রাঞ্চেই ভুল করে (এখানে ৫টি), মোট ১০ সাইকেল অপচয় করে। যদি প্রোগ্রামে বেশিরভাগ ব্রাঞ্চ আসলে লুপ-ক্লোজিং (তাই সাধারণত Taken) হতো, এই একই স্ট্র্যাটেজি অনেক বেশি সাইকেল অপচয় করত — এটিই দেখায় কেন "পেছনের দিকের ব্রাঞ্চ Taken প্রেডিক্ট করো" নিয়মটি বাস্তবে বেশি কার্যকর।
মূল কথা · Key takeaway

কন্ট্রোল হ্যাজার্ড অনিবার্য যতক্ষণ না ব্রাঞ্চের ফলাফল আগেভাগে জানার কোনো উপায় থাকে — প্রেডিকশন সেই "আগেভাগে জানা"-র একটি অনুমান-ভিত্তিক বিকল্প তৈরি করে। যেহেতু ভুল প্রেডিকশনের খরচ কখনও প্রেডিকশন-না-করার খরচের চেয়ে বেশি হয় না, বাস্তব CPU ডিজাইনে প্রেডিকশন প্রায় সবসময়ই যুক্ত থাকে — নির্ভুলতা যত বেশি, গড় পারফরম্যান্স তত ভালো।

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

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

প্র ০১ যদি একটি CPU ব্রাঞ্চের ফলাফল Decode স্টেজেই (EX-এর আগে) নির্ধারণ করতে পারত, মিসপ্রেডিকশন পেনাল্টি কীভাবে বদলাত?

পেনাল্টি কমে যেত। উপরের কোডে MISPREDICTION_PENALTY = BRANCH_RESOLVE_STAGE - 1 — যদি ব্রাঞ্চ ID স্টেজে (স্টেজ ২) সমাধান হতো, পেনাল্টি হতো $2-1=1$ সাইকেল, EX-এ সমাধানের (২ সাইকেল) চেয়ে অর্ধেক। এটিই দেখায় কেন অনেক বাস্তব CPU ডিজাইনে ব্রাঞ্চের শর্ত যত দ্রুত সম্ভব (আদর্শভাবে Decode-এই) নির্ধারণের জন্য অতিরিক্ত তুলনা-হার্ডওয়্যার যোগ করা হয় — পেনাল্টি সরাসরি কমে যায়।

প্র ০২ কোড সেলের actual_outcomes যদি সবগুলোই False (কখনও Taken না) হতো, "সবসময় Not-Taken" প্রেডিকশনের মোট অপচয় কত হতো?

শূন্য (০) সাইকেল — প্রতিটি ব্রাঞ্চেই actual_taken == predicted_taken (উভয়ই False) মিলে যেত, তাই simulate_branch প্রতিবার ০ রিটার্ন করত। এটি দেখায় প্রেডিকশন স্ট্র্যাটেজি প্রোগ্রামের প্রকৃত আচরণের সাথে যত ভালো মেলে, বাস্তব খরচ ততই পারফেক্ট-প্রেডিক্টরের কাছাকাছি চলে যায় — "সবসময় Not-Taken" এমন প্রোগ্রামের জন্য আসলে পারফেক্ট প্রেডিক্টরের সমতুল্য।

প্র ০৩ ডায়নামিক প্রেডিকশন কি সবসময় স্ট্যাটিক প্রেডিকশনের চেয়ে ভালো ফলাফল দেয়, নাকি কোনো বাস্তব খরচ আছে?

ডায়নামিক প্রেডিকশন সাধারণত বেশি নির্ভুল হয়, কিন্তু বিনামূল্যে নয় — প্রতিটি ব্রাঞ্চের ঐতিহাসিক আচরণ মনে রাখার জন্য অতিরিক্ত হার্ডওয়্যার (একটি "ব্রাঞ্চ হিস্ট্রি টেবিল," এই কোর্সের পরিধির বাইরে বিস্তারিতভাবে) প্রয়োজন — চিপের জায়গা ও শক্তি খরচ করে। তাই এটি একটি বাস্তব ইঞ্জিনিয়ারিং ট্রেড-অফ — বেশি নির্ভুলতার জন্য বেশি হার্ডওয়্যার খরচ, ঠিক যেমন M8-এর ক্যাশ ডিজাইনে বড় ক্যাশ বেশি হিট রেট দেয় কিন্তু বেশি চিপ-এরিয়া নেয়।

অনুশীলন

  1. চিন্তা করুন: "পেছনের দিকের ব্রাঞ্চ Taken, সামনের দিকের Not-Taken" স্ট্যাটিক নিয়মটি কেন একটি সাধারণ লুপ (যেমন for i in range(100)) চালানোর সময় প্রায় সবসময় সঠিক প্রেডিকশন দেবে?

    একটি লুপের শেষে থাকা "লুপ-ক্লোজিং" ব্রাঞ্চ (যেমন "যদি i < 100, লুপের শুরুতে ফিরে যাও") আসলে একটি পেছনের দিকের (backward) ব্রাঞ্চ — এবং ১০০ বার লুপের মধ্যে ৯৯ বারই এটি Taken হয় (শুধু শেষবার Not-Taken)। তাই "পেছনের দিকের ব্রাঞ্চ = Taken" অনুমান করলে ৯৯টি সঠিক প্রেডিকশন, মাত্র ১টি ভুল — অত্যন্ত উচ্চ নির্ভুলতা, কোনো রানটাইম হিস্ট্রি ছাড়াই।

  2. পরীক্ষা করুন: উপরের কোড সেলে predicted_outcomes = [False] * len(actual_outcomes)-এর বদলে predicted_outcomes = actual_outcomes.copy() (অর্থাৎ একটি পারফেক্ট প্রেডিক্টর সিমুলেট) করে Run চাপুন — total_wasted কত হয়?

    এখন প্রতিটি predicted ঠিক সংশ্লিষ্ট actual-এর সমান, তাই simulate_branch প্রতিবারই actual_taken == predicted_taken সত্য পেয়ে ০ রিটার্ন করবে — total_wasted = 0, ঠিক perfect_wasted ভেরিয়েবলের মতোই। এটি নিশ্চিত করে কোডটি সত্যিই সাধারণ (যেকোনো প্রেডিকশন স্ট্র্যাটেজির জন্য কাজ করে), শুধু "সবসময় Not-Taken"-এর জন্য হার্ডকোড করা নয়।

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

পূর্ববর্তী পাঠ
ডেটা হ্যাজার্ড ও ফরওয়ার্ডিং