لماذا تختلف طرق جمع البيانات المهملة في Java وPython؟

StackOverflow https://stackoverflow.com/questions/21934

  •  09-06-2019
  •  | 
  •  

سؤال

تستخدم Python طريقة العد المرجعي للتعامل مع مدة حياة الكائن.لذلك سيتم تدمير الكائن الذي لم يعد له أي استخدام على الفور.

ولكن في Java، يقوم GC (مجمع البيانات المهملة) بتدمير الكائنات التي لم تعد تُستخدم في وقت محدد.

لماذا تختار Java هذه الإستراتيجية وما الفائدة من ذلك؟

هل هذا أفضل من نهج بايثون؟

هل كانت مفيدة؟

المحلول

هناك عيوب في استخدام العد المرجعي.ومن أكثر ما ذكر المراجع الدائرية:لنفترض أن A يشير إلى B، وB يشير إلى C، وC يشير إلى B.إذا قام A بإسقاط مرجعه إلى B، فسيظل لدى كل من B وC عدد مرجعي قدره 1 ولن يتم حذفه باستخدام العد المرجعي التقليدي.CPython (عد المراجع ليس جزءًا من python نفسه، ولكنه جزء من تطبيق C منه) يلتقط المراجع الدائرية من خلال روتين منفصل لجمع البيانات المهملة يتم تشغيله بشكل دوري ...

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

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

نصائح أخرى

في الواقع، يعد العد المرجعي والاستراتيجيات المستخدمة بواسطة Sun JVM أنواعًا مختلفة من خوارزميات جمع البيانات المهملة.

هناك طريقتان واسعتان لتعقب الأجسام الميتة:التتبع والعد المرجعي.في التتبع، يبدأ GC من "الجذور" - أشياء مثل مراجع المكدس، ويتتبع جميع الكائنات (الحية) التي يمكن الوصول إليها.أي شيء لا يمكن الوصول إليه يعتبر ميتا.في حساب المرجع في كل مرة يتم فيها تعديل مرجع، يتم تحديث عدد الكائنات المعنية.أي كائن يتم تعيين العدد المرجعي له على الصفر يعتبر ميتًا.

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

بالإضافة إلى ذلك، يتطلب العد المرجعي لـ GC كاشف دورة لتنظيف أي كائنات في الدورة لن يتم اكتشافها من خلال العد المرجعي الخاص بها وحده.لم يكن لدى Perl 5 كاشف دورة في تطبيق GC الخاص به ويمكن أن يتسرب من الذاكرة الدورية.

تم إجراء الأبحاث أيضًا للحصول على أفضل ما في كلا العالمين (أوقات توقف منخفضة، وإنتاجية عالية):http://cs.anu.edu.au/~Steve.Blackburn/pubs/papers/urc-oopsla-2003.pdf

دارين توماس يعطي إجابة جيدة.ومع ذلك، أحد الاختلافات الكبيرة بين نهجي Java وPython هو أنه مع حساب المراجع في الحالة الشائعة (بدون مراجع دائرية)، يتم تنظيف الكائنات على الفور بدلاً من تنظيفها في وقت لاحق غير محدد.

على سبيل المثال، يمكنني كتابة تعليمات برمجية قذرة وغير محمولة في CPython مثل

def parse_some_attrs(fname):
    return open(fname).read().split("~~~")[2:4]

وسيتم تنظيف واصف الملف لهذا الملف الذي فتحته على الفور لأنه بمجرد اختفاء المرجع إلى الملف المفتوح، يتم جمع البيانات المهملة ويتم تحرير واصف الملف.بالطبع، إذا قمت بتشغيل Jython أو IronPython أو ربما PyPy، فلن يعمل جامع البيانات المهملة بالضرورة إلا بعد وقت طويل؛ربما ستنفد واصفات الملفات أولاً وسيتعطل برنامجي.

لذلك يجب عليك كتابة التعليمات البرمجية التي تبدو

def parse_some_attrs(fname):
    with open(fname) as f:
        return f.read().split("~~~")[2:4]

لكن في بعض الأحيان يحب الأشخاص الاعتماد على العد المرجعي لتحرير مواردهم دائمًا لأنه قد يؤدي في بعض الأحيان إلى جعل التعليمات البرمجية الخاصة بك أقصر قليلاً.

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

أعتقد أن المقال "نظرية وممارسة جافا:تاريخ موجز لجمع القمامة" من IBM يجب أن يساعد في شرح بعض الأسئلة التي لديك.

تعتبر عملية جمع البيانات المهملة أسرع (أكثر كفاءة في استخدام الوقت) من حساب المراجع، إذا كانت لديك ذاكرة كافية.على سبيل المثال، يجتاز نسخ gc الكائنات "الحية" وينسخها إلى مساحة جديدة، ويمكنه استعادة جميع الكائنات "الميتة" في خطوة واحدة عن طريق تحديد منطقة ذاكرة كاملة.وهذا فعال للغاية، لو لديك ذاكرة كافية.تستخدم مجموعات الأجيال المعرفة بأن "معظم الأشياء تموت في سن مبكرة"؛في كثير من الأحيان يجب نسخ نسبة قليلة فقط من الكائنات.

[وهذا أيضًا هو السبب وراء كون gc أسرع من malloc/free]

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

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

أحد العيوب الكبيرة في تتبع GC لـ Java هو أنه من وقت لآخر سوف "يوقف العالم" ويجمد التطبيق لفترة طويلة نسبيًا لإجراء GC كامل.إذا كانت الكومة كبيرة وكانت شجرة الكائن معقدة، فسوف تتجمد لبضع ثوان.كما يقوم كل GC كامل بزيارة شجرة الكائنات بأكملها مرارًا وتكرارًا، وهو أمر ربما يكون غير فعال تمامًا.عيب آخر للطريقة التي تعمل بها Java مع GC هو أنه يجب عليك إخبار jvm بحجم الكومة الذي تريده (إذا لم يكن الإعداد الافتراضي جيدًا بما يكفي)؛يستمد JVM من هذه القيمة عدة عتبات من شأنها تشغيل عملية GC عندما يكون هناك الكثير من تكديس البيانات المهملة في الكومة.

أفترض أن هذا هو في الواقع السبب الرئيسي للشعور المتشنج بنظام Android (المعتمد على Java)، حتى على الهواتف المحمولة الأكثر تكلفة، مقارنة بسلاسة نظام iOS (المعتمد على ObjectiveC، واستخدام RC).

أرغب في رؤية خيار jvm لتمكين إدارة ذاكرة RC، وربما الاحتفاظ بـ GC فقط للتشغيل كحل أخير عندما لا يكون هناك المزيد من الذاكرة.

يحتوي أحدث إصدار من Sun Java VM على خوارزميات GC متعددة يمكنك تعديلها.تم حذف مواصفات Java VM عمدًا لتحديد سلوك GC الفعلي للسماح بخوارزميات GC مختلفة (ومتعددة) لأجهزة افتراضية مختلفة.

على سبيل المثال، بالنسبة لجميع الأشخاص الذين لا يحبون أسلوب "إيقاف العالم" لسلوك Sun Java VM GC الافتراضي، هناك أجهزة افتراضية مثل IBM WebSphere في الوقت الحقيقي والذي يسمح بتشغيل التطبيق في الوقت الفعلي على Java.

نظرًا لأن مواصفات Java VM متاحة للعامة، فلا يوجد (نظريًا) ما يمنع أي شخص من تنفيذ Java VM الذي يستخدم خوارزمية GC الخاصة بـ CPython.

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

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

في وقت متأخر من اللعبة، ولكن أعتقد أن أحد الأسباب المنطقية المهمة لـ RC في بايثون هو بساطته.انظر الى هذا البريد الإلكتروني من اليكس مارتيلي, ، على سبيل المثال.

(لم أتمكن من العثور على رابط خارج ذاكرة التخزين المؤقت في Google، تاريخ البريد الإلكتروني هو 13 أكتوبر 2005 في قائمة بايثون).

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top