캐시 라인은 어떻게 작동합니까?


166

프로세서는 캐시 라인을 통해 데이터를 캐시에 가져옵니다. 예를 들어, Atom 프로세서에서 실제 데이터의 크기에 관계없이 한 번에 약 64 바이트를 가져옵니다.

내 질문은 :

캐시에서 가져올 64 바이트는 메모리에서 1 바이트를 읽어야한다고 상상해보십시오.

내가 볼 수있는 두 가지 가능성은 64 바이트가 관심있는 바이트 아래의 가장 가까운 64 바이트 경계에서 시작하거나 64 바이트가 미리 결정된 방식으로 바이트 주위에 퍼져 있다는 것입니다 (예 : 절반 이하, 절반 위 또는 모든 것 위에).

무엇 이니?


22
이것을 읽으십시오 : 모든 프로그래머가 기억해야 할 것들 . 그런 다음 다시 읽으십시오. 더 나은 (pdf) 출처 here .
andersoj

답변:


128

로드중인 바이트 또는 단어를 포함하는 캐시 라인이 캐시에 아직없는 경우 CPU는 캐시 라인 경계에서 시작하는 64 바이트 (필요한 주소 아래의 가장 큰 주소 인 64의 배수)를 요청합니다. .

최신 PC 메모리 모듈은 한 번에 64 비트 (8 바이트)를 8 번의 전송 버스트로 전송 하므로 한 명령으로 메모리에서 전체 캐시 라인의 읽기 또는 쓰기를 트리거합니다. (DDR1 / 2 / 3 / 4 SDRAM 버스트 전송 크기는 최대 64B로 구성 가능합니다. CPU는 캐시 라인 크기와 일치하도록 버스트 전송 크기를 선택하지만 64B는 일반적입니다)

일반적으로 프로세서가 메모리 액세스를 예측할 수없고 (프리 페치 할 수없는 경우) 검색 프로세스는 ~ 90 나노초 또는 ~ 250 클럭 사이클 (주소를 알고있는 CPU에서 데이터를 수신하는 CPU까지)이 걸릴 수 있습니다.

대조적으로 L1 캐시의 적중은 3 또는 4주기의로드 사용 대기 시간을 가지며, 상점 재로드는 최신 x86 CPU에서 4 또는 5주기의 상점 전달 대기 시간을 갖습니다. 다른 아키텍처에서도 상황이 비슷합니다.

추가 자료 : Ulrich Drepper의 모든 프로그래머가 기억해야 할 사항 . 소프트웨어 프리 페치 조언은 약간 구식입니다. 최신 HW 프리 페처는 더 똑똑하고 하이퍼 스레딩은 P4 일보다 훨씬 좋습니다 (따라서 프리 페치 스레드는 일반적으로 낭비입니다). 또한 태그 위키에는 해당 아키텍처에 대한 많은 성능 링크가 있습니다.


1
이 대답은 전혀 의미가 없습니다. 64 비트 (!)와 64 비트 메모리 대역폭은 비트와 무관하게 어떤 관계가 있습니까? 또한 Ram에 부딪 치면 10 ~ 30ns도 완전히 잘못되었습니다. L3 또는 L2 캐시에는 해당되지만 90ns 이상의 RAM에는 해당되지 않습니다. 버스트 시간은 버스트 모드에서 다음 쿼드 워드에 액세스 할 수있는 시간입니다 (실제로 정답입니다)
Martin Kersten

5
@MartinKersten : DDR1 / 2 / 3 / 4 SDRAM의 한 채널은 64 비트 데이터 버스 폭을 사용합니다. 전체 캐시 라인의 버스트 전송은 각각 8B의 8 회 전송을 수행하며 실제로 발생합니다. 원하는 바이트를 포함하는 8B 정렬 청크를 먼저 전송하여 (즉, 버스트를 시작하고 버스트 전송 크기의 첫 번째 8B가 아닌 경우 래핑) 프로세스를 최적화하는 것이 여전히 옳습니다. 멀티 레벨 캐시가있는 최신 CPU는 버스트의 첫 번째 블록을 L1 캐시로 일찍 릴레이하는 것을 의미하기 때문에 더 이상 그렇게하지 않을 것입니다.
Peter Cordes

2
Haswell은 L2와 L1D 캐시 사이의 64B 경로 (즉, 전체 캐시 라인 너비)를 가지므로 요청 된 바이트를 포함하는 8B를 전송하면 해당 버스를 비효율적으로 사용할 수 있습니다. @Martin은 메인 메모리로 가야하는로드의 액세스 시간에 대해서도 정확합니다.
Peter Cordes

3
데이터가 한 번에 메모리 계층 구조로 올라가는 지 또는 L3이 메모리에서 L2까지 데이터를 보내기 전에 전체 라인을 기다리는 지에 대한 좋은 질문입니다. 서로 다른 레벨의 캐시 사이에 전송 버퍼가 있으며 각각의 미해결 요구 사항이 있습니다. 따라서 ( 총 추측 ) L3은 메모리 컨트롤러의 바이트를 원하는 L2 캐시의 적절한로드 버퍼에 넣는 것과 동시에 메모리 컨트롤러의 바이트를 자체 수신 버퍼에 넣습니다. 라인이 메모리에서 완전히 전송되면 L3은 L2에 라인이 준비되었음을 알리고이를 자체 어레이에 복사합니다.
Peter Cordes

2
@ 마틴 : 나는이 답변을 편집하기로 결정했습니다. 나는 그것이 더 정확하고 여전히 간단하다고 생각합니다. 미래 독자 : Mike76의 질문과 대답 : stackoverflow.com/questions/39182060/…
Peter Cordes

22

캐시 라인의 폭이 64 바이트 인 경우, 이는 64로 나눌 수있는 주소에서 시작하는 메모리 블록에 해당합니다. 주소의 최하위 6 비트는 캐시 라인에 대한 오프셋입니다.

따라서 임의의 주어진 바이트에 대해, 페치되어야하는 캐시 라인은 주소의 가장 작은 6 비트를 클리어함으로써 찾을 수 있는데, 이는 64로 나눌 수있는 가장 가까운 어드레스로 반올림하는 것에 해당한다.

이 작업은 하드웨어에서 수행되지만 일부 참조 C 매크로 정의를 사용하여 계산을 표시 할 수 있습니다.

#define CACHE_BLOCK_BITS 6
#define CACHE_BLOCK_SIZE (1U << CACHE_BLOCK_BITS)  /* 64 */
#define CACHE_BLOCK_MASK (CACHE_BLOCK_SIZE - 1)    /* 63, 0x3F */

/* Which byte offset in its cache block does this address reference? */
#define CACHE_BLOCK_OFFSET(ADDR) ((ADDR) & CACHE_BLOCK_MASK)

/* Address of 64 byte block brought into the cache when ADDR accessed */
#define CACHE_BLOCK_ALIGNED_ADDR(ADDR) ((ADDR) & ~CACHE_BLOCK_MASK)

1
나는 이것을 이해하기가 어렵다. 나는 2 년 후인 것을 알고 있지만 이것에 대한 예제 코드를 줄 수 있습니까? 하나 또는 두 줄.
Nick

1
@Nick이 방법이 작동하는 이유는 이진수 시스템에 있습니다. 2의 거듭 제곱에는 하나의 비트 세트 만 있고 나머지 비트는 모두 지워졌으므로 64의 0b1000000경우 마지막 6 자리가 0임을 알 수 있습니다. % 64)를 지우면 가장 가까운 64 바이트 정렬 메모리 주소가 제공됩니다.
legends2k

21

우선 주 메모리 액세스는 매우 비쌉니다. 현재 2GHz CPU (가장 느린 속도)에는 초당 2G 틱 (사이클)이 있습니다. CPU (현재 가상 코어)는 틱당 한 번 레지스터에서 값을 가져올 수 있습니다. 가상 코어는 여러 처리 장치 (ALU-산술 논리 장치, FPU 등)로 구성되므로 가능한 경우 특정 명령을 실제로 병렬로 처리 할 수 ​​있습니다.

메인 메모리 액세스 비용은 약 70ns ~ 100ns입니다 (DDR4가 약간 빠름). 이번에는 기본적으로 L1, L2 및 L3 캐시를 찾고 메모리 (메모리 컨트롤러에 명령을 보내 메모리 뱅크로 전송)를 누르고 응답을 기다렸다가 완료합니다.

100ns는 약 200 틱을 의미합니다. 따라서 기본적으로 프로그램이 항상 각 메모리가 액세스하는 캐시를 놓치면 CPU는 메모리를 기다리는 동안 유휴 상태 (메모리 만 읽는 경우)의 약 99,5 %를 소비합니다.

속도를 높이기 위해 L1, L2, L3 캐시가 있습니다. 그들은 칩에 직접 놓인 메모리를 사용하고 다른 종류의 트랜지스터 회로를 사용하여 주어진 비트를 저장합니다. CPU는 일반적으로 고급 기술을 사용하여 생성되고 L1, L2, L3 메모리에서 생산 오류가 발생하여 CPU를 쓸모 없게 만들 수 있기 때문에 더 많은 공간과 에너지가 필요하며 주 메모리보다 비용이 많이 듭니다. 큰 L1, L2, L3 캐시는 오류율을 증가시켜 ROI를 직접 감소시키는 수율을 감소시킵니다. 따라서 사용 가능한 캐시 크기와 관련하여 큰 절충안이 있습니다.

(현재 실제 생산 결함이 캐시 메모리 영역이 CPU 결함을 전체적으로 렌더링 할 가능성을 줄이기 위해 특정 부분을 비활성화 할 수 있도록 L1, L2, L3 캐시를 더 생성합니다).

타이밍 아이디어를 제공하려면 (출처 : 캐시 및 메모리 액세스 비용 )

  • L1 캐시 : 1ns ~ 2ns (2-4주기)
  • L2 캐시 : 3ns ~ 5ns (6-10 사이클)
  • L3 캐시 : 12ns ~ 20ns (24-40주기)
  • RAM : 60ns (120 회)

우리는 서로 다른 CPU 유형을 혼합하기 때문에 추정치 일 뿐이지 만 메모리 값을 가져올 때 실제로 어떤 일이 발생하는지 알고 특정 캐시 계층에서 적중이나 누락이 발생할 수 있습니다.

따라서 캐시는 기본적으로 메모리 액세스 속도를 크게 향상시킵니다 (60ns 대 1ns).

값을 가져 와서 다시 읽을 수 있도록 캐시에 저장하면 자주 액세스하는 변수에는 적합하지만 메모리 복사 작업의 경우 값을 읽고 어딘가에 값을 쓰고 절대 읽지 않기 때문에 여전히 느려집니다. 다시 ... 캐시 적중이없고, 죽은 느린 (이것은 우리가 순서가 잘못되어 병렬로 발생할 수 있습니다).

이 메모리 사본은 매우 중요하므로 속도를 높일 수있는 다른 방법이 있습니다. 초기에는 메모리가 종종 CPU 외부의 메모리를 복사 할 수있었습니다. 메모리 컨트롤러에서 직접 처리했기 때문에 메모리 복사 작업으로 인해 캐시가 오염되지 않았습니다.

그러나 일반 메모리 사본 외에도 메모리의 다른 직렬 액세스가 매우 일반적이었습니다. 일련의 정보를 분석하는 것이 한 예입니다. 정수 배열을 가지며 합계, 평균, 평균 또는 더 간단한 특정 값 찾기 (필터 / 검색)는 범용 CPU에서 매번 실행되는 또 다른 매우 중요한 알고리즘 클래스였습니다.

따라서 메모리 액세스 패턴을 분석함으로써 데이터를 매우 자주 읽는다는 것이 명백해졌습니다. 프로그램이 인덱스 i에서 값을 읽는 경우 프로그램도 값 i + 1을 읽을 가능성이 높습니다. 이 확률은 같은 프로그램이 i + 2 등을 읽는 확률보다 약간 높습니다.

따라서 메모리 주소가 주어지면 미리 읽고 추가 값을 가져 오는 것이 좋습니다. 이것이 부스트 모드가있는 이유입니다.

부스트 모드에서의 메모리 액세스는 주소가 전송되고 여러 값이 순차적으로 전송됨을 의미합니다. 각각의 추가 값 전송에는 추가로 약 10ns (또는 그 이하)가 소요됩니다.

또 다른 문제는 주소였습니다. 주소를 보내는 데 시간이 걸립니다. 메모리의 많은 부분을 처리하려면 큰 주소를 보내야합니다. 초기에는 주소 버스가 단일주기 (틱)로 주소를 전송하기에 충분히 크지 않았으며 더 많은 지연을 추가하여 주소를 전송하기 위해 하나 이상의주기가 필요했습니다.

예를 들어 64 바이트의 캐시 라인은 메모리가 64 바이트 크기의 별개의 (겹치지 않는) 메모리 블록으로 분할됨을 의미합니다. 64 바이트는 각 블록의 시작 주소가 가장 낮은 6 개의 주소 비트를 가지며 항상 0임을 의미합니다. 따라서 매번이 6 개의 0 비트를 전송할 필요가 없으며, 주소 버스 폭에 관계없이 주소 공간을 64 배 늘릴 필요가 없습니다 (환영).

캐시 라인이 해결하는 또 다른 문제 (주소 버스에서 6 비트를 저장 / 해제 / 저장하는 것 외에)는 캐시가 구성되는 방식에 있습니다. 예를 들어, 캐시가 8 바이트 (64 비트) 블록 (셀)으로 분할 될 경우, 메모리 셀의 주소를 저장해야하는 경우이 캐시 셀은 그와 함께 값을 보유합니다. 주소가 64 비트 일 경우 이는 캐시 크기의 절반이 주소에서 소비되어 100 %의 오버 헤드가 발생 함을 의미합니다.

캐시 라인이 64 바이트이고 CPU가 64 비트-6 비트 = 58 비트 (0 비트를 너무 정확하게 저장할 필요가 없음)를 사용할 수 있으므로 오버 헤드 58 비트 (11 % 오버 헤드)로 64 바이트 또는 512 비트를 캐시 할 수 있습니다. 실제로 저장된 주소는 이보다 훨씬 작지만 상태 정보가 있습니다 (캐시 라인이 유효하고 정확하고 더럽고 램으로 다시 써야하는 등).

또 다른 측면은 세트 연관 캐시가 있다는 것입니다. 모든 캐시 셀이 특정 주소를 저장할 수있는 것은 아니며 일부 주소 만 저장할 수 있습니다. 이것은 필요한 저장된 주소 비트를 더 작게 만들고, 캐시의 병렬 액세스를 허용합니다 (각 서브 세트는 한 번 액세스 할 수 있지만 다른 서브 세트와는 독립적 임).

다른 가상 코어, 코어 당 독립적 인 다중 처리 장치 및 하나의 메인 보드 (최대 48 개 이상의 프로세서를 수용하는 보드가 있음)에 여러 개의 프로세서간에 캐시 / 메모리 액세스를 동기화 할 때 더욱 특히 문제가 있습니다.

이것이 기본적으로 현재 캐시 라인을 가진 이유입니다. 미리 읽는 것의 이점은 매우 높으며 캐시 라인에서 단일 바이트를 읽고 다시 읽지 않는 최악의 경우는 확률이 매우 얇기 때문에 매우 얇습니다.

캐시 라인 (64)의 크기는 더 큰 캐시 라인들 사이에서 현명하게 선택된 트레이드 오프 (trade-off)로서, 가까운 미래에도 전체 캐시 라인을 페치하는 데 걸리는 시간 동안 마지막 바이트가 읽히지 않을 수있다 메모리에서 (및 다시 쓰기) 캐시 구성의 오버 헤드와 캐시 및 메모리 액세스의 병렬화.


1
세트 연관 캐시는 일부 주소 비트를 사용하여 세트를 선택하므로 태그가 예제보다 짧을 수 있습니다. 물론, 캐시는 또한 어떤 태그가 어떤 데이터 배열과 어떤 세트에 속하는지 추적해야하지만, 일반적으로 한 세트 내의 방법보다 더 많은 세트가 있습니다. (예 : Intel x86 CPU에서 32BB 8 웨이 연관 L1D 캐시 (64B 라인 포함) : 오프셋 6 비트, 인덱스 6 비트. x86-64 (현재)에는 48- 비트 만 있으므로 태그는 48-12 비트 만 필요합니다. 알다시피, 낮은 12 비트가 페이지 오프셋이므로 L1이 앨리어싱없이 VIPT가 될 수 있음은 우연이 아닙니다.)
Peter Cordes

놀라운 답변 새싹 ... "어딘가에"버튼이 어디에 있습니까?
Edgard Lima

@EdgardLima, upvote 버튼이 아니십니까?
Pacerier

6

프로세서에는 다중 레벨 캐시 (L1, L2, L3)가있을 수 있으며 크기와 속도가 다릅니다.

그러나 각 캐시에 정확히 들어가는 내용과 해당 특정 프로세서에서 사용하는 분기 예측 변수 및 프로그램의 명령 / 데이터가 어떻게 작동하는지 이해해야합니다.

분기 예측기 , CPU 캐시교체 정책 에 대해 읽으십시오 .

이것은 쉬운 일이 아닙니다. 하루가 끝나고 성능 테스트를 원하는 경우 Cachegrind 와 같은 도구를 사용할 수 있습니다 . 그러나 이것은 시뮬레이션이므로 결과가 다소 다를 수 있습니다.


4

모든 하드웨어가 다르기 때문에 확실하게 말할 수는 없지만 일반적으로 "64 바이트는 가장 가까운 64 바이트 경계에서 시작합니다"는 CPU에 대한 매우 빠르고 간단한 조작이기 때문입니다.


2
나는 확실히 말할 수 있습니다 . 합리적인 캐시 디자인은 2의 거듭 제곱과 자연스럽게 정렬 된 크기의 라인을 갖습니다. (예 : 64B 정렬). 빠르고 간단하지 않고 문자 그대로 무료입니다. 예를 들어 주소의 하위 6 비트 만 무시하십시오. 캐시는 종종 주소 범위가 다른 여러 가지 작업을 수행합니다. (예 : 캐시는 적중과 미스를 감지하기 위해 태그와 인덱스를 관리 한 다음 데이터를 삽입 / 추출하기 위해 캐시 라인 내 오프셋 만 사용)
Peter Cordes
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.