Post List

2018/03/04

System__메모리 계층과 캐쉬


이번에는 메모리 계층 구조에 대해서 알아보겠습니다. 컴퓨터를 구성하는 요소 중에서 임시적이든, 영구적이든 저장 기능을 조금이라도 가지고 있으면 무조건 메모리의 범위에 포함이 됩니다. 그리고 메모리는 무한한 공간을 가지지 않기 때문에 제한된 환경에서 가급적 높은 성능을   있도록 프로그래밍하기 위해서는 메모리의 특성을 잘 파악하고 있어야 합니다.
그럼 메모리라 불릴 수 있는 요소들에 대해서 나열해보겠습니다.

메인 메모리
- 가장 먼저 떠올릴 있는 것은 메인 메모리인 램입니다. 보다 정확히 말하면 D 계열의 메모리입니다. 참고로 메인 메모리가 반드시 램이어야 이유는 없습니다. 따라서 메인 메모리와 램에는 등호 관계가 성립하지 않습니다. 그러나 거의 모든 컴퓨터가 메인 메모리로 램을 사용하므로 메인 메모리와 램을 동일한 의미로 사용됩니다.

레지스터
- CPU안에 내장되어 있어서 연산을 위한 저장소를 제공합니다.

캐쉬
- 캐쉬는 D램보다 빠른 S램으로 구성하는데, 램이라는 단어는 메인 메모리를 의미하는 용도로 사용되므로, 캐쉬 메모리는 그냥 캐쉬라고 합니다. 캐쉬는 CPU 사이에서 중간 저장소 역할을 하는 메모리입니다. 그리고 캐쉬 메모리는 원래 CPU 일부로 존재하는 메모리 개념이 아닌, CPU 근접해 있는 메모리 개념입니다. CPU 일부로 존재하는 메모리는 레지스터입니다.

하드디스크와 이외의 저장 장치들
- 하드디스크도 당현히 메모리입니다. 하드디스크는 크고 작은 파일들을 저장하기 위한 용도로도 사용되지만, 프로그램 실행에 있어서도 중요한 의미를 지닙니다. 그리고 그밖에 SD카드, CD-ROM과 같은 I/O 장치들도 메모리에 해당합니다. 


프로그램이 실행되는 동안에 메모리가 하는 역할은 데이터의 입력 출력입니다. 따라서 기본적인 역할은 모든 메모리가 동일하며, 대상이 레지스터이건, 하드디스크이건 모두가 동일합니다.
그러면 메모리들은 어떤차이점을 가지고 있느냐 하면, CPU 기준으로 얼마나 멀리 떨어져 있느냐입니다.

레지스터는 CPU 가깝다 못해 CPU안에 존재하는 메모리입니다. 다음으로 캐쉬 메모리, 그리고 메인 메모리입니다. 가장 멀리 있는것이 하드 디스크입니다.

CPU 가까이에 있을수록 빠르고, 멀리 있을수록 속도가 느립니다. CPU 레지스터 접근은 별다른 절차가 필요 없습니다. 그러나 메인 메모리에 접근하기 위해서는 몇몇 복잡한 과정을 거쳐야 합니다. 대표적인 것이 버스 인터페이스 컨트롤입니다.
데이터를 입출력하기 위해서 메모리 버스를 거쳐야 하기 때문에 만큼 느립니다.

그럼 모든 메모리가 하는 일이 똑같고, CPU 근처에 있을수록 빠르다면 굳이 하드디스크와 같이 느린 메모리를 사용하는 이유는 뭘까요?

부분은 기술이고 비용문제입니다. CPU근처로 대용량의 메모리를 가져 갈수록 기술적인 문제들도 발생하고 비용도 훨씬 많이 든다고 합니다. 하드디스크 용량을 수십 기가바이트 늘리는 것보다 캐쉬 메모리 1Mbyte 늘리는데 드는 비용이 훠어어어얼씬 크다고 합니다.

아래는 메모리의 피라미드 계층 구조를 보여줍니다.

위 피라미드 구조를 보면 가장 위쪽에 있는 것이 레지스터입니다. 차지하고 있는 크기가 가장 작지만 가장 빠릅니다. 그 다음 L1캐쉬와 L2캐쉬가 존재하며, 둘 다 캐쉬라는 점에 있어서는 동일합니다. L1캐쉬가 CPU에 보다 근접해있지요. 그리고 그 아래에 메인 메모리가 존재하며, 캐쉬보다는 크지만 상대적으로 느립니다. 마지막으로 가장 하단에 존재하는 것이 하드디스크입니다. 가장 크지만 가장 느리기도 합니다. 

프로그램의 실행을 위해서 하드디스크에 있는 내용은 메인 메모리로 이동합니다. 그리고 메인 메모리에 있는 데이터 일부도 실행을 위해서 L2캐쉬로 이동합니다. 마찬가지로 L2캐쉬에 있는 데이터 일부는 L1캐쉬로 이동을 하고, L1캐쉬에 있는 데이터 중에서 연산에 필요한 데이터가 레지스터로 이동합니다. 즉 모든 메모리의 역할이 피라미드 구조에서 자신보다 아래에 있는 메모리를 캐쉬(자주 사용되는 메모리의 일부를 저장해서 속도를 향상시키는 것)하기 위해서 존재하는 것으로 이해해야합니다.

반대의 경우 만약 연산에 필요한 데이터가 레지스터에 존재하지 않는다면 L1캐쉬를 확인합니다. L1캐쉬도 가지고 있지 않다면 L2캐쉬를 확인하게 되고, 이곳에도 없다면 메인 메모리를 확인합니다. 그래도 존재하지 않는다면 결국은 하드디스크에서 읽어 들이게 됩니다.

만약에 이러한 식으로 피라미드 구조의 아래 단까지 접근하는 빈도수가 총 메모리 접근 빈도수의 반만 차지해도 상당히 느려질것이라고 생각이 되며, 차라리 CPU와 하드디스크를 직접 연결하는 것이 빠르겠다는 생각이 들지도 모릅니다. 

하지만 우리가 사용하는 시스템에서 캐쉬 메모리가 높은 성능 향상을 가져다 주고 있습니다. 그만큼 연산에 필요한 데이터가 캐쉬 메모리에 존재할 확률이 아주 높다는 의미입니다.

메인 메모리를 제외한 L1캐쉬와 L2캐쉬에, 연산에 필요한 데이터가 존재할 확률이 90% 이상 된다고 합니다. 그리고 메인 메모리에 존재할 확률까지 포함한다면, 하드디스크에서 데이터를 읽어들이는 빈도수는 전체 메모리 접근 빈도수의 불과 몇 퍼센트도 되지 않는다는 계산이 나옵니다. 


[컴퓨터 프로그램의 특성] 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void bubblesort(int srcArr[], int n)
{
    int i, j, temp;
    for(i = 0; i < n; i++)
    {
        for(j=1; j < n-1; j++)
        {
            if(srcArr[j-1> srcArr[j])
            {
                temp = srcArr[j-1];
                srcArr[j-1= srcArr[j];
                srcArr[j] = temp;
            }
        }
    }
}
cs

코드는 정렬 알고리즘중에 버블정렬에 관한 코드입니다. 코드의 기능보다 선언되어있는 코드를 중점으로 보겠습니다.

변수i,j,temp 보면 잦은빈도로 접근되고 있습니다. 이러한 특성을 가리켜 템퍼럴 로컬리티(Temporal Locality)라고 합니다.
템퍼럴 로컬리티(Temporal Locality), 프로그램 실행 한번 접근이 이뤄진 주소의 메모리 영역은 자주 접근하게 된다는 프로그램 특성을 표현할 사용합니다.

다음 코드를 보면,
1
srcArr[j-1= srcArr[j]
cs

코드에서 j값의 변화(증가)입니다. J값은 0부터 시작해서 배열의 크기만큼 증가할 것입니다. 물론 과정에서도 템퍼럴 로컬리티의 특성을 발견할 있습니다. 번씩 접근하고 있으니 말이죠. J값이 증가하면, 접근하는 메모리의 주소값은 4바이트씩 증가합니다. 이러한 특성을 가리켜 스페이셜 로컬리티(spatial Locality)라고합니다.
스페이셜 로컬리티(Spatial Locality), 프로그램 실행 접근하는 메모리 영역은 이미 접근이 이루어진 영역의 근처일 확률이 높다는 프로그램 성격을 표현할 사용하는 말입니다. , 0x12번지 메모리에 접근했다면, 다음 메모리 접근은 주소에서 멀리 떨어지지 않은 곳일 확률이 높다는 프로그램의 성격을 표현합니다.

소프트웨어의 이러한 가지 특성 때문에 캐쉬는 성능향상에 많은 도움이 됩니다.

물론 캐쉬의 도움을 받지 못하도록 프로그램이 구현될 수도 있습니다. 하지만 캐쉬 프렌드리 코드를 만들기 위해 굳이 노력하지 않아도, 고의적으로 캐쉬의 도움을 받지 않으려고 코드를 만들지 않는 이상, 캐쉬의 도움을 충분히 받을 있도록 프로그램이 만들어질 것입니다.
( * 캐쉬 프렌드리 코드(Cache Friendly Code), 캐쉬의 도움을 많이 받을 있도록 프로그램을 구현한 것을 말합니다. )