كل مثيل لفئة في 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
.