مرحبا بالجميع! لذلك انتهت عطلة نهاية الأسبوع الطويلة في مارس. نريد أن نكرس أول منشور لما بعد الإجازة للعديد من الدورات -
"Python Developer" ، الذي يبدأ في أقل من أسبوعين. دعنا نذهب.
المحتويات- الذاكرة كتاب فارغ
- إدارة الذاكرة: من الأجهزة إلى البرامج
- تنفيذ قاعدة بايثون
- مفهوم قفل المترجم العالمي (GIL)
- جامع القمامة
- إدارة الذاكرة في CPython:
- الخاتمة

هل تساءلت يوما كيف تقوم بيثون وراء الكواليس بمعالجة بياناتك؟ كيف يتم تخزين المتغيرات الخاصة بك في الذاكرة؟ في أي نقطة يتم إزالتها؟
في هذه المقالة ، سوف نتعمق في البنية الداخلية لبيثون لفهم كيفية عمل إدارة الذاكرة.
بعد قراءة هذا المقال ، أنت:
- تعرف على المزيد حول العمليات منخفضة المستوى ، وخاصة الذاكرة.
- فهم كيف تقوم بايثون بتجريد العمليات منخفضة المستوى.
- تعرف على خوارزميات إدارة الذاكرة في بيثون.
معرفة البنية الداخلية لبيثون ستوفر فهمًا أفضل لمبادئ سلوكها. آمل أن تتمكن من إلقاء نظرة على بيثون من منظور جديد. وراء الكواليس ، هناك العديد من العمليات المنطقية لجعل البرنامج يعمل بشكل صحيح.
الذاكرة كتاب فارغيمكنك تخيل ذاكرة الكمبيوتر ككتاب فارغ ، في انتظار أن تكتب الكثير من القصص القصيرة. لا يوجد شيء على صفحاتها حتى الآن ، ولكن سيظهر المؤلفون قريبًا ممن يريدون كتابة قصصهم فيه. للقيام بذلك ، سوف يحتاجون إلى مكان.
نظرًا لعدم قدرتهم على كتابة قصة فوق أخرى ، يجب عليهم توخي الحذر الشديد بشأن الصفحات التي يكتبون عليها. قبل البدء في الكتابة ، يتشاورون مع مدير الكتاب. يقرر المدير مكان كتاب المؤلفين في قصتهم.
منذ أن كان الكتاب موجودا منذ سنوات عديدة ، أصبحت العديد من القصص التي عفا عليها الزمن قديمة. عندما لا يقرأ أحد التاريخ أو يعالجه ، يزيلونه لإفساح المجال أمام القصص الجديدة.
في جوهرها ، ذاكرة الكمبيوتر مثل كتاب فارغ. عادة ما تسمى الكتل المستمرة من الذاكرة ذات الطول الثابت الصفحات ، لذلك هذا القياس سهل الاستخدام.
يمكن أن يكون المؤلفون تطبيقات أو عمليات مختلفة تحتاج إلى تخزين البيانات في الذاكرة. يلعب المدير الذي يقرر أين يمكن للمؤلفين كتابة قصصهم دور مدير الذاكرة - وهو فارز. والشخص الذي يمحو القصص القديمة هو جامع للقمامة.
إدارة الذاكرة: من الأجهزة إلى البرامجإدارة الذاكرة هي العملية التي تقوم فيها تطبيقات البرامج بقراءة وكتابة البيانات. يحدد مدير الذاكرة مكان وضع بيانات البرنامج. نظرًا لأن حجم الذاكرة هو بالطبع ، مثل عدد الصفحات الموجودة في الكتاب ، وفقًا لذلك ، يحتاج المدير إلى إيجاد مساحة حرة لتوفيرها للاستخدام من قبل التطبيق. وتسمى هذه العملية "تخصيص الذاكرة".
من ناحية أخرى ، عندما لم تعد هناك حاجة إلى البيانات ، يمكن حذفها. في هذه الحالة ، يتحدثون عن تحرير الذاكرة. ولكن من أين يتم تحريرها ومن أين تأتي؟
يوجد في مكان ما داخل الكمبيوتر جهاز فعلي يخزن البيانات عند تشغيل برامج Python. يمر رمز Python بالعديد من مستويات التجريد قبل الوصول إلى هذا الجهاز.
يعد نظام التشغيل أحد المستويات الرئيسية الموجودة فوق الجهاز (ذاكرة الوصول العشوائي ، القرص الثابت ، إلخ). يدير قراءة وكتابة الطلبات إلى الذاكرة.
يوجد فوق نظام التشغيل طبقة تطبيق يوجد بها أحد تطبيقات Python (السلكية في نظام التشغيل الخاص بك أو التي تم تنزيلها من python.org). يتم تنظيم إدارة الذاكرة للرمز في لغة البرمجة هذه بواسطة أدوات Python الخاصة. تعد الخوارزميات والهياكل التي تستخدمها Python لإدارة الذاكرة هي الموضوع الرئيسي لهذه المقالة.
تنفيذ قاعدة بايثونالتنفيذ الأساسي لبيثون ، أو "بيثون النقي" ، هو مكتوب بـ CPython باللغة C.
لقد فوجئت للغاية عندما سمعت عن ذلك لأول مرة. كيف يمكن كتابة لغة بلغة أخرى؟! حسنًا ، ليس حرفيًا ، بالطبع ، لكن الفكرة شيء من هذا القبيل.
تم وصف لغة بايثون في
دليل مرجعي خاص باللغة الإنجليزية . ومع ذلك ، فإن هذا الدليل وحده ليس مفيدًا للغاية. ما زلت بحاجة إلى أداة لتفسير التعليمات البرمجية المكتوبة بواسطة قواعد الدليل.
ستحتاج أيضًا إلى شيء لتنفيذ الرمز على جهاز الكمبيوتر الخاص بك. يوفر تطبيق Python الأساسي كلا الشرطين. يقوم بتحويل كود Python إلى إرشادات يتم تنفيذها في جهاز افتراضي.
ملاحظة: تشبه الأجهزة الافتراضية أجهزة الكمبيوتر الفعلية ، ولكنها مضمنة في البرنامج. يعالجون التعليمات الأساسية المشابهة لرمز التجميع .
بيثون هي لغة البرمجة تفسيرها. يتم تجميع شفرة Python الخاصة بك باستخدام تعليمات يفهمها الكمبيوتر بسهولة أكبر -
bytecode . يتم تفسير هذه الإرشادات بواسطة الجهاز الظاهري عند تشغيل التعليمات البرمجية.
هل سبق لك أن رأيت الملفات ذات الامتداد
.pyc أو مجلد
__pycache__ ؟ هذا هو نفس الرمز الذي يتم تفسيره بواسطة الجهاز الظاهري.
من المهم أن نفهم أن هناك تطبيقات أخرى إلى جانب CPython ، على سبيل المثال
IronPython ، الذي يجمع ويشتغل في وقت تشغيل اللغة العامة لـ Microsoft (CLR). يجمع
Jython إلى Java bytecode لتشغيله في جهاز Java الظاهري. هناك أيضًا
PyPy التي يمكنك من خلالها كتابة مقالة منفصلة ، لذلك
سأذكرها فقط.
في هذه المقالة ، سوف نركز على إدارة الذاكرة باستخدام أدوات CPython.
ملاحظة: يتم تحديث إصدارات بايثون ويمكن أن يحدث أي شيء في المستقبل. في وقت كتابة هذا التقرير ، كان الإصدار الأخير
Python 3.7 .
حسنًا ، لدينا CPython مكتوب بلغة C يفسر Python bytecode. كيف يرتبط هذا بإدارة الذاكرة؟ بادئ ذي بدء ، توجد خوارزميات وهياكل لإدارة الذاكرة في رمز CPython ، في C. لفهم هذه المبادئ في Python ، تحتاج إلى فهم أساسي لـ CPython.
يتم كتابة CPython في C ، والذي بدوره لا يدعم البرمجة الموجهة للكائنات. وبسبب هذا ، رمز CPython لديه بنية مثيرة للاهتمام إلى حد ما.
يجب أن تكون قد سمعت أن كل شيء في Python هو كائن ، حتى أنواع مثل int و str ، على سبيل المثال. هذا صحيح على مستوى تنفيذ CPython. هناك بنية تسمى PyObject التي يستخدمها كل كائن في CPython.
ملاحظة: البنية في C هي نوع بيانات معرف من قبل المستخدم يجمع أنواع مختلفة من البيانات في حد ذاته. يمكننا استخلاص تشبيه للغات الموجهة للكائنات ونقول أن البنية عبارة عن فئة ذات سمات ، ولكن بدون طرق.
PyObject هو السلف لجميع الكائنات في Python ، ويحتوي فقط على شيئين:
- ob_refcnt : مرجع العداد؛
- ob_type : مؤشر إلى نوع آخر.
مطلوب عداد مرجعي لجمع القمامة. لدينا أيضًا مؤشر لنوع معين من الكائنات. نوع الكائن هو مجرد بنية أخرى تصف الكائنات في Python (مثل dict أو int).
يحتوي كل كائن على مخصص ذاكرة مخصص للكائن يعرف كيفية تخصيص الذاكرة وتخزينها. يحتوي كل كائن أيضًا على مُحرر موارد موجه للكائنات يقوم بتنظيف الذاكرة إذا لم تعد محتوياته مطلوبة.
هناك عامل واحد مهم في الحديث عن تخصيص الذاكرة وتطهيرها. الذاكرة هي مورد مشترك لجهاز الكمبيوتر ، ويمكن أن يحدث شيء غير سارة إلى حد ما إذا حاولت عمليتان كتابة البيانات إلى موقع الذاكرة نفسه في نفس الوقت.
قفل التفسير العالمي (GIL)يعد GIL حلاً للمشكلة العامة المتمثلة في مشاركة الذاكرة بين الموارد المشتركة مثل ذاكرة الكمبيوتر. عندما يحاول مؤشر ترابط اثنين تغيير نفس المورد في نفس الوقت ، فإنهم يخطئون في أعقاب بعضهم البعض. نتيجة لذلك ، يتم تشكيل فوضى كاملة في الذاكرة ولن تنتهي أي عملية من عملها بالنتيجة المرجوة.
بالعودة إلى التشابه مع الكتاب ، لنفترض أن المؤلفين ، يقرر كل منهما أنه هو الذي ينبغي أن يكتب قصته على الصفحة الحالية في هذه اللحظة بالذات. يتجاهل كل منهم محاولات الآخر لكتابة قصة ويبدأ في الكتابة بعناد على الصفحة. ونتيجة لذلك ، لدينا قصتان ، واحدة فوق الأخرى ، وصفحة غير قابلة للقراءة على الإطلاق.
أحد حلول هذه المشكلة بالتحديد هو GIL ، الذي يحظر المترجم الشفهي بينما يتفاعل مؤشر الترابط مع المورد المخصص ، وبالتالي يسمح لمؤشر واحد وواحد فقط بالكتابة إلى منطقة الذاكرة المخصصة. عندما يخصص CPython الذاكرة ، فإنه يستخدم GIL للتأكد من أنه يقوم بذلك بشكل صحيح.
هذا النهج له العديد من المزايا والعديد من العيوب ، وهذا هو السبب في أن جيل يسبب الفتنة في مجتمع بيثون. لمعرفة المزيد عن GIL ، أقترح قراءة
المقال التالي.
جامع القمامةدعنا نعود إلى قياسنا على الكتاب ونتخيل أن بعض القصص الموجودة فيه قد عفا عليها الزمن بشكل يائس. لا أحد يقرأهم ويعالجهم. في هذه الحالة ، سيكون الحل الطبيعي هو التخلص منها على أنها غير ضرورية ، وبالتالي توفير مساحة لقصص جديدة.
يمكن مقارنة هذه القصص القديمة غير المستخدمة بالكائنات في Python التي انخفض عدد مرجعها إلى 0. تذكر أن كل كائن في Python له عدد مرجعي ومؤشر إلى نوع.
قد يزيد عدد المرجع لعدة أسباب. على سبيل المثال ، سوف تزيد إذا قمت بتعيين متغير واحد إلى متغير آخر.

ستزداد أيضًا إذا قمت بتمرير الكائن كوسيطة.

في المثال الأخير ، سيزداد عدد المرجع إذا قمت بتضمين الكائن في القائمة.

يتيح لك Python معرفة القيمة الحالية للعداد المرجعي باستخدام وحدة sys. يمكنك استخدام
sys.getrefcount(numbers)
، ولكن تذكر أن استدعاء
getrefcount()
سيزيد العداد المرجعي بواسطة عداد آخر.
على أي حال ، إذا كان الكائن في الكود الخاص بك لا يزال مطلوبًا ، فستكون قيمته في عداد المرجع الخاص به أكثر من 0. وعندما تنخفض إلى الصفر ، سيتم بدء وظيفة خاصة لمسح الذاكرة ، والتي ستقوم بتحريرها وإتاحتها لكائنات أخرى.
ولكن ماذا يعني "تحرير الذاكرة" وكيف تستخدمها الكائنات الأخرى؟ دعونا الغوص مباشرة في إدارة الذاكرة في CPython.
إدارة الذاكرة في CPythonفي هذا الجزء ، سننتقل إلى بنية ذاكرة CPython والخوارزميات التي تعمل بها.
كما ذكرنا سابقًا ، هناك شيء مثل مستويات التجريد بين المعدات الفيزيائية و CPython. يستخلص نظام التشغيل (OS) الذاكرة الفعلية ويقوم بإنشاء مستوى من الذاكرة الظاهرية يمكن للتطبيقات ، بما في ذلك Python ، الوصول إليها.
يخصص مدير الذاكرة الظاهرية الموجهة لنظام التشغيل منطقة ذاكرة محددة لعمليات بيثون. في الصورة ، المناطق الرمادية الداكنة هي المساحة التي تشغلها عملية بيثون.

يستخدم Python جزءًا من الذاكرة للاستخدام الداخلي وذاكرة غير كائن. ينقسم الجزء الآخر إلى تخزين الأشياء (
int ، dict ، إلخ.) الآن أتحدث بلغة بسيطة للغاية ، ومع ذلك يمكنك أن تنظر مباشرة أسفل الغطاء ، أي إلى
الكود المصدري لـ CPython وشاهد كيف يحدث كل هذا من وجهة نظر عملية .
في CPython ، يوجد مخصص كائن مسؤول عن تخصيص الذاكرة داخل منطقة ذاكرة كائن. في هذا الموزع من الكائنات التي يتم تنفيذ كل السحر. يتم استدعاؤه في كل مرة يحتاج فيها كل كائن جديد إلى شغل أو تحرير الذاكرة.
عادة ، لا تستخدم إضافة البيانات وإزالتها في Python ، مثل int أو list ، على سبيل المثال ، الكثير من البيانات في وقت واحد. هذا هو السبب في أن بنية موزع البيانات تركز على العمل بكميات صغيرة من البيانات لكل وحدة زمنية. كما أنه لا يخصص الذاكرة مقدمًا ، أي حتى تلك اللحظة حتى تصبح ضرورية للغاية.
تُعرّف التعليقات في التعليمات البرمجية المصدر المُخصص بأنها "مُخصص ذاكرة سريع للأغراض الخاصة مثل وظيفة malloc العالمية." وفقا لذلك ، في C ، يتم استخدام malloc لتخصيص الذاكرة.
الآن دعونا نلقي نظرة على استراتيجية تخصيص ذاكرة CPython. أولاً ، دعنا نتحدث عن الأجزاء الرئيسية الثلاثة وكيفية ارتباطها ببعضها البعض.
تعد Arenas أكبر مناطق الذاكرة التي تشغل مساحة حتى حدود الصفحات في الذاكرة. حدود الصفحة (انتشار الصفحة) هي النقطة القصوى لكتلة مستمرة من الذاكرة ذات طول ثابت يستخدمه نظام التشغيل. تعيّن Python حد صفحة النظام إلى 256 كيلو بايت.

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

على سبيل المثال ، إذا كانت هناك حاجة إلى 42 بايت ، فسيتم وضع البيانات في كتلة 48 بايت.
حماماتبرك مصنوعة من كتل من نفس الفئة الحجم. يعمل كل تجمع على مبدأ قائمة مرتبطة مضاعفة مع تجمعات أخرى من نفس الفئة الحجم. لذلك ، يمكن أن تجد الخوارزمية بسهولة المكان اللازم لحجم الكتلة المطلوبة ، حتى بين العديد من التجمعات.
usedpools list
جميع المسابح التي لديها نوع من المساحة الحرة المتاحة للبيانات من كل فئة الحجم. عند طلب حجم الكتلة المطلوب ، تتحقق الخوارزمية من قائمة التجمعات المستخدمة للعثور على تجمع مناسب لها.
تجمعات في ثلاث ولايات: المستخدمة ، والكامل ، فارغة. يحتوي التجمع المستخدم على كتل يمكن فيها كتابة بعض المعلومات. جميع كتل التجمع الكامل موزعة وتحتوي بالفعل على بيانات. لا تحتوي التجمعات الفارغة على أي بيانات ويمكن تقسيمها إلى فئات الحجم المناسبة إذا لزم الأمر.
تحتوي قائمة
freepools list
الفارغة (
freepools list
) ، على التوالي ، على كل
freepools list
في حالة فارغة. ولكن في أي نقطة يتم استخدامها؟
لنفترض أن الشفرة تحتاج إلى مساحة ذاكرة تبلغ 8 بايت. إذا لم يكن هناك تجمعات في قائمة التجمعات المستخدمة بحجم فئة من 8 بايت ، فسيتم تهيئة تجمع فارغ جديد كتخزين كتل من 8 بايت. ثم يتم إضافة تجمع فارغ إلى قائمة تجمعات المستخدمة ويمكن استخدامها في الاستعلامات التالية.
تجمع كامل يحرر بعض الكتل عندما لم تعد هناك حاجة لهذه المعلومات فيها. سيتم إضافة هذا التجمع إلى القائمة المستخدمة وفقا لفئة الحجم. يمكنك ملاحظة كيف تغيّر التجمعات حالاتها وحتى فئات الحجم وفقًا للخوارزمية.
كتل
كما يتضح من الشكل ، تحتوي التجمعات على مؤشرات لتحرير كتل الذاكرة. هناك فارق بسيط في عملهم. وفقًا للتعليقات الواردة في الكود المصدري ، فإن الموزع "يسعى جاهداً لعدم لمس أي منطقة ذاكرة في أي من المستويات (الساحة ، التجمع ، الكتلة) حتى تكون هناك حاجة إليها"
هذا يعني أن كتلة يمكن أن يكون ثلاث حالات. يمكن تعريفها على النحو التالي:
- لم يمس : مناطق الذاكرة التي لم يتم تخصيصها ؛
- مجاني : مناطق الذاكرة التي تم تخصيصها ولكن تم تحريرها لاحقًا بواسطة CPython لأنها لا تحتوي على معلومات ذات صلة ؛
- الموزعة : مناطق الذاكرة التي تحتوي حاليًا على معلومات حالية.
مؤشر freeblock عبارة عن قائمة مرتبطة من كتل الذاكرة الحرة. بمعنى آخر ، هذه قائمة بالأماكن المجانية حيث يمكنك كتابة المعلومات. إذا كانت هناك حاجة إلى ذاكرة أكبر من تلك الموجودة في الكتل الحرة ، فإن المُخصص يستخدم الكتل التي لم تمس في التجمع.
بمجرد تحرير مدير الذاكرة للكتل ، تتم إضافة هذه الكتل إلى أعلى قائمة الكتل المجانية. قد لا تحتوي القائمة الفعلية على تسلسل مستمر من كتل الذاكرة ، كما في الشكل "الناجح" الأول.
الساحاتالساحات تحتوي على برك. الساحات ، على عكس التجمعات ، ليس لديها انقسامات واضحة للدولة.
يتم تنظيمهم أنفسهم في قائمة مرتبطة بشكل مضاعف تسمى قائمة الساحات القابلة للاستخدام (usable_arenas). يتم فرز هذه القائمة حسب عدد حمامات السباحة المجانية. كلما قل عدد حمامات السباحة المجانية ، كلما اقتربت الساحة من أعلى القائمة.

هذا يعني أنه سيتم تحديد الساحة الأكثر اكتمالا لتسجيل المزيد من البيانات. لكن لماذا بالضبط؟ لماذا لا تكتب البيانات إلى حيث المساحة الأكثر حرية؟
هذا يقودنا إلى فكرة تحرير الذاكرة بالكامل. الحقيقة هي أنه في بعض الحالات ، عند تحرير الذاكرة ، لا يزال الوصول إليها غير ممكن لنظام التشغيل. تحافظ عملية Python على توزيعها وتستخدمها لاحقًا للبيانات الجديدة. يؤدي إلغاء تخصيص الذاكرة بالكامل إلى إرجاع الذاكرة إلى نظام التشغيل.
الساحات ليست هي المناطق الوحيدة التي يمكن إخلاؤها بالكامل. وبالتالي ، فإننا نفهم أن تلك الساحات الموجودة في قائمة "الأقرب إلى الفارغة" يجب تحريرها. في هذه الحالة ، يمكن حقًا تحرير مساحة الذاكرة تمامًا ، وبالتالي ، تقل سعة الذاكرة الإجمالية لبرنامج Python.
استنتاجتعد إدارة الذاكرة واحدة من أهم الأجزاء في العمل مع الكمبيوتر. بطريقة أو بأخرى ، تقوم بيثون بجميع عملياتها تقريبًا في وضع التخفي.
من هذا المقال تعلمته:
- ما هي إدارة الذاكرة ولماذا هي مهمة؟
- ما هو CPython ، تنفيذ Python الأساسي ؛
- كيف تعمل بنية البيانات والخوارزميات في إدارة ذاكرة CPython وتخزين بياناتك.
تستخلص بايثون الفروق الدقيقة الكثيرة للعمل مع الكمبيوتر. هذا يجعل من الممكن العمل على مستوى أعلى والتخلص من الصداع في موضوع أين وكيف يتم تخزين بايت من البرنامج.
لذلك علمنا عن إدارة الذاكرة في بيثون. تقليديًا ، ننتظر تعليقاتك ، ونحن ندعوك أيضًا
ليوم مفتوح في دورة Python Developer ، والتي ستتم في 13 مارس