الذاكرة وجامع القمامة في Go: كيف يدير GC الموارد

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

الذاكرة وجامع القمامة في Go: كيف لا تصاب بالجنون من GC

\n

هل تساءلت يومًا أين تذهب الذاكرة في تطبيق Go الخاص بك؟ خاصة عندما يبدأ في استهلاك الجيجابايتات وأنت فقط تقوم بتحليل JSON؟ مألوف؟ مرحبًا بك في عالم جامع القمامة (GC) في Go — الآلية الغامضة التي إما تنقذ مشروعك أو تحوله إلى منصة بطيئة. اليوم سنشرح كيف تعمل الذاكرة في Go، وكيف يقرر GC متى يجب التنظيف، وكيف لا تقع في فخ "انتظار STW".

\n

إذا كنت تكتب بلغة Go لبضعة أشهر على الأقل، فمن المحتمل أنك واجهت هذا: توقفات مفاجئة، زيادة في استهلاك الذاكرة، gc pause غامضة في ملفات التعريف. دعنا ننظر تحت الغطاء ونفهم كيفية إدارة الذاكرة في Go بوعي.

\n\n

كيف يتم تنظيم الذاكرة في Go: الكومة والمكدس

\n

قبل الحديث عن جامع القمامة، دعنا نفهم أين تعيش البيانات في Go. على عكس لغات مثل C حيث تتحمل أنت مسؤولية malloc/free، يستخدم Go إدارة ذاكرة تلقائية. لكن هذا لا يعني أن كل شيء يُلقى في كومة واحدة.

\n

في Go هناك منطقتان رئيسيتان للذاكرة:

\n
    \n
  • المكدس (stack) — ذاكرة سريعة تُدار تلقائيًا للمتغيرات المحلية. كل goroutine لها مكدس خاص بها (يبدأ من 2 كيلوبايت وينمو ديناميكيًا).
  • \n
  • الكومة (heap) — ذاكرة أبطأ ولكنها مرنة للبيانات التي "تعيش" بعد استدعاء الدالة.
  • \n
\n

السؤال الرئيسي: ما الذي يذهب إلى الكومة وما الذي يبقى على المكدس؟ المسؤول عن ذلك هو تحليل الهروب (escape analysis) — آلية ذكية في مترجم Go. يحلل ما إذا كان المتغير "يهرب" خارج نطاق الدالة. إذا كان الأمر كذلك، فإنه يذهب إلى الكومة. إذا لم يكن كذلك، يبقى على المكدس، ولا يعلم GC عنه حتى.

\n

مثال:

\n
package main\n\nfunc createPointer() *int {\n    x := 42\n    return &x // x "يهرب" — يذهب إلى الكومة\n}\n\nfunc noEscape() int {\n    x := 42\n    return x // x يبقى على المكدس\n}\n
\n

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

\n\n

جامع القمامة في Go: كيف يعمل

\n

الآن عن البطل الرئيسي — جامع القمامة (GC). في Go يُستخدم جامع متوازي، ثلاثي الألوان، غير تزايدي مع خوارزمية "mark-and-sweep". يبدو مخيفًا؟ في الواقع كل شيء منطقي.

\n

يمر GC في Go بثلاث مراحل:

\n
    \n
  1. إعداد العلامات (Mark Setup - STW) — توقف قصير لتفعيل حاجز الكتابة. عادةً ما يستغرق 10-30 ميكروثانية.
  2. \n
  3. العلامات (Marking - Concurrent) — العمل الرئيسي: يتجول GC في الرسم البياني للكائنات، ويضع علامات على الكائنات الحية. يتم تنفيذه بالتوازي مع كودك.
  4. \n
  5. المسح (Sweep - Concurrent + STW) — تنظيف الكائنات الميتة. يتم جزئيًا بالتوازي، لكن المزامنة النهائية قد تسبب توقفًا قصيرًا.
  6. \n
\n

الميزة الرئيسية لـ GC في Go هي أنه لا يقوم بإزالة التجزؤ. هذا يعني أنه بعد التجميع قد تتجزأ الذاكرة، لكن Go يستخدم خرائط بتات لتتبع الكتل الحرة، مما يجعل تخصيص الذاكرة سريعًا.

\n

يمكن رؤية مثال على عمل GC في السجلات:

\n
GODEBUG=gctrace=1 go run main.go\n\ngc 1 @0.013s 4%: 0.031+0.42+0.017 ms clock, 0.25+0.10/0.42/0.0+0.13 ms cpu, 4->4->2 MB, 5 MB goal, 8 P\n
\n

هنا: 0.031+0.42+0.017 ms — هذا هو STW + Mark + STW. 4->4->2 MB — حجم الكومة قبل، بعد العلامات وبعد المسح.

\n\n

لماذا يبطئ GC: STW والتوقفات

\n

العدو الرئيسي للأداء هو STW (Stop The World). على الرغم من أن Go يحاول تقليل التوقفات، إلا أنها لا تزال تحدث. خاصة إذا:

\n
    \n
  • لديك العديد من المؤشرات (يجب على GC المرور على جميع الكائنات)
  • \n
  • الكومة كبيرة (كلما زادت الكائنات، زاد وقت العلامات)
  • \n
  • تقوم بإنشاء الكثير من القمامة (معدل تخصيص مرتفع)
  • \n

المدونات

توصيات الكتب