게임 업계에서 오랫동안 프로그래머 필독 자료로 돌려 읽혀온 문서가 하나 있어요. 배틀필드 시리즈로 유명한 스웨덴 스튜디오 DICE에서 나온 'Introduction to Data-Oriented Design(데이터 지향 설계 입문)'이라는 발표 자료인데요. 나온 지 꽤 된 자료인데도 요즘 다시 회자되고 있어요. 여기서 다루는 문제, 그러니까 "CPU는 빠른데 메모리가 느려서 생기는 병목"이 시간이 갈수록 오히려 심해지고 있거든요. 오늘은 이 자료의 핵심을 차근차근 풀어볼게요.
문제의 시작: CPU는 빨라졌는데 메모리는 그대로예요
지난 수십 년간 CPU의 연산 속도는 어마어마하게 빨라졌어요. 그런데 메모리(RAM)에서 데이터를 가져오는 속도는 그만큼 빨라지지 못했어요. 그 결과 요즘 CPU는 메인 메모리에서 데이터 하나를 가져오는 동안 수백 번의 연산을 할 수 있을 정도로 격차가 벌어졌죠. 이걸 '메모리 벽(memory wall)'이라고 불러요.
이 격차를 메우려고 CPU 안에는 '캐시(cache)'라는 작고 아주 빠른 임시 저장소가 있어요. CPU는 메모리에서 데이터를 가져올 때 한 바이트씩이 아니라 '캐시 라인'이라는 단위(보통 64바이트)로 뭉텅이째 가져와요. 그러니까 내가 쓸 데이터가 메모리에 나란히 붙어 있으면, 한 번 가져올 때 다음에 쓸 데이터까지 딸려 와서 공짜로 쓸 수 있는 거예요. 반대로 데이터가 메모리 여기저기에 흩어져 있으면? 매번 수백 사이클을 기다리는 '캐시 미스(cache miss)'가 나는 거죠.
객체지향이 캐시와 상극인 이유
여기서 불편한 진실이 나와요. 우리가 배운 객체지향 설계는 "관련된 데이터와 동작을 하나의 객체로 묶어라"라고 가르치잖아요. 그런데 이게 캐시 관점에서는 최악에 가까워요.
예를 들어 게임 캐릭터 클래스에 위치, 체력, 이름, 인벤토리, AI 상태 같은 필드가 전부 들어 있다고 해볼게요. 매 프레임 "모든 캐릭터의 위치만 업데이트"하는 작업을 돌리면, CPU는 캐릭터 하나를 캐시에 올릴 때마다 위치 몇 바이트만 쓰고 나머지 이름이니 인벤토리니 하는 데이터는 그냥 버려요. 캐시 라인의 대부분이 낭비되는 거죠. 게다가 객체들이 포인터를 따라 힙 여기저기에 흩어져 있으면 캐시 미스는 더 늘어나요. 가상 함수 호출까지 겹치면 CPU가 다음 명령을 미리 예측하기도 어려워지고요.
데이터 지향 설계: 데이터를 '쓰는 방식대로' 배치하기
데이터 지향 설계(DOD)는 발상을 뒤집어요. "코드 모델을 중심으로 데이터를 묶지 말고, 데이터가 실제로 처리되는 패턴을 중심으로 배치하라"는 거예요.
대표적인 기법이 SoA(Structure of Arrays)예요. 캐릭터 '객체들의 배열(AoS, Array of Structures)'을 만드는 대신, 위치만 모은 배열, 체력만 모은 배열을 따로 두는 거죠. 이러면 "모든 위치 업데이트" 작업을 할 때 위치 데이터가 메모리에 쭉 붙어 있으니 캐시 적중률이 확 올라가요. 같은 연산을 반복하는 단순한 루프가 되니까 컴파일러가 SIMD(명령 하나로 여러 데이터를 동시에 처리하는 CPU 기능) 최적화를 걸기도 쉬워지고요. 자주 쓰는 데이터와 가끔 쓰는 데이터를 분리하는 'hot/cold 분리'도 같은 맥락의 기법이에요. 알고리즘은 그대로 두고 데이터 배치만 바꿨는데 몇 배씩 빨라지는 사례가 드물지 않아요.
이 아이디어는 이미 주류가 됐어요
"게임에서나 쓰는 거 아냐?"라고 생각하실 수 있는데, 그렇지 않아요. 유니티(Unity)의 DOTS와 ECS(엔티티 컴포넌트 시스템)가 바로 이 철학 위에 세워졌고, 러스트 게임 엔진 Bevy도 ECS가 기본 구조예요. 오버워치 같은 대형 게임도 ECS 기반으로 만들어진 걸로 알려져 있죠. 게임 밖으로 나가면 더 흔해요. ClickHouse 같은 컬럼형 데이터베이스가 분석 쿼리에서 빠른 이유, 판다스(pandas)나 NumPy에서 for문 대신 벡터화 연산을 쓰라고 하는 이유, Apache Arrow가 데이터를 컬럼 단위로 메모리에 배치하는 이유가 전부 같은 원리거든요.
한국 개발자에게 드리는 제안
웹 백엔드를 하시는 분이라면 당장 클래스 구조를 갈아엎을 필요는 없어요. 대부분의 웹 서비스 병목은 네트워크와 DB에 있으니까요. 하지만 대량 데이터 배치 처리, 실시간 파이프라인, 게임 서버처럼 CPU가 병목인 코드를 만나면 이 관점이 무기가 돼요. 그리고 무엇보다, 하드웨어가 실제로 어떻게 동작하는지 이해하고 나면 성능 문제를 보는 눈 자체가 달라져요. 추상화 위에서만 일하다 보면 잊기 쉬운 감각이거든요.
한 줄로 정리하면 이래요. 성능이 중요한 코드에서는 '예쁜 객체 모델'보다 '메모리 위의 데이터 배치'가 승부를 가른다. 여러분은 실무에서 캐시 미스 때문에 성능 문제를 겪어본 적 있으신가요? 아니면 그래도 유지보수에는 OOP 모델링이 낫다고 보시나요?
🔗 출처: Hacker News
"비전공 직장인인데 반년 만에 수익 파이프라인을 여러 개 만들었습니다"
실제 수강생 후기- 비전공자도 6개월이면 첫 수익
- 20년 경력 개발자 직강
- 자동화 프로그램 + 소스코드 제공