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

قرر مؤلف المادة ، التي ننشر ترجمتها ، الوصول إلى أسفل وسائل تخصيص الذاكرة في Go والتحدث عنها.
الذاكرة المادية والظاهرية
يجب أن تعمل جميع وسائل تخصيص الذاكرة مع مساحة عنوان الذاكرة الظاهرية ، والتي يتم التحكم فيها بواسطة نظام التشغيل. دعونا ننظر في كيفية عمل الذاكرة ، بدءا من أدنى مستوى - مع خلايا الذاكرة.
إليك كيفية تخيل خلية ذاكرة الوصول العشوائي.
تخطيط خلية الذاكرةإذا تخيلنا ، بساطة شديدة ، خلية ذاكرة وما يحيط بها ، فسنحصل على ما يلي:
- سطر العنوان (يعمل الترانزستور كمحول) هو ما يتيح الوصول إلى المكثف (خط البيانات).
- عندما تظهر إشارة في سطر العنوان (الخط الأحمر) ، يتيح لك خط البيانات كتابة البيانات إلى خلية الذاكرة ، أي شحن المكثف ، مما يجعل من الممكن تخزين قيمة منطقية مقابلة لقيمة 1 فيها.
- عندما لا توجد إشارة في سطر العنوان (الخط الأخضر) ، يتم عزل المكثف ولا يتغير شحنته. للكتابة إلى الخلية 0 ، يجب عليك تحديد عنوانها وتقديم 0 منطقية من خلال خط البيانات ، أي توصيل خط البيانات بطرح ، وبالتالي تفريغ المكثف.
- عندما يحتاج المعالج إلى قراءة القيمة من الذاكرة ، يتم إرسال الإشارة على طول خط العنوان (يتم إغلاق المفتاح). إذا تم شحن المكثف ، فإن الإشارة تمر عبر خط البيانات (تتم قراءة 1) ، وإلا فإن الإشارة لا تمر عبر خط البيانات (تتم قراءة 0).
مخطط تفاعل الذاكرة الفعلية والمعالجناقل البيانات مسؤول عن نقل البيانات بين المعالج والذاكرة الفعلية.
الآن دعونا نتحدث عن سطر العنوان والبايت عنونة.
خطوط عنوان الناقل بين المعالج والذاكرة الفعلية- يتم تعيين كل بايت في RAM معرف رقمي فريد (عنوان). تجدر الإشارة إلى أن عدد وحدات البايت الفعلية الموجودة في الذاكرة لا يساوي عدد أسطر العناوين.
- يمكن لكل سطر عنوان تحديد قيمة 1 بت ، لذلك يشير إلى واحد بت في عنوان بايت معين.
- دائرتنا بها 32 سطر عنوان. نتيجة لذلك ، يستخدم كل بايت addressable رقم 32 بت كعنوانه. [00000000000000000000000000000000] - أدنى عنوان ذاكرة. [1111111111111111111111111111111111] - أعلى عنوان الذاكرة.
- نظرًا لأن كل بايت يحتوي على عنوان 32 بت ، فإن مساحة العنوان الخاصة بنا تتكون من 2 32 بايت قابل للعنونة (4 جيجابايت).
نتيجة لذلك ، اتضح أن عدد البايتات القابلة للعنونة يعتمد على إجمالي عدد أسطر العناوين. على سبيل المثال ، إذا كان هناك 64 سطر عنوان (x86-64 معالجات) ، يمكنك معالجة 2
64 بايت (16 إكسبايت) من الذاكرة ، ولكن معظم البنيات التي تستخدم مؤشرات 64 بت تستخدم بالفعل خطوط عناوين 48 بت (AMD64) وخطوط عنوان 42 بت (Intel) ، التي تسمح نظريًا بتزويد أجهزة الكمبيوتر بـ 256 تيرابايت من الذاكرة الفعلية (يسمح Linux ، على بنية x86-64 ، عند استخدام صفحات عناوين المستوى 4 ، بتخصيص ما يصل إلى 128 تيرابايت من مساحة العنوان للعمليات ، 192 تيرابايت).
نظرًا لأن حجم ذاكرة الوصول العشوائي الفعلية محدود ، يتم تشغيل كل عملية في "صندوق الحماية" الخاص بها - في ما يسمى "مساحة العنوان الافتراضية" ، والتي تسمى الذاكرة الظاهرية.
لا تطابق عناوين البايت في مساحة العنوان الافتراضية العناوين التي يستخدمها المعالج للوصول إلى الذاكرة الفعلية. نتيجة لذلك ، نحتاج إلى نظام يسمح لنا بتحويل العناوين الافتراضية إلى عناوين فعلية. ألقِ نظرة على شكل عناوين الذاكرة الافتراضية.
التمثيل الافتراضي لمساحة العنواننتيجة لذلك ، عندما ينفذ المعالج تعليمة تشير إلى عنوان الذاكرة ، فإن الخطوة الأولى تتمثل في ترجمة العنوان المنطقي إلى عنوان خطي. يتم تنفيذ هذا التحويل بواسطة وحدة إدارة الذاكرة.
تمثيل مبسط للعلاقة بين الذاكرة الفعلية والذاكرة الفعليةنظرًا لأن العناوين المنطقية أكبر من أن تكون مريحة للعمل معها بشكل منفصل (يعتمد هذا على عوامل متعددة) ، يتم تنظيم الذاكرة في هياكل تسمى الصفحات. في هذه الحالة ، يتم تقسيم مساحة العنوان الافتراضية إلى مساحات صغيرة وصفحات يبلغ حجمها 4 كيلوبايت في معظم أنظمة التشغيل ، رغم أنه يمكن تغيير هذا الحجم عادةً. هذه هي أصغر وحدة لإدارة الذاكرة في الذاكرة الافتراضية. الذاكرة الظاهرية لا تخزن أي شيء ، فهي ببساطة تحدد المراسلات بين مساحة عنوان البرنامج والذاكرة الفعلية.
ترى العمليات فقط عناوين الذاكرة الظاهرية. ماذا يحدث إذا كان البرنامج يحتاج إلى ذاكرة أكثر ديناميكية (وتسمى أيضًا ذاكرة الكومة أو "الكومة")؟ فيما يلي مثال لرمز المجمّع البسيط الذي تطلب فيه الذاكرة الإضافية المخصصة ديناميكيًا من النظام:
_start: mov $12, %rax # brk mov $0, %rdi # 0 - , syscall b0: mov %rax, %rsi # rsi , mov %rax, %rdi # ... add $4, %rdi # .. 4 , mov $12, %rax # brk syscall
هنا هو كيف يمكن تمثيله في شكل رسم بياني.
زيادة الذاكرة المخصصة بشكل حيوييطلب البرنامج ذاكرة إضافية باستخدام استدعاء نظام
brk (sbrk / mmap وما إلى ذلك). يقوم kernel بتحديث المعلومات المتعلقة بالذاكرة الظاهرية ، ولكن لم يتم تقديم صفحات جديدة في الذاكرة الفعلية ، وهنا يوجد فرق بين الذاكرة الظاهرية والذاكرة الفعلية.
مخصص الذاكرة
بعد أن ناقشنا ، بشكل عام ، العمل مع مساحة العنوان الافتراضية ، وتحدثنا عن كيفية طلب ذاكرة ديناميكية إضافية (الذاكرة على الكومة) ، سيكون من الأسهل بالنسبة لنا أن نتحدث عن وسائل تخصيص الذاكرة.
إذا كانت الكومة تحتوي على ذاكرة كافية لتلبية طلبات التعليمات البرمجية الخاصة بنا ، يمكن لمخصص الذاكرة تنفيذ هذه الطلبات دون الوصول إلى kernel. خلاف ذلك ، يجب عليه زيادة حجم الكومة باستخدام استدعاء نظام (باستخدام brk ، على سبيل المثال) ، أثناء طلب كتلة كبيرة من الذاكرة. في حالة malloc ، يعني "كبير" الحجم الموصوف بواسطة المعلمة
MMAP_THRESHOLD
، والتي ، افتراضيًا ، هي 128 كيلو بايت.
ومع ذلك ، فإن مخصص الذاكرة لديه مسؤوليات أكثر من مجرد تخصيص الذاكرة. تتمثل إحدى أهم مسؤولياته في تقليل تجزئة الذاكرة الداخلية والخارجية ، وتخصيص كتل الذاكرة في أسرع وقت ممكن. افترض أن برنامجنا ينفذ بشكل متتالي طلبات تخصيص كتل مستمرة من الذاكرة باستخدام دالة من
malloc(size)
النموذج
malloc(size)
، وبعد ذلك يتم تحرير هذه الذاكرة باستخدام دالة للنموذج
free(pointer)
.
مظاهرة تجزئة الخارجيةفي الرسم البياني السابق ، في الخطوة p4 ، ليس لدينا ما يكفي من كتل الذاكرة الموجودة بالتسلسل للوفاء بطلب تخصيص ستة كتل من هذا القبيل ، على الرغم من أن إجمالي حجم الذاكرة الخالية يسمح بذلك. هذا الموقف يؤدي إلى تجزئة الذاكرة.
كيفية الحد من تجزئة الذاكرة؟ تعتمد إجابة هذا السؤال على خوارزمية تخصيص الذاكرة المحددة ، والتي تستخدم المكتبة الأساسية للتعامل مع الذاكرة.
سننظر الآن في أداة تخصيص الذاكرة TCMalloc ، التي تستند إليها آليات تخصيص ذاكرة Go.
TCMalloc
يعتمد
TCMalloc على فكرة تقسيم الذاكرة إلى عدة مستويات لتقليل تجزئة الذاكرة. داخل TCMalloc ، تنقسم إدارة الذاكرة إلى قسمين: العمل مع ذاكرة مؤشر الترابط والعمل مع كومة الذاكرة المؤقتة.
▍ ذاكرة الموضوع
تنقسم كل صفحة من الذاكرة إلى سلسلة من الأجزاء ذات الأحجام المحددة ، يتم اختيارها وفقًا لفئات الحجم. هذا يقلل من تجزئة. نتيجة لذلك ، يوجد تحت تصرف كل مؤشر ترابط ذاكرة تخزين مؤقت للكائنات الصغيرة ، مما يسمح بتخصيص ذاكرة فعال للكائنات التي يقل حجمها عن 32 كيلو بايت.
دفق ذاكرة التخزين المؤقتunch حفنة
الكومة التي تديرها TCMalloc هي عبارة عن مجموعة من الصفحات التي يمكن فيها تمثيل مجموعة من الصفحات المتتالية كمجموعة من الصفحات (span). عندما تحتاج إلى تخصيص ذاكرة لكائن أكبر من 32 كيلو بايت ، يتم استخدام كومة الذاكرة المؤقتة لتخصيص الذاكرة.
كومة والعمل مع الصفحاتعندما لا توجد مساحة كافية لوضع الأشياء الصغيرة في الذاكرة ، فإنها تتحول إلى كومة الذاكرة. إذا كان الكومة لا تحتوي على ذاكرة حرة كافية ، يتم طلب ذاكرة إضافية من نظام التشغيل.
كنتيجة لذلك ، يدعم النموذج المقدم للعمل مع الذاكرة تجمع ذاكرة المستخدم ؛ يحسن استخدامه بشكل كبير من كفاءة تخصيص الذاكرة وتحريرها.
تجدر الإشارة إلى أن أداة تخصيص ذاكرة Go تستند في الأصل إلى TCMalloc ، ولكنها تختلف قليلاً عنها.
الذهاب مخصص الذاكرة
نحن نعلم أن وقت التشغيل Go يخطط لتشغيل goroutines على معالجات منطقية. وبالمثل ، فإن إصدار TCMalloc المستخدم من قبل Go يقسم صفحات الذاكرة إلى كتل تتوافق أحجامها مع فئات حجم معينة منها 67.
إذا لم تكن معتادًا على برنامج جدولة Go
، فيمكنك القراءة عنه
هنا .
الذهاب الطبقات الحجمنظرًا لأن الحد الأدنى لحجم الصفحة في Go هو 8192 بايت (8 كيلو بايت) ، إذا كانت هذه الصفحة مقسمة إلى كتل تبلغ 1 كيلو بايت ، فسنحصل على 8 كتل من هذا القبيل.
حجم الصفحة 8 كيلو بايت مقسم إلى كتل المطابقة لحجم فئة 1 كيلو بايتيتم التحكم في تسلسل الصفحات المشابهة في Go باستخدام بنية تسمى mspan.
ruct هيكل mspan
هيكل mspan هو قائمة مرتبطة مزدوجة ، كائن يحتوي على عنوان البداية للصفحة ، ومعلومات حول حجم الصفحة وعدد الصفحات المضمنة فيه.
هيكل Mspanc هيكل مكاش
مثل TCMalloc ، يوفر Go لكل معالج منطقي ذاكرة تخزين مؤقت لمؤشر الترابط المحلي ، والمعروفة باسم mcache. نتيجة لذلك ، إذا كان goroutine يحتاج إلى ذاكرة ، فيمكنه الحصول عليها مباشرةً من mcache. للقيام بذلك ، لا تحتاج إلى القيام بالأقفال ، لأنه في أي وقت معين يتم تنفيذ goroutin واحد فقط على معالج منطقي واحد.
تحتوي بنية mcache ، في شكل ذاكرة التخزين المؤقت ، على هياكل mspan من مختلف فئات الحجم.
التفاعل بين المعالج المنطقي و mcache و mspan في Goنظرًا لأن كل معالج منطقي به mcache الخاص به ، فليست هناك حاجة للأقفال عند تخصيص الذاكرة من mcache.
يمكن تمثيل كل فئة حجم بأحد الكائنات التالية:
- كائن المسح هو كائن يحتوي على مؤشر.
- كائن noscan هو كائن لا يوجد فيه مؤشر.
تتمثل إحدى نقاط القوة في هذا الأسلوب في أنه عند إجراء تجميع البيانات المهملة ، لا تحتاج كائنات noscan إلى التحايل عليها ، لأنها لا تحتوي على كائنات مخصصة للذاكرة.
ما يحصل في mcache؟ الكائنات التي لا يزيد حجمها عن 32 كيلو بايت ، تنتقل مباشرةً إلى mcache باستخدام mspan من فئة الحجم المقابلة.
ماذا يحدث إذا لم يكن mcache يحتوي على خلية حرة؟ ثم يحصلون على mspan جديد من فئة الحجم المطلوب من قائمة كائنات mspan تسمى mcentral.
cent الهيكل المركزي
تجمع البنية المركزية جميع نطاقات الصفحات لفئة حجم معينة. يحتوي كل كائن mcentral على قائمتين من الكائنات mspan.
- قائمة كائنات mspan التي لا توجد بها كائنات مجانية ، أو تلك الكائنات الموجودة في mcache.
- قائمة الكائنات mspan التي تحتوي على كائنات مجانية.
الهيكل المركزيكل هيكل mcentral موجود داخل هيكل mheap.
هيكل ▍Mheap
يتم تمثيل بنية mheap بواسطة كائن يعالج إدارة كومة الذاكرة المؤقتة في Go. يوجد كائن عمومي واحد فقط يمتلك مساحة عنوان افتراضية.
هيكل مهيبكما ترون من الرسم البياني أعلاه ، تحتوي بنية mheap على مجموعة من الهياكل المركزية. تحتوي هذه المجموعة على هياكل مركزية لجميع فئات الحجم.
central [numSpanClasses]struct { mcentral mcentral pad [sys.CacheLineSize unsafe.Sizeof(mcentral{})%sys.CacheLineSize]byte }
نظرًا لأن لدينا هيكل مركزي لكل فئة حجم ، عندما يطلب mcache بنية mspan من mcentral ، يتم تطبيق القفل على المستوى المركزي الفردي ، ونتيجة لذلك ، يمكن تقديم طلبات من mcache أخرى تطلب هياكل mspan ذات أحجام أخرى في نفس الوقت.
يضمن المحاذاة (الوسادة) فصل الهياكل المركزية عن بعضها البعض بعدد البايتات المتوافقة مع قيمة
CacheLineSize
. نتيجة لذلك ، كل
mcentral.lock
لديه خط ذاكرة التخزين المؤقت الخاص به ، والذي يتجنب المشاكل المرتبطة بمشاركة الذاكرة الخاطئة.
ماذا يحدث إذا كانت القائمة المركزية فارغة؟ ثم يتلقى mcentral سلسلة من الصفحات من mheap لتخصيص أجزاء الذاكرة لفئة الحجم المطلوبة.
free[_MaxMHeapList]mSpanList
عبارة عن صفيف من spanList. تتكون بنية mspan في كل قائمة من 1 إلى 127 صفحة (_MaxMHeapList - 1). على سبيل المثال ، free [3] هي قائمة مرتبطة بهياكل mspan التي تحتوي على 3 صفحات. تشير كلمة "free" في هذه الحالة إلى أننا نتحدث عن قائمة فارغة لا يتم تخصيص الذاكرة فيها. يمكن أن تكون القائمة ، بدلاً من فارغة ، قائمة يتم تخصيص الذاكرة فيها (مشغول).freelarge mSpanList
هي قائمة هياكل mspan المجانية. عدد الصفحات لكل عنصر (أي ، mspan) هو أكثر من 127. لدعم هذه القائمة ، يتم استخدام بنية بيانات mtreap. تسمى قائمة هياكل mspan المزدحمة busylarge.
كائنات أكبر من 32 كيلو بايت تعتبر كائنات كبيرة ، يتم تخصيص الذاكرة لها مباشرة من mheap. يتم تنفيذ طلبات تخصيص الذاكرة لهذه الكائنات باستخدام قفل ، ونتيجة لذلك ، في وقت معين ، يمكن معالجة طلب مماثل من معالج منطقي واحد فقط.
عملية تخصيص الذاكرة للكائنات
- إذا تجاوز حجم الكائن 32 كيلو بايت ، فسيتم اعتباره كبيرًا ، ويتم تخصيص الذاكرة الخاصة به مباشرةً من mheap.
- إذا كان حجم الكائن أقل من 16 كيلو بايت ، يتم استخدام آلية mcache التي تسمى أداة التخصيص الصغيرة.
- إذا كان حجم الكائن في حدود 16-32 كيلوبايت ، فسوف يتم تحديد فئة الحجم (sizeClass) المراد استخدامها ، ثم يتم تخصيص كتلة مناسبة في mcache.
- إذا لم تكن هناك كتل متوفرة في sizeClass المطابق لـ mcache ، فسيتم استدعاء mcentral.
- إذا لم يكن لدى mcentral كتل حرة ، فإنهم يسمون mheap ويبحثون عن mspan الأكثر ملائمة. إذا تبين أن حجم الذاكرة المطلوب من قبل التطبيق أكبر مما يمكن تخصيصه ، فستتم معالجة حجم الذاكرة المطلوب بحيث يكون من الممكن إرجاع أكبر عدد ممكن من الصفحات حسب حاجة البرنامج ، مما يشكل بنية mspan جديدة.
- إذا كانت الذاكرة الافتراضية للتطبيق لا تزال غير كافية ، فسيتم الوصول إلى نظام التشغيل للحصول على مجموعة جديدة من الصفحات (مطلوب 1 ميغابايت على الأقل من الذاكرة).
في الواقع ، على مستوى نظام التشغيل ، يطلب Go تخصيص أجزاء أكبر من الذاكرة تسمى الساحات. يتيح لك التخصيص المتزامن لشظايا كبيرة من الذاكرة إيجاد حل وسط بين مقدار الذاكرة المخصصة للتطبيق والوصول المكلف إلى نظام التشغيل من حيث الأداء.
يتم تخصيص الذاكرة المطلوبة على الكومة من الساحة. النظر في هذه الآلية.
الذاكرة الظاهرية تذهب
ألقِ نظرة على استخدام الذاكرة من خلال برنامج بسيط مكتوب في Go:
func main() { for {} }
معلومات عملية البرنامجتبلغ مساحة العنوان الافتراضية حتى في مثل هذا البرنامج البسيط حوالي 100 ميجابايت ، بينما يبلغ مؤشر RSS 696 كيلو بايت فقط. أولاً ، دعونا نحاول معرفة سبب هذا التناقض.
خريطة و smap المعلوماتهنا يمكنك رؤية مناطق الذاكرة ، وحجمها يساوي تقريبًا 2 ميغابايت و 64 ميجابايت و 32 ميجابايت. أي نوع من الذاكرة هذا؟
ren أرينا
اتضح أن الذاكرة الظاهرية في Go تتكون من مجموعة من الساحات. يتوافق حجم الذاكرة الأولي المخصص للكومب مع ساحة واحدة ، أي 64 ميجابايت (هذا مناسب لـ Go 1.11.5).
حجم الساحة الحالي في النظم المختلفةنتيجة لذلك ، يتم تخصيص الذاكرة اللازمة للاحتياجات الحالية للبرنامج في أجزاء صغيرة. تبدأ هذه العملية بساحة 64 ميغابايت.
لا ينبغي أن تؤخذ تلك المؤشرات العددية التي نتحدث عنها هنا لبعض القيم المطلقة وغير المتغيرة. يمكنهم التغيير. في وقت سابق ، على سبيل المثال ، خصص Go مساحة افتراضية مستمرة مقدمًا ، على أنظمة 64 بت ، كان حجم الساحة 512 جيجابايت (من المثير للاهتمام أن نفكر فيما يحدث إذا كان الطلب على الذاكرة الحقيقية كبيرًا جدًا بحيث يتم رفض الطلب المقابل بواسطة mmap؟).
في الواقع ، ندعو مجموعة من الساحات حفنة. في Go ، تُرى الساحات على أنها أجزاء من الذاكرة ، مقسمة إلى كتل بحجم 8192 بايت (8 كيلو بايت) في الحجم.
واحد 64 ميغابايت الساحةGo لديه بضعة نكهات من الكتل - span و نقطية. يتم تخصيص الذاكرة لهم خارج كومة الذاكرة المؤقتة ، فإنها تخزن البيانات الوصفية الساحة. وهي تستخدم أساسا في جمع القمامة.
فيما يلي مخطط عام لكيفية عمل آليات تخصيص الذاكرة في Go.
مخطط عام لآليات تخصيص الذاكرة في Goالنتائج
بشكل عام ، يمكن الإشارة إلى أننا في هذه المادة وصفنا النظم الفرعية للعمل مع ذاكرة Go بعبارات عامة جدًا. تتمثل الفكرة الرئيسية للنظام الفرعي للذاكرة في Go في تخصيص الذاكرة باستخدام مختلف الهياكل وذاكرة التخزين المؤقت من مستويات مختلفة. هذا يأخذ في الاعتبار حجم الكائنات التي يتم تخصيص الذاكرة.
تمثيل كتلة واحدة من عناوين الذاكرة المستمرة المستلمة من نظام التشغيل في شكل بنية متعددة المستويات يزيد من كفاءة آلية تخصيص الذاكرة بسبب حقيقة أن هذا النهج يتجنب الحجب. إن تخصيص الموارد ، مع مراعاة حجم الكائنات التي يجب تخزينها في الذاكرة ، يقلل من التفتت ، وبعد تحرير الذاكرة ، يتيح لك تسريع عملية جمع القمامة.
أعزائي القراء! هل واجهتك مشاكل بسبب خلل في الذاكرة في البرامج المكتوبة في Go؟
