brunch

You can make anything
by writing

C.S.Lewis

by 이경종 Apr 08. 2020

Ch5-4. 캐시(Cache memory)

캐시(Cache)는 임시로 쓰이는 버퍼(buffer) 메모리라고 생각하면 된다. 빈번하게 쓰이는 데이터들을 하위 메모리보다 빠른 공간에 임시로 넣어두고, 빨리빨리 가져다 쓸수 있도록 하기 위해 생겨난 기술이자 구조이자 장치라 할 수 있겠다. 메모리가 다른 IO장치보다는 빠르다고 하지만, 그래도 프로세서의 속도보다는 많이 느리다. 전체적인 시스템 성능을 높이기 위해서는 메모리에서 데이터를 읽어오는 속도를 빠르게 할 필요가 있다. 그래서 등장한 것이 캐시다.


캐시는 통상적으로 프로세서의 내부에 존재한다. 즉, CPU 바로 옆에 붙어서 메모리 처리속도를 높여주는 역할을 한다. 프로세서 내부에 존재하므로 빠르다. 하지만 크기가 작다. 그래서 자신의 크기보다 훨씬 큰 메모리를 어떻게 효율적으로 캐싱(Caching)할 수 있느냐의 고민이 있을수밖에 없다.


기본적으로 캐시가 동작하는 프로세스는 다음과 같다.

프로세서가 특정주소의 메모리를 읽으려고 할때 해당 주소의 데이터가 캐시에 있으면 그걸 가져다 쓰고, 없는 경우 메모리에서 가져와서 사용한 다음, 향후 사용을 위해 캐시에 저장한다. 

메모리에 데이터를 쓰는 경우는 일단 캐시에 데이터를 썼다가 나중에 실제 메모리에 업데이트하는 방식(write-back) 내지는 캐시와 메모리 둘다에 한꺼번에 쓰는 방식(write-through)이 있다.


얼핏 생각하기에 캐시를 쓴다고 더 속도가 빨라지는 것은 아닐것만 같다는 생각이 들 것이다. 결국 데이터가 없으면 다시 메모리에서 찾고, 캐시에도 쓸 때 메모리에도 써야 하고... 왠지 도움이 안될 것만 같다... 나 역시 그런 의문을 품었었지만, 결론적으로 쓸데없는 걱정이니 안해도 된다. 


캐시에 찾고자 하는 데이터가 있는 것을 Cache Hit이라고 하고, 데이터가 없는 경우를 Cache Miss라고 하는데, 실제로 동작하는 시스템에서 프로세서의 Cache Hit의 확률은 매우 높은 편이다. 그 이유는 이전에 메모리 개론(링크)에서 잠깐 언급한 바 있지만, 다음과 같은 특성 때문이다.


Temporal Locality

시간적인 국소성을 말한다. 이미 읽었던 데이터는 나중에 다시 참조할 확률이 높다. 그래서 빈번하게 사용되는 데이터를 빠른 캐시에 넣어두면 성능이 배가된다. 특히 프로그램 코드(instruction)의 경우 한번 참조된 코드(함수, 변수)는 다시 사용될 확률이 높다. 일례가 while이나 for 루프같은 반복코드다. 동일한 코드를 수차례 반복하는 구조이므로 캐시사용으로 인한 성능효과가 두드러질수 있다.


Spatial Locality

이것은 공간적인 국소성을 말한다. 사용했던 데이터와 인접한 데이터 역시 참조될 확률이 높다는 것이다. 사용된 포인트와 인접한 코드나 데이터는 실제 추후 사용될 확률이 높다.


아주아주 간단히 얘기하고 있지만, 실제 캐시는 여러 가지 방식과 구조, 그리고 최적화 알고리즘을 사용하여 데이터 처리속도를 높인다. 프로세서는 L1, L2, L3와 같은 다층 캐시 메모리를 사용하여 Cache Hit을 극대화시키는 것이 보편적인 구조다. 컴퓨팅 아키텍처는 프로세서와 가까운 순서대로 가장 빠른 메모리들을 위치시켜서 성능을 극대화하는 것이 그 핵심구조다.  



I-Cache와 D-Cache

캐시에서 처리하는 데이터는 코드/명령어와 같은 Instruction과 일반적인 Data로 구분된다. 각각의 특성이 있으므로, 프로세서의 L1캐시는 이 둘을 따로 따로 처리한다. 하여 보편적으로 L1 캐시에는 I-Cache와 D-Cache가 따로 따로 존재한다. 그 하위 L2 캐시의 경우 Instruction과 Data를 구분하지 않고 처리한다.


이미 말했듯이 캐시는 프로세서에서 알아서 처리하므로, 소프트웨어 개발할때 신경쓰지 않아도 된다.

단, 임베디드 소프트웨어 개발자라면 캐시에 대해 이상에서 설명한 기본적인 사항들을 알고 있어야 한다. 

실제 개발할때 캐시를 건드릴 일이 있기 때문이다.

어떤 경우인가?


다른 소프트웨어로 점프(jump)하는 경우에는 기본적으로 캐시를 모두 비워야 한다.

시스템에 전원이 들어오면 부트로더(Bootloader)라는 시스템의 첫 소프트웨어가 실행된다. 이 부트로더의 역할은 메인 소프트웨어(Main Software)를 구동시키는 것이 주임무다. 메인 소프트웨어는 통상 운영체제(OS)의 커널(Kernel)인 경우가 대부분이다. 부트로더와 커널은 서로 완전히 다른 소프트웨어이므로, 부트로더는 커널을 실행하기 위해, 자신을 종료시키고 커널코드를 메모리에 적재한 이후 커널의 맨처음 코드로 분기(jump)한다.


이때 만약 캐시를 비우지 않으면, 프로세서는 커널이 실행되고 난 이후 동일주소에 대한 데이터요청이 들어오는 경우 캐시에 있는 데이터를 반환할 것이다. 이 데이터는 부트로더에서 사용하던 데이터이므로 커널에서는 유효한 데이터가 아니다. 잘못된 데이터를 사용하게 되면 시스템이 멈추는 등의 심각한 문제를 초래할 수 있다.

그래서 프로세서의 low level API에서는 캐시를 비울수 있는 기본적인 코드를 제공한다.



운영체제(OS) 및 파일시스템에 캐시의 사용

캐시의 개념은 처리속도가 더 느린 하위메모리를 보조하기 위해 상위 메모리 공간을 사용하는 것이다. 그렇다면, 꼭 프로세서만 캐시를 사용하라는 법은 없다. 실제로 캐시는 거의 대부분의 소프트웨어 구조에서 광범위하게 사용된다. 운영체제에서 관장하는 메모리는  DRAM과 디스크라는 설명은 이미 했다. 

DRAM은 빠르고 디스크는 느리다. DRAM에 디스크의 캐시를 설정할 수 있다.

운영체제가 바로 그런 기능을 제공한다. DRAM의 특정영역을 캐시 영역으로 잡아놓고 활용한다.


디스크에서 빈번하게 읽고 쓰는 파일의 경우  캐시를 쓰면 성능개선에 효과가 있다.

운영체제에서 관장하는 파일 캐시에서 관장하는 데이터는 리눅스의 경우 다음 두가지로 구분된다.

첫째는 page cache로 일반적인 디스크의 파일안의 데이터라고 보면 된다. 

두번째는 i-node와 dentry cache로 파일시스템에서 사용하는 파일 인덱스와 디렉토리정보에 대한 데이터다.

drop_cache라는 리눅스 명령어를 사용하면 같이 사용하는 옵션에 따라 page cache만 비울수도 있고, i-node와 dentry cache까지 모두 다 깔끔하게 비울수도 있다. 프로세서에서 캐시를 플러쉬(Flush)하는 것과 동일 맥락이다.


파일시스템에서나 프로세서에서나 캐시를 비우는 것에는 많은 주의가 필요하다. 

시스템 동작중에 캐시를 건드리면 시스템이 오동작하거나 멈출수 있으니 주의해야 한다.


       링크 1  링크 2 







작가의 이전글 Ch5-3. 메모리 인터페이스
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari