مرحبا بالجميع! الاثنين المقبل ، ستبدأ الفصول في المجموعة الجديدة من
دورة Python Developer ، مما يعني أن لدينا الوقت لنشر مادة أخرى مثيرة للاهتمام ، وهو ما سنفعله الآن. هل لديك قراءة جيدة.

في عام 2003 ، أطلقت Intel معالج Pentium 4 "HT" الجديد. هذا المعالج فيركلوكيد إلى 3GHz ودعم تقنية خيوط المعالجة المتعددة.

في السنوات التالية ، كافحت Intel و AMD لتحقيق أفضل أداء لسطح المكتب من خلال زيادة سرعة الناقل وحجم ذاكرة التخزين المؤقت L2 وتقليل حجم المصفوفة لتقليل زمن الوصول إلى أدنى حد. في عام 2004 ، تم استبدال طراز HT بتردد 3 جيجا هرتز بنموذج 580 Prescott مع رفع تردد التشغيل إلى 4 جيجا هرتز.
يبدو أنه من الضروري المضي قدمًا في زيادة تردد الساعة ، ولكن المعالجات الجديدة عانت من ارتفاع استهلاك الطاقة وتبديد الحرارة.
هل يقدم معالج سطح المكتب الخاص بك 4 جيجا هرتز اليوم؟ من غير المرجح ، لأن المسار لتحسين الأداء يكمن في النهاية من خلال زيادة سرعات الحافلات وزيادة عدد النوى. في عام 2006 ، حل Intel Core 2 محل Pentium 4 وكان أقل سرعة على مدار الساعة.
بالإضافة إلى إطلاق معالجات متعددة النواة لجمهور مستخدم واسع ، حدث شيء آخر في عام 2006. رأى بيثون 2.5 أخيرا النور! لقد جاء بالفعل مع إصدار تجريبي من الكلمة الأساسية ، والتي تعرفها وتحبها جميعًا.
كان لبيثون 2.5 قيود رئيسية عندما يتعلق الأمر باستخدام Intel Core 2 أو AMD Athlon X2.
كان جيل.
ما هو جيل؟
GIL (Global Interpreter Lock) هي قيمة منطقية في مترجم Python المحمي بواسطة كائن مزامنة. يتم استخدام القفل في حلقة حساب CPython bytecode الرئيسية لتحديد أي مؤشر ترابط ينفذ التعليمات حاليًا.
يدعم CPython استخدام مؤشرات ترابط متعددة في مترجم واحد ، ولكن يجب أن تطلب مؤشرات الترابط الوصول إلى GIL من أجل إجراء عمليات منخفضة المستوى. وهذا بدوره يعني أن مطوري Python يمكنهم استخدام الشفرة غير المتزامنة ، والترابط المتعدد ، ولم يعد عليهم القلق بشأن حظر أي متغيرات أو تعطل على مستوى المعالج أثناء حالة توقف تام.
GIL يبسط البرمجة بيثون متعددة مؤشرات الترابط.

يخبرنا GIL أيضًا أنه بينما يمكن أن يكون CPython متعدد الخيوط ، يمكن تنفيذ مؤشر ترابط واحد فقط في كل مرة. هذا يعني أن المعالج رباعي النواة يقوم بشيء من هذا القبيل (باستثناء الشاشة الزرقاء ، نأمل).
تمت كتابة الإصدار الحالي من GIL
في عام 2009 لدعم الوظائف غير المتزامنة وبقيت على حالها حتى بعد محاولات عديدة لإزالتها من حيث المبدأ أو تغيير متطلباتها.
تم تبرير أي اقتراح لإزالة GIL بحقيقة أن القفل العمومي للمترجم لا ينبغي أن يؤدي إلى انخفاض أداء الشفرة المفردة الترابط. أي شخص حاول تمكين hyperthreading في عام 2003 سوف يفهم ما
أتحدث عنه .
جيل التخلي في CPython
إذا كنت تريد بالفعل موازنة الكود في CPython ، فسيتعين عليك استخدام عدة عمليات.
في CPython 2.6 ، تمت إضافة وحدة
المعالجة المتعددة إلى المكتبة القياسية. المعالجة المتعددة ملثمين توليد العمليات في CPython (كل عملية مع GIL الخاصة به).
from multiprocessing import Process def f(name): print 'hello', name if __name__ == '__main__': p = Process(target=f, args=('bob',)) p.start() p.join()
يتم إنشاء العمليات ، ويتم إرسال الأوامر إليهم باستخدام الوحدات النمطية المترجمة ووظائف Python ، ثم يتم إعادة انضمامهم إلى العملية الرئيسية.
كما تدعم المعالجة المتعددة استخدام المتغيرات من خلال قائمة انتظار أو قناة. لديها كائن قفل ، والذي يستخدم لقفل الكائنات في العملية الرئيسية والكتابة من العمليات الأخرى.
المعالجة المتعددة لها عيب رئيسي واحد. إنه يحمل حمولة حسابية كبيرة ، والتي تؤثر على كل من وقت المعالجة واستخدام الذاكرة. تبلغ مدة بدء تشغيل CPython حتى بدون الموقع 100-200 مللي ثانية (راجع
https://hackernoon.com/which-is-the-fastest-version-of-python-2ae7c61a6b2b لمعرفة المزيد).
نتيجةً لذلك ، قد يكون لديك رمز متوازي في CPython ، لكن لا يزال يتعين عليك التخطيط بعناية لعمليات العمليات الطويلة الأمد التي تشترك في عدة كائنات.
بديل آخر قد يكون استخدام حزمة خارجية مثل Twisted.
PEP554 وموت جيل؟
لذلك ، اسمحوا لي أن أذكرك بأن تعدد مؤشرات الترابط في CPython بسيط ، ولكنه في الواقع ليس موازاة ، ولكن المعالجة المتعددة متوازية ، ولكنها تنطوي على حمل كبير.
ماذا لو كان هناك طريقة أفضل؟المفتاح لتجاوز GIL يكمن في الاسم ، والقفل العام للمترجم هو جزء من الحالة العالمية للمترجم. يمكن أن تحتوي عمليات CPython على العديد من المترجمين الفوريين ، وبالتالي ، فإن العديد من الأقفال ، نادراً ما يتم استخدام هذه الوظيفة ، لأن الوصول إليها يتم فقط من خلال C-API.
واحدة من ميزات CPython 3.8 هي PEP554 ، وهو تطبيق للمترجمين الفوريين وواجهة برمجة التطبيقات مع وحدة نمطية جديدة
interpreters
في المكتبة القياسية.
يسمح لك هذا بإنشاء عدة مترجمين شفويين من Python في عملية واحدة. آخر بيثون 3.8 الابتكار هو أن جميع المترجمين الفوريين سيكون لديهم جيل خاص بهم.

نظرًا لأن حالة المترجم الشفوي تحتوي على منطقة مخصصة في الذاكرة ، وهي مجموعة من جميع مؤشرات كائنات Python (محلية وعالمية) ، لا يمكن للمترجمين الفوريين في PEP554 الوصول إلى المتغيرات العامة للمترجمين الفوريين الآخرين.
مثل المعالجة المتعددة ، فإن المترجمين الفوريين الذين يشاركون الكائنات يتكونون من إجراء تسلسل لهم واستخدام نموذج IPC (شبكة ، قرص ، أو ذاكرة مشتركة). هناك العديد من الطرق لتسلسل الكائنات في Python ، على سبيل المثال ، وحدة
json
، أو وحدة
pickle
، أو طرق أكثر توحيدًا مثل
json
أو
json
. كل واحد منهم لديه إيجابيات وسلبيات ، وكل منهم يعطي حمولة الحوسبة.
من الأفضل أن يكون لديك مساحة ذاكرة مشتركة يمكن تغييرها والتحكم فيها من خلال عملية محددة. وبالتالي ، يمكن إرسال الكائنات بواسطة المترجم الرئيسي واستلامها بواسطة مترجم آخر. ستكون هذه هي مساحة الذاكرة المدارة للبحث عن مؤشرات PyObject ، والتي يمكن لكل مترجم الوصول إليها ، في حين أن العملية الرئيسية ستدير الأقفال.

لا يزال يتم تطوير واجهة برمجة تطبيقات لهذا ، لكن من المحتمل أن تبدو كما يلي:
import _xxsubinterpreters as interpreters import threading import textwrap as tw import marshal
يستخدم هذا المثال NumPy. يتم إرسال مجموعة numpy عبر القناة ، ويتم إجراء تسلسل باستخدام وحدة التنظيم ، ثم يقوم المترجم الفرعي بمعالجة البيانات (على GIL منفصلة) ، لذلك قد تكون هناك مشكلة موازية مرتبطة بوحدة المعالجة المركزية ، وهي مثالية للمترجمين الفوريين.
يبدو غير فعال
تعمل وحدة
marshal
بسرعة كبيرة ، ولكن ليس بنفس سرعة مشاركة الكائنات مباشرةً من الذاكرة.
يقدم PEP574 بروتوكول
مخلل جديد
(v5) يدعم القدرة على معالجة مخازن الذاكرة المؤقتة بشكل منفصل عن بقية تيار المخلل. بالنسبة لكائنات البيانات الكبيرة ، سيؤدي إجراء تسلسل لها جميعًا دفعة واحدة وإلغاء التسلسل من مترجم فرعي إلى إضافة الكثير من النفقات العامة.
يمكن تنفيذ واجهة برمجة التطبيقات الجديدة (من الناحية الافتراضية) على النحو التالي -
import _xxsubinterpreters as interpreters import threading import textwrap as tw import pickle
يبدو منقوشة
في جوهره ، تم بناء هذا المثال على استخدام API للمترجمين الفوريين من المستوى المنخفض. إذا لم تستخدم مكتبة
multiprocessing
،
multiprocessing
بعض المشكلات مألوفة بالنسبة لك. ليس الأمر بسيطًا مثل معالجة الدفق ، فلا يمكنك فقط تشغيل هذه الوظيفة بقائمة بيانات الإدخال هذه في مترجمين منفصلين (في الوقت الحالي).
بمجرد دمج PEP مع الآخرين ، أعتقد أننا سنرى العديد من واجهات برمجة التطبيقات الجديدة في PyPi.
كم مقدار الحمل غير المترجم الفرعي؟
إجابة قصيرة: أكثر من مجرى ، أقل من عملية.
إجابة طويلة: لدى المترجم حالته الخاصة ، لذلك سيحتاج إلى استنساخ وتهيئة ما يلي ، على الرغم من أن PEP554 يبسط إنشاء المترجمين الفوريين:
- الوحدات النمطية في
importlib
و importlib
؛ - محتويات القاموس
sys
. - وظائف مدمجة (
print()
، assert
، إلخ) ؛ - التدفقات.
- تكوين النواة.
يمكن استنساخ تكوين kernel بسهولة من الذاكرة ، لكن استيراد الوحدات ليست بهذه البساطة. يكون استيراد الوحدات النمطية في Python بطيئًا ، لذا إذا كان إنشاء مترجم فرعي يعني استيراد الوحدات النمطية إلى مساحة اسم مختلفة في كل مرة ، يتم تقليل الفوائد.
ماذا عن المزاح؟
asyncio
التنفيذ الحالي
asyncio
حدث
asyncio
في المكتبة القياسية إطارات مكدس للتقييم ،
asyncio
أيضًا الحالة في المترجم الرئيسي (وبالتالي يشارك GIL).
بعد الجمع بين PEP554 ، وربما بالفعل في بيثون 3.9 ، يمكن استخدام تطبيق بديل لحلقة الحدث (على الرغم من أنه لم يقم أحد بذلك بعد) ، والذي يعمل بطرق غير متزامنة في المترجمين الفوريين المتوازيين.
تبدو باردة ، لف لي أيضا!
حسنا ، ليس حقا.
نظرًا لأن CPython يعمل على نفس المترجم الشفوي لفترة طويلة ، فإن العديد من أجزاء قاعدة الكود تستخدم "حالة وقت التشغيل" بدلاً من "حالة المترجم" ، لذلك إذا تم تقديم PEP554 الآن ، فسيظل هناك الكثير من المشاكل.
على سبيل المثال ، تنتمي حالة أداة تجميع مجمعي البيانات المهملة (في الإصدارات 3.7 <) إلى وقت التشغيل.
في التغييرات التي تحدث خلال سباقات PyCon ، بدأت حالة أداة تجميع مجمعي البيانات المهملة
في الانتقال إلى المترجم الفوري ، بحيث يكون لكل مترجم فرعي جامع البيانات المهملة الخاص به (كما يجب أن يكون).
مشكلة أخرى هي أن هناك بعض المتغيرات "العالمية" التي لا تزال قائمة في قاعدة كود CPython إلى جانب العديد من الامتدادات في C. لذلك ، عندما بدأ الناس فجأة بموازنة الكود بشكل صحيح ، رأينا بعض المشاكل.
هناك مشكلة أخرى وهي أن واصفات الملف تنتمي إلى العملية ، لذلك إذا كان لديك ملف مفتوح للكتابة في مترجم واحد ، فلن يتمكن المترجم الفرعي من الوصول إلى هذا الملف (بدون مزيد من التغييرات على CPython).
باختصار ، لا تزال هناك العديد من المشكلات التي تحتاج إلى معالجة.
الخلاصة: هل جيل صحيح بعد الآن؟
سيستمر استخدام GIL للتطبيقات ذات الترابط الفردي. لذلك ، حتى عند اتباع PEP554 ، فإن الكود المفرد المترابط فجأة لن يصبح موازياً.
إذا كنت ترغب في كتابة التعليمات البرمجية المتوازية في Python 3.8 ، فستواجه مشكلات موازية مرتبطة بالمعالج ، لكن هذه أيضًا تذكرة إلى المستقبل!
متى؟
غالبًا ما يكون Pickle v5 ومشاركة الذاكرة للمعالجة المتعددة في Python 3.8 (أكتوبر 2019) ، وسيظهر المترجمون الفوريون بين الإصدارين 3.8 و 3.9.
إذا كانت لديك رغبة في الالتحاق بالأمثلة المقدمة ، فقد أنشأت فرعًا منفصلاً بكل الشفرة اللازمة:
https://github.com/tonybaloney/cpython/tree/subinterpreters.ما رأيك في هذا؟ اكتب تعليقاتك وأراك في الدورة.