게임 엔진 내부를 들여다보면 데이터를 관리하는 방식이 정말 중요해요. 캐릭터 목록이나 처리해야 할 이벤트처럼 여러 데이터를 한곳에 모아두는 일이 많기 때문이죠. 이때 사용하는 것이 바로 컨테이너라는 개념입니다. 데이터를 저장하고 필요할 때 꺼내 쓰는 보관함이라고 생각하면 이해가 쉬워요.
어떤 컨테이너를 쓰느냐에 따라 게임의 성능이 완전히 달라질 수 있습니다. 검색하고 정렬하는 속도에 영향을 주기 때문이에요. 그래서 프로그래머는 각 컨테이너의 특징을 정확히 알고 있어야 합니다. 상황에 맞는 도구를 골라야 최적화된 성능을 낼 수 있으니까요.
배열과 동적 배열의 특징
가장 익숙하고 직관적인 컨테이너는 역시 배열이에요. 배열은 메모리 상에 데이터가 나란히 붙어 있는 형태를 가집니다. 덕분에 몇 번째 데이터를 가져오라는 명령을 내리면 아주 빠르게 찾아낼 수 있죠. 데이터 접근 속도가 무엇보다 중요하다면 배열은 아주 좋은 선택이 됩니다.

여기에 크기를 자유롭게 조절할 수 있도록 만든 것이 동적 배열입니다. 처음엔 정해진 크기로 시작하지만 데이터가 늘어나면 알아서 공간을 넓혀주죠. 사용하기는 편하지만 내부 공간이 꽉 찼을 때 새로운 메모리를 할당하는 과정이 필요해요. 이때 데이터를 옮기는 비용이 발생할 수 있다는 점을 기억해야 합니다.
연결 리스트가 필요한 상황
배열과는 조금 다른 성격을 가진 연결 리스트라는 친구도 있습니다. 각 데이터가 다음 데이터의 위치를 가리키는 방식으로 연결되어 있어요. 덕분에 중간에 새로운 데이터를 끼워 넣거나 삭제하는 작업이 아주 수월합니다. 데이터 위치만 살짝 바꿔주면 되니까요.
하지만 데이터가 메모리 여기저기에 흩어져 있다는 단점이 있어요. 그래서 처음부터 끝까지 순서대로 훑어보는 작업에서는 효율이 떨어질 수 있습니다. 캐시 메모리를 제대로 활용하지 못하기 때문이죠. 삽입과 삭제가 빈번한지, 아니면 순차적인 접근이 많은지를 먼저 고민해 봐야 합니다.
상황에 딱 맞는 컨테이너 찾기
사용 목적이 아주 뚜렷한 컨테이너들도 있습니다. 스택은 나중에 들어간 데이터가 가장 먼저 나오는 방식이라 임시 데이터를 관리할 때 유용해요. 반대로 큐는 먼저 들어온 것을 먼저 처리하므로 작업 대기열을 만들 때 자주 쓰입니다.
데이터 간의 복잡한 관계를 표현해야 한다면 트리나 그래프를 사용하기도 해요. 특정 ID로 게임 오브젝트를 바로 찾아야 한다면 해시 테이블이 큰 힘을 발휘합니다. 이렇게 내가 다루려는 데이터의 성격이 무엇인지 파악하는 것이 우선이에요. 용도에 맞는 컨테이너를 고르면 구현도 쉬워지고 성능도 챙길 수 있습니다.
알고리즘 복잡도를 따져야 하는 이유
컨테이너를 선택할 때 가장 신경 써야 할 기준은 바로 알고리즘 복잡도입니다. 쉽게 말해 데이터가 늘어날수록 연산 속도가 얼마나 느려지는지를 따져보는 것이죠. Big-O 표기법이라는 개념이 여기서 등장합니다. 어떤 연산은 즉시 끝나지만, 어떤 연산은 전체를 다 뒤져야 할 수도 있어요.
데이터가 수만 개로 늘어났을 때 O(1)과 O(n)의 차이는 어마어마합니다. 특히 게임처럼 매 프레임 반복되는 연산이 많은 환경에서는 이 차이가 곧바로 체감 성능으로 이어져요. 프로그래머가 이 비용을 미리 계산하고 있어야 나중에 문제가 생기지 않습니다.
반복자를 똑똑하게 사용하는 법
컨테이너 내부가 어떻게 생겼든 상관없이 데이터를 다루고 싶을 때 반복자를 사용합니다. 반복자는 마치 포인터처럼 다음 요소로 이동하며 데이터를 순회할 수 있게 해 주는 도구예요. 복잡한 내부 구현은 숨기고 외부에서는 안전하게 접근할 수 있도록 도와주죠.
반복자를 쓸 때는 전위 증가와 후위 증가의 차이도 알아두면 좋아요. 후위 증가는 현재 값을 임시로 저장한 뒤 증가시키기 때문에 불필요한 복사가 일어날 수 있습니다. 반면 전위 증가는 바로 값을 증가시켜 사용하므로 더 효율적일 때가 많아요. 이런 작은 습관들이 모여 전체 성능을 높여줍니다.
게임 엔진이 직접 컨테이너를 만드는 이유
가끔은 표준 라이브러리에서 제공하는 컨테이너만으로는 부족할 때가 있습니다. 게임 엔진은 메모리 관리나 멀티스레드 환경에서 아주 까다로운 조건을 요구하기 때문이죠. 그래서 많은 엔진 개발자들이 직접 커스텀 컨테이너를 제작해서 사용합니다.
범용성을 조금 포기하더라도 예측 가능하고 안정적인 성능을 얻기 위함이에요. 메모리를 배치하는 방식이나 캐시 효율까지 직접 제어하고 싶을 때 이런 선택을 합니다. 데이터가 어떻게 쓰이는지 정확히 이해하고 그에 맞는 도구를 직접 깎아 쓰는 장인 정신과도 같죠.