Vec<T>는 동적이고, 확장 가능한 배열로, 요소를 단일 할당된(힙에) 메모리 블록에 저장합니다. 새 요소를 추가(push)할 때 길이가 증가하며, 필요에 따라 새로운 메모리가 할당(재할당)됩니다. push 시 내부적으로 용량(capacity)을 지수적으로 증가시켜 지속적인 재할당을 피합니다. 요소를 삭제(pop/remove)할 때 용량은 자동으로 줄어들지 않습니다.
자주 발생하는 문제는 지속적인 추가 시 과도한 할당 및 재할당입니다.
사전 할당 예시:
let mut v = Vec::with_capacity(1000); for i in 0..1000 { v.push(i); } assert_eq!(v.capacity(), 1000);
질문: v.shrink_to_fit() 호출 후 vec의 capacity는 어떻게 될까요? 길이와 같아질까요?
잘못된 답변: 네, 항상, shrink_to_fit 이후 capacity == len.
올바른 답변: 꼭 그런 것은 아닙니다. shrink_to_fit의 구현은 할당자에게 "요청"입니다. 일반적으로 가능한 최소 용량으로 시도하지만, 할당자의 구현에 따라 다를 수 있습니다(예: 길이보다 클 수 있음).
예시:
let mut v = Vec::with_capacity(10); for i in 0..5 { v.push(i); } v.shrink_to_fit(); // capacity ≥ len (5), 하지만 꼭 == len이라고 보장되지 않음
이야기
개발자가 capacity를 설정하지 않고 Vec에 객체를 여러 번 푸시 하여, 대량 데이터에서 재할당이 기하급수적으로 증가해 전체 처리가 "무거운" 루프에서 지연되었습니다.
with_capacity로 최적화하니 10배로 시간이 단축되었습니다.
이야기
팀은 매 pop() 후에
shrink_to_fit를 정기적으로 호출하여 메모리를 절약하려고 했습니다. 결국, 지속적인 재할당/해제가 발생하여 성능이 저하되어 서비스가 거의 DoS에 도달할 뻔했습니다.
이야기
Vec를 구조체의 내부 요소에 대한 참조로 필드로 남겨두었습니다. 재할당(push가 capacity를 초과할 때) 후 참조가 무효화되어 발생한 버그는 production에서 실행될 때까지 추적하기 어려웠습니다.