لماذا يوجد الكثير من بايثون؟ مقارنة تنفيذات Python

بايثون

المثير للدهشة أن هذا بيان غامض إلى حد ما. ماذا أعني ب “بايثون”؟ هل أقصد بايثون الواجهة المجردة؟ هل أعني CPython تطبيق Python الشائع (ولا يجب الخلط بينه وبين Cython المسمى بالمثل)؟ أم أقصد شيئًا آخر تمامًا؟ ربما أشير بشكل غير مباشر إلى جايثون أو أيرون بايثون أو PyPy. أو ربما أكون قد خرجت حقًا من النهاية العميقة وأنا أتحدث عن RPython أو RubyPython  (وهما أمران مختلفان جدًا).

بينما يتم تسمية التقنيات المذكورة أعلاه بشكل عام ومشار إليها بشكل شائع فإن بعضها يخدم أغراضًا مختلفة تمامًا (أو على الأقل تعمل بطرق مختلفة تمامًا).

طوال فترة عملي في واجهات Python مررت بالعديد من هذه. * أدوات Ython. ولكن حتى وقت قريب لم أستغل الوقت الكافي لفهم ماهيتها وكيفية عملها ولماذا هي ضرورية (بطريقتها الخاصة).

في هذا البرنامج التعليمي سأبدأ من نقطة الصفر وأنتقل عبر تطبيقات Python المختلفة وأختتم بمقدمة شاملة لـ PyPy والتي أعتقد أنها مستقبل اللغة.

يبدأ كل شيء بفهم ماهية Python”” في الواقع.

هل لغة بايثون مفسرة (interpreted) أم مجمعة (compiled)؟

هذه نقطة ارتباك شائعة للمبتدئين في بايثون.

أول شيء يجب إدراكه عند إجراء مقارنة هو أن Python”” هي واجهة. هناك تحديد لما يجب أن تفعله Python وكيف ينبغي أن تتصرف (كما هو الحال مع أي واجهة). وهناك تطبيقات متعددة (كما هو الحال مع أي واجهة).

الشيء الثاني الذي يجب إدراكه هو أن “المفسرة (interpreted)” و “المترجمة” هي خصائص تنفيذ وليست واجهة.

لذا فإن السؤال نفسه ليس جيدًا حقًا.

هل لغة بايثون مفسرة أم مجمعة؟ السؤال ليس جيدًا حقًا!

ومع ذلك بالنسبة لتطبيق Python الأكثر شيوعًا (CPython: مكتوب بلغة C يُشار إليه غالبًا باسم Python  وبالتأكيد ما تستخدمه إذا لم يكن لديك أي فكرة عما أتحدث عنه) فإن الإجابة هي: مفسر مع بعض التجميع. يقوم CPython بترجمة * شفرة مصدر Python إلى رمز ثانوي ثم يفسر هذا الرمز الثانوي وينفذه كما هو.

* ملاحظة: هذا ليس “تجميعًا(compilation)” بالمعنى التقليدي للكلمة. عادةً ما نقول إن “التجميع” يأخذ لغة عالية المستوى ويحولها إلى رمز آلي. لكنها “تجميع” من نوع ما.

لنلقِ نظرة عن كثب على هذه الإجابة حيث ستساعدنا في فهم بعض المفاهيم التي تظهر لاحقًا في المنشور.

بايت كود (bytecode) مقابل كود الآلة (machine code)

من المهم جدًا فهم الفرق بين رمز بايت مقابل. رمز الجهاز (المعروف أيضًا باسم الكود الأصلي) ربما يكون أفضل توضيح له من خلال المثال:

يُجمّع C إلى رمز الجهاز والذي يتم تشغيله بعد ذلك مباشرةً على معالجك. كل تعليمات ترشد وحدة المعالجة المركزية الخاصة بك إلى تحريك الأشياء.

تقوم Java بالترجمة إلى bytecode والتي يتم تشغيلها بعد ذلك على Java Virtual Machine (JVM) وهو تجريد للكمبيوتر الذي ينفذ البرامج. ثم يتم التعامل مع كل تعليمات بواسطة JVM والتي تتفاعل مع جهاز الكمبيوتر الخاص بك.

باختصار شديد: كود الآلة أسرع بكثير لكن الرمز الثانوي أكثر قابلية للنقل والأمان.

يبدو رمز الجهاز مختلفًا اعتمادًا على جهازك ولكن يبدو الرمز الثانوي كما هو على جميع الأجهزة. قد يقول المرء أن رمز الجهاز مُحسّن للإعداد الخاص بك.

بالعودة إلى تطبيق CPython تكون عملية toolchain كما يلي:

  • يقوم CPython بترجمة شفرة مصدر Python الخاصة بك إلى كود ثانوي.
  • ثم يتم تنفيذ هذا الرمز الثانوي على الجهاز الظاهري CPython.

غالبًا ما يفترض المبتدئون أن Python قد تم تجميعها بسبب ملفات  .pyc. هناك بعض الحقيقة في ذلك: ملف .pyc هو الرمز الثانوي المترجم والذي يتم تفسيره بعد ذلك. لذلك إذا قمت بتشغيل كود Python الخاص بك من قبل وكان لديك ملف .pyc في متناول يديك فسيتم تشغيله بشكل أسرع في المرة الثانية حيث لا يتعين عليه إعادة تجميع الرمز الثانوي.

الأجهزة الافتراضية البديلة: Jython و IronPython والمزيد

كما ذكرت سابقًا لدى Python العديد من التطبيقات. مرة أخرى كما ذكرنا سابقًا الأكثر شيوعًا هو CPython ولكن هناك أشياء أخرى يجب ذكرها من أجل دليل المقارنة هذا. هذا تطبيق Python مكتوب بلغة C ويعتبر التنفيذ “الافتراضي”.

ولكن ماذا عن تطبيقات Python البديلة؟ أحد أبرزها هو Jython وهو تطبيق Python مكتوب بلغة Java يستخدم JVM. بينما ينتج CPython رمز بايت لتشغيله على CPython VM ينتج Jython رمز Java bytecode لتشغيله على JVM  (هذه هي نفس الأشياء التي يتم إنتاجها عند تجميع برنامج Java).

تطبيقات Python البديلة

قد تسأل “لماذا قد تستخدم تطبيقًا بديلًا؟” حسنًا على سبيل المثال تعمل تطبيقات Python المختلفة هذه بشكل جيد مع مجموعات التكنولوجيا المختلفة.

يجعل CPython من السهل جدًا كتابة امتدادات C لكود Python الخاص بك لأنه في النهاية يتم تنفيذه بواسطة مترجم C. من ناحية أخرى يجعل Jython من السهل جدًا العمل مع برامج Java الأخرى: يمكنك استيراد أي فئات Java دون أي جهد إضافي واستدعاء واستخدام فئات Java الخاصة بك من داخل برامج Jython الخاصة بك. (بصرف النظر: إذا لم تكن قد فكرت في الأمر عن كثب فهذه في الواقع مكسرات. نحن في مرحلة حيث يمكنك مزج ومزج لغات مختلفة وتجميعها جميعًا إلى نفس المادة. (كما ذكر روستين البرامج التي تمزج بين كود Fortran و C موجودة منذ فترة. لذلك بالطبع هذا ليس بالضرورة جديدًا. لكنه لا يزال رائعًا.))

كمثال هذا كود جايثون صالح:

[Java HotSpot(TM) 64-Bit Server VM (Apple Inc.)] on java1.6.0_51
>>> from java.util import HashSet
>>> s = HashSet(5)
>>> s.add("Foo")
>>> s.add("Bar")
>>> s
[Foo, Bar]

IronPython هو تطبيق شائع آخر للبايثون مكتوب بالكامل بلغة C #  ويستهدف حزمة NET. . على وجه الخصوص يتم تشغيله على ما قد تسميه.NET Virtual

Machine

Microsoft Common Language Runtime (CLR) مقارنة بـ JVM

قد تقول أن جايثون Jython : Java :: IronPython : C#  تعمل على نفس الأجهزة الافتراضية الخاصة يمكنك استيراد فئات C #  من كود IronPython وفئات Java من كود Jython الخاص بك إلخ.

من الممكن تمامًا البقاء على قيد الحياة دون الحاجة إلى لمس تطبيق لا يتبع CPython Python. ولكن هناك مزايا يمكن الاستفادة منها من التبديل ويعتمد معظمها على مجموعة التكنولوجيا الخاصة بك. هل تستخدم الكثير من اللغات القائمة على JVM؟ قد يكون جايثون مناسبًا لك. كل شيء عن المكدس NET؟ ربما يجب عليك تجربة IronPython  (وربما لديك بالفعل).

تطبيقات Python البديلة

بالمناسبة، في حين أن هذا لن يكون سببًا لاستخدام تطبيق مختلف لاحظ أن هذه التطبيقات تختلف في الواقع في السلوك بخلاف كيفية تعاملها مع شفرة مصدر Python. ومع ذلك عادة ما تكون هذه الاختلافات طفيفة وتتلاشى أو تظهر بمرور الوقت حيث أن هذه التطبيقات قيد التطوير النشط. على سبيل المثال يستخدم IronPython سلاسل Unicode افتراضيًا؛ ومع ذلك يقوم CPython بالتعيين الافتراضي لـ ASCII للإصدارات 2.x (فشل مع UnicodeEncodeError للأحرف غير ASCII) ولكنه يدعم سلاسل Unicode افتراضيًا لـ 3.x.

التجميع (compilation) في الوقت المناسب: PyPy والمستقبل

لدينا تطبيق Python مكتوب بلغة C وواحد في Java وواحد في C #. الخطوة المنطقية التالية: تطبيق Python مكتوب بلغة… Python. (سيلاحظ القارئ المتعلم أن هذا مضلل بعض الشيء).

هنا حيث قد تصبح الأمور مربكة. أولاً دعنا نناقش التجميع في الوقت المناسب (just-in-time) .(JIT)

جيت (jit): لماذا وكيف؟

تذكر أن كود الجهاز الأصلي أسرع بكثير من الرمز الثانوي. حسنًا ماذا لو تمكنا من تجميع بعض الرموز الثانوية الخاصة بنا ثم تشغيلها كرمز أصلي؟ سيتعين علينا دفع بعض الثمن لتجميع الرمز الثانوي (أي الوقت) ولكن إذا كانت النتيجة النهائية أسرع فسيكون ذلك رائعًا! هذا هو الدافع وراء تجميع JIT وهي تقنية هجينة تمزج بين مزايا المترجمين الفوريين والمترجمين. بعبارات أساسية يريد JIT استخدام التجميع لتسريع نظام مفسر.

على سبيل المثال نهج مشترك تتبعه JITs:

  1. تحديد رمز بايت يتم تنفيذه بشكل متكرر.
  2. قم بتجميعها إلى رمز الجهاز الأصلي.
  3. تخزين النتيجة مؤقتًا.
  4. عندما يتم تعيين نفس الرمز الثانوي ليتم تشغيله بدلاً من ذلك احصل على رمز الجهاز المجمع مسبقًا وجني الفوائد (أي زيادة السرعة).

هذا ما يدور حوله تطبيق PyPy: إحضار JIT إلى Python (انظر الملحق لمعرفة الجهود السابقة). هناك بالطبع أهداف أخرى: تهدف PyPy إلى أن تكون عبر النظام الأساسي وخفيفة الذاكرة وداعمة بدون تكديس. لكن JIT هي حقًا نقطة بيعها. كمتوسط ​​على مدى مجموعة من الاختبارات الزمنية يُقال إنه يحسن الأداء بعامل 6.27. للحصول على تفصيل راجع هذا المخطط من PyPy Speed ​​Center:

يصعب فهم PyPy

تتمتع PyPy بإمكانيات هائلة وفي هذه المرحلة تتوافق بشكل كبير مع CPython  (لذا يمكنها تشغيل Flask و Django وما إلى ذلك).

ولكن هناك الكثير من الالتباس حول PyPy  (انظر على سبيل المثال هذا الاقتراح غير المنطقي لإنشاء PyPyPy …). في رأيي هذا في الأساس لأن PyPy هي في الواقع شيئين:

  • مترجم بايثون مكتوب بلغة RPython  (وليس لغة Python  (لقد كذبت من قبل)). RPython هي مجموعة فرعية من Python مع كتابة ثابتة. في بايثون من “المستحيل في الغالب” التفكير بصرامة حول الأنواع (لماذا يصعب ذلك؟ حسنًا ضع في اعتبارك حقيقة ما يلي:
x = random.choice([1, "foo"])

    سيكون كود Python صالحًا (الائتمان إلى Ademan). ما هو نوع x؟ كيف يمكننا التفكير في أنواع المتغيرات عندما لا يتم تطبيق الأنواع بشكل صارم؟). مع RPython أنت تضحي ببعض المرونة ولكن بدلاً من ذلك تجعل التفكير في إدارة الذاكرة وغير ذلك أسهل بكثير مما يسمح بالتحسينات.

  • مترجم يقوم بتجميع كود RPython لأهداف مختلفة ويضيف في JIT. النظام الأساسي الافتراضي هو C أي مترجم RPython-to-C ولكن يمكنك أيضًا استهداف JVM وغيرها.

من أجل الوضوح فقط في دليل مقارنة بايثون هذا سأشير إليها باسم PyPy (1) و PyPy (2).

لماذا تحتاج هذين الشيئين ولماذا تحت سقف واحد؟ فكر في الأمر بهذه الطريقة: PyPy (1)  مترجم مكتوب بلغة RPython. لذلك يأخذ المستخدم كود Python ويجمعه إلى الرمز الثانوي. لكن المترجم نفسه (المكتوب بلغة RPython) يجب أن يتم تفسيره من خلال تطبيق Python آخر لكي يعمل أليس كذلك؟

حسنًا يمكننا فقط استخدام CPython لتشغيل المترجم. لكن ذلك لن يكون سريعًا جدًا.

بدلاً من ذلك ، الفكرة هي أننا نستخدم PyPy (2) (يشار إليها باسم RPython Toolchain) لتجميع مترجم PyPy وصولاً إلى رمز لمنصة أخرى (على سبيل المثال C أو JVM أو CLI) للتشغيل على أجهزتنا مع إضافة JIT كـ حسنا. إنه أمر سحري: يضيف PyPy JIT ديناميكيًا إلى المترجم الفوري وينشئ مترجمًا خاصًا به! (مرة أخرى هذا هراء: نحن نجمع مترجمًا فوريًا ونضيف مترجمًا آخر منفصلًا ومستقلًا.)

في النهاية تكون النتيجة ملف تنفيذي مستقل يفسر كود مصدر Python ويستغل تحسينات JIT. وهو بالضبط ما أردناه! إنه أمر ممتع ولكن ربما يساعد هذا الرسم التخطيطي:

للتكرار فإن الجمال الحقيقي لـ PyPy هو أننا يمكن أن نكتب لأنفسنا مجموعة من مترجمي Python المختلفين في RPython دون القلق بشأن JIT. ستقوم PyPy بعد ذلك بتنفيذ JIT لنا باستخدام RPython Toolchain / PyPy (2).

في الواقع إذا أصبحنا أكثر تجريدًا يمكنك نظريًا كتابة مترجم لأي لغة وإطعامه إلى PyPy والحصول على JIT لتلك اللغة. وذلك لأن PyPy يركز على تحسين المترجم الفعلي بدلاً من تفاصيل اللغة التي يترجمها.

كتابة مترجم فوري (interpreter)

يمكنك نظريًا كتابة مترجم فوري (interpreter) لأي لغة وإطعامه إلى PyPy والحصول على JIT لتلك اللغة.

كاستطراداً موجزاً أود أن أذكر أن JIT نفسها رائعة للغاية. يستخدم تقنية تسمى التتبع والتي تنفذ على النحو التالي:

  1. قم بتشغيل المترجم الفوري وتفسير كل شيء (مع عدم إضافة JIT).
  2. قم ببعض التنميط الخفيف للشفرة المفسرة.
  3. حدد العمليات التي أجريتها من قبل.
  4. قم بتجميع أجزاء التعليمات البرمجية هذه وصولاً إلى رمز الجهاز.

للختام: نستخدم مترجم  PyPy’s RPython-to-C (أو النظام الأساسي المستهدف الآخر) لتجميع مترجم PyPy الذي تم تنفيذه بواسطة RPython.

النتیجة

بعد مقارنة مطولة لتطبيقات بايثون يجب أن أسأل نفسي: لماذا هذا رائع جدًا؟ لماذا هذه الفكرة المجنونة تستحق المتابعة؟ أعتقد أن أليكس جاينور صاغها جيدًا في مدونته: “[PyPy هو المستقبل] لأنه يوفر سرعة أفضل ومزيدًا من المرونة وهو منصة أفضل لنمو Python”.

باختصار:

  • إنه سريع لأنه يجمع شفرة المصدر إلى كود أصلي (باستخدام JIT).
  • إنه مرن لأنه يضيف JIT إلى المترجم الفوري الخاص بك مع القليل جدًا من العمل الإضافي.
  • وإنه مرن (مرة أخرى) لأنه يمكنك كتابة المترجمين الفوريين في RPython وهو أسهل في التمديد من على سبيل المثال  C (في الواقع من السهل جدًا وجود برنامج تعليمي لكتابة المترجمين الفوريين الخاصين بك).

المصدر

منشور ذات صلة
Scala مقابل Python 3 Minutes

Scala مقابل Python

محسن البوغبيش

بایثون وسکالا، هما لغتا برمجة للأغراض العامة، التي تدعمان نموذج برمجة كائنية التوجه (Object oriented programming) لإنشاء التطبيقات. تلعب هاتين اللغتين دورا مهم للغاية في نمو مشاريع علوم البيانات ومستقبلها.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *

السلة