Anophel | آنوفل
149 subscribers
278 photos
294 links
آنوفل | Anophel: دنیای بی ‌پایان امکانات برای برنامه‌ نویسان

https://anophel.com

پشتیبانی :
@anophel_support
Download Telegram
💠داستان نشت حافظه در Map، مقایسه Go و Rust

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

بررسی کد
ساختار برنامه ساده بود: بارگذاری یک میلیون رکورد در حافظه، پردازش آن‌ها، حذف داده‌ها و تکرار این چرخه. پیاده‌سازی با زبان Go انجام شده بود و map اصلی به شکل زیر تعریف شده بود:
treasureChest := make(map[int][128]byte)
// پر کردن map
for i := 0; i < 1_000_000; i++ {
treasureChest[i] = [128]byte{}
}
// پردازش داده‌ها...
// پاک‌سازی map
for i := 0; i < 1_000_000; i++ {
delete(treasureChest, i)
}
runtime.GC() // اجرای دستی جمع‌آوری زباله

با وجود اجرای کامل delete روی کل map و همچنین فراخوانی مستقیم runtime.GC(), حافظه اشغال‌شده توسط برنامه به‌طور محسوسی کاهش نیافت.

💢علت رفتار Go
در Go، ساختار داخلی map به‌گونه‌ای طراحی شده که برای حفظ عملکرد، حافظه‌ی اختصاص‌یافته به bucketها را حتی پس از حذف مقادیر، بلافاصله آزاد نمی‌کند. این یعنی حتی زمانی‌که map منطقی خالی است، ساختار داده‌ای پشت آن همچنان بخش قابل‌توجهی از حافظه را نگه می‌دارد. جمع‌آورنده زباله (GC) نیز به‌دلیل باقی‌ماندن رفرنس‌های داخلی، این حافظه را آزاد نخواهد کرد — مگر در شرایط خاصی که بازسازی map یا اختصاص مجدد صورت گیرد.

📱مقایسه با Rust
برای ارزیابی رفتار متفاوت، همان سناریو در Rust با استفاده از HashMap پیاده‌سازی شد. پس از بارگذاری داده‌ها و استفاده از clear() برای حذف کامل محتوا، حافظه بلافاصله آزاد شد و مصرف به وضعیت اولیه بازگشت. در Rust، حافظه تحت مالکیت صریح داده‌هاست و زمانی که داده‌ها حذف می‌شوند، حافظه‌ی مربوط به آن‌ها نیز آزاد می‌شود؛ مگر این‌که عمداً نگه‌داری شود.

use std::collections::HashMap;

fn main() {
let mut treasure_chest: HashMap<u32, [u8; 128]> = HashMap::new();
for i in 0..1_000_000 {
treasure_chest.insert(i, [0u8; 128]);
}
// پردازش داده‌ها...
treasure_chest.clear(); // آزادسازی حافظه
}

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

نکته کلیدی: استفاده از map در زبان‌هایی مانند Go ممکن است رفتارهایی فراتر از سطح کد نشان دهد که در سناریوهای حساس به حافظه باید مورد توجه قرار گیرد. در شرایطی که کنترل دقیق بر حافظه لازم است، زبان‌هایی با مدل حافظه صریح‌تر مانند Rust انتخاب بهتری خواهند بود.

💙Anophel
Please open Telegram to view this post
VIEW IN TELEGRAM