كائنات بدون مراجع دائرية وجمع القمامة الدائرية

كل مثيل لفئة في CPython تم إنشاؤه باستخدام بناء جملة الفئة يشارك في آلية تجميع البيانات المهملة . هذا يزيد من مساحة الذاكرة لكل مثيل ويمكن أن يسبب مشاكل في الذاكرة في الأنظمة المحملة بشكل كبير.


إذا لزم الأمر ، هل من الممكن الاستغناء عن آلية حساب أساسية واحدة للارتباط ؟

دعنا نحلل أحد الأساليب التي ستساعد في إنشاء فئات سيتم حذف مثيلاتها فقط باستخدام آلية حساب الارتباط .


قليلا عن جمع القمامة في CPython


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


lst = [] lst.append(lst) del lst 

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


النفقات العامة المرتبطة آلية جمع القمامة


عادةً ، آلية جمع البيانات المهملة لا يسبب مشاكل. ولكن هناك بعض النفقات العامة المرتبطة به:


عند تخصيص كل فئة من فئات الذاكرة ، تتم إضافة رأس PyGC_Head : (على الأقل 24 بايت في Python <= 3.7 و 16 بايت على الأقل في 3.8 على نظام أساسي 64 بت.

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


هل من الممكن في بعض الأحيان أن يقتصر المرء على الآلية الأساسية لحساب الارتباط؟


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


 class Point: x: int y: int 

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


  p = Point(0, 0) px = p 

ومع ذلك ، بالنسبة للفئة Point ، يمكن للمرء أن يقتصر على آلية حساب الارتباط. ولكن لا توجد آلية قياسية لرفض مجموعة البيانات المهملة الدورية لفئة واحدة حتى الآن.


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


عن تنفيذ واحد


كمثال على تنفيذ الفكرة ، ضع في الاعتبار ملف dataobject من الفئة الأساسية من مشروع recordclass . باستخدامه ، يمكنك إنشاء فئات لا تشارك Py_TPFLAGS_HAVE_GC في آلية تجميع البيانات المهملة الدائرية (لم Py_TPFLAGS_HAVE_GC تثبيت Py_TPFLAGS_HAVE_GC ، وبالتالي لا يوجد رأس PyGC_Head إضافي). لديهم نفس البنية في الذاكرة تمامًا مثل مثيلات الفئة مع __slots__ ، ولكن بدون PyGC_Head :


 from recordclass import dataobject class Point(dataobject): x:int y:int >>> p = Point(1,2) >>> print(p.__sizeof__(), sys.getsizeof(p)) 32 32 

للمقارنة ، نعطي فئة مماثلة مع __slots__ :


 class Point: __slots__ = 'x', 'y' x:int y:int >>> p = Point(1,2) >>> print(p.__sizeof__(), sys.getsizeof(p)) 32 64 

الفرق في الحجم هو بالضبط حجم رأس PyGC_Head . بالنسبة للمثيلات ذات السمات المتعددة ، قد تكون هذه الزيادة في حجم تتبعها في ذاكرة الوصول العشوائي كبيرة. بالنسبة لمثيلات فئة Point ، PyGC_Head إضافة PyGC_Head زيادة حجمها بمقدار مرتين.


لتحقيق هذا التأثير ، datatype metaclass خاص ، والذي يوفر إعداد الفئات الفرعية من dataobject . نتيجة التكوين ، تتم Py_TPFLAGS_HAVE_GC علامة Py_TPFLAGS_HAVE_GC ، Py_TPFLAGS_HAVE_GC الحجم الأساسي لنسخة tp_basicsize بالمقدار اللازم لتخزين فتحات الحقل الإضافية. يتم سرد أسماء الحقول المطابقة عندما يتم الإعلان عن الفئة (فئة Point تحتوي على اثنين: x و y ). يوفر datatype metatlass أيضًا إعداد قيم فتحات tp_alloc و tp_new و tp_dealloc و tp_free ، والتي تنفذ الخوارزميات الصحيحة لإنشاء مثيلات في التدمير وتدميرها. بشكل افتراضي ، تفتقر المثيلات إلى __weakref__ و __dict__ (وكذلك مثيلات للفئات ذات __slots__ ).


استنتاج


كما يمكن للمرء أن يرى ، في CPython ، إذا لزم الأمر ، فمن الممكن تعطيل آلية جمع القمامة الدورية لفئة معينة ، عندما يكون هناك ثقة في أن مثيلاتها لن تشكل روابط دائرية. سيؤدي ذلك إلى تقليل تتبعها في الذاكرة بحجم رأس PyGC_Head .

Source: https://habr.com/ru/post/ar463269/


All Articles