البدء مع PyPy

0 الأسهم
0
0
0
0

مقدمة

لغة برمجة بايثون هي واجهة يمكن تنفيذها بطرق مختلفة. من الأمثلة على ذلك CPython التي تستخدم لغة C، وJython التي تُنفَّذ باستخدام Java، وغيرها.

على الرغم من كونه الأكثر شيوعًا، إلا أن CPython ليس الأسرع. PyPy هو تطبيق بديل لبايثون، يتميز بالتوافق والسرعة. يعتمد PyPy على التجميع في الوقت المناسب (JIT)، مما يُقلل بشكل كبير من وقت تنفيذ العمليات طويلة الأمد.

في هذا البرنامج التعليمي، سنقدم PyPy للمبتدئين ونسلط الضوء على اختلافاته عن CPython. سنناقش أيضًا مزاياه وعيوبه. بعد ذلك، سنتناول كيفية تنزيل PyPy واستخدامه لتشغيل نص برمجي بسيط بلغة بايثون.

ويغطي هذا التدريب على وجه التحديد ما يلي:

  • نظرة عامة سريعة على CPython
  • مقدمة عن PyPy وميزاته
  • قيود PyPy
  • تشغيل PyPy على Ubuntu
  • وقت تشغيل PyPy مقابل CPython

نظرة عامة سريعة على CPython

قبل مناقشة PyPy، من المهم فهم آلية عمل CPython. في الصورة أدناه، يمكنك رؤية مسار تنفيذ نص برمجي باستخدام CPython.

في حالة وجود نص برمجي بلغة بايثون بامتداد .py، يُجمّع الكود المصدري أولًا إلى بايت كود باستخدام مُجمّع CPython. يُولّد البايت كود ويُخزّن في ملف ذي امتداد pyc. ثم يُنفّذ البايت كود في بيئة افتراضية باستخدام مُفسّر CPython.

هناك مزايا لاستخدام مُجمِّع لتحويل الشيفرة المصدرية إلى شيفرة ثنائية. في حال عدم استخدام مُجمِّع، يعمل المُفسِّر مباشرةً على الشيفرة المصدرية ويُترجمها سطرًا بسطر إلى شيفرة آلية. عيب هذا الإجراء هو ضرورة تطبيق بعض العمليات لترجمة كل سطر من الشيفرة المصدرية إلى شيفرة آلية، وتتكرر هذه العمليات لكل سطر. على سبيل المثال، يُطبَّق تحليل بناء الجملة على كل سطر بشكل مُستقل عن الأسطر الأخرى، وبالتالي يُمضي المُفسِّر وقتًا طويلًا في ترجمة الشيفرة. يُحل المُجمِّع هذه المُشكلة لأنه يُمكنه معالجة جميع الشيفرة دفعةً واحدة، وبالتالي يُطبَّق تحليل بناء الجملة مرةً واحدةً فقط بدلاً من تطبيقه على كل سطر. وبالتالي، يُفسَّر الشيفرة الثنائية الناتجة عن المُجمِّع بسهولة. تجدر الإشارة إلى أن تجميع الشيفرة المصدرية بالكامل قد لا يكون مفيدًا في بعض الحالات، وسنرى مثالًا واضحًا على ذلك عند مناقشة PyPy.

بمجرد توليد البايت كود، يُنفّذه المُفسّر المُشغّل في الآلة الافتراضية. تُعدّ البيئة الافتراضية مُفيدة لأنها تفصل بايت كود CPython عن الآلة، مما يجعل بايثون متعدد المنصات.

للأسف، لا يكفي مجرد استخدام مُجمِّع لتوليد الكود الثنائي لتسريع تنفيذ CPython. يعمل المُفسِّر بترجمة الكود إلى كود الآلة في كل مرة يُنفَّذ فيها. لذا، إذا استغرق تنفيذ سطر LX ثانية، فإن تنفيذه 10 مرات سيستغرق X*10 ثانية. بالنسبة للعمليات طويلة الأمد، يكون هذا مُكلفًا للغاية من حيث وقت التنفيذ.

استنادًا إلى أخطاء CPython، دعنا الآن نلقي نظرة على PyPy.

مقدمة عن PyPy وميزاته

PyPy هو تطبيق بايثون مشابه لـ CPython، يتميز بالتوافق والسرعة. "التوافق" يعني أن PyPy متوافق مع CPython، حيث يمكنك استخدام جميع أوامر CPython تقريبًا فيه. كما ذكرنا، هناك بعض الاختلافات في التوافق. أهم ميزة لـ PyPy هي سرعته. فهو أسرع بكثير من CPython. سنرى لاحقًا بعض الاختبارات التي يُظهر فيها أداءً أسرع بحوالي 7 مرات. في بعض الحالات، قد يكون أسرع بعشرات أو مئات المرات من CPython. فكيف يحقق PyPy هذه السرعة؟

سرعة

يستخدم PyPy مُجمِّعًا لحظيًا (JIT) يُسرِّع نصوص Python بشكل كبير. نوع التجميع المُستخدم في CPython هو التجميع المسبق (AOT)، أي أن جميع الشيفرة البرمجية تُترجم إلى بايت كود قبل التنفيذ. يُترجم JIT الشيفرة البرمجية وقت التشغيل فقط، عند الحاجة.

قد يحتوي الكود المصدري على كتل من الكود لا تُنفَّذ إطلاقًا، ولكن تُترجم باستخدام مُجمِّع AOT. هذا يُؤدي إلى بطء في زمن المعالجة. عندما يكون الكود المصدري كبيرًا ويحتوي على آلاف الأسطر، يُحدث استخدام JIT فرقًا كبيرًا. مع AOT، يُترجم الكود المصدري بالكامل، وبالتالي يستغرق وقتًا طويلًا. أما مع JIT، فيُنفَّذ الجزء المطلوب فقط من الكود، مما يُسرِّع العملية بشكل كبير.

بعد أن يترجم PyPy جزءًا من الكود، يُخزَّن مؤقتًا. هذا يعني أن الكود يُترجم مرة واحدة فقط، ثم تُستخدم الترجمة لاحقًا. يُكرِّر مُفسِّر CPython الترجمة في كل مرة يُنفَّذ فيها الكود، وهذا سبب آخر لبطء تنفيذه.

بدون جهد

ليس PyPy الطريقة الوحيدة لتحسين أداء نصوص بايثون، ولكنه الأسهل. على سبيل المثال، يمكن استخدام Cython لتسريع تعيين أنواع C للمتغيرات. تكمن المشكلة في أن Cython يتطلب من المطور مراجعة الكود المصدري يدويًا وتحسينه. هذا الأمر مُمل ويزداد تعقيدًا مع زيادة حجم الكود. باستخدام PyPy، يمكنك تشغيل كود بايثون العادي بشكل أسرع بكثير ودون أي جهد.

لا يوجد كومة

يستخدم بايثون القياسي مكدس C. يخزن هذا المكدس تسلسل الدوال المستدعاة من بعضها البعض (الإرجاع). ولأن المكدس محدود الحجم، فإن عدد استدعاءات الدوال محدود.

يستخدم PyPy بايثون بدون تكديس، وهو تطبيق لبايثون لا يستخدم مكدس C. بدلاً من ذلك، يخزن استدعاءات الدوال على المكدس بجانب الكائنات. حجم المكدس أكبر من حجم الكومة، مما يسمح بإجراء المزيد من استدعاءات الدوال.

يدعم بايثون Stackless أيضًا الخيوط الدقيقة، وهي أفضل من خيوط بايثون العادية. في خيط بايثون Stackless، يمكنك تشغيل آلاف المهام التي تُسمى "مهام صغيرة"، وتعمل جميعها على خيط واحد.

يتيح استخدام وحدات المهام الصغيرة (Tasklets) تنفيذ المهام بشكل متزامن. التزامن يعني تشغيل مهمتين في الوقت نفسه، مع تقاسم الموارد نفسها. تعمل إحدى المهمتين لفترة، ثم تتوقف لإفساح المجال للمهمة الثانية. تجدر الإشارة إلى أن هذا يختلف عن التوازي، الذي يتضمن تشغيل مهمتين منفصلتين ولكن متزامنتين.

يُقلل استخدام Tasklets عدد الخيوط المُنشأة، مما يُقلل من تكلفة إدارة جميع هذه الخيوط بواسطة نظام التشغيل. ونتيجةً لذلك، يستغرق تسريع التنفيذ بالتبديل بين خيطين وقتًا أطول من التبديل بين مهمتين.

كما مهد استخدام بايثون بدون تكديس الطريق للاستمرارية. تتيح لنا الاستمرارية حفظ حالة المهمة واستعادتها لاحقًا لمواصلة المهمة. تجدر الإشارة إلى أن بايثون بدون تكديس لا يختلف عن بايثون القياسي، بل يضيف وظائف إضافية. كل ما هو متاح في بايثون القياسي سيكون متاحًا أيضًا في بايثون بدون تكديس.

بعد مناقشة فوائد PyPy، دعنا نتحدث عن حدوده في القسم التالي.

قيود PyPy

على الرغم من أنه يمكنك استخدام CPython على أي جهاز وأي بنية وحدة معالجة مركزية، إلا أن دعم PyPy محدود نسبيًا.

فيما يلي هياكل وحدة المعالجة المركزية التي يدعمها ويحافظ عليها PyPy (المصدر):

  • x86 (IA-32) و x86_64
  • منصات ARM (ARMv6 أو ARMv7، مع VFPv3)
  • AArch64
  • PowerPC 64 بت، كل من الطرف الصغير والكبير
  • نظام Z (s390x)

لا يعمل PyPy على جميع توزيعات لينكس، لذا يُنصح باستخدام إحدى التوزيعات المدعومة. سيؤدي تشغيل ملف PyPy Linux الثنائي على توزيعة غير مدعومة إلى ظهور خطأ. يدعم PyPy إصدارًا واحدًا فقط من بايثون 2 وبايثون 3، وهما PyPy 2.7 وPyPy 3.6.

إذا كان الكود المُشغّل باستخدام PyPy مُعدّلاً بلغة بايثون خالصة، فعادةً ما تكون سرعة PyPy ملحوظة. ومع ذلك، إذا كان الكود يتضمن امتدادات C مثل NumPy، فقد يزيد PyPy من الوقت. مشروع PyPy قيد التطوير حاليًا، وبالتالي قد يُقدّم دعمًا أفضل لامتدادات C في المستقبل.

لا يدعم عدد من أطر عمل بايثون الشائعة، مثل Kivy، PyPy. يسمح Kivy لـ CPython بالعمل على جميع المنصات، بما في ذلك Android وiOS. هذا يعني أن PyPy لا يعمل على الأجهزة المحمولة.

الآن بعد أن رأينا مزايا وعيوب PyPy، دعنا نشرح كيفية تشغيل PyPy على Ubuntu.

تشغيل PyPy على Ubuntu

يمكنك تشغيل PyPy على أنظمة ماك، لينكس، وويندوز، ولكننا سنناقش تشغيله على أوبونتو. من المهم ملاحظة أن ثنائيات PyPy Linux مدعومة فقط على توزيعات لينكس محددة. يمكنك الاطلاع على ثنائيات PyPy المتوفرة والتوزيعات المدعومة في هذه الصفحة. على سبيل المثال، PyPy (أو بايثون 2.7 أو بايثون 3.6) مدعوم فقط لثلاثة إصدارات من أوبونتو: 18.04، 16.04، و14.04. إذا كان لديك أحدث إصدار من أوبونتو حتى هذا التاريخ (19.10)، فلن تتمكن من تشغيل PyPy عليه. سيؤدي تشغيل PyPy على توزيعة غير مدعومة إلى ظهور الخطأ التالي:

تأتي ملفات PyPy الثنائية كملفات مضغوطة. كل ما عليك فعله هو فك ضغط الملف الذي نزّلته. داخل المجلد غير المضغوط، يوجد مجلد باسم bin، حيث يمكنك العثور على ملف PyPy التنفيذي. أستخدم Python 3.6، لذا يُسمى الملف pypy3. أما بالنسبة لـ Python 2.7، فيُسمى pypy فقط.

بالنسبة لـ CPython، لتشغيل Python 3 من الطرفية، أدخل الأمر python3. لتشغيل PyPy، أدخل الأمر pypy3.

كما هو موضح في الشكل التالي، قد يؤدي إدخال أمر pypy3 في الطرفية إلى ظهور رسالة "pypy3" غير موجود. هذا لأن مسار PyPy لم يُضاف إلى متغير بيئة PATH. الأمر الذي يعمل هو ./pypy3، نظرًا لأن المسار الحالي في الطرفية موجود داخل مجلد PyPy bin. تشير النقطة . إلى المجلد الحالي، ويُضاف / للوصول إلى شيء ما فيه. يؤدي تنفيذ الأمر ./pypy3 إلى تشغيل بايثون بنجاح.

الآن يمكنك العمل مع بايثون كالمعتاد، مستفيدًا من PyPy. على سبيل المثال، يمكننا إنشاء نص برمجي بسيط بلغة بايثون يجمع 1000 رقم، وتشغيله باستخدام PyPy. الكود كالتالي:.

nums = range(1000)
sum = 0
for k in nums:
sum = sum + k
print("Sum of 1,000 numbers is : ", sum)

إذا تم تسمية هذا البرنامج النصي باسم test.py، فيمكنك تشغيله ببساطة باستخدام الأمر التالي (على افتراض أن ملف Python موجود داخل مجلد bin PyPy، وهو نفس موقع الأمر pypy3).

./pypy3 test.py

الشكل التالي يوضح نتيجة تنفيذ الكود السابق.

وقت تشغيل PyPy مقابل CPython

لمقارنة وقت تنفيذ PyPy و CPython لمجموع 1000 رقم، يتغير كود قياس الوقت على النحو التالي.

import time
t1 = time.time()
nums = range(1000)
sum = 0
for k in nums:
sum = sum + k
print("Sum of 1,000 numbers is : ", sum)
t2 = time.time()
t = t2 - t1
print("Elapsed time is : ", t, " seconds")

بالنسبة لـ PyPy، يبلغ الوقت حوالي 0.00045 ثانية، مقارنةً بـ 0.0002 ثانية لـ CPython (شغّلتُ الكود على جهازي Core i7-6500U بتردد 2.5 جيجاهرتز). في هذه الحالة، يستغرق CPython وقتًا أقل من PyPy، وهو أمر متوقع نظرًا لعدم كون هذه المهمة طويلة الأمد. إذا أضاف الكود مليون رقم بدلًا من 1000، فسيفوز PyPy في النهاية. في هذه الحالة، سيستغرق الأمر 0.00035 ثانية لـ PyPy و0.1 ثانية لـ CPython. أصبحت ميزة PyPy واضحة الآن. هذا يُعطيك فكرة عن مدى بطء CPython في المهام طويلة الأمد.

نتيجة

يُقدّم هذا البرنامج التعليمي PyPy، أسرع تطبيق لبايثون. ميزته الرئيسية هي التجميع الفوري (JIT)، الذي يُخزّن الكود الآلي المُجمّع مؤقتًا لمنع إعادة تنفيذه. كما يُسلّط الضوء على بعض عيوب PyPy، وأهمها أنه يعمل بشكل جيد مع كود بايثون الخالص، ولكنه ليس فعالًا مع إضافات C.

رأينا أيضًا كيفية عمل PyPy على أوبونتو، وقارنا أوقات تنفيذ كلٍّ من CPython وPyPy، مسلطين الضوء على أداء PyPy في المهام طويلة الأمد. في الوقت نفسه، لا يزال CPython يتفوق على PyPy في المهام قصيرة الأمد. سنتناول المزيد من المقارنات بين PyPy وCPython وCython في مقالات لاحقة.

اترك تعليقاً

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

قد يعجبك أيضاً