C# capacity

Featured image

Git Source


안녕하세요. chanos입니다. 😉

오늘은 collection 사용 시 속성에 있는 Capacity에 대한 글입니다.

일반적으로 많이 사용하는 generic collection인 List로 설명을 하겠습니다.

우선 List의 내부 구현을 살펴보면 LinkedList가 아닌 ArrayList로 되어 있습니다.

list

내부 구현 자료구조를 설명드린 이유는 Capacity와 관련있기 때문인데요.

ArrayList는 동적배열이기 때문에 element가 add 될 때 마다 현재 size를 비교하여 더 이상 add 할 공간이 없으면 새로운 공간을 할당하게 되는데, 이 때 Capacity를 이용하여 메모리 공간을 할당합니다.

실제 add 메서드 구현부를 살펴보면 다음과 같습니다.

add

ensureCapacity

capacityGetter

현재 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가 추가 된 경우 입니다.

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 객체를 생성할 때 인자로 넘겨줌으로써 초기화할 수 있습니다.

constructor

그렇기 때문에 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}");

capacityX

// 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}");

capacityO


Reference

C# List<T>.Capacity