পাঠ ০৩ · ৫৮-এর মধ্যে · মডিউল ১
Home / Courses / Concepts of Programming Languages & Compiler Design / ট্রান্সলেটর

ট্রান্সলেটর — কম্পাইলার বনাম ইন্টারপ্রেটার বনাম হাইব্রিড (JIT)

Translators — compiler vs interpreter vs hybrid (JIT)
৮ মিনিট পড়া শুরু · Beginner Python কোডসহ সম্পূর্ণ বাংলায়

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

  • কম্পাইলার ও ইন্টারপ্রেটারের সুনির্দিষ্ট সংজ্ঞা ও কার্যপ্রণালীর পার্থক্য
  • গতি, পোর্টেবিলিটি ও ডেভেলপমেন্ট সাইকেলের বাস্তব ট্রেড-অফ
  • হাইব্রিড/JIT কম্পাইলেশন — আধুনিক ভাষাগুলো কীভাবে দুটোর সুবিধা একসাথে পায়
  • Python দিয়ে তিনটি এক্সিকিউশন মডেলের আপেক্ষিক সময়-খরচ সিমুলেশন

১ · কম্পাইলার বনাম ইন্টারপ্রেটার — সংজ্ঞা ও পার্থক্য

L01-এ সংক্ষেপে বলা হয়েছিল — এখন বিস্তারিত দেখা যাক। একটি কম্পাইলারCompilerপুরো সোর্স প্রোগ্রাম রান করার আগেই সম্পূর্ণভাবে অন্য কোনো ফর্মে (সাধারণত মেশিন কোড) অনুবাদ করে রাখে। পুরো সোর্স প্রোগ্রাম রান করার আগেই সম্পূর্ণভাবে অনুবাদ করে রাখে — অনুবাদ ও এক্সিকিউশন সম্পূর্ণ আলাদা দুটি ধাপ। একবার কম্পাইল হয়ে গেলে, প্রোগ্রামটি বারবার রান করা যায় পুনরায় অনুবাদ না করেই। বিপরীতে, একটি ইন্টারপ্রেটারInterpreterসোর্স কোড (বা তার কোনো ইন্টারমিডিয়েট ফর্ম) সরাসরি পড়ে, সাধারণত স্টেটমেন্ট-বাই-স্টেটমেন্ট, সাথে সাথে এক্সিকিউট করে। সোর্স কোড সরাসরি পড়ে, সাধারণত স্টেটমেন্ট-বাই-স্টেটমেন্ট বা এক্সপ্রেশন-বাই-এক্সপ্রেশন, সাথে সাথে এক্সিকিউট করে — কোনো আলাদা, স্থায়ী মেশিন-কোড ফাইল তৈরি না করেই। অনুবাদ ও এক্সিকিউশন এখানে ইন্টারলিভড।

কম্পাইলার
পুরো প্রোগ্রাম আগে থেকেই মেশিন কোডে অনুবাদ। উদাহরণ: C, C++, Rust।
ইন্টারপ্রেটার
সোর্স সরাসরি পড়ে সাথে সাথে এক্সিকিউট। উদাহরণ: শুদ্ধ শেল স্ক্রিপ্ট, ক্লাসিক Python ইন্টারপ্রেটার।
হাইব্রিড/JIT
বাইটকোডে কম্পাইল, তারপর হট কোড রানটাইমে নেটিভ কম্পাইল। উদাহরণ: Java JVM, JavaScript V8।

২ · ট্রেড-অফ — গতি, পোর্টেবিলিটি ও ডেভেলপমেন্ট সাইকেল

এই দুই মডেলের মধ্যে একটি সুনির্দিষ্ট, দ্বিমুখী ট্রেড-অফ আছে —

  • গতি: কম্পাইলড প্রোগ্রাম সাধারণত দ্রুত রান করে, কারণ অনুবাদের খরচ একবারই (আগে থেকে) দেওয়া হয়ে যায়। ইন্টারপ্রেটেড প্রোগ্রাম সাধারণত ধীর, কারণ অনুবাদের ওভারহেড প্রতিটি রানে (এমনকি প্রতিটি স্টেটমেন্টে) বারবার ঘটে।
  • ডেভেলপমেন্ট সাইকেল: কম্পাইলড ভাষায় প্রতিটি পরিবর্তনের পর আবার কম্পাইল করতে হয় — এডিট- কম্পাইল-রান চক্র ধীর। ইন্টারপ্রেটেড ভাষায় কোনো আলাদা কম্পাইল ধাপ নেই — এডিট করে সাথে সাথে রান করা যায়, দ্রুত ইটারেশন সম্ভব।
  • পোর্টেবিলিটি: কম্পাইলারকে টার্গেট মেশিন আগে থেকেই জানতে হয় (কোন CPU-এর জন্য মেশিন কোড তৈরি হচ্ছে)। ইন্টারপ্রেটেড প্রোগ্রাম বেশি পোর্টেবল — একই সোর্স কোড, ইন্টারপ্রেটার থাকা যেকোনো মেশিনে চলে।

৩ · হাইব্রিড/JIT কম্পাইলেশন — আধুনিক মধ্যপন্থা

বাস্তব জগতের অনেক ভাষা (Java, JavaScript) এই দুই মডেলের মাঝামাঝি একটি পন্থা ব্যবহার করে — Just-In-Time (JIT) কম্পাইলেশনপ্রথমে পোর্টেবল বাইটকোডে কম্পাইল, তারপর এক্সিকিউশনের সময় ঘন ঘন চলা "হট" কোড নেটিভ মেশিন কোডে কম্পাইল করা।। প্রথমে সোর্স কোড একটি পোর্টেবল, ইন্টারমিডিয়েট বাইটকোডে কম্পাইল হয় (দ্রুত, পোর্টেবল — ইন্টারপ্রেটারের নমনীয়তার মতো)। তারপর, এক্সিকিউশনের সময়, যেসব কোড-পাথ বারবার চলে ("হট" কোড), সেগুলো অন-দ্য-ফ্লাই নেটিভ মেশিন কোডে কম্পাইল করা হয় — প্রায়-কম্পাইলড গতির জন্য। ফলাফল: পোর্টেবিলিটি ও পারফরম্যান্স একসাথে। Java-র JVM ও JavaScript-এর V8 ইঞ্জিন (আধুনিক ব্রাউজারে) এই পন্থার বাস্তব উদাহরণ।

সোর্স কোড (টেক্সট) বাইটকোডে কম্পাইল (পোর্টেবল IR) কোল্ড কোড → ইন্টারপ্রেট করে চালানো হট কোড → নেটিভ কোডে JIT কম্পাইল এক্সিকিউশন — যত রান, তত দ্রুত
JIT প্রথমে বাইটকোডে কম্পাইল করে, তারপর রানটাইমে ঠিক করে কোন অংশ "হট" — সেই অংশই নেটিভ কোডে কম্পাইল হয়, বাকিটা ইন্টারপ্রেট হতে থাকে।

৪ · কোডে এক্সিকিউশন-টাইম সিমুলেশন

নিচের কোড সেলে তিনটি মোডের আপেক্ষিক সময়-খরচ একটি টয় মডেল দিয়ে সিমুলেট করা হয়েছে — কম্পাইলড মোডে একটি বড় upfront অনুবাদ-খরচ কিন্তু প্রতি-রান দ্রুত; ইন্টারপ্রেটেড মোডে কোনো upfront খরচ নেই কিন্তু প্রতি-রানে বড় ওভারহেড; JIT মোডে ছোট upfront খরচ, আর প্রতি-রান খরচ ধীরে ধীরে কমতে থাকে (হট-পাথ অপ্টিমাইজেশন সিমুলেট করে)।

Python
# তিনটি এক্সিকিউশন মডেলের আপেক্ষিক সময়-খরচ সিমুলেশন
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 খরচ শোধ হয়ে যায়।
মূল কথা · Key takeaway

কম্পাইলার আর ইন্টারপ্রেটার একই কাজের (সোর্স কোড থেকে ফলাফল বের করা) দুটো ভিন্ন কৌশল — একটি "আগে সব অনুবাদ, পরে রান বারবার," আরেকটি "অনুবাদ ও রান একসাথে, প্রতিবার।" 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-কেও সামান্য পেছনে ফেলে দেয়।

অনুশীলন

  1. চিন্তা করুন: Java (JVM) ও JavaScript (V8) বাস্তব জগতে JIT কম্পাইলেশন ব্যবহার করে কেন — এই দুটো ভাষার ব্যবহারিক প্রেক্ষাপট (ওয়েব ব্রাউজার, ক্রস-প্ল্যাটফর্ম অ্যাপ্লিকেশন) মাথায় রেখে কারণ ব্যাখ্যা করুন।

    JavaScript ব্রাউজারে চলে — একই কোড হাজারো ভিন্ন ডিভাইস/OS/CPU-তে চলতে হয়, তাই খাঁটি কম্পাইলেশন (একটি নির্দিষ্ট টার্গেট মেশিনের জন্য) অসম্ভব; কিন্তু ওয়েব পেজ ইন্টারঅ্যাক্টিভ হওয়ায় বিশুদ্ধ ইন্টারপ্রিটেশনও অনেক ক্ষেত্রে অনেক ধীর। JIT (V8) উভয় সমস্যার সমাধান দেয়। Java একই কারণে "write once, run anywhere" লক্ষ্য নিয়ে ডিজাইন হয়েছিল — বাইটকোড যেকোনো JVM-এ পোর্টেবল, আর JIT সেই পোর্টেবল বাইটকোডকে বাস্তব অ্যাপ্লিকেশনের জন্য যথেষ্ট দ্রুত করে তোলে।

  2. পরীক্ষা করুন: উপরের কোড সেলে 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-এ আপনার পরবর্তী পদক্ষেপ

আগের পাঠ
সিনট্যাক্স বনাম সিমান্টিক্স