게임 뿐만아니라 모든 프로그램에 존재하는 '루프' 는 진짜 말 그대로

위 단순화한 그림처럼 while 문 안에 관련 로직이 다때려박혀져 있다.


그림판 같은거라면 굳이 1초에 수십번씩 while문을 돌지않아도 되지만

게임은 다르다, 1초에 수십 수백번씩 while문을 돌아야만 한다.

(흔히 60 fps, 120 fps 하는게 1초동안 while문을 몇 번 도느냐로 측정된다)

그렇기 때문에 while 블록 내부의 속도가 빠르면 빠를수록 사용자에게 있어 더욱 부드러운 조작감을 제공할 수 있다.


이번 포스팅에서는 직접적으로 게임을 만드는데 도움이 되지는 않을지도 모르지만,

컴퓨터가 데이터 조작에 있어 속도를 높이고자했던 기법, 캐시 메모리에 대해서 알아보도록 하겠다!

비전공자에 맞춰서 쉽고 간략하게 설명했고 알아두면 무조건 뼈가되고 살이되니 부랄긁으면서 보도록 하자



그렇다면 렛츠고



0. 들어가기 앞서



컴퓨터를 맞춰본 챈럼들이 있다면 이런 견적을 한 번쯤 짜본적 있을 것이다.

여기서 눈여겨 봐야할건 CPU, 메모리(이하 RAM)

CPU는 계산에 특화된 노예고

RAM은 데이터가 저장된 창고같은 것이다.


사용자가 0100 번째 주소에 있는 데이터를 조작하라고 CPU에 명령했을때,

CPU가 0100 메모리를 가지고 있으면 바로 데이터를 조작하면 되지만,

관련 데이터를 가지고 있지 않으면 RAM에 요청해서 데이터를 가지고 온다.


근데, CPU가 메모리에 접근하는 시간은 CPU의 연산 속도보다 압도적으로 느리다는 것이다.


CPU는 데이터를 처리하고 싶은데,

RAM 메모리가 올 때까지 기다려야하는 불상사가 발생하는 것 


느린 이유는 여러가지 있겠지만,

기본적으로 장치의 용량이 커질수록 접근 속도가 느려지기도 하고

CPU와 RAM은 물리적으로 거리도 멀기 때문에 데이터 이동에 있어 시간이 걸리는 것이다.



1. 메모리 계층구조


(설명을 위해 몇몇 단계를 생략함)

(레지스터는 CPU 내부에 있는 저장장치)

컴퓨터에 많은 데이터를 저장하고, CPU가 메모리에서 데이터를 빠르게 가지고 오기 위해서

컴퓨터의 메모리는 계층구조를 가지고 있다.


계층의 상위에 있을수록 메모리 접근속도가 빠르고, 메모리 용량이 작다.

계층의 하위에 있을수록 메모리 접근속도가 느리고, 메모리 용량이 크다.

그러므로 메모리 접근속도는 CPU > RAM > HDD, SSD... 이고 메모리 용량은 역이다.


메모리 계층구조에서는 핵심은 아래 2가지이다.

1) 메모리 용량과 접근 속도는 반비례한다.

2) CPU에서 물리적인 거리가 멀수록 접근 속도가 느리다.


앞서 말했듯 CPU ~ RAM 사이에 접근 시간이 오래 걸리기 때문에

속도를 더 빠르게 하기위해 탄생한 메모리가 캐시 메모리이다.


캐시 메모리를 추가해서 메모리 계층을 다시 그래면 아래와 같다.



현대 컴퓨터들은 L1, L2, L3 등 캐시 메모리를 여러개 두기도 한다.

또 멀티코어 프로세스에서는 다른 식으로 설계되기도 하는데, 궁금하면 찾아보길 바란다.



계층구조상 위치가 저쪽이다 보니

메모리와 접근속도는 레지스터와 RAM 그 중간쯤이다.



2. 캐시 메모리

캐시 메모리의 역할은 CPU가 매번 RAM을 왔다갔다 하기에는 시간이 오래 걸리니, CPU가 사용할 일부 데이터를 미리 저장하는 것이다.


이것은 마치 굳이 히토미에 가지않고 야짤탭을 이용하는 것과 같이 이치이다.

어차피 내가 블루아카이브 캐릭터로 딸칠건데

굳이 VPN키고 히토미 드가서 태그치고 동인지보는건 시간이 많이 걸리니까

블붕이들이 야짤탭에 올려놓은 야짤을 보고 딸치는 것이다.


컴퓨터 과학에서 증명된 법칙인데

자주 쓰이는 데이터는 계속 자주 쓰이고, 자주 안쓰는 데이터는 계속 자주 쓰이지 않는다.

또한, 내가 쓰는 데이터는 전체 데이터 양에 비해서 매우 작은 양이다.

RAM이 8기가 16기가 정도 돼도 막상 내가 자주 쓰는 데이터는 100MB 조차 안된다.


그래서 자주 쓰이는 데이터를 미리 가져와서 캐쉬에 저장해놓고 캐쉬에서 빼쓰다가

캐쉬에서 조차 없는 데이터를 RAM에서 가지고 오는 것이다.

이것이 캐쉬 메모리의 취지이다.


3. 캐시 메모리의 원리 (매우중요)

이 부분은 면접에서도 나오는 부분이기에 눈여겨보길 바란다.

또한, 이 부분은 프로그래밍할 때 신경써주면 실질적인 속도향상을 맛볼 수 있다.


캐쉬는 RAM 보다 메모리 용량이 작다.

그래서 RAM에 있는 데이터를 모두 담을 수 없다. 

그러면 앞서 말했던 자주 쓰이는 정보를 어떻게 판단해서 저장할까?


캐시 메모리는 데이터 지역성의 원리를 이용하여 앞으로 사용할법한 데이터 뭉치를 미리 가져와서 저장해놓다.


1) 시간 지역성 (Temporal Locality)

한 번 참조한 데이터는 또 참조할 가능성이 높다는 원리이다.



간단하게 1억개의 데이터가 저장된 배열을 순회하면서 시간체크를 해보았다.

처음 접근할 때와 다시 접근할 때의 시간적 차이가 3배정도 차이나고 있는데, 이것이 시간 지역성을 보여주는 예시이다.


처음에는 데이터가 없었는데, 캐시에서 원하는 데이터를 찾을 수 없어 RAM에 방문하느라 시간이 조금 오래걸렸고, 2번째 부터는 캐시에 저장해놓은 데이터를 빠르게 가져와서 속도가 상당히 향상된 모습이다.


2) 공간 지역성 (Spatial Locality)

접근한 데이터의 주변데이터는 앞으로 사용할 가능성이 높다는 원리이다.



위쪽 코드 블럭은 연속적으로 배열에 접근한 경우고

아래 코드 블럭은 행과 열의 순서를 바꿔서 접근한 경우이다.

행과 열만 바꿨는데, 프로그램의 속도가 2배가량 차이난다는 것을 확인할 수 있다.


캐시 관점에서 바라보면, 데이터가 매우 불연속적인 연결리스트의 경우

단순 순회가 배열, vector 보다 현저히 느리다는 것을 확인할 수 있다.


이런 관점에서 map, unordered_map 등보다 단순 전체 순회에 있어서는 vector가 압도적으로 빠르다.

그래서 전체 순회가 필요없고 삽입/삭제가 빠르게 일어나야 하는 경우에는 set, hash 기반 자료구조를 사용하는 것이 좋겠지만, 전체 순회를 자주해야하는 경우에는 vector 를 사용해서 캐쉬를 최대한 활용하는게 속도향상에 도움될 수 있다.


4. 캐시 성능

마지막으로 캐시 성능과 관련된 간략하게 설명하고 끝내도록 하겠다.


좋은 캐시라는 것은 아무래도 사용할 데이터를 잘 예측하는 캐시라고할 수 있다..

CPU가 데이터를 요청했을 때 캐시에 원하는 데이터가 있을 경우를 캐시 히트

아닐 경우에는 캐시 미스라고 부르며,


캐시히트 / (캐시히트+캐시미스) * 100 (%) 가 곧 캐시의 성능을 나타낸다.

요즘 우수한 캐시들은 이 수치가 80%~90% 정도 된다.


만약 캐시미스가 발생했을 경우

캐시 -> RAM -> 캐시 순서대로 접근하면서 추가적인 오버헤드가 발생하는데,

이 때 발생하는 시간적 손실을 미스 페널티라고 부른다.


캐시의 성능은 프로그래머 입장에서 조절할 수 있는 것이 아니라,

캐시 자체의 설계와 알고리즘에 전적으로 달려있다.


프로그래머가 할 수 있는 것은 데이터 지역성의 원리에 따라 캐시 친화적인 코드를 짜는 것인데,

이런 캐시 친화적인 코드를 짜는 것 자체가 최적화 기법 중 하나이다.


5. 마치며

캐시에 대한 지식이 게임 개발에 있어서 직접적인 도움이 되지는 않을 수 있지만, 알아두면 무조건 좋은 지식이다. 

가볍게 보기 좋을정도로 썼기 때문에 전공자 입장에서는 설명이 부족하고 빠진 내용도 있을 것인데 (캐쉬 설계 부분은 아예 날려버림) 딥한 내용이 나오면 뒤로가기를 누를 것이기 때문에 핵심만 간단하게 다뤘음