ট্রান্সলেটর — কম্পাইলার বনাম ইন্টারপ্রেটার বনাম হাইব্রিড (JIT)
এই পাঠে যা শিখবেন
- কম্পাইলার ও ইন্টারপ্রেটারের সুনির্দিষ্ট সংজ্ঞা ও কার্যপ্রণালীর পার্থক্য
- গতি, পোর্টেবিলিটি ও ডেভেলপমেন্ট সাইকেলের বাস্তব ট্রেড-অফ
- হাইব্রিড/JIT কম্পাইলেশন — আধুনিক ভাষাগুলো কীভাবে দুটোর সুবিধা একসাথে পায়
- Python দিয়ে তিনটি এক্সিকিউশন মডেলের আপেক্ষিক সময়-খরচ সিমুলেশন
১ · কম্পাইলার বনাম ইন্টারপ্রেটার — সংজ্ঞা ও পার্থক্য
L01-এ সংক্ষেপে বলা হয়েছিল — এখন বিস্তারিত দেখা যাক। একটি কম্পাইলারCompilerপুরো সোর্স প্রোগ্রাম রান করার আগেই সম্পূর্ণভাবে অন্য কোনো ফর্মে (সাধারণত মেশিন কোড) অনুবাদ করে রাখে। পুরো সোর্স প্রোগ্রাম রান করার আগেই সম্পূর্ণভাবে অনুবাদ করে রাখে — অনুবাদ ও এক্সিকিউশন সম্পূর্ণ আলাদা দুটি ধাপ। একবার কম্পাইল হয়ে গেলে, প্রোগ্রামটি বারবার রান করা যায় পুনরায় অনুবাদ না করেই। বিপরীতে, একটি ইন্টারপ্রেটারInterpreterসোর্স কোড (বা তার কোনো ইন্টারমিডিয়েট ফর্ম) সরাসরি পড়ে, সাধারণত স্টেটমেন্ট-বাই-স্টেটমেন্ট, সাথে সাথে এক্সিকিউট করে। সোর্স কোড সরাসরি পড়ে, সাধারণত স্টেটমেন্ট-বাই-স্টেটমেন্ট বা এক্সপ্রেশন-বাই-এক্সপ্রেশন, সাথে সাথে এক্সিকিউট করে — কোনো আলাদা, স্থায়ী মেশিন-কোড ফাইল তৈরি না করেই। অনুবাদ ও এক্সিকিউশন এখানে ইন্টারলিভড।
পুরো প্রোগ্রাম আগে থেকেই মেশিন কোডে অনুবাদ। উদাহরণ: C, C++, Rust।
সোর্স সরাসরি পড়ে সাথে সাথে এক্সিকিউট। উদাহরণ: শুদ্ধ শেল স্ক্রিপ্ট, ক্লাসিক Python ইন্টারপ্রেটার।
বাইটকোডে কম্পাইল, তারপর হট কোড রানটাইমে নেটিভ কম্পাইল। উদাহরণ: Java JVM, JavaScript V8।
২ · ট্রেড-অফ — গতি, পোর্টেবিলিটি ও ডেভেলপমেন্ট সাইকেল
এই দুই মডেলের মধ্যে একটি সুনির্দিষ্ট, দ্বিমুখী ট্রেড-অফ আছে —
- গতি: কম্পাইলড প্রোগ্রাম সাধারণত দ্রুত রান করে, কারণ অনুবাদের খরচ একবারই (আগে থেকে) দেওয়া হয়ে যায়। ইন্টারপ্রেটেড প্রোগ্রাম সাধারণত ধীর, কারণ অনুবাদের ওভারহেড প্রতিটি রানে (এমনকি প্রতিটি স্টেটমেন্টে) বারবার ঘটে।
- ডেভেলপমেন্ট সাইকেল: কম্পাইলড ভাষায় প্রতিটি পরিবর্তনের পর আবার কম্পাইল করতে হয় — এডিট- কম্পাইল-রান চক্র ধীর। ইন্টারপ্রেটেড ভাষায় কোনো আলাদা কম্পাইল ধাপ নেই — এডিট করে সাথে সাথে রান করা যায়, দ্রুত ইটারেশন সম্ভব।
- পোর্টেবিলিটি: কম্পাইলারকে টার্গেট মেশিন আগে থেকেই জানতে হয় (কোন CPU-এর জন্য মেশিন কোড তৈরি হচ্ছে)। ইন্টারপ্রেটেড প্রোগ্রাম বেশি পোর্টেবল — একই সোর্স কোড, ইন্টারপ্রেটার থাকা যেকোনো মেশিনে চলে।
৩ · হাইব্রিড/JIT কম্পাইলেশন — আধুনিক মধ্যপন্থা
বাস্তব জগতের অনেক ভাষা (Java, JavaScript) এই দুই মডেলের মাঝামাঝি একটি পন্থা ব্যবহার করে — Just-In-Time (JIT) কম্পাইলেশনপ্রথমে পোর্টেবল বাইটকোডে কম্পাইল, তারপর এক্সিকিউশনের সময় ঘন ঘন চলা "হট" কোড নেটিভ মেশিন কোডে কম্পাইল করা।। প্রথমে সোর্স কোড একটি পোর্টেবল, ইন্টারমিডিয়েট বাইটকোডে কম্পাইল হয় (দ্রুত, পোর্টেবল — ইন্টারপ্রেটারের নমনীয়তার মতো)। তারপর, এক্সিকিউশনের সময়, যেসব কোড-পাথ বারবার চলে ("হট" কোড), সেগুলো অন-দ্য-ফ্লাই নেটিভ মেশিন কোডে কম্পাইল করা হয় — প্রায়-কম্পাইলড গতির জন্য। ফলাফল: পোর্টেবিলিটি ও পারফরম্যান্স একসাথে। Java-র JVM ও JavaScript-এর V8 ইঞ্জিন (আধুনিক ব্রাউজারে) এই পন্থার বাস্তব উদাহরণ।
৪ · কোডে এক্সিকিউশন-টাইম সিমুলেশন
নিচের কোড সেলে তিনটি মোডের আপেক্ষিক সময়-খরচ একটি টয় মডেল দিয়ে সিমুলেট করা হয়েছে — কম্পাইলড মোডে একটি বড় upfront অনুবাদ-খরচ কিন্তু প্রতি-রান দ্রুত; ইন্টারপ্রেটেড মোডে কোনো upfront খরচ নেই কিন্তু প্রতি-রানে বড় ওভারহেড; JIT মোডে ছোট upfront খরচ, আর প্রতি-রান খরচ ধীরে ধীরে কমতে থাকে (হট-পাথ অপ্টিমাইজেশন সিমুলেট করে)।
# তিনটি এক্সিকিউশন মডেলের আপেক্ষিক সময়-খরচ সিমুলেশন
def simulate_execution_time(program_size, mode, num_runs):
if mode == "compiled":
translate_cost = program_size * 20 # বড় upfront খরচ -- পুরো প্রোগ্রাম আগে থেকেই মেশিন কোডে অনুবাদ
per_run_cost = program_size * 1 # প্রতি রান দ্রুত -- মেশিন কোড আগে থেকেই রেডি
total = translate_cost + per_run_cost * num_runs
elif mode == "interpreted":
per_run_cost = program_size * 3 # কোনো upfront খরচ নেই, কিন্তু প্রতি রানেই পুরো ওভারহেড
total = per_run_cost * num_runs
elif mode == "jit":
translate_cost = program_size * 8 # compiled-এর চেয়ে ছোট upfront -- বাইটকোডে কম্পাইল
total = translate_cost
for i in range(1, num_runs + 1):
hot_bonus = max(0, 4 - i) # শুরুর কয়েক রানের পর "হট পাথ" অপ্টিমাইজ হয়ে যায়
run_cost = program_size * (1 + hot_bonus)
total += run_cost
else:
raise ValueError(f"অজানা মোড: {mode!r}")
return total
program_size = 100
for num_runs in (1, 100):
print(f"--- {num_runs}টি রান (program_size={program_size}) ---")
results = {}
for mode in ("compiled", "interpreted", "jit"):
t = simulate_execution_time(program_size, mode, num_runs)
results[mode] = t
print(f" {mode:12s}: মোট সময় = {t}")
winner = min(results, key=results.get)
print(f" >>> সবচেয়ে দ্রুত: {winner}\n")
interpreted জেতে (কোনো upfront খরচ নেই, তাই একবারের
জন্য এটিই সস্তা)। কিন্তু ১০০ রানে jit ও compiled অনেক এগিয়ে যায় — upfront খরচ
অনেকগুলো রানের উপর ভাগ হয়ে যায়, আর JIT-এর হট-পাথ অপ্টিমাইজেশন একে compiled-এর চেয়েও কিছুটা
এগিয়ে রাখে এই উদাহরণে, কারণ এর upfront খরচ compiled-এর চেয়ে কম। এটিই ঠিক কেন এক-বারের স্ক্রিপ্টের জন্য
ইন্টারপ্রেটার প্রায়ই যথেষ্ট, কিন্তু বারবার-চলা প্রোগ্রামের জন্য কম্পাইলেশন/JIT-এর upfront খরচ শোধ হয়ে যায়।
কম্পাইলার আর ইন্টারপ্রেটার একই কাজের (সোর্স কোড থেকে ফলাফল বের করা) দুটো ভিন্ন কৌশল — একটি "আগে সব অনুবাদ, পরে রান বারবার," আরেকটি "অনুবাদ ও রান একসাথে, প্রতিবার।" JIT দেখায় এই দুটো আসলে একটি বর্ণালীর দুই প্রান্ত — বাস্তব ভাষাগুলো প্রায়ই মাঝামাঝি কোথাও দাঁড়িয়ে দুটোর সুবিধাই নেওয়ার চেষ্টা করে।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১ কেন কম্পাইলড প্রোগ্রাম বারবার রান করলে দ্রুত হয়ে ওঠে, অথচ ডেভেলপমেন্টের সময় এডিট-কম্পাইল-রান চক্র ইন্টারপ্রেটেড ভাষার চেয়ে ধীর অনুভূত হয়?
কম্পাইলেশনের খরচ একবারই দিতে হয় (upfront translate_cost) — এরপর প্রতিটি রান শুধু ইতিমধ্যে-তৈরি মেশিন কোড এক্সিকিউট করে, যা খুব দ্রুত। কিন্তু ডেভেলপমেন্টের সময়, কোডে প্রতিটি ছোট পরিবর্তনের পরও পুরো কম্পাইলেশন ধাপটি আবার চালাতে হয় — সেই upfront খরচ প্রতিটি এডিটের পরে পুনরায় দিতে হয়, যেখানে ইন্টারপ্রেটার কোনো আলাদা কম্পাইল ধাপ ছাড়াই সাথে সাথে নতুন কোড রান করতে পারে।
প্র ০২ JIT কম্পাইলেশন কীভাবে "পোর্টেবিলিটি" (ইন্টারপ্রেটারের সুবিধা) ও "গতি" (কম্পাইলারের সুবিধা) — দুটোই একসাথে পায়?
প্রথম ধাপে সোর্স কোড একটি পোর্টেবল বাইটকোডে কম্পাইল হয় — এই বাইটকোড যেকোনো মেশিনে (যেখানে উপযুক্ত রানটাইম আছে) চলতে পারে, ঠিক ইন্টারপ্রেটেড কোডের মতো পোর্টেবিলিটি দেয়। কিন্তু এক্সিকিউশনের সময়, যে কোড বারবার চলছে (হট কোড) তা রানটাইমে নেটিভ মেশিন কোডে কম্পাইল হয়ে যায় — তাই সেই অংশটুকু কম্পাইলড প্রোগ্রামের মতোই দ্রুত চলে। ফলাফল: একটিই বাইটকোড ফাইল যেকোনো মেশিনে পোর্টেবল, অথচ ঘন ঘন-চলা অংশ কম্পাইলড-প্রায় গতিতে চলে।
প্র ০৩
উপরের কোড সেলের ফলাফল অনুযায়ী, ১ রানে কেন interpreted জেতে অথচ ১০০ রানে কেন jit জেতে? সংখ্যা দিয়ে ব্যাখ্যা কর।
program_size = 100-এর জন্য: ১ রানে compiled = 2000 + 100 = 2100, interpreted = 300 × 1 =
300, jit = 800 + 400 = 1200 — এখানে interpreted-এর কোনো upfront খরচ নেই বলে সবচেয়ে সস্তা। কিন্তু ১০০
রানে compiled = 2000 + 100×100 = 12000, interpreted = 300 × 100 = 30000 (প্রতি রানের বড় ওভারহেড ১০০ বার
যোগ হয়ে বিশাল অঙ্কে পৌঁছায়), আর jit = 800 + (কমতে-থাকা রান-খরচের যোগফল) = 11400 — jit-এর upfront খরচ
অনেক রানের উপর ভাগ হয়ে যায় এবং হট-পাথ অপ্টিমাইজেশন প্রতি-রান খরচ কমিয়ে দেয়, তাই এটি এমনকি compiled-কেও
সামান্য পেছনে ফেলে দেয়।
অনুশীলন
-
চিন্তা করুন: Java (JVM) ও JavaScript (V8) বাস্তব জগতে JIT কম্পাইলেশন ব্যবহার করে কেন — এই দুটো ভাষার ব্যবহারিক প্রেক্ষাপট (ওয়েব ব্রাউজার, ক্রস-প্ল্যাটফর্ম অ্যাপ্লিকেশন) মাথায় রেখে কারণ ব্যাখ্যা করুন।
JavaScript ব্রাউজারে চলে — একই কোড হাজারো ভিন্ন ডিভাইস/OS/CPU-তে চলতে হয়, তাই খাঁটি কম্পাইলেশন (একটি নির্দিষ্ট টার্গেট মেশিনের জন্য) অসম্ভব; কিন্তু ওয়েব পেজ ইন্টারঅ্যাক্টিভ হওয়ায় বিশুদ্ধ ইন্টারপ্রিটেশনও অনেক ক্ষেত্রে অনেক ধীর। JIT (V8) উভয় সমস্যার সমাধান দেয়। Java একই কারণে "write once, run anywhere" লক্ষ্য নিয়ে ডিজাইন হয়েছিল — বাইটকোড যেকোনো JVM-এ পোর্টেবল, আর JIT সেই পোর্টেবল বাইটকোডকে বাস্তব অ্যাপ্লিকেশনের জন্য যথেষ্ট দ্রুত করে তোলে।
-
পরীক্ষা করুন: উপরের কোড সেলে
program_size-এর মান500করে Run চেপে দেখুনnum_runs = 1-এর জন্য কোন মোড জেতে এবং সংখ্যাগুলো কীভাবে বদলায়।program_size = 500-এ ১ রানের জন্য: compiled = 500×20 + 500×1 = 10500, interpreted = 500×3 = 1500, jit = 500×8 + 500×4 = 4000+2000 = 6000। অনুপাতগুলো একই থাকে (সবকিছু program_size দিয়ে রৈখিকভাবে স্কেল করে), তাই বিজয়ী একই থাকে — interpreted, কারণ এর কোনো upfront খরচ নেই।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৮টি পাঠ প্যারাডাইম, গ্রামার, লেক্সিং, পার্সিং, টাইপ সিস্টেম ও কোড জেনারেশন পর্যন্ত সম্পূর্ণ যাত্রা।
- আগের পাঠ L02 সিনট্যাক্স বনাম সিমান্টিক্স — গঠন ও অর্থের মধ্যে সুনির্দিষ্ট পার্থক্য।
- পরের পাঠ L04 ল্যাঙ্গুয়েজ ডিজাইন গোল ও ট্রেড-অফ — readability, writability, reliability ও efficiency।
- Computer Architecture & Digital Logic কোর্স সহোদর কোর্স JIT-এর "নেটিভ মেশিন কোডে কম্পাইল" ধাপ ঠিক সেই টার্গেট CPU-কেই টার্গেট করে যা এই কোর্সে বিস্তারিত কভার হয়েছে।
- সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems, Computer Architecture ও Programming Languages & Compiler Design — সব এক জায়গায়।