الأربعاء، 14 أكتوبر 2026 القاهرة 21.8°C

ما الفرق بين Compiler وInterpreter وJIT؟ شرح عملي بأمثلة C# وPython وJavaScript

رسم توضيحي لمسارات تنفيذ الكود عبر Compiler وInterpreter وJIT في C# وPython وJavaScript
تصور توضيحي لمسارات Compiler وInterpreter وJIT من كود المصدر إلى التنفيذ، للتبسيط لا لوصف كل Runtime حرفيا.

عندما تكتب 3 + 4 داخل برنامج، ما الذي يحدث قبل أن تظهر النتيجة 7 على الشاشة؟ هل يترجم الكمبيوتر البرنامج بالكامل أولا، أم ينفذ كل تعليمة أثناء التشغيل، أم يجمع بين الطريقتين؟ هذا هو السؤال الذي يجيب عنه الفرق بين Compiler وInterpreter وJIT.

هذا المقال خطوة ثانية بعد شرحنا كيف يفهم الكمبيوتر التعليمات؟ من الكود إلى لغة الآلة. المقال السابق يشرح الصورة العامة وعلاقة الكود بالمعالج، أما هنا فسندخل إلى ما الذي تفعله أدوات الترجمة والتنفيذ فعليا، ونطبق نفس البرنامج الصغير باستخدام C# وPython وJavaScript.

ما الذي ستتعلمه؟ ستفهم معنى Source Code وBytecode وIL وMachine Code، ولماذا قد تستخدم اللغة نفسها Compiler وInterpreter في مراحل مختلفة، وما المقصود بالتجميع الفوري JIT والتجميع المسبق AOT، وكيف ترى بعض هذه المراحل بنفسك.

الخلاصة السريعة: ما الفرق بين Compiler وInterpreter وJIT؟

المفهومما الذي يفعله؟متى يحدث؟مثال في الواقع
Compiler (مترجم)يحول تمثيلا للكود إلى تمثيل آخر، مثل Source Code إلى IL أو Machine Codeقد يحدث قبل التشغيل أو أثناءهمترجم C# يحول الشيفرة إلى IL عند البناء
Interpreter (مفسر)ينفذ تعليمات تمثيل معين باستخدام برنامج أو محرك تشغيلأثناء التنفيذمفسر CPython ينفذ Bytecode
JIT Compiler (تجميع فوري)يترجم أجزاء من الكود إلى Machine Code أثناء تشغيل البرنامجوقت التشغيل Runtimeمترجم .NET JIT وبعض طبقات V8
AOT Compiler (تجميع مسبق)ينتج كودا أصليا قبل تشغيل التطبيققبل التشغيل، غالبا أثناء البناء أو النشرNative AOT في .NET

نقطة محورية: Compiler وInterpreter ليسا نوعين متعارضين من «اللغات» بقدر ما هما آليتان يمكن الجمع بينهما. وJIT ليس نقيض Compiler؛ إنه نوع من الترجمة يحدث أثناء التشغيل.

أولا: ما هو Compiler؟ وما المشكلة التي يحلها؟

الـCompiler برنامج يستقبل شيفرة أو تمثيلا برمجيا ويحلله ثم يحوله إلى تمثيل آخر. أحيانا يكون الناتج تعليمات آلة يفهمها المعالج مباشرة، وأحيانا يكون صيغة وسيطة مثل IL أو Bytecode لا ينفذها المعالج وحدها.

مثلا، في مسار C# المعتاد: C# Source → C# Compiler → IL داخل Assembly. الملف الناتج ليس مجرد نسخة مقروءة من الكود؛ بل يحتوي تعليمات وسيطة ومعلومات Metadata عن الأنواع والطرق وغيرها. وهذا موثق في شرح Microsoft لعملية التنفيذ المُدار.

وعندما نقول إن C أو C++ تُترجمان غالبا إلى ملفات أصلية، فهذا مثال آخر على Compiler، لكنه يختلف في المخرجات والمسار عن C# المعتادة. لذلك ليس كل ناتج Compiler عبارة عن ملف Machine Code نهائي.

ثانيا: ما هو Interpreter؟ ولماذا لا يعني «تنفيذ السطر كما هو»؟

الـInterpreter هو جزء من بيئة التشغيل ينفذ تعليمات برنامج ما. وفي التطبيقات الحديثة قد تكون التعليمات التي ينفذها ليست النص الأصلي الذي كتبه المبرمج، وإنما Bytecode جرى توليده بعد تحليل النص وترجمته إلى صيغة وسيطة.

خذ CPython مثالا: المصدر .py يمر بمرحلة ترجمة إلى Bytecode، ثم ينفذ مفسر CPython هذا التمثيل. لذلك العبارة الشائعة «Python لا تُترجم أبدا» غير دقيقة. وفي النهاية المعالج يشغل البرنامج الأصلي الذي ينفذ المفسر نفسه؛ لا يفهم تعليمات Python مباشرة.

كذلك ليس صحيحا أن المفسر يفحص البرنامج دائما «سطرًا بسطر» كما تظهر الأسطر في المحرر. التفاصيل تتبع تنفيذ اللغة ومحركها، ويمكن أن تتضمن تحليلًا وترجمة داخلية وتحسينات.

ثالثا: ما هو JIT Compiler؟ ومتى يفيد؟

اختصار JIT يعني Just-In-Time Compilation، أي ترجمة الكود إلى تعليمات آلة خلال تشغيل التطبيق، بدلا من الاعتماد فقط على ترجمة مسبقة لكل جزء منه.

في .NET التقليدي، يستطيع JIT ترجمة طريقة (Method) من IL إلى Machine Code عندما تُطلب للتنفيذ. وبعد ذلك يمكن استدعاء النسخة الأصلية المترجمة بدلا من ترجمتها من جديد في كل استدعاء. وتشرح Microsoft هذا المسار في الوثائق الرسمية.

إذن الفكرة المبسطة هي: IL → JIT عند الحاجة → Machine Code → CPU. يمكن أن تضيف الترجمة بعض كلفة بدء التنفيذ، لكنها تسمح بإنتاج كود للمعمارية التي يعمل عليها التطبيق وبالاستفادة من معلومات تتوفر أثناء التشغيل. الأداء الفعلي يعتمد على التطبيق وبيئة التشغيل، ولا يمكن القول إن JIT «أسرع دائما».

التجربة الأولى: ماذا يحدث لبرنامج C# داخل .NET؟

سنستخدم مثالا صغيرا يجمع رقمين. افترض أن لديك .NET SDK مثبتا، ثم أنشئ مشروع Console:

dotnet new console -n CodeJourney
cd CodeJourney

استبدل محتوى Program.cs بالتالي:

Console.WriteLine(Add(3, 4));

static int Add(int a, int b)
{
    return a + b;
}

نفذ:

dotnet build
dotnet run

النتيجة المتوقعة:

7

المهم هنا هو الفرق بين عمليتي البناء والتشغيل:

  1. أثناء dotnet build يحوّل مترجم C# الشيفرة إلى IL مع Metadata، وينتج Assembly.
  2. عند dotnet run تبدأ بيئة .NET في تشغيل البرنامج، ويترجم JIT ما يلزم من IL إلى تعليمات أصلية حسب نظام التشغيل والمعمارية وإعدادات التشغيل.
  3. ينفذ المعالج التعليمات الأصلية فتظهر 7.

انتبه: لا تستنتج من وجود ملف .dll بعد البناء أنه يحتوي بالضرورة على Machine Code نهائي لكل طرقك. كما أن تشغيل .NET قد يستفيد من شيفرة مترجمة مسبقا ضمن Runtime أو ReadyToRun؛ الرسم السابق هو المسار التعليمي المعتاد، لا وصف حرفي لكل طريقة في كل بيئة.

التجربة الثانية: كيف ترى Python Bytecode بنفسك؟

Python مثالية هنا لأن مكتبتها القياسية توفر أداة dis التي تعرض تعليمات Bytecode الخاصة بتنفيذ CPython.

أنشئ ملفا باسم example.py واكتب:

import dis

def add(a, b):
    return a + b

print(add(3, 4))
dis.dis(add)

ثم نفذ:

python example.py

سترى الرقم 7، وبعده تعليمات Bytecode للدالة add. ستجد أسماء عمليات مثل تحميل المتغيرات وإجراء الجمع ثم إعادة النتيجة. قد تتغير أسماء التعليمات وترتيبها بحسب نسخة CPython؛ لذلك لا نحفظ مخرجات dis باعتبارها ثابتة في جميع الإصدارات.

هذا التمرين يثبت نقطة مهمة: الشيفرة التي كتبناها لم تنتقل إلى المعالج مباشرة، بل أصبحت تعليمات وسيطة يمكن لمفسر CPython تنفيذها. تؤكد وثائق Python الرسمية لأداة dis أن Bytecode تفصيل خاص بتنفيذ CPython وقد يتغير بين الإصدارات.

هل هذا يعني أن Python لا يمكن أن تستخدم JIT؟ لا. تتوفر في بعض توزيعات وإصدارات CPython الحديثة آليات تجميع فوري تجريبية؛ على سبيل المثال وثائق Python 3.14 تشرح JIT اختياريًا تجريبيًا. كما توجد تطبيقات أخرى للغة Python تتبع مسارات مختلفة. لذلك حديثنا عن Bytecode والمفسر يصف المسار الشائع في CPython، لا قانونا لكل نسخ Python.

التجربة الثالثة: لماذا JavaScript ليست «لغة مفسرة فقط»؟

لنكتب نفس المثال في ملف example.js:

function add(a, b) {
  return a + b;
}

console.log(add(3, 4));

إن كان Node.js مثبتا، نفذه عبر:

node example.js

وستحصل على:

7

هذا السطر لا يكشف بنفسه كيف نفذ المحرك العملية. لذلك سنستخدم V8، وهو المحرك المستخدم في Node.js وChrome، لتوضيح الفكرة. وفق الوثائق الهندسية الرسمية لمشروع V8، يترجم V8 شيفرة JavaScript أولا إلى Bytecode ينفذه المفسر Ignition، ثم يمكن أن تنتقل أجزاء من الكود إلى طبقات تجميع وتحسين مثل Sparkplug وMaglev وTurboFan وفقا لسلوك التنفيذ وإصدار المحرك.

يمكن تلخيص المسار دون الدخول في تفاصيل كل طبقة:

JavaScript Source → Bytecode → Ignition (Interpreter) → ترجمة وتحسين إلى Machine Code عند الحاجة → CPU

لماذا يحتاج المحرك إلى هذه الطبقات؟ لأن تحويل كل الدوال إلى كود عالي التحسين عند بدء الصفحة قد يزيد زمن البدء واستهلاك الموارد. من الممكن البدء سريعا، ثم تحسين أجزاء ينفذها البرنامج بكثرة. لكن لا يوجد ضمان أن دالة صغيرة بعينها ستصل إلى طبقة معينة، ولا أن كل محركات JavaScript تعمل مثل V8 بالضبط.

أين يدخل AOT؟ وهل يلغي الحاجة إلى JIT؟

AOT اختصار Ahead-of-Time Compilation. في هذا الأسلوب يمكن إنتاج Machine Code قبل بدء تشغيل التطبيق. توفر .NET مثلا خيار Native AOT عند نشر بعض أنواع التطبيقات، وفي هذه الحالة لا يستخدم التطبيق مترجم JIT أثناء التشغيل بالطريقة المعتادة.

قد يساعد ذلك على تقليل زمن البدء واستهلاك الذاكرة في سيناريوهات مناسبة، لكنه يأتي بقيود على بعض إمكانات التحميل أو إنشاء الكود ديناميكيا، ويتطلب البناء للمنصة المستهدفة. راجع وثائق Microsoft لـNative AOT.

إذن JIT وAOT استراتيجيتان لتوقيت الترجمة، وليستا حكمًا مطلقًا على مدى جودة اللغة أو سرعتها.

جدول مقارنة حقيقي بين C# وPython وJavaScript

السؤالC# مع .NET المعتادةPython مع CPython المعتادةJavaScript مع V8
هل توجد مرحلة ترجمة؟نعم: Source إلى ILنعم: Source إلى Bytecodeنعم: Source إلى Bytecode
هل يوجد مفسر؟المسار المعتاد يعتمد على JIT للكود المُدار وليس تفسير IL تعليمة بتعليمةنعم، مفسر Bytecode أساسي في المسار المعتادنعم، Ignition ينفذ Bytecode
هل توجد ترجمة فورية JIT؟نعم، أساسية في المسار المعتادتوجد خيارات وتجارب بحسب الإصدار والبناء، وليست افتراضا عامانعم، ضمن طبقات تحسين V8
هل يمكن وجود مسار مختلف؟نعم، مثل Native AOTنعم، باختلاف التنفيذ أو الإعداداتنعم، حسب محرك JavaScript وإعداده

المقارنة هنا تخص بيئات تشغيل محددة. لذلك السؤال الأدق ليس «هل Python مترجمة أم مفسرة؟»، بل «كيف ينفذ CPython هذه الشيفرة في هذا الإصدار؟».

خمسة مفاهيم خاطئة قد تربكك في مقابلات العمل

  1. «Compiler يخرج Machine Code دائما»: خطأ؛ قد يخرج IL أو Bytecode أو تمثيلا آخر.
  2. «اللغة المفسرة لا تمر بترجمة»: خطأ؛ CPython وV8 يمران بترجمة إلى Bytecode قبل التنفيذ المعتاد.
  3. «JIT هو Interpreter»: خطأ؛ JIT يترجم إلى Machine Code وقت التشغيل، أما المفسر فينفذ تعليمات تمثيل معين.
  4. «وجود JIT يجعل البرنامج أسرع دائما»: خطأ؛ هناك تكلفة ترجمة وبدء تشغيل، وتختلف النتيجة حسب الحمل والإعدادات.
  5. «كل كود JavaScript يعمل بالطريقة نفسها في جميع المتصفحات»: خطأ؛ مواصفات اللغة شيء، وتنفيذ المحرك وخطوطه الداخلية شيء آخر.

تمرين صغير: اختبر فهمك

لنفترض أنك كتبت دالة داخل برنامج C# ولم يستدعها البرنامج مطلقا. هل يجب أن يترجم JIT هذه الدالة إلى Machine Code عند تشغيل التطبيق؟ ليس بالضرورة؛ من مزايا الترجمة عند الحاجة أنها قد تتجنب ترجمة أجزاء غير مستخدمة. وقد تختلف التفاصيل في حالات التجميع المسبق.

السؤال الثاني: عندما تطبع Python نتيجة الجمع 7، هل يعني ذلك أن المعالج يفهم Python Bytecode بنفسه؟ لا؛ مفسر CPython هو البرنامج الذي ينفذ هذه التعليمات، بينما المعالج ينفذ Machine Code الخاص ببيئة التشغيل وأي شيفرة أصلية مستخدمة.

السؤال الثالث: هل يجب أن يكون JIT أكثر سرعة من AOT لكل برنامج؟ لا؛ التقييم يعتمد على وقت بدء التطبيق واستهلاك الذاكرة وطبيعة الحمل والقدرة على التحسين.

متى يفيدك فهم Compiler وInterpreter وJIT في عملك؟

كلمة واحدة في خطأ أو تقرير أداء قد تصبح مفهومة أكثر عندما تعرف المرحلة التي تحدث فيها المشكلة. Compile-time error يعني أن الخطأ ظهر أثناء الترجمة أو التحليل قبل وصول البرنامج إلى مرحلة التنفيذ المعتادة. أما Runtime error فيحدث عندما يعمل التطبيق ويصل إلى حالة لا يستطيع التعامل معها. وهناك كذلك تكاليف startup وتكاليف تنفيذ متكرر قد تتأثر باستراتيجية الترجمة.

إذا كنت تطور تطبيقات .NET، فهم IL وJIT يساعدك في قراءة الخيارات المتعلقة بالنشر وNative AOT وفهم سبب اختلاف الأداء في بعض السيناريوهات. وإذا كنت تطور الويب بـJavaScript، فمعرفة فكرة الطبقات في V8 تفيدك دون أن تكون مضطرا لحفظ التفاصيل المتغيرة داخل المحرك. وإذا كنت تستخدم Python، فإن dis أداة تعليمية جيدة لفهم الخطوة الوسيطة بين مصدر البرنامج وتنفيذه.

أسئلة شائعة

ما الفرق بين Compiler وInterpreter للمبتدئين؟

Compiler يترجم تمثيلا برمجيا إلى آخر، بينما Interpreter ينفذ تعليمات داخل بيئة تشغيل. ويمكن أن يستخدم البرنامج المترجم والمفسر معا.

ما هو JIT في C#؟

مترجم يعمل أثناء تشغيل التطبيق ويحوّل ما يلزم من تعليمات IL إلى تعليمات أصلية يستطيع معالج الجهاز تنفيذها. قد توجد آليات نشر مسبقة تغير هذا المسار.

هل JavaScript لغة مفسرة أم مترجمة؟

الأدق أن نقول إن طريقة التنفيذ تعتمد على المحرك. في V8 مثلا توجد مرحلة ترجمة إلى Bytecode، ثم تفسير، مع إمكانية الانتقال إلى تجميع فوري محسّن.

هل Python تستخدم Compiler؟

نعم. في CPython توجد ترجمة للشيفرة إلى Bytecode قبل التنفيذ المعتاد بالمفسر؛ وقد تتوفر مسارات أخرى في توزيعات وإصدارات معينة.

ما الفرق بين Bytecode وMachine Code؟

Bytecode تمثيل وسيط مخصص عادة لمحرك أو بيئة تشغيل، بينما Machine Code يحتوي تعليمات أصلية لمعمارية معالج بعينها.

الخلاصة

Compiler يترجم الكود، وInterpreter ينفذ تعليمات عبر بيئة تشغيل، وJIT يترجم أجزاء إلى Machine Code أثناء التشغيل، بينما AOT ينفذ الترجمة الأصلية قبل التشغيل. لكن التطبيقات الواقعية تمزج بين هذه المراحل بطرق مختلفة.

لا تحفظ تصنيف «لغة مترجمة ولغة مفسرة» ثم تتوقف؛ جرّب أمثلة المقال بنفسك، وافتح نتيجة dis في Python، وراقب مراحل بناء مشروع .NET، وتذكر أن JavaScript في V8 يمكن أن تمر من المفسر إلى التجميع المُحسّن. هذا الفهم سيجعلك أدق عندما تسأل عن الأداء أو الأخطاء أو طريقة تنفيذ البرنامج.

المراجع الرسمية

آخر مراجعة: 13 أكتوبر 2026. الأمثلة تعليمية ومسارات التنفيذ المبسطة تختلف في التفاصيل باختلاف إصدارات الأدوات وإعدادات النشر.

تعليقات
جارٍ التحميل...