메모리 계층구조

Bor·2021년 12월 4일
0

컴퓨터구조

목록 보기
9/15

5.1 서론

메모리는 무한할 수 없을까? 무한한 느낌이 들게끔 설계할 수는? 어떤 순간에 사용하는 책은 도서관의 책 중 극히 일부. 프로그램의 어떤 특정 시간에는 주소 공간 내의 비교적 작은 부분에만 접근한다는 것이 지역성의 원칙이며 다음 두 가지 종류가 있다.

시간적 지역성 : 어떤 데이터가 참조되면 곧바로 다시 참조될 가능성이 높다는 원칙. 만약 어떤 항목이 참조되면, 곧 다시 참조되기 쉽다. 컴구조 책을 책상으로 가져왔다면, 곧바로 그 책을 다시 찾아볼 확률이 높다.
공간적 지역성 : 하나의 데이터가 참조되면 곧바로 그 주위 데이터가 참조될 가능성이 높다는 원칙 (컴구조 5판 -> 6판도 -> 프로세서 등)

컴퓨터 메모리를 메모리 계층구조(memory hierarchy)로 구현함으로써 지역성의 원칙을 이용할 수 있다. 메모리 계층구조는 서로 다른 속도와 크기를 갖는 여러 계층의 메모리로 구성되어 있다. 가장 빠르 메모리는 가장 느린 메모리보다 비트당 가격이 비싸기 때문에 대개 그 크기가 작다.

아래 그림에서 빠른 메모리는 프로세서에 가깝게 두고, 느리고 싼 메모리를 그 아래 두었다. 메모리 계층구조의 목적은 사용자에게 가장 빠른 메모리가 갖고 있는 접근 속도를 제공하면서 동시에 싸게 제공하는 것.

데이터도 이와 유사한 계층구조를 갖는다. 프로세서에서 가까운 계층 = 먼 계층의 부분집합, 가장 낮은 계층에는 모든 뎅터가 다 저장. 메모리 계층 구조는 여러 계층으로 구성되지만 데이터는 인접한 두 계층 사이에서만 한 번 복사가 된다.

따라서 여기서는 계층을 두 개로 한정해 설명. 상위 계층은 더 비싼 기술 사용 ➡️ 하위 계층보다 작으며 빠르다. 이 두 계층 간 정보의 최소 단위를 블록(block) 또는 라인(line)이라고 부른다.

각 메모리 계층은 상위 계층과 하위 계층을 가지고 있다고 생각할 수 있다
각각의 계층 안에서 정보가 존재하든 존재하지 않든 그 기본 단위는 블록이라고 불린다. 계층간에 무엇인가를 복사할 때에는 대개 블록 전체가 이동한다.

프로세서가 요구한 데이터가 상위 계층의 어떤 블록에 있을 때 이를 적중(hit)이라고 부른다. 그리고 상위 계층에서 찾을 수 없다면 이를 실패(miss)라고 부른다. 이때는 필요한 데이터를 포함하는 블록을 찾기 위해 하위 계층 메모리에 접근하게 된다.

  • 적중률(hit rate or hit ratio)은 메모리 접근 중 상위 계층에서 찾을 수 있는 것의 비율로, 메모리 계층의 성능을 평가하는 척도로 이용된다.
  • 실패율(miss rate, 1-적중률)은 메모리 접근 중 상위 계층에서 찾을 수 없는 것의 비율을 말한다.

메모리 계층 구조를 만든 가장 큰 이유은 성능 향상! 적중과 실패의 처리 속도가 매우 중요.

  • 적중 시간(hit time)은 메모리 계층구조의 상위 계층에 접근하는 시간, 이 시간에는 접근이 적중인지 실패인지 결정하는 데 필요한 시간이 포함된다(책상의 책을 찾아보는 데 걸리는 시간).
  • 실패손실(miss penalty)은 하위 게층에서 상위 계층으로 블록을 인출하는데 걸리는 시간. 블록에 접근하고, 한 계층에서 다른 계층으로 전달하여 실패가 발생한 계층에 넣은 뒤 요청한 장치로 블록을 넘겨주는 시간을 포함

상위 계층은 작고 빠른 메모리를 사용해 만들어졌기 때문에 적중 시간은 실패 손싱의 주요한 부분을 차지하는 다음 계층에 접근하는 시간보다 훨씬 적게 걸린다. 메모리 계층구조는 성능에 큰 영향을 미치며 아래 그림과 같이 작고 빠른 메모를 프로세서 가까이 둔다. 따라서 최상위 계층에서 적중되는 접근은 매우 빠르 처리 가능. 실패가 발생한 접근은 느린 하위 계층으로 내려가 수행. 적중률이 충분히 크면 메모리 계층의 접근 속도는 최상위 계층(가장 빠른)과 유사 크기는 최하위 계층(가장 큰) 메모리와 유사.

5.2 메모리 기술

오늘날 메모리 계층구조에서는 네 가지 주요 기술이 사용됨.

  • 메인 메모리(main memory)는 DRAM(dynamic random acess memory)으로 구현된다.
  • 프로세서에 더 가까운 계층인 캐시(cache)에는 SRAM(static random access memory)이 사용된다. DRAM은 SRAM보다 훨씬 느리지만 싸다.
  • 세 번째 기술은 플레시 메모리. 비휘발성 메모리 개인용 휴대기기에서 2차 메모리로 사용된다.
  • 네번째 기술은 서버의 가장 크고 느린 계층인 자기디스크.


SRAM 기술

SRAM은 단순한 집적회로로서 읽기나 쓰기를 제공하는 접근 포트가 하나 있는 메모리 배열. SRAM은 읽기 접근 시간과 쓰기 접근 시간이 다를 수 있지만, 어떤 데이터든 접근 시간은 동일. SRAM은 리프레시가 필요 없기에 사이클시간과 접근 시간이 거의 동일. 읽을 때 정보가 변경되지 않도록 하기 위해 비트당 6개 혹은 8개의 트렌지스터 사용하며 대기모드에서 데이터 값 유지를 위해 최소한의 전력만 사용, 과거에 대부분 별도의 SRAM 칩을 사용 BUT MOORE의 법칙 덕택에 모든 계층의 캐시가 프로세서 칩에 집적되므로 분리된 SRAM은 거의 사라졌다.

DRAM 기술

SRAM에서는 전력이 공급되는 한 무한이 그 값이 유지. DRAM에서는 셀에 기억되는 값이 전하로 커패시터에 저장된다. 저장된 값을 읽거나 새로 쓰기 위해 저장된 전하에 접근하는 데 트랜지스터를 하나 사용. DRAM은 저장된 비트 하나당 트랜지스터 하나씩 사용 SRAM에 비해 훨씬더 집적도가 높고 값도 싸다. DRAM은 커패시터에 전하를 저장하기에 무한하게 저장될 수도 없고 주기적으로 리프레시가 필요. 그러므로 SRAM 셀에 정적 저장이라고 하는 것에 반해 메모리 구조를 동적이라고 부른다.

그림 5.4 DRAM의 내부 구조. 최신 DRAM은 뱅크로 구성되어 있는데, DDR3는 뱅크 4개가 일반적이다. 각 뱅크는 일련의 행으로 구성되어 있다.

셀을 리프레시하기 위해서는 단순히 저장된 값을 읽고 다시 쓴다. 전하는 수밀리초 동안 유지될 수도 있다. 모든 비트를 하나씩 DRAM에서 읽어서 다시 쓴다면, 계속 DRAM을 리프레시 해야함. 기억된 값에 접근할 시간이 없게 된다. 다행히도 DRAM은 두 단계의 디코딩 구조를 갖는다. 이 구조는 읽기 사이클에서 전체 행(워드라인을 공유하는)을 한꺼번에 읽은 후 바로 쓰기를 해 한행을 통째로 리프레시 할 수 있도록 해준다.

리프레시에 도움이 되는 행 구조는 성능 향상에도 도움이 된다. 성능을 향상시키 위해 DRAM은 행을 버퍼링해서 반복적으로 접근. 버퍼는 SRAM처럼 동작한다. 다음 행에 접근할 때까지 주소 값만 바꾸면 버퍼 내의 아무 비트에나 접근할 수 있다. 이 기능은 같은 행 내의 비트 접근 시간을 훨 줄여주므로 접근 시간을 크게 개선한다. 칩을 더 크게 만들면 칩의 메모리 대역폭 또한 커지게 된다. 어떤 행이 버퍼에 있으면, DRAM의 대역폭(16비트)이 얼마든지 간에 연속된 주소를 사용해서 또는 버퍼 내 시작 주소와 블록 전송을 이용해 전송할 수 있다.

프로세서와의 인터페이스를 더욱 향상시키기 위해 DRAM은 클럭을 추가할 수 있는데 이것을 동기화 DRAM 또는 SDRAM이라 부른다. SDRAM의 장점을 클럭을 사용하므로 메모리와 프로세서를 동기화하는 시간이 필요 없다는 것. 동기화 DRAM의 속도가 빠른 이유는 추가 주소지정 대신 클록이 연속적인 비트들을 버스트 모드로 전송할 수 있게 하기 때문이다. 가장 빠른 버전은 DDR(Double Data Rate) SDRAM이라고 불린다. 이 이름은 클럭의 상승 에지와 하강 에지에서 각각 데이터가 전송되는 것을 의미. 클럭과 데이터 전송 폭으로부터 기대할 수 있듯 2배의 대역폭을 가능하게 한다. 이 중 최선 버전은 DDR4. 초당 32억 번의 전송이 가능하며, 1600MHz클럭을 사용한다.

이와 같이 큰 대역폭을 유지키 위해서는 DRAM 내부에 좋은 구조가 필요. 단순하게 더 빠른 행 버퍼를 사용하는 대신, DRAM은 내부적으로 여러 뱅크에서 읽기와 쓰기가 가능하도록 구성. 각각의 뱅크는 자신의 행 버퍼를 가지고 있다. 여러 뱅크에 주소를 보낼 수 있게 함. 예를 들어 뱅크가 4개일때, 한 번의 접근 시간으로 4배의 대역폭을 제공하기 위해 4개의 뱅크에 돌아가면서 접근. 이 순환 접근 방식은 주소 인터리빙(address interleaving)이라고 한다.

5.3 캐시의 기본

캐시는 이 계층을 갖춘 최초의 사용 컴퓨터에서 메인 메모리와 프로세서 사이에 있는 메모리 계층을 나타내기 위해 선택된 이름이다. 4장 데이터패스의 메모리들은 간단히 캐시로 대체될 수 있다. 현재 모든 범용 컴퓨터가 캐시를 이용하고 있다.

이 절에서는 프로세서가 한 순간에 필요로 하는 데이터는 한 워드이고, 블록 또한 한 워드로만 이뤄진 아주 단순한 캐시를 먼저 살펴본다.

그림 5.7 초기에 캐시에 없는 워드 Xn을 참조하기 직전과 직후의 캐시 상태

위 그림은 캐시에 없는 데이터를 요청하기 전과 후의 캐시 상태를 보여준다. 요청하기 전의 캐시에는 최근에 접근한 x1, x, ... ,xn-1이 존재하고, 프로세서는 캐시에 없는 워드 xn을 요청. 이 요구는 실패를 발생시키고 워드 Xn을 메모리로부터 캐시로 가져오게 된다.

각 워드가 캐시 내의 딱 한 장소에만 있을 수 있다면 워드가 캐시 내에 있는지 없는지 바로 알 수 있다. 메모리의 워드에 캐시 내의 위치를 할당하는 가장 간단한 방법은 메모리 주소에 기반을 두고 할당하는 것. 이 캐시 구조를 직접사상(direct mapped)이라 한다. 왜냐하면 각 메모리 위치는 캐시 내의 딱 한 장소에 직접 사상되기 때문. 직접 사상 캐시가 사용하는 사상 방식은 비교적 단순. 예를 들어 거의 모든 직접 사상 캐시는 블록을 찾기 위해 다음의 사상 방식을 사용한다.

직접 사상 : 각 메모리의 위치가 캐시 내의 정확한 한 곳에만 사상되는 캐시 구조

(블록주소) modulo (캐시 내에 존재하는 전체 캐시 블록 수)

만약 캐시 내부의 전체 블록 수가 2의 지수승이면 modulo 연산은 간단히 주소의 하위log2(캐시 내의 전체 블록 수) 비트만을 취하는 것으로 쉽게 계산할 수 있다. 따라서 8-블록캐시는 블록 주소로 하위 3비트(2^3 = 8)를 사용한다. 예를 들어, 그림 5.8은 8개의워드로 된 직접 사상 캐시와 캐시내의 위치 1(001two)과 5(101two)로 사상되는 메모리 주소는 1(00001two)과 29(11101two) 사이의 메모리 주소들을 보여준다.

그림 5.8 8개의 엔트리를 갖는 직접 사상 캐시. 캐시 내에 8개의 워드가 있기 때문에 주소 X는 캐시 X modulo8 에 사상된다. 즉 하위 비트들(3비트)이 캐시의 인덱스로 사용된다. 그러므로 주소 00001, 01001, 10001, 11001은 모두 캐시 엔트리 001로 사상되며, 00101, 01101, 10101, 11101은 캐시 엔트리 101로 사상된다.

각 캐시 엔트리는 여러 주소의 메모의 내용을 적재할 수 있다. 이 때 캐시 내의 워드가 프로세서가 요구하는 것과 일치하는지를 어떻게 알 수 있을까? 즉 요구하는 워드가 캐시 내에 있는지 없는지 어떻게 알 수 있을까? 캐시에 태그(tag)를 추가함으로써 이 문제를 해결할 수 있다. 태그는 캐시 내의 워드가 요청한 것인지 아닌지를 식별하는데 필요한 주소 정보를 포함한다. 태그는 캐시의 인덱스로 사용되지 않은 주소의 윗부분 비트로 구성된다. 예를 들어 위 그림에서는 주소 비트의 하위 3비트 인덱스 필드가 블록을 선택하는데 사용되었으므로, 태그는 5비트 중 상위 2비트가 된다. 태그 구조는 중복되는 인덱스 비트를 생략한다. 왜냐하면 캐시 블록 주소의 인덱스 필드는 그 블록의 번호.

태그 : 특정 계층에서 해당 블록이 요청한 워드와 일치하는 지를 알려주는 주소 정보를 담고 있는 필드

또한 캐시 블록이 유효한 정보를 가지고 있는지를 알아내는 방법이 필요. 예를 들어 프로세서가 맨 처음 작업을 시작하면 캐시는 비어 있을 것. 태그 필드는 의미가 없고. 많은 명령어를 수행한 이후에도 캐시 엔트리의 일부는 그림 5.7에서와 같이 비어있을 수 있다. 따라서 이들 엔트리를 위한 태그들은 무시되어야 한다. 가장 많이 쓰이는 방법은 엔트리가 타당한 주소를 포함하는지 표시하기 위해 유효비트(valid bit)를 캐시에 첨가 이 비트가 1로 설정돼 있지 않으면, 이 엔트리에는 유효한 블록이 없는 것으로 간주.

유효비트 : 특정 계층에서 해당 블록이 유효한 데이터를 포함하는지를 알려주는 필드

캐시는 예측기법을 사용하는 가장 중요한 예. 지역성의 원칙을 이용해서 메모리 상위 계층에서 필요한 데이터를 찾는다. 상위 계층에서 예측이 틀렸을 경우에는 하위 계층에서 적합한 데이터를 찾을 수 있는 기법을 제공. 현대는 95%이상.

캐시 접근

아래 표는 캐시가 8개의 블록을 가지고 있고 모두 비어 있을 때, 아홉 번의 메모리 참조에 따른 동작을 보여주고 있다. 그림 5.9는 캐시의 내용이 각 캐시 실패에 따라 어떻게 변경되는지 보여준다. 캐시 내에 8개의 블록이 있기에 주소 하위 3비트가 블록의 번호를 나타낸다.

캐시가 비어 있기 때문에 몇 개의 첫 번째 참조는 캐시 실패이다.(아래 그림 참조) 여덟번째 참조에서 블록에 대한 충돌이 생긴다. 주소18(10010)의 워드가 캐시 블록 2(010)로 옮겨져야 한다. 따라서 이미 캐시 블록 2에 있는 주소 26(11010)의 워드는 교체되어야만 한다. 이러한 상황은 캐시가 시간적 지역성을 활용할 수 있도록 해준다. 즉 최근에 접근된 워드는 더 이전에 접근된 워드를 교체한다.

그림 5.9 캐시 실패를 발생시킨 참조 직후의 캐시의 내용을 이진수로 표현된 인덱스 필드 및 태그 필드와 함께 보여 준다

우리는 모든 주소에 대해 캐시 내의 어느 곳을 찾아야 할지 알 수 있다. 주소의 하위 비트들은 주소가 사상되는 유일한 캐시 엔트리를 찾는 데 사용할 수 있다. 그림 5.10은 참조된 주소가 어떻게 다음과 같이 나누어지는지를 보여준다.

  • 캐시의 태그 필드 값과 비교하는데 사용하는 태그 필드
  • 블록을 고르는 데 쓰이는 캐시 인덱스

캐시 블록의 인덱스는 그 블록의 태그 값과 함께 캐시 블록에 있는 워드의 실제 메모리 주소를 유일하게 표시할 수 있다. 인덱스 필드가 캐시에 접근하는 주소로 이용되고, n비트 필드는 2^n 개의 값을 갖게 되므로, 직접 사상 캐시 전체 엔트리 수는 2의 지수승이 되어야 한다. MIPS 구조에서는 모든 주소가 4바이트로 정렬되어 4의 배수이기 때문에 모든 주소의 맨 오른쪽 2비트는 워드 내부의 바이트를 나타낸다. 따라서 최하위 두 비트는 블록 내의 워드를 선택할 때 이용되지 않는다.

캐시 구현에 필요한 총 비트 수는 캐시의 크기와 주소의 크기에 따라 결정된다. 왜냐하면 캐시는 데이터 뿐만 아니라 태그를 위한 장소를 필요로 하기 때문. 위에서 블록의 크기는 한 워드, 일반적으로 여러 개의 워드가 된다. 아래와 같은 가정에서

  • 32비트 주소
  • 직접 사상 캐시
  • 캐시는 2^n 개 블록을 가지고 있고 n개 비트는 인덱스를 위해 사용된다
  • 캐시 블록의 크기는 2^m개 워드(2^m+2개 바이트)이다. m개 비트는 블록 내부에서 워드 구별에 쓰이며, 두 비트는 주소 중 바이트 구별용으로 쓰인다.

태그 필드의 크기는

32 - (n + m + 2)

이다. 그리고 직접 사상 캐시의 전체 비트 수는

2^n * (블록 크기 + 태그 크기 + 유효비트 크기)

이다. 블록의 크기는 2^m 개 웓(2^m+5개 비트)이고, 유효한 필드를 위해 한 비트가 필요하다. 하지만 일반적으로 캐시 크기는 유효 비트와 태그 필드의 크기는 제외하고 데이터 크기만 따진다.

2^n * (2^m * 32 + (32 - n-m-2) +1) = 2^n * (2^m * 32 + 31 -n-m)

따라서 그림 5.10의 캐시는 4KiB 캐시라고 부른다.

예제 : 캐시 내의 비트
16KiB의 데이터와 4워드 블록을 갖는 직접 사상 캐시의 구현에 필요한 전체 비트수는 얼마인가? 단 32비트 주소를 가정한다.

답: 16KiB는 4K(2^12)개 워드. 각 블록은 4개의 워드로 구성 1024(2^10)개의 블록을 갖느다. 각 블록은 4*32 = 128 비트의 데이터와 (32-10-2-2)개 비트의 태그, 그리고 한 비트의 유효한 비트를 갖는다 따라서 전체 캐시의 크기는

2^10 * (4*32 + (32-10-2-2)+1) = 2^10 * 147 = 147KiBibits

다시 말해서 16KiB 데이터를 저장하기 위해 18.4KiB 캐시가 필요하다. 이 캐시에서 캐시의 전체 비트 수는 데이터에 필요한 저장 공간에 비해 약 1.15배 더 크다.


크기가 큰 블록의 공간적 지역성은 실패율을 감소시킨다. 그림 5.11에 보인 것처럼 블록 크기를 늘리면 대개 실패율은 줄어든다. 블록 하나가 캐시 크기의 상당부분을 차지하도록 블록을 크게 만들면 실패율이 올라갈 수도 있다. 왜냐? 캐시 내에 존재할 수 있는 전체 블록의 수가 작아져 블록들 간에 상호 충돌이 생길 수 있기 때문. 결과적으로 블록 내의 많은 워드들이 접근되기 전에 그 블록이 캐시로부터 방출될 수 있기 때문. 블록 크기가 매우 클 경우에 블록 내 워드 간의 공간적 지역성이 감소하게 된다. 결과적으로 실패율의 향상은 둔화된다.

블록 크기를 늘리는 것과 연관되어 발생되는 더 심각한 문제는 실패에 따르는 비용이 증가하는 것. 실패 손실은 메모리 계층구조의 다음 하위 계층에서 블록을 가져오고 캐시에 적재하는 데 걸리는 시간에 의해 결정된다. 블록을 가져오는데 걸리는 시간은 두 부분으로 나뉜다. 첫 번째 워드를 찾는 데 걸리는 접근 지연(latency)과 블록을 이동시키는데 필요한 전송 시간이다. 메모리 시스템을 변경시키지 않는 한 블록 크기가 늘어날수록 전송시간 - 따라서 실패 손실 - 이 증가할 것은 자명하다. 게다가 실패율의 향상도 블록의 크기가 증가함에 따라 감소하기 시작한다. 결과적으로 실패 손실의 증가가 실패율 감소를 압도하게 되고 캐시 성능 또한 감소한다.

비록 실패 손실 중 지연 시간 부분에 대해서는 어떤 조치를 취하기 힘들다 하더라도, 전송 시간의 일부를 감춰 실패 손실을 효과적으로 줄일 수 있다. 이렇게 하는 가장 간단한 방법은 조기 재시작(early restart) 방식으로 블록 전체를 기다리지 않고 블록 내의 요청된 워드가 도달하면 곧바로 실행을 시작.
많은 시스템들이 명령어 접근에 이 기법을 이용하고 있으며 큰 효과를 보고 있다. 명령어 접근은 대개가 순차적. 만약 메모리 시스템이 매 클럭 사이클마다 한 워드를 보낼 수 있다면 프로세서는 요구한 명령어가 도착하자마자 동작을 다시 시작할 수 있게 된다.
이 방법은 데이터 캐시에 대해서는 덜 효과적이다. 왜나면 블록 내의 워드 요청이 예측하기 어려운 방식으로 이뤄지는 경향이 있고 데이터 전송이 다 끝나기도 전에 다른 캐시 블록의 다른 워드를 필요로 할 가능성이 높다. 전송이 진행 중이라 프로세서가 데이터 캐시에 접근할 수 없으면 프로세서는 지연될 수 밖에 없다.
좀 더 정교한 기법은 요청한 워드를 먼저 메모리에서 캐시로 전송할 수 있도록 하는 방식. 블록의 나머지 워드들은 요청된 워드의 다음 주소부터 순서대로 전송. 다시 블록의 처음 워드부터 남은 워드들을 순차적으로 전송. 이 기법은 요구 워드 우선(requested word first) 방식 또는 중요워드우선(critical word first) 방식이라 불리며, 조기 재시작 방식보다는 조금빠르지만 여전히 조기 재시작 방식에서와 마찬가지로 한계를 가지고 있다.

캐시 실패의 처리

제어유닛은 실패를 탐지해야 하며, 메모리(또는 하위 수준의 캐시)로부터 데이터를 가져와서 실패를 처리해야 한다. 만약 캐시 내에 원하는 데이터가 존재하면 컴퓨터는 아무 일도 없는 것처럼 데이터를 사용할 수 있다. 캐시 적중을 처리할 수 있게 프로세서의 제어 유닛을 수정하는 것은 쉬운 일이지만, 실패의 경우에는 몇 가지 조작이 더 필요하다. 캐시 실패 처리는 프로세서의 제어 유닛과 별도 제어기의 공동작업으로 처리된다. 이 제어기는 메모리 접근을 시작하고 캐시를 채우는 일을 한다.

캐시 실패 : 데이터가 캐시에 없어서 충족시킬 수 없는 캐시 데이터 요청

캐시 실패 처리는 모든 레지스터의 상태를 저장하는 인터럽트와는 다른 파이프라인 지연을 발생시킨다. 캐시 실패 발생 시에는 임시 레지스터와 프로그래머에게 보이는 레지스터의 내용을 그대로 유지한 채 메모리로부터 데이터가 오기를 기다리면서 전체 시스템을 지연시킨다. 더 정교한 비순서 실행 프로세서는 캐시 실패 처리를 기다리면서 명령어를 실행시킬 수 있지만 이 장에서는 캐시 실패가 발생하면 지연시키는 순차적 프로세서를 가정한다.

명령어 실패가 어떻게 처리되는지를 조금 더 자세히 살펴보자. 데이터 실패를 처리할 수 있게 이 방법을 확장하는 것은 어렵지 않다. 명령어 접근에 실패하면 명령어 레지스터의 내용이 유효하지 않게 된다. 적합한 명령어를 캐시로 가져오기 위해서는 메모리 계층구조의 낮은 계층에게 읽기를 수행을 시켜야만 한다. PC는 맨 처음 클럭 사이클 동안에 증가되기에 명령어 캐시 실패를 발생시킨 명령어의 주소는 현재의 PC에서 4를 뺀 값과 같다. 주소가 얻어지면 메인메모리에 읽기를 수행하도록 요구해야 한다. 메모리가 응답하기를 기다려서 읽어 온 워드를 캐시에 쓴다.

명령어 캐시 실패의 처리 단계는 다음과 같이 정의할 수 있다.

  • 1) 원래의 PC값(현재PC-4)을 메모리로 보낸다
  • 2) 메인 메모리에 읽기 동작을 지시 메모리가 접근을 끝날 때까지 기다린다
  • 3) 메모리에서 인출한 데이터를 데이터 부분에 쓰고, 태그 필드에 주소(ALU로부처 계산된)의 상위 비트를 쓰고 유효 비트를 1로 만듦으로써 캐시 엔트리에 쓰기를 수행
  • 4) 명령어 수행을 첫 단계에서부터 다시 시작해 캐시에서 명령어를 가져온다. 이제는 필요한 명령어를 캐시에서 찾을 수 있다

데이터 접근을 위한 캐시의 제어는 근본적으로 동일. 실패 발생 시에는 메모리가 데이터를 보내줄 때까지 프로세서를 지연시키면 됨.

쓰기의 처리

쓰기는 약간 다르게 작동한다. 저장 명령을 가정해 보자. 데이터를 데이터 캐시에만 쓰고 메인 메모리에는 쓰지 않을 경우, 메인 메모리는 캐시와 다른 값을 갖게 되며 이 경우를 캐시와 메모리는 불일치(inconsistent)한다고 말한다. 메인 메모리와 캐시를 일치시키는 가장 쉬운 방법은 항상 데이터를 메모리와 캐시에 같이 쓰는 것이다. 이 방법을 즉시 쓰기(write-through)라고 부른다.

쓰기의 다른 중요한 측면은 쓰기 실패 시 어떤 일이 발생하는가이다. 먼저 메모리에서 해당 워드가 포함된 블록을 가져와서 캐시에 넣은 뒤 이 워드에 쓰기를 수행. 또한 전체 주소를 사용하여 이 워드를 메인 메모리에도 쓴다.

이 방식은 설계를 잘한다고 하더라도 좋은 성능을 제공하기 어렵다. 즉시 쓰기의 방식에서는 모든 쓰기가 메인 메모리에 데이터를 써야만 한다. 이 쓰기가 시간이 오래 걸려서 최소 100개의 프로세서 클럭 사이클이 필요하고 따라서 프로세서의 성능을 심하게 저하시킨다. 예를 들어 명령어의 10%가 저장명령어라고 가정. 캐시 실패가 없는 경우 CPI가 1.0 이고 모든 쓰기에 100개의 추가 사이클이 필요하게 되면, CPI 값은 1.0 + 100 * 10% = 11, 즉 약 10배 정도의 성능 저하가 생긴다.

이 문제를 해결하기 위한 한 가지 방법은 쓰기 버퍼(write buffer)를 이용하는 것. 쓰기 버퍼는 메모리에 쓰이기 위해 기다리는 동안 데이터를 저장하는 큐이다. 데이터를 캐시와 쓰기 버퍼에 쓰고 난 후에 프로세서는 수행을 계속할 수 있다. 메인 메모리에 쓰기를 완료 -> 버퍼에 있는 엔트리는 다시 비게 된다. 프로세서가 쓰기 처리를 하려할 때 쓰기 버퍼가 모두 차 있으면 쓰기 버퍼에 빈공간이 생길 때까지 멈춰있어야 한다. 물론 메모리가 쓰기를 완료할 수 있는 속도가 프로세서의 쓰기 동작 발생 속도보다 느리면, 아무리 큰 버퍼를 사용해도 도움이 되지 않는다. 왜냐하면 메모리 시스템이 쓰기를 받아들이는 것보다도 쓰기가 더 빨리 발생되기 때문이다.

쓰기 신호가 발생되는 속도가 메모리가 받아들이는 속도보다 느리다고 해도 지연이 발생할 수 있다. 쓰기 신호가 폭주 할때. 이러한 지연이 일어나는 것을 줄이기 위해서는 컴퓨터의 쓰기 버퍼(큐) 크기를 두 배 이상으로 늘려야 한다.

즉시 쓰기 방식의 대안은 나중 쓰기(write -back)이라고 불리는 방식이다. 이 나중 쓰기 방식에서는 쓰기가 발생했을 때 새로운 값은 캐시 내의 블록에서만 쓴다. 그러다가 나중에 캐시에서 쫓겨날 때 쓰기에 의해 내용이 바뀐 블록이면 메모리 계층구조의 더 낮은 계층에 써짐. 나중 쓰기 방식은 특히 메인 메로리가 처리할 수 있는 속도보다 프로세서가 쓰기를 더 빠르게 발생시키는 경우 성능을 향상 시킬 수 있다. 다만 구현이 어렵다.

그래서 나중쓰기란?
단지 캐시에 있는 블록 값을 갱신해 쓰기를 하고 그 블록이 교체될 때 메모리 계층구조의 하위 계층에 갱신된 블록이 쓰이는 방식

  • 즉시 쓰기 캐시의 실패 시 가장 일반적인 방법은 캐시에 블록을 할당하는 것. 이를 쓰기 할당(write allocate)이라 부른다. 일단 전체 블록을 메모리에서 읽은 후, 블록 중에서 쓰기 실패를 발생시킨 워드만 덮어 쓴다.
  • 다른 방법은 메모리에 있는 블록의 해당 영역만 갱신하고 캐시에는 쓰지 않는 방식으로 쓰기 비할당(no write allocate)라고 부른다. 운영체제가 메모리의 한 페이지를 지워버리는 것과 같이 프로그램이 때때로 데이터의 모든 블록을 쓰기 때문에 사용. 이 경우 초기 쓰기 실패와 관련한 가져오기는 불필요.
  • some 컴퓨터 페이지마다 쓰기할당정책 바뀌는 것을 허용
  • 실제로 나중 쓰기 방식 효과적으로 구현하는 것은 복잡. 캐시에 있는 데이터가 갱신된 값인데 캐시 실패가 발생하면, 그 블록을 먼저 메모리에 써야만 한다. 저장 명령어가 캐시 적중이 확인되기도 전에 덮어 쓰면(즉시 쓰기처럼), 하위 계층 메모리에 저장하지도 않은 블록을 망가뜨린다.
  • 나중 쓰기 캐시는 블록에 바로 덮어 쓸 수 없기 때문에, 두 사이클(적중인지를 확인하는 데 한 사이클과 실제로 쓰기를 수행하는 한 사이클)을 사용하거나 또는 데이터를 보관하기 위해 저장 버퍼(store buffer)를 사용함.
  • 이 버퍼는 쓰기 과정을 파이프라이닝해 저장 명령어가 한 사이클만 걸린다. 저장 버퍼를 사용하면 정상적인 캐시 접근 사이클에 프로세서가 캐시를 검색하고 데이터를 저장버터에 넣는다. 검색 결과가 캐시 적중으로 밝혀지면 사용되지 않는 캐시 접근 사이클이 나올 때까지 기다렸다가 새로운 데이터를 저장 버퍼에서 꺼내 캐시에 쓴다.
  • 반면에 즉시 쓰기 캐시에서는 쓰기가 항상 한 사이클에 수행된다. 태그 값을 읽고 선택된 블록의 데이터 영역에 쓰기만 하면 된다.
  • 만약에 태그 값이 써지는 블록의 주소와 일치 ==> 맞는 블록이 갯인됨. 때문에 프로세서는 정상 동작을 수행.
  • 만약 태그가 일치하지 않으면 ==> 프로세서는 그 주소에 해당하는 블록의 나머지 부분을 인출하기 위해 쓰기 실패를 발생시킨다.
  • 많은 나중 쓰기 캐시들은 실패 시 이미 갱신된 블록을 교체할 때 생기는 실패 손실을 줄이기 위해 쓰기 버퍼를 사용.
  • 이 경우 요청한 블록이 메모리에서 읽히는 동안 캐시에 있던 갱신된 블록은 나중 쓰기 버퍼로 옮겨진 후 나중에 메모리로 쓰인다. 다른 실패가 즉시 실패하지 않는다면, 이 기법은 갱신된 블록이 교체될 때의 실패 손실을 절반으로 줄일 수 있다.

캐시의 한 예: intrinsity FastMATH 프로세서

intrinsity FastMATH는 MIPS 구조와 단순한 캐시 형식을 취한 빠른 임베디드 마이크로프로세서. 아래 그림은 FastMATH의 데이터 캐시 구조를 보여준다. 처리 단계는 다음과 같다.

그림 5.12 intrinsity FastMATH 프로세서에 사용되는 16KiB 캐시는 블록당 16 워드를 갖는 256 개의 블록으로 구성되어 있다

태그 필드는 18비트이고 인덱스 필드는 8비트. 블록 내부를 인덱스 하는데는 4비트가 필요. 요구한 워드를 선택하기 위해 16:1 멀티플렉서를 사용한다. 실제로는 태그와 데이터를 별도의 RAM에 저장. 캐시 인덱스와 블록 변위를 연접하여 데이터 RAM 주소로 사용하면 멀티플렉서를 없앨 수 있다. 이 경우 데이터 RAM의 폭은 32비트이며 캐시보다 16배 많은 블록을 가져야 한다.

  1. 주소를 해당 캐시에 보낸다. 주소는 PC(명령어의 경우)나 ALU(데이터의 경우)로부터 나온다.
  2. 캐시 적중일 경우 요청된 워드가 캐시의 데이터 선에서 나온다. 요청된 블록에는 16새의 워드가 존재하므로 정확한 워드를 선택해야 함. 블록 인덱스 필드가 멀티플렉서 제어를 위해 사용된다(그림에서 Block offset 신호) 이 신호는 블록 내 16개의 워드 중에서 요청한 워드를 선택
  3. 캐시 실패일 경우 메인 메모리로 주소를 보내야함. 메모리가 데이터를 보내주면, 캐시에 쓰고 난 뒤 요청을 처리하기 위해 읽는다.

intrinsity FastMATH는 즉시 쓰기와 나중 쓰기 두 가지 방식을 다 지원. 운영체제가 선택. 한 항목만 담을 수 있는 쓰기 버퍼도 갖고 있다. intrinsity FastMATH에서 사용되는 캐시의 실패율. 실패율이 캐시 설계의 중요한 특성 but 궁극적인 척도는 프로그램 수행 시간에 메모리 시스템이 미치는 영향. 실패율과 수행 시간의 연관을 잠시 살펴보자.

분할 캐시(split cash)와 같은 크기를 갖는 통합 캐시(combined cache)는 일반적으로 더 좋은 적중률. 통합 캐시는 데이터가 상요하는 엔트리와 명령어가 상요하는 엔트리의 수를 엄밀하게 나누지 않기 때문에 더 높은 적중률. 그럼에도 불구 많은 컴퓨터들이 캐시 대역폭 늘리기 위해 분리된 명령어와 캐시를 사용.

  • 다음은 intrinsity FastMATH 프로세서의 캐시 크기에 대한 실패율. 분할 캐시와 통합캐시에 대한 실패율은 다음과 같음
    - 전체 캐시 크기 : 32KiB
    • 분할 캐시의 유효 실패율 : 3.24%
    • 통합 캐시의 실패율: 3.18 %
      분할 캐시의 실패율이 더 나쁘게 나타남. 명령어와 데이터 접근을 동시에 지원함으로써 캐시 대역폭을 두 배로 늘려 얻을 수 있는 이저이 실패율이 증가되는 단점보다 크다. 곧바로 알아볼 예정. 그래서 캐시 성능 측정 지수로 실패율 만을 사용할 수는 없음

0개의 댓글