مجمّع الكائنات
^title مجمّع الكائنات ^section أنماط التحسين
النية
حسّن الأداء واستخدام الذاكرة عبر إعادة استخدام كائنات من مجمّع ثابت بدلاً من تخصيصها وتحريرها بشكل فردي.
الدافع
نعمل على المؤثّرات البصرية للعبتنا. فحين يرمي البطل تعويذة، نريد وميضاً من البريق المتلألئ يتفشّى عبر الشاشة. وهذا يستدعي نظام جسيمات (particle system)، وهو محرّك يُنشئ رسوماً بغيرة صغيرة ويُحرّكها حتى تنطفئ.
ولأن موجة واحدة من عصا الساحر قد تسبّب إخراج مئات الجسيمات، يحتاج نظامنا إلى القدرة على إنشائها بسرعة كبيرة. والأهم، نحتاج إلى التأكّد من أن إنشاء هذه الجسيمات وتدميرها لا يسبّب تجزئة الذاكرة (memory fragmentation).
لعنة التجزئة
البرمجة لأجهزة الألعاب أو الأجهزة المحمولة أقرب في جوانب كثيرة إلى البرمجة المدمجة منها إلى البرمجة التقليدية للحاسوب الشخصي. فالذاكرة نادرة، والمستخدمون يتوقّعون أن تكون الألعاب صامدة كالصخر، ومديرو الذاكرة الفعّالون المضغوطون نادراً ما يتوفّرون. وفي هذه البيئة، تكون تجزئة الذاكرة قاتلة.
تعني التجزئة أن المساحة الحرّة في كومتنا مجزّأة إلى أجزاء ذاكرة أصغر بدلاً من كتلة مفتوحة واحدة كبيرة. فقد تكون الذاكرة الإجمالية المتاحة كبيرة، لكن أكبر منطقة متّصلة قد تكون صغيرة بشكل مؤلم. لنفترض لدينا أربعة عشر بايت حرّة، لكنها مجزّأة إلى قطعتين كل منهما سبعة بايتات، بينهما كتلة ذاكرة مستخدَمة. فإذا حاولنا تخصيص كائن من اثني عشر بايت، فسنفشل. لا مزيد من البريق على الشاشة.
حتى لو كانت التجزئة نادرة، فإنها لا تزال تستطيع أن تُرهق الكومة تدريجياً حتى تتحوّل إلى رغوة غير قابلة للاستخدام من الثقوب المفتوحة والشقوق المملوءة، وفي النهاية تُسقط اللعبة تماماً.
أفضل العالمين معاً
بسبب التجزئة ولأن التخصيص قد يكون بطيئاً، تكون الألعاب حذرة جداً في متى وكيف تدير الذاكرة. والحلّ البسيط هو غالباً الأفضل -- خُذ كتلة كبيرة من الذاكرة حين تبدأ اللعبة، ولا تحرّرها حتى تنتهي اللعبة. لكن هذا إزعاج للأنظمة التي نحتاج فيها إلى إنشاء أشياء وتدميرها أثناء تشغيل اللعبة.
ويمنحنا مجمّع الكائنات أفضل العالمين معاً. فبالنسبة إلى مدير الذاكرة، نحن نخصّص قطعة كبيرة واحدة من الذاكرة مقدّماً ولا نحرّرها أثناء اللعب. وبالنسبة إلى مستخدمي المجمّع، يمكننا بحرّية تخصيص الكائنات وتحريرها كما نشاء.
النمط
عرّف صنف مجمّع (pool) يحتفظ بمجموعة من كائنات قابلة لإعادة الاستخدام. ويدعم كل كائن استعلام «قيد الاستخدام» ليخبرنا ما إذا كان «حيّاً» حالياً. وعند تهيئة المجمّع، ينشئ المجموعة الكاملة من الكائنات مقدّماً (عادةً في تخصيص واحد متّصل) ويهيّئها جميعاً إلى حالة «غير قيد الاستخدام».
وحين تريد كائناً جديداً، اطلبه من المجمّع. فيجد كائناً متاحاً، ويهيّئه إلى «قيد الاستخدام»، ويعيده إليك. وحين لا يعود الكائن مطلوباً، يُضبط مجدداً على حالة «غير قيد الاستخدام». بهذه الطريقة، يمكن إنشاء الكائنات وإتلافها بحرّية دون حاجة إلى تخصيص ذاكرة أو موارد أخرى.
متى تستخدمه
يُستخدم هذا النمط على نطاق واسع في الألعاب لأشياء بديهية مثل كيانات اللعبة والمؤثّرات البصرية، لكنه يُستخدم أيضاً لبنى بيانات أقل وضوحاً مثل الأصوات قيد التشغيل حالياً. استخدم مجمّع الكائنات عندما:
-
تحتاج إلى إنشاء الكائنات وإتلافها بشكل متكرّر.
-
تكون الكائنات متشابهة في الحجم.
-
يكون تخصيص الكائنات في الكومة بطيئاً أو قد يؤدّي إلى تجزئة الذاكرة.
-
يغلّف كل كائن مورداً مثل قاعدة بيانات أو اتصال شبكي مكلف الحصول عليه وقابل لإعادة الاستخدام.
في بالك
عادةً ما تعتمد على جامع القمامة أو على new وdelete ليتولّيا عنك إدارة الذاكرة.
وباستخدام مجمّع كائنات، فأنت تقول: «أعرف على نحو أفضل كيف ينبغي التعامل مع هذه
البايتات». وهذا يعني أن العبء يقع عليك في التعامل مع قيود هذا النمط.
قد يهدّر المجمّع ذاكرة على كائنات غير مطلوبة
يحتاج حجم مجمّع الكائنات إلى المعايرة وفق احتياجات اللعبة. وأثناء المعايرة، يكون من الواضح عادةً عندما يكون المجمّع صغيراً جداً (فلا شيء يلفت انتباهك كرميّة). لكن احرص أيضاً على ألّا يكون المجمّع كبيراً جداً. فالمجمّع الأصغر يحرّر ذاكرة يمكن استخدامها في أمور ممتعة أخرى.
عدد محدود فقط من الكائنات يمكن أن يكون نشطاً في أي لحظة
في بعض الجوانب، هذا أمر جيد. فتقسيم الذاكرة إلى مجمّعات منفصلة لأنواع مختلفة من الكائنات يضمن، مثلاً، أن لن تجعل سلسلة هائلة من الانفجارات نظام جسيماتك يلتهم كل الذاكرة المتاحة، مما يمنع إنشاء شيء أكثر أهمية مثل عدوّ جديد.
لكن هذا يعني أيضاً أن عليك الاستعداد لاحتمال أن تفشل محاولتك لإعادة استخدام كائن من المجمّع لأنها كلها قيد الاستخدام. وهناك بضع استراتيجيات شائعة للتعامل مع ذلك:
-
امنع ذلك تماماً. هذا هو «الإصلاح» الأكثر شيوعاً: عاير أحجام المجمّعات بحيث لا تفيض أبداً مهما فعل المستخدم. وبالنسبة إلى مجمّعات كائنات مهمة مثل الأعداء أو عناصر اللعب، فغالباً ما يكون هذا هو الجواب الصحيح. فربما لا توجد طريقة «صحيحة» للتعامل مع عدم وجود خانة حرّة لإنشاء الرئيس العملاق حين يصل اللاعب إلى نهاية المستوى، لذا فالأمر الذكي هو التأكّد من أن ذلك لا يحدث أبداً.
والعيب هو أن هذا قد يفرض عليك الاحتفاظ بكميات كبيرة من الذاكرة لخانات كائنات لا يلزم إلا في حالات حافّة نادرة قليلة. ولهذا، قد لا يكون حجم مجمّع واحد ثابت هو الأنسب لجميع حالات اللعبة. فبعض المستويات قد تُبرز المؤثّرات بينما يركّز آخرون على الصوت. وفي تلك الحالات، فكّر في معايرة أحجام المجمّعات بشكل مختلف لكل سيناريو.
-
لا تنشئ الكائن فحسب. يبدو هذا قاسياً، لكنه منطقي في حالات مثل نظام جسيماتنا. فإذا كانت جميع الجسيمات قيد الاستخدام، فالشاشة على الأرجح مليئة برسوم وامضة. ولن يلاحظ المستخدم إن لم يكن الانفجار التالي باهراً بقدر الانفجارات الجارية.
-
اقتل كائناً قائماً قسراً. لنفترض مجمّعاً للأصوات قيد التشغيل، وأنت تريد بدء صوت جديد لكن المجمّع ممتلئ. أنت لا تريد ببساطة تجاهل الصوت الجديد -- فالمستخدم سيلاحظ إن كانت عصاه السحرية تُصدر صوت حفيف دراماتيكياً أحياناً وتبقى صامتة العناد في أحيان أخرى. والحلّ الأفضل هو العثور على أهدأ صوت قيد التشغيل واستبداله بصوتك الجديد. فصوتك الجديد سيقنّع قطع الصوت السابق المسموع.
بشكل عام، إن كان اختفاء كائن قائم أقل ملحوظية من غياب كائن جديد، فقد يكون هذا هو الخيار الصحيح.
-
زد حجم المجمّع. إن كانت لعبتك تتيح لك قدراً أكبر من المرونة مع الذاكرة، فقد تستطيع زيادة حجم المجمّع في وقت التشغيل أو إنشاء مجمّع فائض ثانٍ. فإذا التقطت ذاكرة إضافية بأي من هاتين الطريقتين، ففكّر فيما إذا كان ينبغي للمجمّع أن ينكمش إلى حجمه السابق عندما لم تعد السعة الإضافية مطلوبة.
حجم ذاكرة كل كائن ثابت
تخزّن معظم تنفيذات المجمّع الكائنات في مصفوفة من الكائنات في مواضعها. فإذا كانت كل كائناتك من النوع نفسه، فذلك مقبول. لكن إن أردت تخزين كائنات من أنواع مختلفة في المجمّع، أو نسخ من أصناف فرعية قد تضيف حقولاً، فعليك التأكّد من أن كل خانة في المجمّع تملك ذاكرة كافية لأكبر كائن ممكن. وإلا فسيطم كائن كبير بشكل مفاجئ على الذي يليه ويتلف الذاكرة.
وفي الوقت نفسه، حين تختلف كائناتك في الحجم، فإنك تهدّر ذاكرة. فكل خانة يلزم أن تكون كبيرة بما يكفي لاستيعاب أكبر كائن. فإذا كانت الكائنات نادراً ما تكون بهذا الحجم، فأنت تهدر ذاكرة كلما وضعت كائناً أصغر في تلك الخانة. إنه أشبه بعبور تفتيش المطار واستخدام صينية أمتعة ضخمة بحجم حقيبة يد سفر لمفاتيحك ومحفظتك فقط.
وحين تجد نفسك تحرق ذاكرة كبيرة بهذه الطريقة، فكّر في تقسيم المجمّع إلى مجمّعات منفصلة لأحجام الكائنات المختلفة -- صوانٍ كبيرة للأمتعة، وصوانٍ صغيرة لأشياء الجيوب.
الكائنات المعاد استخدامها لا تُمحو تلقائياً
معظم مديري الذاكرة لديهم ميزة تنقيح تمسح الذاكرة المخصّصة حديثاً أو المحرَّرة إلى قيمة
سحرية واضحة مثل 0xdeadbeef. وهذا يساعدك على اكتشاف أخطاء مؤلمة ناتجة عن متغيّرات غير
مهيّأة أو استخدام ذاكرة بعد تحريرها.
وبما أن مجمّعنا لم يعد يمرّ عبر مدير الذاكرة عند إعادة استخدام كائن، فنحن نفقد شبكة الأمان تلك. والأسوأ، أن الذاكرة المستخدَمة لكائن «جديد» كانت تحوي سابقاً كائناً من النوع نفسه تماماً. وهذا يجعل من شبه المستحيل أن تكتشف أنك نسيت تهيئة شيء ما حين أنشأت الكائن الجديد: فذاكرة الكائن قد تحتوي أصلاً على بيانات شبه صحيحة من حياته السابقة.
ولهذا، احرص بعناية على أن الشيفرة التي تهيّئ الكائنات الجديدة في المجمّع تهيّئ الكائن تهيئة كاملة. وقد يستحق الأمر أيضاً قضاء بعض الوقت في إضافة ميزة تنقيح تمسح ذاكرة خانة الكائن عند استرداد الكائن.
الكائنات غير المستخدمة ستبقى في الذاكرة
مجمّعات الكائنات أقل شيوعاً في الأنظمة التي تدعم جمع القمامة، لأن مدير الذاكرة سيتولّى عادةً معالجة التجزئة نيابةً عنك. لكن المجمّعات ما زالت مفيدة هناك لتفادي كلفة التخصيص وإلغاء التخصيص، ولا سيما على الأجهزة المحمولة ذات المعالجات الأبطأ وجامعات القمامة الأبسط.
وإن استخدمت مجمّع كائنات بالتوازي مع جامع قمامة، فانتبه لتعارض محتمل. فبما أن المجمّع لا يلغي تخصيص الكائنات فعلياً حين تتوقّف عن الاستخدام، فإنها تبقى في الذاكرة. وإن كانت تحوي إشارات إلى كائنات *أخرى، فسيمنع ذلك الجامع من استرجاع تلك أيضاً. ولتفادي ذلك، حين لا يعود كائن المجمّع قيد الاستخدام، امسح أي إشارات لديه إلى كائنات أخرى.
شيفرة نموذجية
غالباً ما تطبّق أنظمة الجسيمات الحقيقية الجاذبية والرياح والاحتكاك ومؤثّرات فيزيائية أخرى. وسياقنا الأبسط بكثير سيحرّك الجسيمات في خط مستقيم لعدد معيّن من الإطارات ثم يقتل الجسيم. ليس بجودة السينما بالضبط، لكنه ينبغي أن يوضّح كيفية استخدام مجمّع كائنات.
سنبدأ بأبسط تنفيذ ممكن. وأول ما نبدأ به هو صنف الجسيمات الصغير:
^code 1
يهيّئ الباني الافتراضي الجسيم على حالة «غير قيد الاستخدام». ثم تستدعي لاحقة لـinit()
تهيّئ الجسيم إلى حالة حيّة. وتُحرَّك الجسيمات مع الزمن عبر الدالة animate() -- كما
يخبرنا اسمها بوضوح -- والتي ينبغي استدعاؤها مرة واحدة في كل إطار.
يحتاج المجمّع إلى معرفة أي الجسيمات متاحة لإعادة الاستخدام. ويحصل على ذلك من دالة
inUse() في الجسيم. وتستفيد هذه الدالة من الحقيقة أنّ الجسيمات لها عمر محدود، وتستخدم
المتغيّر framesLeft_ لاكتشاف أي الجسيمات قيد الاستخدام دون الحاجة إلى تخزين علم
منفصل.
وصنف المجمّع بسيط أيضاً:
^code 2
تتيح دالة create() للشيفرة الخارجية إنشاء جسيمات جديدة. وتستدعي اللعبة animate() مرة واحدة في كل إطار، وهي بدورها تحرّك كل جسيم في
المجمّع.
تُخزَّن الجسيمات نفسها ببساطة في مصفوفة ذات حجم ثابت داخل الصنف. وفي تنفيذ هذا المثال، يكون حجم المجمّع مكتوباً بشكل ثابت في تصريح الصنف، لكن يمكن تعريفه من الخارج باستخدام مصفوفة ديناميكية بحجم معيّن أو باستخدام معامل قالب (template parameter) للقيمة.
وإنشاء جسيم جديد مباشر:
^code 3
نمرّ على المجمّع بحثاً عن أول جسيم متاح. وحين نجده، نهيّئه وانتهينا. ولاحظ أن في هذا التنفيذ، إن لم يكن هناك أي جسيمات متاحة، فإننا ببساطة لا ننشئ واحداً جديداً.
وهذا كل ما في نظام جسيمات بسيط، باستثناء رسم الجسيمات بالطبع. يمكننا الآن إنشاء مجمّع وإنشاء بعض الجسيمات باستخدامه. وستُعطّل الجسيمات نفسها تلقائياً حين ينتهي عمرها.
وهذا يكفي لإطلاق لعبة، لكن العيون الفاحصة قد لاحظت أن إنشاء جسيم جديد يتطلّب المرور على المجموعة (الكامنة) بالكامل حتى نجد خانة مفتوحة. فإذا كان المجمّع كبيراً جداً وممتلئاً في معظمه، فقد يصبح ذلك بطيئاً. ولنرَ كيف يمكننا تحسين ذلك.
قائمة متاحة
إن لم نرغب في إضاعة وقت البحث عن جسيمات حرّة، فالجواب الواضح هو ألّا نفقد أثرها. يمكننا تخزين قائمة منفصلة من المؤشرات إلى كل جسيم غير مستخدم. وعندما نحتاج إلى إنشاء جسيم، نزيل المؤشر الأول من القائمة ونعيد استخدام الجسيم الذي يشير إليه.
لكن هذا يتطلب إنشاء مصفوفة منفصلة كاملة بعدد مؤشرات يساوي عدد الكائنات في المجمّع. فبعد كل شيء، حين ننشئ المجمّع لأول مرة، تكون جميع الجسيمات غير مستخدمة، لذا ستكون القائمة في البداية تحمل مؤشراً إلى كل كائن في المجمّع.
ولكان من الجميل أن نصلح مشكلات أداءنا دون التضحية بأي ذاكرة. ولسوء الحظ، هناك بعض الذاكرة متراكمة في الجوار يمكننا استئجارها -- بيانات الجسيمات غير المستخدمة نفسها.
وحين لا يكون الجسيم قيد الاستخدام، تكون معظم حالته غير ذات صلة. فموضعه وسرعته ليسا
مستخدَمين. والحالة الوحيدة التي يحتاجها هي ما يلزم لتحديد ما إذا كان ميتاً. وفي مثالنا،
تلك هي العضو framesLeft_. وكل تلك البتات الأخرى يمكن إعادة استخدامها. وهذه جسيمات
منقّحة:
^code 4
لقد نقلنا كل المتغيّرات الأعضاء عدا framesLeft_ إلى بنية live داخل اتحاد (union) state_. وتحمل هذه البنية حالة الجسيم أثناء تحريكه.
وحين يكون الجسيم غير مستخدم، تُستخدم الحالة الأخرى في الاتحاد، وهي العضو next. وهي
تحمل مؤشراً إلى الجسيم المتاح التالي بعد هذا.
ويمكننا استخدام هذه المؤشرات لبناء قائمة مترابطة تربط كل جسيم غير مستخدم في المجمّع. فلدينا قائمة الجسيمات المتاحة التي نحتاجها، لكننا لم نضطر إلى استخدام أي ذاكرة إضافية. بدلاً من ذلك، نهش ذاكرة الجسيمات الميتة نفسها لتخزين القائمة.
وتُسمّى هذه التقنية الأنيقة قائمة الحرّة. ولكي تعمل، علينا التأكّد من تهيئة المؤشرات تهيئةً صحيحة وصيانتها حين تُنشأ الجسيمات وتُدمَّر. ونحتاج بالطبع إلى الاحتفاظ برأس القائمة:
^code 5
وحين يُنشأ المجمّع لأول مرة، تكون جميع الجسيمات متاحة، لذا يجب أن تمرّ قائمتنا الحرّة عبر المجمّع بأكمله. ويضبط باني المجمّع ذلك:
^code 6
والآن لإنشاء جسيم جديد، نقفز مباشرةً إلى الجسيم المتاح الأول:
^code 7
نحتاج إلى معرفة متى يموت الجسيم كي نعيده إلى القائمة الحرّة، لذا سنغيّر animate()
لتُعيد true إذا كان الجسيم الحيّ سابقاً قد ترك العالم في ذلك الإطار:
^code particle-animate
وحين يحدث ذلك، فإننا نُعيد ربطه ببساطة إلى القائمة:
^code 8
إذن، إليك مجمّع كائنات لطيفاً بصيغة ثابتة الإنشاء والحذف.
قرارات التصميم
كما رأيت، فإن أبسط تنفيذ لمجمّع الكائنات يكاد يكون تافهاً: أنشئ مصفوفة من الكائنات وأعد تهيئتها حسب الحاجة. ونادراً ما تكون شيفرة الإنتاج بهذا الحدّ الأدنى. وهناك عدة طرق للتوسّع على ذلك لجعل المجمّع أكثر عمومية أو أماناً في الاستخدام أو أسهل في الصيانة. وعند تنفيذك للمجمّعات في ألعابك، ستحتاج إلى الإجابة عن هذه الأسئلة:
هل الكائنات مرتبطة بالمجمّع؟
السؤال الأول الذي ستصادفه عند كتابة مجمّع كائنات هو ما إذا كانت الكائنات نفسها تعرف أنها في مجمّع. في معظم الوقت ستعرف، لكنك لن تتمتّع بهذه الرفاهية عند كتابة صنف مجمّع عامّ قادر على استيعاب كائنات اعتباطية.
-
إذا كانت الكائنات مرتبطة بالمجمّع:
-
التنفيذ أبسط. يمكنك ببساطة وضع علم «قيد الاستخدام» أو دالة في الكائن المُخزَّن ثم تنتهي الأمر.
-
يمكنك ضمان إنشاء الكائنات بواسطة المجمّع وحده. في C++، إحدى الطرق البسيطة لذلك هي جعل صنف المجمّع صديقاً (friend) لصنف الكائن ثم جعل باني الكائن خاصاً.
^code 10
وهذه العلاقة توثّق طريقة استخدام الصنف المقصودة وتضمن ألا ينشئ مستخدموك كائنات لا يتتبّعها المجمّع.
-
قد تتمكّن من تفادي تخزين علم «قيد الاستخدام» صريح. فكثير من الكائنات تحتفظ أصلاً ببعض الحالة التي يمكن استخدامها لتحديد ما إذا كانت حيّة أم لا. مثلاً، قد تكون جسيمات متاحة لإعادة الاستخدام إذا كان موضعها الحالي خارج الشاشة. فإذا كان صنف الكائن يعرف أنه قد يُستخدم في مجمّع، فيستطيع توفير دالة
inUse()لاستعلام تلك الحالة. وهذا يوفّر على المجمّع حرق بعض الذاكرة الإضافية لتخزين كومة من أعلام «قيد الاستخدام».
-
-
إذا لم تكن الكائنات مرتبطة بالمجمّع:
-
يمكن تجميع كائنات من أي نوع. وهذه هي الميزة الكبرى. فبفصل الكائنات عن المجمّع، قد تتمكّن من تنفيذ صنف مجمّع عام قابل لإعادة الاستخدام.
-
يجب تتبّع حالة «قيد الاستخدام» خارج الكائنات. وأبسط طريقة لل doing ذلك هي إنشاء حقل بت منفصل:
^code 11
-
ما المسؤول عن تهيئة الكائنات المعاد استخدامها؟
لكي يُعاد استخدام كائن قائم، لا بدّ من إعادة تهيئته بحالة جديدة. والسؤال المفتاح هنا هو هل نعيد تهيئة الكائن داخل صنف المجمّع أم خارجه.
-
إذا كان المجمّع يعيد التهيئة داخلياً:
-
يستطيع المجمّع تغليف كائناته بالكامل. فحسب القدرات الأخرى التي تحتاجها كائناتك، قد تتمكّن من إبقائها داخليةً تماماً في المجمّع. وهذا يضمن ألا تحتفظ شيفرة أخرى بإشارات إلى كائنات قد يُعاد استخدامها بشكل مفاجئ.
-
المجمّع مرتبط بكيفية تهيئة الكائنات. فقد يقدّم الكائن المُخزَّن عدة دوال تهيّئه. فإذا أدار المجمّع التهيئة، فيحتاج واجهته إلى دعم كل تلك الدوال وتوجيهها إلى الكائن.
^code 12
-
-
إذا كانت شيفرة خارجية تهيّئ الكائن:
-
يمكن أن تكون واجهة المجمّع أبسط. فبدلاً من تقديم عدة دوال لتغطية كل طريقة يمكن بها تهيئة كائن، يمكن للمجمّع ببساطة إعادة إشارة إلى الكائن الجديد:
^code 13
يمكن للاستدعاء عندئذٍ تهيئة الكائن باستدعاء أي دالة يعرّضها الكائن:
^code 14
-
قد تحتاج الشيفرة الخارجية إلى التعامل مع فشل إنشاء كائن جديد. يفترض المثال السابق أن
create()ستعيد دائماً المؤشر إلى كائن بنجاح. لكن إذا كان المجمّع ممتلئاً، فقد تُعيدNULLبدلاً من ذلك. وللأمان، ستحتاج إلى فحص ذلك قبل أن تحاول تهيئة الكائن:^code 15
-
انظر أيضاً
-
يشبه هذا النمط إلى حدٍّ كبير نمط الرمادي (Flyweight). فكلاهما يحتفظ بمجموعة من الكائنات القابلة لإعادة الاستخدام. والفرق هو معنى «إعادة الاستخدام». فكائنات Flyweight يُعاد استخدامها عبر مشاركة النسخة نفسها بين عدة مالكين في الوقت نفسه. ويتجنّب نمط Flyweight استهلاك ذاكرة مكرّراً باستخدام الكائن نفسه في سياقات متعددة.
أما الكائنات في المجمّع فتُعاد استخدامها أيضاً، لكن على مرور الوقت فقط. فـ«إعادة الاستخدام» في سياق مجمّع الكائنات تعني استرداد ذاكرة كائن بعد أن ينهي مالكه الأصلي عمله به. ومع مجمّع الكائنات، لا يوجد أي توقّع بأن يُشارَك الكائن خلال عمره.
-
حزم كومة من الكائنات من النوع نفسه في الذاكرة يساعد على إبقاء ذاكرة المعالج المؤقتة ممتلئة أثناء مرور اللعبة على تلك الكائنات. ونمط محلية البيانات يدور حول ذلك كله.