8 نکته پرفومنسی در گولنگ (Golang)

در این مقاله با ۸ نکته کلیدی برای بهبود عملکرد برنامه‌های نوشته‌شده با زبان گولنگ آشنا می‌شویم. گولنگ به‌صورت پیش‌فرض زبانی سریع و بهینه است، اما با رعایت برخی نکات و الگوهای حرفه‌ای می‌توان سرعت اجرای برنامه، مصرف حافظه، و کارایی کلی را به سطح بالاتری رساند. این نکات برای توسعه‌دهندگانی که به ساخت سرویس‌های مقیاس‌پذیر و سریع علاقه‌مندند، بسیار کاربردی خواهند بود.


1) Use Goroutines Wisely

گوروتین‌ها نوشتن کد هم‌زمان (concurrent) را آسان می‌کنند، اما ایجاد تعداد زیادی از آن‌ها می‌تواند منجر به مشکلات عملکردی شود. هر گوروتین فضای کوچکی دارد، اما هزاران گوروتین می‌توانند مقدار قابل توجهی از حافظه را مصرف کنند:

for _, item := range items {
    go process(item)
}

استفاده از Worker Pool برای مصرف بهینه حاقظه:

const numWorkers = 10
jobs := make(chan Item, len(items))
results := make(chan Result, len(items))

for w := 1; w <= numWorkers; w++ {
    go worker(w, jobs, results)
}

for _, item := range items {
    jobs <- item
}
close(jobs)

for i := 0; i < len(items); i++ {
    result := <-results
    // Do something with result
}

با استفاده از Worker Pool ها میتوانید یک اپلیکیشن بهینه تر و کارآمد تر داشته باشید.

نکته حرفه ای: با استفاده از Buffered Channels ها از بلاک شدن گوروتین ها جلوگیری کنید.


2) Avoid Unnecessary Memory Allocations

اختصاص دادن حافظه (Memory Allocation) پرهزینه است. استفاده‌ٔ مجدد از حافظه می‌تواند منجر به بهبود عملکرد شود، مخصوصاً در حلقه‌های فشرده (tight loops). در اپلیکیشن های بزرگ این موارد خیلی میتونه تاثیر زیادی در روند یک تسک بزاره:

for i := 0; i < 1_000_000; i++ {
    data := make([]byte, 1024)
    // Use data
}

استفاده از buffers:

data := make([]byte, 1024)
for i := 0; i < 1_000_000; i++ {
    // Reset data if necessary
    // Use data
}

با استفاده از بافر ها (Buffers)، میتونید بار کاری Garbage Collector کم کنید همینطور تاخیر (Latency) هم بهبود ببخشید.

واقعا جالبه که استفاده‌ٔ دوباره از یه اسلایس ساده می‌تونه این‌قدر تأثیر قابل‌توجهی داشته باشه.


3) Use Profiler to Find Bottlenecks

از pprof استفاده کن تا بخش‌های کند کدت رو شناسایی کنی. با استفاده از این ابزار که داخل خوده گولنگ هست بخش های کند کدت رو شناسایی میکنه و میتونه پرفومنس بهتری رقم بزنی:

import (
    "net/http"
    _ "net/http/pprof"
)

func main() {
    go func() {
       log.Println(http.ListenAndServe("localhost:6060", nil))
    }()
    // Your code here
}

برای پروفایل گیری از دستور زیر توی ترمینال استفاده کنید:

go tool pprof http://localhost:6060/debug/pprof/profile

نکته: ممکنه در نگاه اول به کد بگید که معلومه پرفومنس خوبی داره و کدم اوکیه، ولی به جای فکر کردن با پرفایل گیری کار رو دربیار مشتی :)



4) Minimize Garbage Collection Impact

Garbage Collector در Go ممکنه باعث توقف موقت برنامه‌ات بشه. کاهش تعداد اختصاص‌های حافظه می‌تونه بار اضافه‌ی GC رو به حداقل برسونه:

برای استفاده مجدد از آبجکت، از sync.Pool استفاده کن:

var bufPool = sync.Pool{
    New: func() interface{} {
       return new(bytes.Buffer)
    },
}

buf := bufPool.Get().(*bytes.Buffer)
buf.Reset()
// Use buf
bufPool.Put(buf)


5) Optimize String Operations

رشته‌ها (Strings) در گولنگ، اسلایس‌های بایت غیرقابل تغییر (immutable) هستند. زمان استفاده آنها در حلقه ها باید مواظب بود و گرنه پرفومنس داستان دار میشه:

var result string
for _, s := range strings {
    result += s
}

از ماژول strings خوده گولنگ استفاده کنید:

var builder strings.Builder
for _, s := range strings {
    builder.WriteString(s)
}
result := builder.String()

این روش از اختصاص و کپی‌های غیرضروری جلوگیری می‌کنه. با استفاده از این کار پرفومنس خیلی خوبی نسبت به قبل دریافت میکنید.


6) Use Appropriate Data Structures

انتخاب ساختار داده مناسب می‌تونه تأثیر زیادی داشته باشه. برای جستجوهای سریع، از map استفاده کن:

itemsMap := make(map[string]Item)
for _, item := range items {
    itemsMap[item.ID] = item
}

if item, exists := itemsMap["desired_id"]; exists {
    // Use item
}


7) Limit Mutex Contention

وقتی برای هم‌زمان‌سازی از mutex استفاده می‌کنی، رقابت زیاد (high contention) می‌تونه باعث کاهش عملکرد بشه:

var mu sync.Mutex
var counter int

func increment() {
    mu.Lock()
    defer mu.Unlock()
    counter++
}

به جای این کار از atomic operation ها استفاده کن:

var counter int64

func increment() {
    atomic.AddInt64(&counter, 1)
}

استفاده از atomic operations می‌تونه توی برنامه‌هایی با هم‌زمانی بالا، عملکرد رو به‌طرز چشم‌گیری بهتر کنه.


8) Use copy() function

در مواقعی که نیاز به کپی کردن یک slice داری، استفاده از تابع داخلی copy می‌تونه خیلی سریع‌تر از حلقه دستی باشه — هم به لحاظ عملکرد و هم خوانایی کد:

dst := make([]int, len(src))
for i := 0; i < len(src); i++ {
    dst[i] = src[i]
}

استفاده از copy:

dst := make([]int, len(src))
copy(dst, src)

تابع copy() در سطح پایین‌تر به زبان اسمبلی پیاده‌سازی شده و بسیار بهینه است.


2 🔥
0 🎉
0 😮
0 👍
0 💜
0 👏
میلاد خسروی
نویسنده کد نیوز

برنامه نویس فان | Fun Developer یک آدم ساده که عاشق برنامه نویسی و کد زدنه :) تلاش میکنه تا به بقیه کمک کنه. توسعه دهنده هسته لاراول و فضای اوپن سورس. فاندر پرانتز و کد نیوز.

0+ نظر

برای ثبت نظر ابتدا ورود کنید.

0 نظر

    اولین نفر باش که نظر ثبت میکنی :) یعنی یه کامنت به ما نمیرسه 😁