تحسين نصوص Python باستخدام Cython

0 الأسهم
0
0
0
0

مقدمة

قد تكون بايثون من أشهر لغات البرمجة اليوم، لكنها بالتأكيد ليست الأكثر كفاءة. ففي عالم التعلم الآلي، يضحي المستخدمون بالكفاءة من أجل سهولة الاستخدام التي توفرها بايثون.

لكن هذا لا يعني أنه لا يمكنك تسريع الأمور بطرق أخرى. يُعدّ Cython طريقة سهلة لتقليل وقت حساب نصوص Python دون المساس بالوظائف التي يُمكن تحقيقها بسهولة باستخدام Python.

سيُعرّفك هذا البرنامج التعليمي على استخدام سايثون لتسريع نصوص بايثون. سنتناول مهمة بسيطة ولكنها مُكلفة حسابيًا: إنشاء حلقة for تُكرر قائمة بايثون تضم مليار رقم وتجمعها. ولأن الوقت عاملٌ أساسي عند تشغيل الشيفرة البرمجية على أجهزة محدودة الموارد، سنستكشف هذه المسألة من خلال النظر في كيفية تطبيق شيفرة بايثون في سايثون على راسبيري باي (RPi). يُحدث سايثون فرقًا كبيرًا في سرعة الحوسبة؛ تمامًا مثل شخص كسول أو شخص مُرهق.

المتطلبات الأساسية لتحسين نصوص Python باستخدام Cython

  • المعرفة الأساسية بلغة بايثون: التعرف على قواعد لغة بايثون، والوظائف، وأنواع البيانات، والوحدات النمطية.
  • فهم المفاهيم الأساسية للغة C/C++: التعرف على المفاهيم الأساسية للغة C أو C++ مثل المؤشرات وأنواع البيانات وهياكل التحكم.
  • بيئة تطوير Python: قم بتثبيت Python (يفضل Python 3.x) باستخدام مدير الحزم مثل pip.
  • تثبيت Cython: قم بتثبيت Cython باستخدام الأمر pip install cython.
  • معرفة المحطة الطرفية/سطر الأوامر: القدرة الأساسية على التنقل وتنفيذ الأوامر في المحطة الطرفية أو سطر الأوامر.

ستساعدك هذه المتطلبات الأساسية على الاستعداد لبدء تحسين كود Python باستخدام Cython.

بايثون و سي بايثون

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

سي-بايثون هو التطبيق الافتراضي والأكثر شيوعًا للغة بايثون. لاستخدامها ميزة مهمة، وهي أن سي لغة مُجمّعة، ويُحوّل كودها إلى كود آلة تُنفّذه وحدة المعالجة المركزية (CPU) مباشرةً. قد تتساءل الآن: إذا كانت سي لغة مُجمّعة، فهل يعني ذلك أن بايثون هي نفسها؟

تطبيق بايثون في لغة C (Cpython) 100% لا يُجمّع ولا يُفسّر. في الواقع، يحدث كلٌّ من التجميع والتفسير أثناء تنفيذ سكربت بايثون. لتوضيح ذلك، لنلقِ نظرة على خطوات تنفيذ سكربت بايثون:

  1. تجميع الكود المصدر باستخدام CPython لإنتاج الكود الثنائي
  2. تفسير البايت كود بواسطة مترجم CPython
  3. تشغيل مخرجات مترجم CPython في الآلة الافتراضية CPython

تتم عملية التجميع عندما يُجمّع Cpython الكود المصدري (ملف .py) ويُنتج بايت كود Cpython (ملف .pyc). بعد ذلك، يُفسّر مُفسّر Cpython بايت كود Cpython، ويُنفّذ الناتج في الآلة الافتراضية Cpython. كما هو موضح في الخطوات السابقة، تتضمن عملية تنفيذ سكربت Python كلاً من التجميع والتفسير.

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

إذا لم يُستخدم مُفسِّر، فسيُنتج مُجمِّع Cpython شيفرةً آليةً تُشغَّل مُباشرةً على وحدة المعالجة المركزية. ولأنَّ لكلِّ منصةٍ تعليماتٍ مُختلفة، لا يُمكن تشغيل الشيفرة على منصاتٍ مُختلفة.

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

بايثون أبطأ بكثير من سي، لكن العديد من المبرمجين ما زالوا يفضلونها لسهولة استخدامها. تُخفي بايثون الكثير من التفاصيل عن المبرمج، مما يُجنّبه عناء تصحيح الأخطاء. على سبيل المثال، نظرًا لأن بايثون لغة كتابة ديناميكية، فلا حاجة لتحديد نوع كل متغير في الكود، إذ تستنتجه بايثون تلقائيًا. على النقيض من ذلك، في اللغات ذات الكتابة الثابتة (مثل سي، سي++، أو جافا)، يجب تحديد أنواع المتغيرات، كما هو موضح أدناه.

int x = 10
string s = "Hello"

قارن مع التنفيذ التالي في بايثون:

الكتابة الديناميكية تُسهّل عملية الترميز، لكنها تُثقل كاهل الآلة في إيجاد نوع البيانات المناسب، مما يُبطئ عملية التنفيذ.

x = 10
s = "Hello"

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

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

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

ما هو الفرق بين Cython؟

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

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

تحويل كود بايثون البسيط إلى سيترون

لتحويل كود Python إلى Cython، تحتاج أولاً إلى إنشاء ملف بالامتداد . .pyx إنشاء بدلاً من التمديد .py. بداخل هذا الملف، يمكنك البدء في كتابة كود Python العادي (لاحظ أن هناك بعض القيود على الكود الذي يقبله Cython، والتي تم شرحها في وثائق Cython).

قبل المتابعة، تأكد من تثبيت Cython. يمكنك القيام بذلك باستخدام الأمر التالي.

pip install cython

لإنشاء ملف .pyd/.so، نحتاج أولًا إلى بناء ملف Cython. يُمثل ملف .pyd/.so الوحدة النمطية التي سنستوردها لاحقًا. يُستخدم ملف setup.py لبناء ملف Cython. أنشئ هذا الملف وأدخل الكود التالي فيه. سنستخدم الدالة distutils.core.setup() لاستدعاء الدالة Cython.Build.cythonize()، والتي ستُحوّل ملف .pyx إلى سيانوجين. تقبل هذه الدالة مسار الملف الذي تريد تحويله إلى سيانوجين. هنا، افترضتُ أن ملف setup.py موجود في نفس موقع ملف test_cython.pyx.

import distutils.core
import Cython.Build
distutils.core.setup(
ext_modules = Cython.Build.cythonize("test_cython.pyx"))

لإنشاء ملف Cython، أدخل الأمر التالي في سطر الأوامر. من المتوقع أن يكون المجلد الحالي لسطر الأوامر هو نفس مجلد ملف setup.py.

python setup.py build_ext --inplace

بعد تنفيذ هذا الأمر، سيتم وضع ملفين بجوار ملف .pyx. الملف الأول بامتداد .c، والآخر بامتداد .pyd (أو ما شابه، حسب نظام التشغيل المُستخدم). لاستخدام الملف المُولّد، ما عليك سوى استيراد وحدة test_cython، وستظهر رسالة "Hello Cython" مباشرةً، كما هو موضح أدناه.

لقد نجحنا الآن في تحويل شيفرة بايثون إلى سيتونية. سيتناول القسم التالي تحويل ملف .pyx الذي أُنشئت فيه حلقة إلى سيتونية.

تحويل حلقة "for" إلى حلقة سايتونيكية“

الآن، لنُحسّن مهمتنا السابقة: حلقة for تُجري عمليات على مليون رقم وتُجمعها. لنبدأ بفحص كفاءة الحلقة نفسها. تُقدّر وحدة الوقت الوقت اللازم للتنفيذ.

import time
t1 = time.time()
for k in range(1000000):
pass
t2 = time.time()
t = t2-t1
print("%.20f" % t)

في ملف .pyx، متوسط وقت تنفيذ ثلاث عمليات هو 0.0281 ثانية. يعمل الكود على جهاز مزود بمعالج Core i7-6500U بتردد 2.5 جيجاهرتز وذاكرة وصول عشوائي DDR3 سعة 16 جيجابايت.

قارن هذا بوقت تنفيذ ملف بايثون نموذجي، والذي يبلغ في المتوسط 0.0411 ثانية. هذا يعني أن سايثون أسرع من بايثون بـ 1.46 مرة فقط في التكرارات، حتى دون الحاجة إلى تعديل حلقة for ليعمل بسرعة C.

الآن، لنُجرِّب عملية الجمع. سنستخدم دالة range() للقيام بذلك.

import time
t1 = time.time()
total = 0
for k in range(1000000):
total = total + k
print "Total =", total
t2 = time.time()
t = t2-t1
print("%.100f" % t)

لاحظ أن كلا النصين يُرجعان القيمة نفسها، وهي 499999500000. في بايثون، يستغرق تشغيل هذه العملية 0.1183 ثانية في المتوسط (عبر ثلاثة اختبارات). أما في سايثون، فهي أسرع بـ 1.35 مرة، وتستغرق 0.0875 ثانية في المتوسط.

الآن دعونا نلقي نظرة على مثال آخر حيث تبدأ الحلقة عند 0 وتمر عبر مليار رقم.

import time
t1 = time.time()
total = 0
for k in range(1000000000):
total = total + k
print "Total =", total
t2 = time.time()
t = t2-t1
print("%.20f" % t)

انتهى نص سايثون في حوالي 85 ثانية (1.4 دقيقة)، بينما انتهى نص بايثون في حوالي 115 ثانية (1.9 دقيقة). في كلتا الحالتين، يستغرق الأمر وقتًا طويلاً. ما فائدة استخدام سايثون إذا استغرق إنجاز مهمة بسيطة كهذه أكثر من دقيقة؟ تجدر الإشارة إلى أن هذا خطأنا، وليس خطأ سايثون.

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

تعيين أنواع بيانات C للمتغيرات

كما هو الحال في الكود السابق، تُستخدم خمسة متغيرات: الإجمالي، k، t1، t2، وt. يُستنتج نوع بيانات جميع هذه المتغيرات ضمنيًا من الكود، مما يتطلب وقتًا أطول. لتوفير الوقت المُستغرق في استنتاج نوع البيانات، لنُعيّن نوع البيانات من لغة C.

نوع المتغير total هو unsigned long long int. وهو عدد صحيح لأن مجموع جميع الأعداد صحيح، وهو غير موقّع لأن المجموع سيكون دائمًا موجبًا. ولكن لماذا long long؟ لأن مجموع جميع الأعداد كبير جدًا، لذا يُستخدم long long لجعل المتغير كبيرًا قدر الإمكان.

نوع البيانات المعين للمتغير k هو int والمتغيرات الثلاثة المتبقية t1 و t2 و t يتم تعيين نوع البيانات float لها.

import time
cdef unsigned long long int total
cdef int k
cdef float t1, t2, t
t1 = time.time()
for k in range(1000000000):
total = total + k
print "Total =", total
t2 = time.time()
t = t2-t1
print("%.100f" % t)

لاحظ أن الدقة المحددة في آخر عبارة طباعة مضبوطة على ١٠٠، وجميع هذه الأرقام صفر (انظر الصورة التالية). هذا ما نتوقعه من استخدام سايثون. بينما يستغرق بايثون أكثر من ١.٩ دقيقة، لا يستغرق سايثون أي وقت على الإطلاق. لا أستطيع حتى القول إنه أسرع من بايثون بألف أو مئة ألف مرة؛ فقد جربت دقة مختلفة لوقت الطباعة، وما زلت لا أرى أي رقم.

لاحظ أنه يمكنك أيضًا إنشاء متغير عدد صحيح لحفظ القيمة المُمررة إلى دالة range(). سيؤدي هذا إلى تحسين الأداء بشكل أكبر. الكود الجديد أدناه، حيث تُخزَّن القيمة في متغير العدد الصحيح maxval.

import time
cdef unsigned long long int maxval
cdef unsigned long long int total
cdef int k
cdef float t1, t2, t
maxval=1000000000
t1=time.time()
for k in range(maxval):
total = total + k
print "Total =", total
t2=time.time()
t = t2-t1
print("%.100f" % t)

الآن بعد أن رأينا كيفية زيادة أداء نصوص Python باستخدام Cython، دعنا نطبق ذلك على Raspberry Pi (RPi).

إمكانية الوصول إلى Raspberry Pi من جهاز كمبيوتر شخصي

إذا كانت هذه هي المرة الأولى التي تستخدم فيها جهاز Raspberry Pi، فستحتاج إلى توصيل كلٍّ من جهاز الكمبيوتر وجهاز Raspberry Pi بالشبكة. يمكنك القيام بذلك بتوصيل كلا الجهازين بمُبدِّل مُفعَّل عليه بروتوكول DHCP (بروتوكول تكوين المضيف الديناميكي) لتعيين عناوين IP تلقائيًا. بمجرد إنشاء الشبكة بنجاح، يمكنك الوصول إلى جهاز Raspberry Pi بناءً على عنوان IPv4 المُخصَّص له. كيف يمكنك معرفة عنوان IPv4 المُخصَّص لجهاز Raspberry Pi؟ لا تقلق، يمكنك ببساطة استخدام أداة فحص IP. في هذا البرنامج التعليمي، سأستخدم تطبيقًا مجانيًا يُسمى Advanced IP Scanner.

واجهة المستخدم لهذا التطبيق هي كما يلي. يقبل هذا التطبيق نطاقًا من عناوين IPv4 للبحث، ويعرض معلومات حول الأجهزة النشطة.

يجب عليك إدخال نطاق عناوين IPv4 على شبكتك المحلية. إذا كنت لا تعرف هذا النطاق، فما عليك سوى تشغيل أمر ipconfig في نظام ويندوز (أو ifconfig في لينكس) للعثور على عنوان IPv4 لجهاز الكمبيوتر (كما هو موضح في الشكل أدناه). في حالتي، عنوان IPv4 المخصص لمحول Wi-Fi الخاص بجهاز الكمبيوتر هو 192.168.43.177، وقناع الشبكة الفرعية هو 255.255.255.0. هذا يعني أن نطاق عناوين IPv4 على الشبكة يتراوح من 192.168.43.1 إلى 192.168.43.255. كما هو موضح في الشكل، عنوان IPv4 192.168.43.1 مخصص للبوابة. لاحظ أن آخر عنوان IPv4 في هذا النطاق، 192.168.43.255، مخصص لرسائل البث. لذا، فإن النطاق الذي يجب عليك البحث فيه يبدأ عند 192.168.43.2 وينتهي عند 192.168.43.254.

وفقًا لنتيجة الفحص الموضحة في الشكل التالي، فإن عنوان IPv4 المُخصص لجهاز RPi هو 192.168.43.63. يُمكن استخدام هذا العنوان لإنشاء جلسة Secure Shell (SSH).

لإنشاء جلسة SSH، سأستخدم برنامجًا مجانيًا يُدعى MobaXterm. واجهة المستخدم لهذا البرنامج هي كما يلي.

لإنشاء جلسة SSH، انقر ببساطة على زر "الجلسة" في الزاوية العلوية اليسرى. ستظهر نافذة جديدة كما هو موضح أدناه.

من هذه النافذة، انقر على زر SSH في الزاوية العلوية اليسرى لفتح النافذة الموضحة أدناه. ما عليك سوى إدخال عنوان IPv4 الخاص بجهاز Raspberry Pi واسم المستخدم (وهو pi افتراضيًا)، ثم انقر على "موافق" لبدء الجلسة.

بعد النقر على زر "موافق"، ستظهر نافذة جديدة تطلب منك إدخال كلمة المرور. كلمة المرور الافتراضية هي raspberrypi. بعد تسجيل الدخول، ستظهر النافذة التالية. سيساعدك الجزء الأيسر على التنقل بسهولة بين مجلدات Raspberry Pi. كما يوجد سطر أوامر لإدخال الأوامر.

استخدام Cython مع Raspberry Pi

أنشئ ملفًا جديدًا وغيّر امتداده إلى .pyx لكتابة الكود من المثال السابق. في شريط اللوحة اليسرى، ستجد خيارات لإنشاء ملفات ومجلدات جديدة. يمكنك استخدام أيقونة الملف الجديد لتسهيل ذلك، كما هو موضح في الصورة أدناه. أنشأتُ ملفًا باسم test_cython.pyx في المجلد الجذر لجهاز Raspberry Pi.

انقر نقرًا مزدوجًا على الملف لفتحه، ثم الصق الكود واحفظه. بعد ذلك، نحتاج إلى إنشاء ملف setup.py، والذي سيكون مطابقًا تمامًا لما ذكرناه سابقًا. بعد ذلك، نحتاج إلى تنفيذ الأمر التالي لبناء سكربت Cython.

python3 setup.py build_ext --inplace

بعد إتمام هذا الأمر بنجاح، يمكنك العثور على ملفات الإخراج في اللوحة اليسرى، كما هو موضح في الشكل التالي. لاحظ أن امتداد الوحدة المراد استيرادها هو .so، لأننا لم نعد نستخدم ويندوز.

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

نتيجة

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

اترك تعليقاً

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

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