থ্র্যাশিং ও ওয়ার্কিং সেট মডেল
এই পাঠে যা শিখবেন
- থ্র্যাশিং কী, কখন ঘটে এবং কেন CPU ইউটিলাইজেশন প্যারাডক্সিক্যালি কমে যায়
- কীভাবে একটি ঐতিহাসিক ডিজাইন ভুল (কম ইউটিলাইজেশন দেখে আরও প্রসেস যোগ করা) থ্র্যাশিংকে আরও খারাপ করত
- ওয়ার্কিং সেট মডেল — Δ উইন্ডো, ওয়ার্কিং সেটের সংজ্ঞা এবং এটি কীভাবে থ্র্যাশিং প্রতিরোধে সাহায্য করে
- Python-এ একটি বাস্তব working-set ট্র্যাকার বাস্তবায়ন করা এবং সময়ের সাথে এর আকার পরিবর্তন পর্যবেক্ষণ করা
১ · থ্র্যাশিং কী ও কেন ঘটে
L32-এ আমরা দেখেছি পেজ ফল্টের খরচ কতটা বিশাল (ডিস্ক I/O)। এখন কল্পনা করুন — একসাথে অনেকগুলো প্রসেস চলছে, কিন্তু মিলিতভাবে তাদের যতগুলো ফ্রেম এখন সক্রিয়ভাবে দরকার ফিজিক্যাল মেমরিতে ততগুলো ফ্রেমই নেই। প্রতিটি প্রসেস বারবার তার নিজের প্রয়োজনীয় পেজ হারায় (অন্য কোনো প্রসেসের জন্য জায়গা করতে গিয়ে সরিয়ে দেওয়া হয়), আর তারপর প্রায় সাথে সাথেই আবার সেই পেজেই ফল্ট ঘটিয়ে ফেরত আনতে বাধ্য হয়। একেই বলে থ্র্যাশিং (Thrashing)Thrashingএকটি গুরুতর পারফরম্যান্স ধস, যেখানে প্রসেস প্রকৃত কাজের চেয়ে বেশি সময় ব্যয় করে পেজ ফল্ট হ্যান্ডলিং (সোয়াপ ইন/আউট)-এ, কারণ মিলিতভাবে যথেষ্ট ফ্রেম নেই। — একটি গুরুতর পারফরম্যান্স ধস, যেখানে সিস্টেম প্রকৃত কাজের চেয়ে বেশি সময় ব্যয় করে ফেলে শুধু পেজ সোয়াপ ইন/আউট করতেই।
২ · একটি ঐতিহাসিক ভুল — CPU ইউটিলাইজেশন প্যারাডক্স
এটি OS ডিজাইনের ইতিহাসে একটি বিখ্যাত, বাস্তব শিক্ষা। কিছু early scheduler কম CPU ইউটিলাইজেশন দেখে ধরে নিত যে সিস্টেমে "আরও কাজের জায়গা" আছে — তাই তারা আরও বেশি প্রসেস চালু করে দিত মাল্টিপ্রোগ্রামিং ডিগ্রি বাড়িয়ে। কিন্তু বাস্তবে সমস্যা ছিল উল্টো — প্রসেসগুলোর যথেষ্ট ফ্রেম ছিল না, তাই CPU অলস ছিল থ্র্যাশিংয়ের কারণে। আরও প্রসেস যোগ করায় প্রতিটি প্রসেসের ভাগে আরও কম ফ্রেম পড়ত, ফল্ট আরও বাড়ত, আর CPU ইউটিলাইজেশন আরও কমে যেত — একটি দুষ্টচক্র (vicious cycle)।
৩ · ওয়ার্কিং সেট মডেল
থ্র্যাশিং প্রতিরোধের একটি প্রমিত ধারণা হলো ওয়ার্কিং সেট মডেল (Working Set Model)Working Set Modelপ্রতিটি প্রসেসের সাম্প্রতিক Δ সময়ের মধ্যে অ্যাক্সেস করা স্বতন্ত্র পেজের সেট ট্র্যাক করে তার জন্য যথেষ্ট ফ্রেম নিশ্চিত করার মডেল। — প্রতিটি প্রসেসের জন্য তার ওয়ার্কিং সেট ট্র্যাক করা: একটি নির্দিষ্ট সাম্প্রতিক সময়-উইন্ডো Δ-এর মধ্যে সেই প্রসেস যেসব স্বতন্ত্র পেজ অ্যাক্সেস করেছে, তাদের সেট। ধারণাটি হলো — একটি প্রসেসকে ভালোভাবে চালাতে হলে তার ওয়ার্কিং সেটের সমান (বা তার বেশি) ফ্রেম তাকে দিতেই হবে; কম দিলে সেই প্রসেস ক্রমাগত ফল্ট ঘটাবে (thrashing-এর একটি লক্ষণ)।
OS তখন সব প্রসেসের ওয়ার্কিং সেটের সমষ্টি হিসাব করে দেখে — যদি এই সমষ্টি মোট ফিজিক্যাল ফ্রেমের চেয়ে বেশি হয়ে যায়, তাহলে সবাইকে সমান ভাগে ভাগ করার চেষ্টা না করে OS-এর উচিত একটি বা একাধিক প্রসেসকে সম্পূর্ণ সাসপেন্ড করা (তার সব ফ্রেম সাময়িকভাবে খালি করে) — যতক্ষণ না বাকি প্রসেসগুলোর জন্য যথেষ্ট মেমরি ফাঁকা হয়।
লক্ষ্য করুন — ওয়ার্কিং সেট মডেল সরাসরি L36-এর ফ্রেম অ্যালোকেশন প্রশ্নের সাথে যুক্ত: একটি প্রসেসের আকার (মোট ভার্চুয়াল মেমরি) আর তার বর্তমান ওয়ার্কিং সেট এক জিনিস নয় — একটি বড় প্রোগ্রামের ওয়ার্কিং সেট একটি নির্দিষ্ট মুহূর্তে ছোট হতে পারে (শুধু একটি ছোট অংশ নিয়ে কাজ করছে), আবার সময়ের সাথে বদলেও যেতে পারে।
৪ · কোডে: ওয়ার্কিং সেটের আকার সময়ের সাথে কীভাবে বদলায়
নিচের কোড সেলে একটি working_set() ফাংশন — একটি রেফারেন্স স্ট্রিং, একটি বর্তমান সময় (current_time)
ও একটি উইন্ডো সাইজ Δ দিলে সেই উইন্ডোর মধ্যে থাকা স্বতন্ত্র পেজের সেট রিটার্ন করে। রেফারেন্স স্ট্রিংটি ইচ্ছাকৃতভাবে
তিনটি ভিন্ন "পর্যায়ে" ভাগ করা — প্রথমে সংকীর্ণ locality (মাত্র ২টি পেজের মধ্যে ঘোরাফেরা), মাঝে বিক্ষিপ্ত অ্যাক্সেস
(৬টি ভিন্ন পেজ পরপর), শেষে আবার সংকীর্ণ locality — যাতে ওয়ার্কিং সেটের আকার বাড়া-কমা স্পষ্ট দেখা যায়।
# ওয়ার্কিং সেট ট্র্যাকার -- বাস্তব প্রসেস নয়, একটি টয় রেফারেন্স স্ট্রিং-এর ওপর সিমুলেশন
def working_set(reference_string, current_time, delta):
"""current_time পর্যন্ত (সহ) সাম্প্রতিক delta-টি অ্যাক্সেসের মধ্যে থাকা স্বতন্ত্র পেজের সেট।"""
start = max(0, current_time - delta + 1)
window = reference_string[start:current_time + 1]
return set(window)
# পর্যায় ১ (সংকীর্ণ locality) -> পর্যায় ২ (বিক্ষিপ্ত, খারাপ locality) -> পর্যায় ৩ (আবার সংকীর্ণ)
reference_string = [1, 2, 1, 2, 1, 2, # পর্যায় ১ -- শুধু পেজ 1, 2
3, 4, 5, 6, 7, 8, # পর্যায় ২ -- ৬টি ভিন্ন পেজ, কোনো পুনরাবৃত্তি নেই
3, 4, 3, 4, 3, 4, 3, 4] # পর্যায় ৩ -- আবার সংকীর্ণ, পেজ 3, 4
DELTA = 4 # উইন্ডো সাইজ -- সাম্প্রতিক ৪টি অ্যাক্সেস বিবেচনা করা হবে
checkpoints = [5, 8, 11, 15, 19]
print(f"রেফারেন্স স্ট্রিং: {reference_string}")
print(f"উইন্ডো সাইজ (Δ): {DELTA}\n")
for t in checkpoints:
ws = working_set(reference_string, t, DELTA)
print(f"সময় t={t:2d} (এই মুহূর্তে অ্যাক্সেস: পেজ {reference_string[t]}) "
f"-> ওয়ার্কিং সেট = {sorted(ws)}, আকার = {len(ws)}")
থ্র্যাশিং প্রমাণ করে যে শুধু একটি ভালো পেজ-রিপ্লেসমেন্ট অ্যালগরিদম (L33-L34) থাকাই যথেষ্ট নয় — যদি মিলিতভাবে যথেষ্ট ফ্রেম না থাকে, তাহলে যেকোনো অ্যালগরিদমই ব্যর্থ হবে। ওয়ার্কিং সেট মডেল OS-কে একটি ব্যবহারিক নিয়ম দেয়: প্রতিটি প্রসেসের বর্তমান চাহিদা (তার সামগ্রিক আকার নয়) অনুযায়ী ফ্রেম বরাদ্দ করার চেষ্টা করা, এবং সামগ্রিক চাহিদা বেশি হয়ে গেলে কাউকে সাসপেন্ড করে দেওয়া — যা পরের পাঠ, ফ্রেম অ্যালোকেশন স্ট্র্যাটেজির (L36) সরাসরি প্রেক্ষাপট তৈরি করে।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১ "কম CPU ইউটিলাইজেশন" সবসময় কি "আরও প্রসেসের জায়গা আছে" বোঝায়? উদাহরণ দিয়ে ব্যাখ্যা করুন।
না — এটাই এই পাঠের কেন্দ্রীয় প্যারাডক্স। কম CPU ইউটিলাইজেশনের দুটো সম্পূর্ণ বিপরীত কারণ থাকতে পারে: (১) সত্যিই সিস্টেমে বাড়তি ক্ষমতা আছে, আরও কাজ নেওয়া যায় — এই ক্ষেত্রে আরও প্রসেস যোগ করা ঠিক। (২) সিস্টেম ইতিমধ্যে থ্র্যাশ করছে, প্রতিটি প্রসেস I/O-এর জন্য অপেক্ষা করছে — এই ক্ষেত্রে আরও প্রসেস যোগ করলে অবস্থা আরও খারাপ হবে। শুধু CPU ইউটিলাইজেশনের সংখ্যা দেখে এই দুটোর মধ্যে পার্থক্য করা যায় না — পেজ-ফল্ট রেটও একসাথে দেখতে হয়।
প্র ০২ ওয়ার্কিং সেট মডেলে উইন্ডো সাইজ Δ খুব ছোট (যেমন ১) বা খুব বড় (পুরো রেফারেন্স স্ট্রিং) করলে কী সমস্যা হবে?
Δ খুব ছোট হলে ওয়ার্কিং সেট শুধু "এইমাত্র যা অ্যাক্সেস হলো" তা দেখাবে — locality-এর প্রকৃত প্যাটার্ন ধরতে ব্যর্থ হবে, প্রতিটি নতুন পেজেই ওয়ার্কিং সেট বদলে যাবে অতিরিক্তভাবে। Δ খুব বড় (পুরো ইতিহাস) হলে ওয়ার্কিং সেট ধীরে ধীরে প্রসেসের সব পেজ অন্তর্ভুক্ত করে ফেলবে, এমনকি অনেক আগে ব্যবহৃত হয়ে এখন অপ্রাসঙ্গিক পেজও — অর্থাৎ এটি সাম্প্রতিকতার কোনো প্রকৃত অর্থ বহন করবে না। Δ-এর সঠিক মান বাছাই তাই একটি বাস্তব ট্রেড-অফ।
প্র ০৩ উপরের কোডে t=8 ও t=11-এ ওয়ার্কিং সেটের আকার একই (৪) হলেও পেজগুলো ভিন্ন কেন?
কারণ ওয়ার্কিং সেট প্রতিবার একটি নির্দিষ্ট সময়ের সাপেক্ষে সাম্প্রতিক Δ-টি অ্যাক্সেসের সেট — সময় এগিয়ে গেলে উইন্ডোটিও এগিয়ে যায় (একটি "স্লাইডিং উইন্ডো")। t=8-এ উইন্ডো হলো ইনডেক্স ৫ থেকে ৮ (পেজ 2,3,4,5), আর t=11-এ উইন্ডো হলো ইনডেক্স ৮ থেকে ১১ (পেজ 5,6,7,8) — দুটোতেই আকার ৪ (কারণ পর্যায় ২-তে কোনো পুনরাবৃত্তি নেই), কিন্তু আসল পেজগুলো সম্পূর্ণ ভিন্ন, কারণ উইন্ডোটি সময়ের সাথে সরে গেছে।
অনুশীলন
-
পরীক্ষা করুন: উপরের কোড সেলে
DELTA-কে 4 থেকে 6 করে Run চেপে দেখুন t=8 ও t=11-এ ওয়ার্কিং সেটের আকার কীভাবে বদলায়।বড় উইন্ডো (Δ=6) মানে আরও পুরনো অ্যাক্সেসও এখন হিসাবে ঢুকে যাবে — যেমন t=8-এ উইন্ডো এখন ইনডেক্স ৩ থেকে ৮ পর্যন্ত বিস্তৃত হবে (পেজ 2,1,2,3,4,5), যাতে পর্যায় ১-এর কিছু পেজও (1, 2) এখনো "সাম্প্রতিক" হিসেবে গণ্য হবে। ফলে ওয়ার্কিং সেটের আকার সাধারণত বড় বা সমান হবে, কারণ বড় উইন্ডো মানেই বেশি স্বতন্ত্র পেজ ধরার সুযোগ।
-
চিন্তা করুন: ধরুন দুটো প্রসেস একই সময়ে চলছে, প্রতিটির ওয়ার্কিং সেট আকার এই মুহূর্তে ৪ (মোট ৮
ফ্রেম দরকার), কিন্তু সিস্টেমে মোট ফিজিক্যাল ফ্রেম আছে মাত্র ৬টি। ওয়ার্কিং সেট মডেল অনুযায়ী OS-এর কী করা উচিত?
দুটো প্রসেসের ওয়ার্কিং সেটের সমষ্টি (৮) মোট ফ্রেমের (৬) চেয়ে বেশি — তাই দুটো প্রসেসকেই সমান ভাগে (৩+৩) ফ্রেম দিলে দুটোই তাদের নিজস্ব ওয়ার্কিং সেটের চেয়ে কম ফ্রেম পাবে, ফলে দুটোই ফল্ট ঘটাতে থাকবে — থ্র্যাশিং। ওয়ার্কিং সেট মডেল অনুযায়ী সঠিক সিদ্ধান্ত হলো একটি প্রসেসকে সাময়িকভাবে সম্পূর্ণ সাসপেন্ড করা (তার সব ফ্রেম খালি করে অন্যটিকে পূর্ণ ৪টি ফ্রেম দেওয়া), যাতে অন্তত একটি প্রসেস স্বাভাবিকভাবে এগিয়ে যেতে পারে।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পেজ রিপ্লেসমেন্ট — LRU (L34) আবার দেখুন রিক্যাপ দেখুন কেন একটি ভালো রিপ্লেসমেন্ট অ্যালগরিদমও যথেষ্ট ফ্রেম ছাড়া থ্র্যাশিং ঠেকাতে পারে না।
- পরের পাঠ: ফ্রেম অ্যালোকেশন স্ট্র্যাটেজি L36 প্রতিটি প্রসেসকে ঠিক কতগুলো ফ্রেম দেওয়া উচিত তা ঠিক করার নিয়মগুলো।
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৬টি পাঠ M8-এর বাকি পাঠ (ফ্রেম অ্যালোকেশন) এবং পুরো কোর্স।