[Study - CS > Computer Architecture] 캐시 (Cache)
Cache
memory에 접근하는 것은 느리지만, processor가 연산하는 것이 훨씬 빠르다.
memory에서 data를 읽고 쓰기 위해 access하는 성능이 느려서, 그동안 processor는 놀고 있게 된다.
따라서 자주 사용하는 데이터는 memory까지 가지 않고, processor 안에 미리 가져와서(Prefetching) 사용함으로써 processor가 계속 바쁘게 돌아갈 수 있게 만드는 기술이다.
즉, Cache는 memory에 접근하는 비용을 줄이는 기법 중 하나이다.
processor 입장에서 cache는 성능 개선 부분에서 굉장히 중요한 역할을 한다. (요즘 HW에서 점점 processor의 상당 부분을 cache가 차지하고 있는 추세이다.)
- processor의 성능은 빨라지는데 memory가 그만큼 못 따라오니 시간이 지날수록 성능 차이가 커진다.
- application이 점점 더 data를 많이 쓰게 되어 이를 커버하기 위해 cache의 크기가 커진다.
HW적으로 보면 cache는 SRAM이고, memory는 DRAM이다.
- SRAM : access가 빠르지만, capacity가 커질 수 없다.
- DRAM : access가 느리지만, capacity가 더 크다.
Cache capacity
cache의 capacity가 몇 MB라면, memory의 capacity는 수~수십 GB일 것이다.
cache의 capacity의 크기에 따라서 담을 수 있는 데이터가 결정된다.
capacity가 크면 생기는 장점과 단점이 있다.
- 장점 : 많은 양의 데이터를 cache 안에 보관할 수 있다.
- 단점 : cache 안에서 찾아야 할 데이터가 많아, 찾는데 오래 걸린다.
Cache block
- Memory
memory에 접근할 때 address 기준으로 접근하여 data를 찾아서 가져오거나 기록한다.이때 address는 byte 단위라, memory를 byte addressable하다고 말한다.
- Cache
cache는 byte 보다 큰 chunk인 block 단위로 접근한다. (64byte 또는 32byte 사용)
매번 memory를 access해서 data를 가져오는 것은 비용이 커서 한번에 많이 가져온다.
Hit, Miss
cache 안에 찾는 data가 있으면 hit, 없으면 miss라고 한다.
- Hit latency : 접근하고자 하는 data가 cache에 있을 때, 이 data를 가져오는 데 걸리는 시간
L1은 1~3 cycle이 걸리고, L1에 없어서 L2로 갔을 때는 8~20 cycle이 걸린다.
따라서 최대한 L1 cache에 있으면 좋다. (L1이 Processor와 더 가까움) - Miss latency : cache 안에 data가 없어서 아래 단계 cache나 memory에서 data를 가져오는 데 걸리는 시간
Cache의 구성
32KB의 cache가 있다고 가정하고, cache block sizefmf 32byte라고 가정
memory는 address 단위로 접근하고, cache는 block 단위로 접근한다.
memory의 address는 32bit로 나타내고, 한 공간마다 1byte의 address로 접근한다.
크기가 32KB이고, block size가 32byte이니, block의 개수는 32KB/32byte = 1K로 1024가 된다.
즉, cache는 32byte 크기의 block이 1024개 있는 것이다.
cache 안에 data가 없어서 memory에 접근하여 data를 가져올 때, 1byte만 가져오는 것이 아니라 연속된 메모리 주소 32byte의 1block 단위로 가져온다.
cache는 block size가 보통 32byte 또는 64byte이다.
memory에서는 1byte 단위로 접근하는데 cache는 왜 큰 단위로 접근하는 이유
- memory에서 data를 가져오는데 비용이 많이 들어서 한번에 가져온다.
- Spatial locality가 있다.
Spatial locality : 접근했던 data 근처에 있는 data들이 다음에 접근될 확률이 높다.
Indexing
Indexing이란 접근하는 memory address에 대해 cache 안의 1024개 공간 중 어디에 있는지 찾는 방법이다.
cache의 block이 총 1024개이면, 1024 = 2^10 을 표현하기 위해서 10bit가 필요하다.
이 10bit를 memory address에서 뽑아와서 사용하는 것이 Indexing이다.
memory address가 32bit 일때, 32bit 중에서 5~14번째 bit를 사용한다.
만약 32KB cache가 아니라 64KB cache인 경우
block의 개수는 64KB/32byte = 2KB = 2048개이고, 2^11로 11bit 이다.
11bit 이므로, Index bit는 5~15번째 bit를 사용하면 된다.
Block offset
결국 필요한 data에 접근하기 위해서는 memory 접근 단위인 byte 단위로 접근해야 한다.
즉, 32 byte 중 몇 번째 byte에 접근하는지를 알아야 한다.
32 = 2^5 로, 5bit를 block offset이라고 한다.
block offset은 memory address의 0~4번째 bit를 사용한다.
Tag
Indexing에는 문제점이 있다.
두 개의 memory address가 있을 때, 0~14번 bit(block offset + index bit)는 같고, 15~31번 bit가 다르면
cache에서는 같은 공간의 같은 data에 접근하는 것인데, 실제 접근하고자 하는 memory data와는 다르게 되어 충돌이 나는 것이다.
0x12345678과 0x43215678은 다른 memory address 공간인데,
0~14bit를 보면 5678로 같은 cache 공간을 가리키게 된다.
만약 충돌이 난다면, cache 공간에 있던 data를 memory에 저장하고, 지금 접근하는 data를 cache 공간에 넣는 방법으로 Indexing 문제를 해결한다.
이 과정을 위해서 tag가 필요하다.
tag matching을 하기 위해 cache 안에 있는 data마다 tag를 달아 놓는다.
즉, 32byte의 block. 1024개 각각의 block마다 tag를 달아준다.
index와 offset으로 사용하고 남은 나머지 15~31번 bit를 tag로 사용한다.
17 bit의 tag bit를 cache에 저장할 때, 이 tag가 유효한지 검사하는 valid bit를 1bit 추가해서 cache에는 18bit의 tag bit가 들어간다.
만약 index가 같다면, tag bit로 Tag matching이란 것을 진행한다.
Tag matching
접근하고자 하는 memory address에서 index 충돌이 났다면,
현재 memory address의 tag bit와 cache에 있는 tag bit를 비교하는 tag matching을 시도한다.
memory address의 index bit를 보고 cache의 어떤 block인지를 찾아간다.
cache의 tag를 가져와서 valid bit가 1인지 확인한다.
valid bit가 0이면, tag matching을 확인하지 않고, memory에서 data를 가져온다.
valid bit가 1이면, tag matching을 진행한다.
- address의 tag bit와 cache에 있는 tag bit가 같은지 검사한다. (같으면 hit이고, 다르면 miss이다.)
- hit 됐다면, 접근하려는 data가 맞으니, data를 가져가서 사용한다.
miss 됐다면, 다른 data가 cache에 올라와있음을 뜻하니, 원래 있던 data를 쫓아내고, 최신 data를 cache 안에 채우게 된다.
cache miss 시 원래 있던 data를 쫓아내고, data를 low level에 쓰는 방식에 차이가 있다.
- Write-back : data를 일단 cache에만 쓰고, data가 쫓겨날 때 memory에 쓰는 방식
cache에 data를 쓰는데 쫓겨나기 전까지는 memory에 쓰지 않는다.
쫓겨날 때 data를 low level cache 또는 memory에 쓰게 된다. - Write-through : data가 update되면 cache에도 쓰고, memory에도 쓰는 방식
data가 update 될 때마다 memory에 기록한다.
쫓겨날 때 따로 처리해 주지 않아도 된다.
cache에 쓸 때마다 memory에 접근해서 비용이 많이 든다. (최근에는 많이 사용하지 않음)