گولنگ یا Go یک زبان برنامه نویسی تایپ شده و کامپایل شده است. در میان بسیاری از ویژگیهای آن، مکانیسم جمعآوری زباله یا همان Garbage Collection در Go به عنوان یک جزء حیاتی برای مدیریت حافظه برجسته است. در این مقاله از آنوفل، به نحوه عملکرد Garbage Collection در Go، تأثیر آن بر عملکرد و بهترین شیوه ها برای توسعه دهندگان و به بررسی تکنیک های مدیریت حافظه و جمع آوری زباله های مورد استفاده توسط زبان برنامه نویسی Go می پردازیم..
مدیریت حافظه در دنیای شلوغ برنامه نویسی اغلب شبیه مدیریت یک رستوران شلوغ و همیشه باز است. تقاضای ثابت برای خدمات موثر و نیاز به عملکرد بهینه توسط هر غذاخوری یا متغیری که به میز یا فضای حافظه خود نیاز دارد تکرار می شود. زبان Go خود را به عنوان یک سرآشپز برتر در این محیط پویا متمایز کرده است که به دلیل سهولت استفاده، اثربخشی و پشتیبانی قوی از برنامه نویسی همزمان مشهور است.
با این حال، Go چگونه از حافظه رستوران شلوغ را اداره می کند؟ چگونه مطمئن می شود که هر غذاخوری سر وقت بنشیند، به خوبی از آن مراقبت شود، و هنوز هیچ میزی توسط مشتری که مدت ها پیش رفته است اشغال نشده باشد؟
مدیریت حافظه در Go
1.تخصیص حافظه: رستورانی را تصور کنید که پر از میز (حافظه) و مشتریان (متغیرها) است تا به شما در درک بهتر این فرآیند کمک کند. یک جدول (آدرس حافظه) به بازدید کننده (متغیر) در هنگام ورود توسط میزبان (کامپایلر) داده می شود. گزینه های مدیریت حافظه Go عبارتند از:
1.1 - تخصیص استک: تخصیص و توزیع سریع، ایده آل برای متغیرهای کوتاه مدت. مشابه مشتریانی که فقط برای مدت کوتاهی در رستوران می مانند.
در اینجا، x
یک متغیر محلی است که در استک تخصیص داده شده است، که با بازگشت تابع، به طور خودکار تخصیص داده می شود.
func stackAlloc() int {
x := 42 // x is allocated on the stack
return x
}
1.2- تخصیص هیپ (Heap): تخصیص و توزیع طولانی تر، اما کندتر. مناسب برای متغیرهای با عمر طولانی یا چیزای بزرگ. قابل مقایسه با مشتریانی که برای مدت طولانی در رستوران می مانند.
type myStruct struct {
data []int
}
func heapAlloc() *myStruct {
obj := &myStruct{data: make([]int, 100)} // obj is allocated on the heap
return obj
}
obj
روی Heap تخصیص داده می شود زیرا از محدوده آن فرار می کند، زیرا پس از بازگشت تابع همچنان قابل دسترسی است.
2. تجزیه و تحلیل فرار (Escape): تجزیه و تحلیل Escape انجام شده توسط کامپایلر Go تعیین می کند که آیا یک متغیر باید روی استک یا Heap تخصیص داده شود. اگر متغیری از محدوده خود خارج شود، یا اگر بتوان پس از تکمیل عملکرد به آن دسترسی پیدا کرد، روی Heap تخصیص داده می شود. در رستوران فرضی ما، این مشابه مشتریانی است که ترجیح میدهند مدت طولانیتری بمانند و به ترتیب صندلیهای پایدارتر نیاز دارند.
مثال:
package main
import "fmt"
// This function returns an integer pointer.
// The integer i is created within the function scope,
// but because we're returning the address of i, it "escapes" from the function.
// The Go compiler will decide to put this on the heap.
func escapeAnalysis() *int {
i := 10 // i is initially created here, within the function's scope
return &i // The address of i is returned here, which means it "escapes" from the function
}
// This function also returns an integer, but the integer does not escape
// This integer will be stored on the stack as it doesn't need to be accessed outside the function.
func noEscapeAnalysis() int {
j := 20 // j is created here, within the function's scope
return j // The value of j is returned here, but it doesn't escape from the function
}
func main() {
// Call both functions and print the results
fmt.Println(*escapeAnalysis()) // Output: 10
fmt.Println(noEscapeAnalysis()) // Output: 20
}
در تابع ()escapeAnalysis
، متغیر escape i
می کند زیرا آدرس آن توسط تابع برگردانده می شود. این بدان معناست که متغیر i
حتی پس از اتمام اجرای تابع باید در دسترس باشد. بنابراین، روی Heap ذخیره می شود.
در مقابل، در تابع ()noEscapeAnalysis
متغیر escape j
نمی کند زیرا فقط مقدار آن برگردانده می شود. بنابراین، پس از اتمام عملکرد، می توان آن را با خیال راحت دور انداخت و در استک ذخیره می شود.
کامپایلر Go به طور خودکار تجزیه و تحلیل escape را انجام می دهد، بنابراین شما نیازی به مدیریت صریح تخصیص استک و Heap ندارید. این امر مدیریت حافظه را ساده می کند و به جلوگیری از نشت حافظه و سایر خطاها کمک می کند.
3. تکنیک های مدیریت حافظه: Go از چندین تکنیک مدیریت حافظه استفاده می کند، مانند:
3.1 - Value Semantics: بله Go ترجیح میدهد متغیرها را به جای ارجاع، بر اساس مقدار ارسال کند، به این معنی که از value semantics استفاده میکند. مدیریت حافظه با این روش آسان تر شده و احتمال نشت حافظه کمتر است. این قابل مقایسه با دادن میز به هر مشتری در یک رستوران است که احتمال سوء تفاهم را کاهش می دهد.
package main
import "fmt"
// incrementByValue takes an integer as a parameter and increments it.
// Since Go uses value semantics by default, the function receives a copy of the original value.
// Changing the value of i inside this function does not affect the original value.
func incrementByValue(i int) {
i++ // increment i
fmt.Println("Inside incrementByValue, i =", i)
}
// incrementByReference takes a pointer to an integer as a parameter and increments the integer.
// In this case, the function is dealing with a reference to the original value,
// so changing the value of *p will affect the original value.
func incrementByReference(p *int) {
(*p)++ // increment the value that p points to
fmt.Println("Inside incrementByReference, *p =", *p)
}
func main() {
var x int = 10
fmt.Println("Before incrementByValue, x =", x) // Output: Before incrementByValue, x = 10
incrementByValue(x)
fmt.Println("After incrementByValue, x =", x) // Output: After incrementByValue, x = 10
var y int = 10
fmt.Println("\nBefore incrementByReference, y =", y) // Output: Before incrementByReference, y = 10
incrementByReference(&y)
fmt.Println("After incrementByReference, y =", y) // Output: After incrementByReference, y = 11
}
در تابع incrementByValue
، متغیر i
یک کپی از آرگومان ارسال شده است، بنابراین وقتی i
افزایش مییابد، روی مقدار اصلی تأثیر نمیگذارد. این به عنوان مقدار عبور شناخته می شود و پیش فرض در Go است.
از طرف دیگر، در تابع incrementByReference
، متغیر p
یک اشاره گر به آرگومان اصلی است، بنابراین وقتی مقدار p به آن اشاره می کند، مقدار اصلی را تغییر می دهد. این به عنوان عبور از طریق مرجع (passing by reference) شناخته می شود.
به طور کلی، Go ترجیح می دهد از value semantics (گذر از مقدار) استفاده کند زیرا مدیریت حافظه را ساده می کند و خطر عوارض جانبی غیرمنتظره را به حداقل می رساند. با این حال، Go همچنین در صورت لزوم از semantic مرجع (گذر با مرجع) پشتیبانی می کند.
3.2 - Sliceها و Mapها : گو استفاده از اسلایس ها و مپ ها را به جای آرایه ها و pointerها تبلیغ می کند زیرا مدیریت حافظه را بهبود می بخشد. این امکان استفاده مؤثرتر از منابع را فراهم میکند، شبیه به رستورانی که بوفه (اسلایس ها/مپ ها) را به جای (آرایهها/pointerها) ارائه میدهد.
package main
import (
"fmt"
)
func main() {
// SLICES
// Creating a slice with initial values
slice := []string{"Table1", "Table2", "Table3"}
fmt.Println("Initial slice:", slice) // Output: Initial slice: [Table1 Table2 Table3]
// Adding an element to the slice (like adding a table in the restaurant)
slice = append(slice, "Table4")
fmt.Println("Slice after append:", slice) // Output: Slice after append: [Table1 Table2 Table3 Table4]
// Removing the first element from the slice (like freeing up the first table in the restaurant)
slice = slice[1:]
fmt.Println("Slice after removing first element:", slice) // Output: Slice after removing
first element: [Table2 Table3 Table4]
// MAPS
// Creating a map to represent tables in the restaurant and their status
tables := map[string]string{
"Table1": "occupied",
"Table2": "free",
"Table3": "free",
}
fmt.Println("\nInitial map:", tables) // Output: Initial map: map[Table1:occupied Table2:free Table3:free]
// Adding an entry to the map (like adding a table in the restaurant)
tables["Table4"] = "free"
fmt.Println("Map after adding a table:", tables) // Output: Map after adding a table:
map[Table1:occupied Table2:free Table3:free Table4:free]
// Changing an entry in the map (like changing the status of a table in the restaurant)
tables["Table2"] = "occupied"
fmt.Println("Map after changing status of Table2:", tables)
// Output: Map after changing status of Table2: map[Table1:occupied
Table2:occupied Table3:free Table4:free]
// Removing an entry from the map (like removing a table from the restaurant)
delete(tables, "Table1")
fmt.Println("Map after removing Table1:", tables) // Output: Map after removing Table1:
map[Table2:occupied Table3:free Table4:free]
}
برای مدیریت لیستی از میزهای یک رستوران، از اسلایس ها استفاده می کنیم. استفاده از تابع append
و دستکاری اسلایس تمام چیزی است که برای افزودن و حذف جداول لازم است.
یک مپ نیز برای پیگیری وضعیت هر جدول استفاده می شود. استفاده از عملیات مپ افزودن، حذف و به روز رسانی وضعیت جدول را ساده می کند.
این نشان دهنده مزایای اسلایس ها و مپ ها نسبت به آرایه ها و pointerها است. آنها ساختارهای داده قابل تطبیق و پویا را با توابع داخلی برای کارهای معمولی ارائه می دهند که می توانند در صورت نیاز گسترش یابند. برنامه نویسی نیز کاربردی تر است و در نتیجه مدیریت حافظه موثرتر است.
زباله جمع آوری (Garbage Collection) در Go چیست؟
جمع آوری زباله (GC) نوعی مدیریت خودکار حافظه است. جمعآورنده زباله تلاش میکند تا حافظه اشغال شده توسط اشیایی که دیگر توسط برنامه استفاده نمیشوند را بازیابی کند. این برای جلوگیری از نشت حافظه، که زمانی رخ میدهد که برنامهها نتوانند حافظهای را که دیگر مورد نیاز نیست، آزاد کنند، بسیار مهم است.
چگونه Garbage Collection در Go کار می کند؟
جمعآوری زباله در Go یک جمعآورنده concurrent و tri-color و mark-and-sweep است. این ممکن است پیچیده به نظر برسد، اما پس از تجزیه، نسبتاً ساده است:
Concurrent: در GC موازی با سایر گوروتین ها بدون نیاز به توقف کل برنامه اجرا می شود.
Tri-color Marking: این الگوریتم اشیاء را به سه رنگ سفید، خاکستری و سیاه دسته بندی می کند. در ابتدا، تمام اشیاء با رنگ سفید مشخص می شوند. اشیایی که در دسترس هستند (یعنی در حال استفاده) به رنگ خاکستری و سپس سیاه مشخص می شوند. اشیاء غیر قابل دسترس سفید باقی می مانند و برای جمع آوری در نظر گرفته می شوند.
Mark-and-Sweep: در GC اشیاء قابل دسترسی را علامتگذاری میکند و سپس آنهایی را که بدون علامت باقی میمانند، جارو میکند یا جمعآوری میکند.
عمق جمع آوری زباله در Go
1.جمع آوری زباله Generational: جمع آوری زباله در Go، اشیاء را بر اساس طول عمرشان به نسل ها تقسیم می کند. تولید یک شی که از جمع آوری زباله زنده می ماند، پیشرفته است. این قابل مقایسه با رستورانی است که مشتریان را به عنوان بازگشته یا جدید دسته بندی می کند و به آن امکان می دهد منابع را به طور مؤثرتری تخصیص دهد.
2.Concurrent Mark and Sweep (CMS): الگوریتمی به نام علامت گذاری و جارو همزمان توسط جمع کننده زباله Go استفاده می شود. فاز "مارک" اشیایی را که دور از دسترس هستند شناسایی می کند و فاز "جارو کردن" حافظه ای را که این اشیاء استفاده می کردند آزاد می کند. این قابل مقایسه با کارکنان انتظار است که میزهایی را برای مشتریان جدید تمیز می کنند در حالی که به طور مداوم به دنبال جداول خالی برای علامت گذاری و جارو هستند.
در Go، فرآیند علامت گذاری و جابجایی همزمان (CMS) در سه مرحله اصلی کار می کند:
الف- مرحله علامت گذاری: این مرحله تمام اشیاء قابل دسترسی را شناسایی می کند. با شروع از ریشه ها، که متغیرهای سراسری و متغیرهای محلی در استک هستند، جمع کننده زباله همه اشیاء قابل دسترسی را ردیابی می کند و آنها را به عنوان زنده علامت گذاری می کند.
ب- مرحله جارو کردن: این مرحله بعد از مرحله علامت گذاری می آید. در اینجا، جمع کننده زباله، استک را اسکن می کند و حافظه اشیایی را که در مرحله علامت گذاری به عنوان زنده علامت گذاری نشده اند، آزاد می کند.
ج- فاز مکث: بین فاز علامت گذاری و جارو کردن، مکث کوتاهی وجود دارد. این تنها زمانی است که جمع کننده زباله باید روتین را متوقف کند، یعنی اجرای روتین های Go را متوقف کند.
3. علامت گذاری سه رنگ (Tri-color): در Go از یک الگوریتم علامت گذاری Tri-color برای جلوگیری از توقف برنامه در حین جمع آوری زباله استفاده می کند. سفید (بدون علامت)، خاکستری (علامت گذاری شده اما با ارجاعات ناشناخته)، و سیاه (علامت گذاری شده و کاوش شده) سه رنگ مورد استفاده در این فرآیند هستند. میزهای سفید در رستوران خالی هستند، میزهای خاکستری غذاخوری مینشینند و میزهای مشکی اشغال شدهاند، اما نیازی به توجه بیشتری ندارند.
بهبود کد Go برای کارایی و عملکرد حافظه
اجتناب از متغیرهای گلوبال: استفاده از متغیرهای سراسری (گلوبال) را کاهش دهید زیرا باعث نشت حافظه می شوند زیرا در کل عمر برنامه دوام می آورند. این معادل کنار گذاشتن یک میز به طور نامحدود برای یک غذاخوری است که فقط گهگاه از رستوران فرضی ما دیدن می کند.
// A global variable
var global *Type
func badFunc() {
var local Type
global = &local
}
func main() {
badFunc()
// Now `global` holds a pointer to `local`, which is out of scope and
// should have been garbage collected. This is a memory leak.
}
badFunc
یک متغیر محلی ایجاد می کند و سپس آدرس آن را به متغیر گلوبال global
اختصاص می دهد. پس از بازگشت badFunc
، محلی باید خارج از محدوده باشد و حافظه آن باید آزاد شود. با این حال، از آنجا که global
هنوز آدرس خود را نگه داشته است، حافظه نمی تواند آزاد شود و باعث نشت حافظه می شود.
راه حل این است که از چنین استفاده غیر ضروری از متغیرهای سراسری اجتناب کنید. اگر نیاز به اشتراک گذاری داده ها بین بخش های مختلف برنامه خود دارید، به جای آن از پارامترهای تابع، مقادیر برگردانده یا فیلدهای struct استفاده کنید. در اینجا نحوه اصلاح کد بالا آمده است:
type MyStruct struct {
field Type
}
func goodFunc(s *MyStruct) {
var local Type
s.field = local
}
func main() {
var s MyStruct
goodFunc(&s)
// Now `s.field` holds the value of `local`, which was copied.
// There is no memory leak because `local`'s memory can be safely released after `goodFunc` returns.
}
goodFunc
یک اشاره گر به MyStruct
می گیرد و مقدار local
را به فیلد آن اختصاص می دهد. به این ترتیب، حافظه محلی می تواند پس از بازگشت GoodFunc
با خیال راحت آزاد شود و از نشت حافظه جلوگیری شود.
2. استفاده عاقلانه از اشاره گرها: هنگام کار با ساختارهای داده بزرگ، استفاده از اشاره گرها می تواند به صرفه جویی در حافظه کمک کند. با این حال، باید مراقب باشید که ارجاعات غیرمنطقی ایجاد نکنید که باعث نشت حافظه یا اختلال در جمع آوری زباله شود. این قابل مقایسه با نشستن مشتریان کنار هم بر روی میزهای رستوران برای به حداکثر رساندن کارایی و در عین حال به حداقل رساندن ازدحام یا سردرگمی است.
type BigStruct struct {
data [1 << 20]int
}
func newBigStruct() *BigStruct {
var bs BigStruct
return &bs
}
func main() {
bs := newBigStruct()
fmt.Println(bs.data[0])
}
newBigStruct
یک BigStruct
در استک ایجاد می کند و یک اشاره گر به آن برمی گرداند. با این حال، به محض بازگشت newBigStruct
آن وقتbs
از محدوده خارج می شود و حافظه آن باید آزاد شود، که باعث می شود اشاره گر برگشتی توسط newBigStruct
نامعتبر باشد.
راه صحیح برای انجام این کار، تخصیص BigStruct
روی heap با استفاده از تابع جدید است، که تا زمانی که ارجاعاتی به آن وجود دارد، آن را زنده نگه می دارد:
func newBigStruct() *BigStruct {
bs := new(BigStruct)
return bs
}
func main() {
bs := newBigStruct()
fmt.Println(bs.data[0])
// When we're done with bs, it's a good idea to set it to nil to avoid unnecessary memory holding.
bs = nil
}
در این کد اصلاح شده، newBigStruct
یک BigStruct
را روی heap اختصاص می دهد، بنابراین تا زمانی که هیچ مرجع دیگری به آن وجود نداشته باشد، حافظه آن آزاد نخواهد شد. در main
، ما یک اشاره گر به یک BigStruct
از newBigStruct
دریافت می کنیم، از آن استفاده می کنیم و بعد از اتمام کار آن را روی nil
قرار می دهیم تا اجازه جمع آوری زباله در حافظه را بدهیم. این یک استفاده عاقلانه از اشاره گرها است، زیرا به ما این امکان را می دهد که بدون ایجاد نشت حافظه، با ساختارهای داده بزرگ کار کنیم.
3. Pool Resources: استفاده از نوع sync.Pool
را برای عملیات فشرده حافظه برای استفاده مجدد از اشیاء به جای تخصیص موارد جدید در نظر بگیرید. این کار با کاهش هزینه های جمع آوری زباله باعث حفظ حافظه می شود. در یک رستوران، این را می توان با استفاده مجدد از تنظیمات میز برای مشتریان جدید به جای تنظیم همیشه موارد جدید مقایسه کرد.
package main
import (
"fmt"
"sync"
"time"
)
// We'll be pooling these ExpensiveResource types.
type ExpensiveResource struct {
id int
}
func main() {
// Create a pool of ExpensiveResource objects.
var pool = &sync.Pool{
New: func() interface{} {
fmt.Println("Creating new resource")
return &ExpensiveResource{id: time.Now().Nanosecond()}
},
}
// Allocate a new ExpensiveResource and put it in the pool.
resource := pool.Get().(*ExpensiveResource)
pool.Put(resource)
// When we need to use the resource, get it from the pool.
resource2 := pool.Get().(*ExpensiveResource)
fmt.Println("Resource ID:", resource2.id)
pool.Put(resource2)
}
ما یک sync.Pool
از اشیاء ExpensiveResource
ایجاد می کنیم. ما یک تابع New
برای pool
تعریف می کنیم تا وقتی pool
خالی است یک ExpensiveResource
جدید ایجاد کند.
سپس، از ()pool.Get
برای دریافت ExpensiveResource
از Pool
استفاده می کنیم. اگر Pool
خالی باشد، تابع New
ما را برای ایجاد آن فراخوانی می کند. ما از منبع استفاده می کنیم و بعد از اتمام کار، آن را در Pool
با pool.Put(resource)
قرار می دهیم.
به این ترتیب، میتوانیم از اشیاء ExpensiveResource
بهجای تخصیص موارد جدید در هر زمانی که به یکی از آنها نیاز داریم، دوباره استفاده کنیم، حافظه را ذخیره کنیم و هزینههای جمعآوری زباله را کاهش دهیم. در قیاس رستوران، این مانند استفاده مجدد از تنظیمات میز برای مشتریان جدید به جای تنظیم همیشه موارد جدید است.
برای آشنایی با امنیت در داکر و 14 روش امنیتی در آن این مقاله را بررسی کنید.
برای آشنایی با 10 دیزاین پترن میکروسرویس که باید آن را رعایت کنید این مقاله را بررسی کنید.
برای آشنایی با معماری میکروسرویس این مقاله را بررسی کنید.
4. محدود کردن دامنه متغیرها: منابع را زمانی که دیگر مورد نیاز نیستند آزاد کنید و محدوده متغیرها را تا حد امکان کوچک نگه دارید. در نتیجه مدیریت حافظه موثرتر است و جمع آوری زباله سریعتر امکان پذیر می شود. این معادل پاک کردن سریع میزها پس از خروج مشتریان از رستوران ما است.
package main
import (
"fmt"
)
func main() {
// This variable has the whole function scope
wholeFunctionScope := "I'm available in the whole function"
fmt.Println(wholeFunctionScope)
{
// This variable has only limited scope
limitedScope := "I'm available only in this block"
fmt.Println(limitedScope)
// Releasing the resource manually (just for the sake of this example)
limitedScope = ""
}
// This will cause a compilation error, as limitedScope is not available here
// fmt.Println(limitedScope)
}
wholeFunctionScope
دامنه کل تابع را دارد، در حالی که limitedScope
فقط در بلوک کدی که در آن تعریف شده است وجود دارد. با محدود کردن دامنه محدود، اطمینان حاصل می کنیم که حافظه ای که استفاده می کند می تواند به محض اتمام کار آزاد شود، که در این مورد در انتهای بلوک است.
این عمل شبیه به پاک کردن سریع جداول پس از خروج مشتریان از رستوران، آزاد کردن منابع (فضای میز در رستوران، حافظه در برنامه ما) برای مشتریان جدید (متغیرهای جدید) است.
5. بهینه سازی ساختارهای داده: ساختارهای داده مناسب را انتخاب کنید و نیازهای حافظه آنها را در نظر بگیرید. از اسلایس ها و مپ ها به عنوان مثال به جای آرایه ها و اشاره گرها استفاده کنید. این کار جمع آوری زباله را تسهیل می کند و تخصیص حافظه را بهینه می کند. این معادل انتخاب عملی ترین چیدمان صندلی در یک رستوران است.
6. Profile و معیار: برای شناسایی تنگناهای حافظه و بهینه سازی عملکرد، به طور منظم کد Go خود را Profile و benchmark کنید. ابزارهایی مانند pprof
و benchmem
میتوانند به تجزیه و تحلیل استفاده از حافظه و یافتن زمینههایی برای بهبود کمک کنند. این قابل مقایسه با مدیر رستورانی است که جریان مشتری را برای بهینه سازی عملیات مشاهده و تجزیه و تحلیل می کند.
بهترین روش ها برای توسعه دهندگان در Go Garbage Collection
به حداقل رساندن تخصیص ها: کاهش تعداد تخصیص ها می تواند بار کاری GC را کاهش دهد. ادغام و استفاده مجدد از اشیا می تواند مفید باشد.
استفاده از اشاره گر را درک کنید: اشاره گرهای غیر ضروری می توانند اشیاء را بیشتر از زمان مورد نیاز زنده نگه دارند. درک چگونگی تأثیر اشاره گرها بر طول عمر شی می تواند به نوشتن کد Go کارآمد کمک کند.
مانیتور GC Metrics: در Go معیارهای مختلفی را برای نظارت بر جمع آوری زباله ارائه می دهد، مانند runtime.ReadMemStats
. توجه به این موارد می تواند به شناسایی مشکلات مربوط به عملکرد GC کمک کند.
از پارامترهای تنظیم GC استفاده کنید: Go چندین پارامتر تنظیم GC مانند GOGC را ارائه می دهد. تنظیم این موارد می تواند به بهینه سازی رفتار GC برای برنامه های خاص کمک کند.
نوشتن تستهای معیار: تستهای معیار میتوانند به شناسایی گلوگاههای عملکرد مربوط به جمعآوری زباله کمک کنند.
نتیجه
در نتیجه، نوشتن برنامههای Go کارآمد و مؤثر نیاز به درک کاملی از مدیریت حافظه و Garbage Collection دارد. کد Go ما باید به گونه ای نوشته شود که حافظه را به طور هوشمندانه تخصیص دهد، دامنه متغیرها را محدود نگه دارد، از ساختارهایی مانند اسلایس ها و مپ هایی استفاده کند که Garbage Collection را تسهیل می کند، و از دام هایی مانند متغیرهای غیر ضروری گلوبال یا ارجاعات اشاره گر غیرمنطقی جلوگیری کند. این شبیه به این است که چگونه یک رستوران با مدیریت خوب، چیدمان صندلیها را بهینه میکند و با پشتکار میزها را برای مشتریان جدید پاک میکند.
Garbage Collection Go یک متحد قوی است، اما چوبدستی جادویی نیست. برای عملکرد صحیح به کمک ما نیاز دارد، جایی که شیوه های برنامه نویسی خوب وارد می شوند.
مدیریت حافظه در Go در نهایت شبیه یک رقص بین برنامه نویس و Garbage Collection است. یک برنامه خیره کننده و با کارایی بالا که از منابع سیستم حداکثر استفاده را می کند، نتیجه زمانی است که هر دو شریک از مسئولیت های خود آگاه باشند و به طور هماهنگ با هم کار کنند.