
No need for programmer or compiler intervention
No changes to existing executables
Take advantage of run-time information
앞 글의 프리페칭은 프로그래머나 컴파일러가 "이거 미리 가져와" 하고 코드에 넣는 방식이었어요. 하드웨어 프리페칭은 CPU가 실행 중에 메모리 접근 패턴을 지켜보다가 알아서 미리 가져오는 방식이에요.
프로그램을 고칠 필요가 없어요. 이미 만들어진 실행 파일도 그대로 빨라져요.
실행 중에 실제로 일어나는 일을 보고 예측해요.
Tagged prefetch: 미리 가져온 블록에 표시(tag)를 해 둬요. 그 블록이 처음 쓰이는 순간 "이 예측이 맞았네" 하고 다음 블록을 가져와요. 실제로 쓰이는 속도에 맞춰 한 발씩 앞서가는 방식이에요.
페이지를 넘지 않는 이유: 가상 메모리에서 바로 다음 페이지가 실제 메모리에서도 옆에 있다는 보장이 없어요. 아예 할당이 안 돼 있을 수도 있어요. 함부로 넘어가면 엉뚱한 데이터를 가져오거나 페이지 폴트를 일으킬 수 있어요.
미스가 나면 그 다음 블록 하나도 같이 가져와요.
블록 A에서 미스 → A를 가져오면서 A+1도 미리 가져옴
→ 프로그램이 A+1을 쓸 때 이미 와 있음 (미스 없음)
효과는 블록 크기를 키운 것과 비슷해요. 하지만 실제 블록 크기는 그대로라서, 큰 블록의 단점(블록 개수가 줄어드는 것)은 없어요.
인텔은 이걸 “Adjacent Cache Line Prefetch”라고 불러요.
한 번에 N개를 가져오도록 늘릴 수도 있어요. N은 고정일 수도 있고, 상황에 따라 바뀔 수도 있어요.
Pros:
Cons:
문제: 미스가 났을 때 N개를 한꺼번에 가져오면, N개를 다 쓰고 나서 또 미스가 나요. N블록마다 한 번씩 멈추는 셈이에요.
해결: 미리 가져온 블록이 하나 쓰일 때마다 하나를 더 가져와서, 항상 N개 앞서 있게 해요. 이걸 위한 별도 공간이 스트림 버퍼예요.
Sequential prefetching problem
Stream prefetching is a continuous version of prefetching
Design issues

동작 예시 (N = 4)
① A에서 캐시 미스
→ A는 캐시로, A+1 ~ A+4는 스트림 버퍼로 가져오기 시작
스트림 버퍼: [A+1][A+2][A+3][A+4] (도착 전엔 "아직 안 옴" 표시)
② 프로그램이 A+1 요청 → 캐시엔 없지만 스트림 버퍼에 있음!
→ 1사이클 만에 캐시로 옮김, 나머지 한 칸씩 앞으로, 빈자리에 A+5 가져오기
스트림 버퍼: [A+2][A+3][A+4][A+5]
③ A+2 요청 → 또 버퍼에서 → [A+3][A+4][A+5][A+6] ... 계속 4개 앞서감
④ 갑자기 전혀 다른 주소 Z에서 미스 (순서가 끊김)
→ 버퍼를 비우고 Z부터 다시 시작: [Z+1][Z+2][Z+3][Z+4]
왜 캐시가 아니라 별도 버퍼에 넣을까? 예측이 틀리면 쓸데없는 데이터가 캐시의 좋은 데이터를 밀어내요(캐시 오염). 버퍼에 두면 실제로 쓰일 때만 캐시로 들어가니 안전해요.
각 칸에는 "데이터 도착함/아직" 표시(available bit)가 있어요. 요청한 블록이 아직 오는 중이면 기다려요.
비유: 회전초밥 레일에 다음 접시 4개를 항상 올려 두는 것. 하나 집으면 하나를 새로 올려요. 손님이 갑자기 다른 메뉴를 시키면 레일을 비우고 새 메뉴로 채워요.
마지막 단점을 해결하는 방법이에요. 루프에서는 같은 명령어가 매번 일정한 간격으로 떨어진 주소를 읽는 경우가 많아요.
같은 명령어가 읽은 주소: 1000 → 1400 → 1800 → 2200 ...
(+400) (+400) (+400) ← 간격 D = 400
명령어 mi가 반복할 때마다 주소 a1, a2, a3…를 읽는다고 해요.
순차 프리페칭과의 차이: 순차는 "바로 옆"만 가져오지만, stride는 얼마나 건너뛰는지 학습해서 정확히 그 위치를 가져와요.
stride 프리페칭을 실제로 구현하는 표예요. 명령어마다 한 줄씩, 그 명령어의 접근 패턴을 기록해요.
Hold information for the most recently used memory instructions to predict their access pattern
it's not in the Final test
but it's used alot in industry


float a[100][100], b[100][100], c[100][100];
for (i = 0; i < 100; i++)
for (j = 0; j < 100; j++)
for (k = 0; k < 100; k++)
a[i][j] += b[i][k] * c[k][j]




예를 들어 b가 주소 50000, c가 90000, a가 10000에서 시작한다고 하면 RPT는 이렇게 변해요.
1회차: b: 이전=50000 stride=0 initial (처음 봄)
c: 이전=90000 stride=0 initial
a: 이전=10000 stride=0 initial
2회차: b: 이전=50004 stride=4 transient (간격 4 발견, 확인 중)
c: 이전=90400 stride=400 transient (간격 400 발견)
a: 이전=10000 stride=0 steady (같은 주소 → 간격 0이 맞음)
3회차: b: 이전=50008 stride=4 steady → 50012 미리 가져옴
c: 이전=90800 stride=400 steady → 91200 미리 가져옴
핵심은 명령어(PC)별로 따로 기록한다는 점이에요. 그래서 b, c처럼 간격이 서로 다른 접근이 섞여 있어도 각각 정확히 예측할 수 있어요.
for (i=0; i<N; i++) {
prefetch(a[i+8]); // 8번 뒤에 쓸 a를 지금 미리 요청
prefetch(b[i+8]); // b도 마찬가지
sum += a[i]*b[i]; // 지금 계산은 이미 와 있는 데이터로
}
Doesn’t have to be correct!
prefetch(N+8);
프로그래머나 컴파일러가 “8번 뒤에 쓸 데이터를 지금 가져와 둬” 라고 코드에 직접 넣는 방식이에요. 앞 글의 하드웨어 프리페칭과 반대예요.
왜 8번 뒤일까? 메모리에서 데이터가 오는 데 시간이 걸리니까, 그 시간 동안 루프가 몇 번 돌지 계산해서 정해요. 지금 요청하면 8번 돌고 나서 도착하는 셈이라, 그때 쓸 데이터를 지금 요청하는 거예요.
“Doesn’t have to be correct!”의 의미
루프 끝부분에서는 a[N+7]처럼 배열 범위를 벗어난 주소를 프리페치해요. 그래도 괜찮아요. 프리페치는 "가능하면 가져와 줘"라는 힌트일 뿐이라서, 틀려도 결과는 바뀌지 않고 대역폭만 조금 낭비돼요. 일반 load였다면 프로그램이 죽을 수도 있는 주소예요.

Issuing Prefetch Instructions takes time
프리페치 명령어가 갖춰야 할 조건
비용 문제
프리페치도 명령어라서 실행하는 데 시간이 걸려요. 그래서 따져봐야 해요.
프리페치 명령어 실행 비용 < 줄어든 미스로 아끼는 시간 이어야 이득
예를 들어 이미 캐시에 있는 데이터를 프리페치하면 시간만 날려요. 아래 Ex 1에서 이게 중요해져요.