اگر بخواهیم یک لیستی از داده ها را نگهداری کنیم، اولین راهی که به ذهنمان میرسد List یا Array است. در این مطلب میخواهیم یک نکته در مورد فضای اشغال شده توسط List را بررسی کنیم. کلاس List از یک آرایه داخلی برای نگهداری عناصر استفاده میکند. زمانی که یک List را نمونه سازی میکنید، اگر سازنده پیشفرض آن استفاده کنید، در اولین عملیات اضافه کردن عنصر، آرایه داخلی کلاس List با 4 عنصر ایجاد میشود و هر زمانی که یک عنصر جدید به List اضافه میکنید، ابتدا بررسی میکند که ظرفیت آرایه داخلی به حداکثر ظرفیت خود رسیده است یا نه؟ اگر به حداکثر مقدار رسیده باشد، یک آرایه جدید با طول دو برابر آرایه قبلی ایجاد میکند و تمامی عناصر آرایه قبلی را درون آرایه جدید کپی میکند و سپس عنصر جدید را به آرایه اضافه میکند. در کد زیر وضعیت آرایه داخلی کلاس List را نشان میدهد:
var list = new List<char>(); // [] Count: 0 Capacity: 0
list.Add('h'); // ['h', null, null, null] Count: 1 Capacity: 4
list.Add('e'); // ['h', 'e', null, null] Count: 2 Capacity: 4
list.Add('l'); // ['h', 'e', 'l', null] Count: 3 Capacity: 4
list.Add('l'); // ['h', 'e', 'l', 'l'] Count: 4 Capacity: 4
list.Add('o'); // ['h', 'e', 'l', 'l', 'o' , null, null, null] Count:5 Capacity: 8
list.Add(' '); // ['h', 'e', 'l', 'l', 'o' , ' ', null, null] Count:6 Capacity: 8
list.Add('w'); // ['h', 'e', 'l', 'l', 'o' , ' ', 'w', null] Count:7 Capacity: 8
list.Add('o'); // ['h', 'e', 'l', 'l', 'o' , ' ', 'w', 'o'] Count:8 Capacity: 8
list.Add('r'); // ['h', 'e', 'l', 'l', 'o' , ' ', 'w', 'o', 'r', null, null, null, null, null, null, null] Count: 9 Capacity: 16
list.Add('l'); // ['h', 'e', 'l', 'l', 'o' , ' ', 'w', 'o', 'r', 'l', null, null, null, null, null, null] Count: 10 Capacity: 16
list.Add('d'); // ['h', 'e', 'l', 'l', 'o' , ' ', 'w', 'o', 'r', 'l', 'd', null, null, null, null, null] Count: 11 Capacity: 16
همانطور که مشاهده میکنید، زمانی که به حداکثر ظرفیت آرایه میرسد، آرایه جدید با طول دو برابر آرایه قبلی ایجاد میشود 4 > 8 > 16 > 32 > 64 > 128 ...
اما اگر در همان اول ساخت کلاس List فضای موردنیاز برای افزودن عناصر را مشخص کنید به صورت زیر:
var list = new List<char>(11);
هزینه نمونه سازی جدید آرایه و کپی کردن را از بین میبرید و عملا از لحاظ پرفورمنسی بهتر خواهد بود.
نکته دوم درمورد حذف کردن آیتم های یک لیست است: برخلاف اضافه کردن عنصر که اندازه آرایه تغییر میکند، حذف کردن یک عنصر از لیست فضای اشغال شده را آزاد نمیکند. برای حذف فضای اشغال شده میتوانید متد TrimExcess را فراخوانی کنید. در کد زیر دیتای متغییری که لیست درون آن قرار دارد Clear میشود ولی فضای اشغال شده هنوز پابرجاست:
List<int> numbers = new List<int>();
numbers.Add(5);
numbers.Add(1);
numbers.Add(3);
numbers.Add(2);
numbers.Add(0);
Console.WriteLine(numbers.Capacity);
numbers.Clear();
Console.WriteLine(numbers.Capacity);
numbers.TrimExcess();
Console.WriteLine(numbers.Capacity);
در صفحه کنسول مقادیر زیر را مشاهده میکنید:
8
8
0
که بعد از متد Clear فضای اشغال شده درون آرایه هنوز پاک نشده است و همان 8 را نشان میدهد. اما بعد از متد TrimExcess مقدار پراپرتی Capacity به 0 تغییر میکند. بنابراین اگر میخواهید فضای اشغال شده را پاک کنید، ابتدا باید متد Clear و بعد از آن TrimExcess را فراخوانی کنید.
برای بررسی این مورد از کتابخانه Benchmark استفاده کردیم:
[MemoryDiagnoser]
public class ListMemoryAllocationTest
{
public int Count { get; set; } = 100000;
[Benchmark]
public void DefaultConstructor()
{
List<int> numbers = new List<int>();
for (int i = 0; i < Count; i++)
{
numbers.Add(i);
}
}
[Benchmark]
public void AddCountToConstructor()
{
List<int> numbers = new List<int>(Count);
for (int i = 0; i < Count; i++)
{
numbers.Add(i);
}
}
}
public class Program
{
public static void Main()
{
BenchmarkRunner.Run<ListMemoryAllocationTest>();
}
}
نتیجه Benchmark:
| Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
| DefaultConstructor | 753.4 us | 14.80 us | 15.20 us | 628.9063 | 627.9297 | 237.3047 | 1,024 KB |
| AddCountToConstructor | 499.6 us | 9.86 us | 17.78 us | 255.8594 | 255.8594 | 95.7031 | 391 KB |
زمانی که از سازنده پیشفرض کلاس List استفاده میشود حافظه بیشتری اشغال میشود.
:)
Powered by Froala Editor
سلام و درودبسیار عالیممنون
ارسال شده در تاریخ شنبه، ۲۰ شهریور ۱۴۰۰با سلام، خواهش میکنم.
ارسال شده در تاریخ جمعه، ۰۲ مهر ۱۴۰۰مطلب بسیار جالبی هست.با تشکر
ارسال شده در تاریخ شنبه، ۲۰ شهریور ۱۴۰۰خواهش میکنم
ارسال شده در تاریخ جمعه، ۰۲ مهر ۱۴۰۰نکته جالبی بود، در ضمن ممنون از معرفی کتابخانه Benchmark
ارسال شده در تاریخ شنبه، ۲۰ شهریور ۱۴۰۰خواهش میکنم ;)
ارسال شده در تاریخ جمعه، ۰۲ مهر ۱۴۰۰ممنونم از توضیحات خوبتون.می تونیم از کلاس LinkedList یا همون لیست های پیوندی استفاده کنیم. در این صورت هم از حافظه به صورت بهینه استفاده کردیم و هم با محدودیت های آرایه روبرو نمی شیم.https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.linkedlist-1?view=net-5.0
ارسال شده در تاریخ یکشنبه، ۲۱ شهریور ۱۴۰۰خواهش میکنم، ممنون بابت نظرتون و لینک ارسالی
ارسال شده در تاریخ جمعه، ۰۲ مهر ۱۴۰۰