3 min to read
C# capacity
안녕하세요. chanos입니다. 😉
오늘은 collection 사용 시 속성에 있는 Capacity에 대한 글입니다.
일반적으로 많이 사용하는 generic collection인 List
우선 List의 내부 구현을 살펴보면 LinkedList가 아닌 ArrayList로 되어 있습니다.
- LinkedList 구현으로된 collection은 c#에서 LinkedList<T>로 제공하고 있고 Doubly Linked List로 구현되어 있습니다.
내부 구현 자료구조를 설명드린 이유는 Capacity와 관련있기 때문인데요.
ArrayList는 동적배열이기 때문에 element가 add 될 때 마다 현재 size를 비교하여 더 이상 add 할 공간이 없으면 새로운 공간을 할당하게 되는데, 이 때 Capacity를 이용하여 메모리 공간을 할당합니다.
실제 add 메서드 구현부를 살펴보면 다음과 같습니다.
현재 element의 size가 꽉 찼을 경우 현재 size+1 만큼 argument로 EnsureCapacity
를 호출 합니다.
이 EnsureCapacity
에서는 현재 _items.Length의 *2 만큼 Capacity
에 새로이 할당하는데 이 때 주의점이 있습니다.
Capacity에 할당할 경우 setter에서 새로운 array를 value(capacity)만큼 할당하여 기존 array를 복사합니다. (*복사 비용 발생) 또한 새로운 array가 할당됐기 때문에 기존에 참조한 array는 GC가 발생하게 됩니다.
추가적으로 _items.Length *2 만큼 할당하여 불필요한 메모리 공간을 차지할 수 있게 됩니다.
다음과 같이 size가 max인 상태에서 1개의 element가 추가 된 경우 입니다.
- defaultCapacity는 4 입니다.
List<int> list = new List<int>();
list.AddRange(Enumerable.Range(1, 1 << 14));
Console.WriteLine($"capacity : {list.Capacity}, count : {list.Count}");
list.Add(-1);
Console.WriteLine($"capacity : {list.Capacity}, count : {list.Count}");
// output
capacity : 16384, count : 16384
capacity : 32768, count : 16385
capacity가 설정이 안 되어있으면 default 값(=4)에 따라 GC 및 array copy 비용, 불필요한 메모리 공간이 발생하게 되는데 이 capacity를 List 객체를 생성할 때 인자로 넘겨줌으로써 초기화할 수 있습니다.
그렇기 때문에 collection에 추가할 데이터의 개수를 미리 알고 있는 경우 capacity를 초기화하는 습관을 갖는 것이 좋을 것 같습니다.
마지막으로 Capacity 초기화 여부에 따른 GC 성능 프로파일러입니다.
// Capacity (X)
List<int> list = new List<int>();
list.AddRange(Enumerable.Range(1, 1 << 26));
Console.WriteLine($"capacity : {list.Capacity}, count : {list.Count}");
list.Add(-1);
Console.WriteLine($"capacity : {list.Capacity}, count : {list.Count}");
// Capacity (O)
List<int> list = new List<int>(1 << 26);
list.AddRange(Enumerable.Range(1, 1 << 26));
Console.WriteLine($"capacity : {list.Capacity}, count : {list.Count}");
list.Add(-1);
Console.WriteLine($"capacity : {list.Capacity}, count : {list.Count}");