서울대학교 컴퓨터공학부 유승주 교수님의 "고급 컴퓨터 구조" 강의를 필기한 내용입니다.

Memory Hierarchy

  • 일단 기술 발전 상황은 흔히 들었던 얘기다.
    • Single core power 는 계속 증가하다가 정체되기 시작한다.
    • 이에 따라 multicore 시대가 오고, 따라서 전체적인 compute power 는 계속 증가하는 추세를 보인다.
    • 근데 DRAM 은 그 속도를 따라오지 못하고 그 격차는 점점 벌어지게 된다.

  • 이러한 간극을 줄이기 위해 Memory Hierarchy 가 등장하게 된다.
    • 일단 간단히 생각해 보면 data access latency 는 데이터가 있는 위치까지의 distance 에 영향을 받는다.
    • 근데 capacity 가 커질수록 당연히 매체 (medium) 의 크기도 커지고, 따라서 distance 도 커져 latency 도 커지게 된다고 직관적으로 이해할 수 있을 것이다.
    • 따라서 점점 더 작은 capacity 를 가지지만 더욱 빠른 매체를 계층적으로 (Hierarchy) 배치하여 자주 사용하는, 혹은 앞으로 사용할 데이터를 더 빠르게 접근할 수 있게 해주는 것이 기본 아이디어인 셈.

Locality

  • 위에서 “자주 사용되는, 혹은 앞으로 사용할 데이터를 빠르게 접근” 한다고 했다.
  • 즉, 이 말이 곧 Locality 를 의미하는 것이고 따라서 locality 가 없다면 caching 은 별 의미가 없다.
  • 그래서 위의 memory access trace 를 보면
    • 일자로 쭉 뻗어있는 놈은 같은놈이 계속 사용된다는 것이다. 즉, 이것이 Temporal locality 이다.
    • 반면에, 넓은 주소 공간에 걸쳐서 두껍게 trace 가 찍혀있는 부분에 대해서는 주변의 것들이 같이 사용된다는 것이다. 즉, 이것이 Spatial locality 인 것.
  • 이 두 locality 에 대해서는 접근법이 다르다.
    • 일단 temporal locality 에 대해서는 이놈을 계속 붙들고 있어야 하기 때문에 Replacement Policy 가 중요하다.
    • 반면에 spatial locality 에 대해서는 주변 애들을 같이 caching 해야 하기 때문에 Prefetching 이 중요하다.

Terminology & Metrics

  • Hit: 원하는 데이터가 cache 에 있는 것.
    • Hit rate: 전체 request 중에 Hit 이 발생한 비율.
    • Hit time: Hit 일 때, 그 데이터를 가져오는 총 시간. 즉, 이것은 Hit 인지 판단하는 시간과 데이터를 가져오는 시간의 합이다.
  • Miss: 원하는 데이터가 cache 에 없는 것.
    • Miss rate: 전체 request 중에 Hit 이 발생한 비율. 즉, 1 에서 Hit rate 을 뺀 것과 동일하다.
    • Miss penalty: Miss 가 났을 때, 그 데이터를 가져오는 총 시간. 즉, 이것은 lower level memory 로 부터 가져오는 시간과 Miss 인지 판단하는 시간의 합이다.
  • Misses Per Killo Instructions (MPKI): 이것이 결국에는 Miss rate 이긴 한데, 다음과 같이 계산한다.

  • Average memory access time 은 말 그대로 memory access 에 걸리는 평균 시간으로, 다음과 같이 계산할 수 있다.
    • 이놈을 줄이는 것은 항상 좋다. 다만, 이 수식이 의미하는 것처럼 Hit time, Miss rate, Miss penalty 가 모두 잘 조화되어야 한다.
    • 가령, Miss rate 를 줄이더라도 Miss penalty 가 아주 커져버리면 결과적으로 별로 의미가 없어지게 되는 것.

  • Block: caching 단위, 보통 우리가 access 하는 단위보다 크다
    • CPU cache 에서는 cacheline 이라고 생각하면 될듯

Impact of Memory Access Latency

  • 간단한 예시로 memory access latency 가 가지는 영향력에 대해 알아보자.
  • 다음은 CPI (Cycles Per Instruction) 이 1 인 아주 간단하고 이상적인 상황에서의 Pipeline 이다.

  • 하지만 이때 memory access latency 가 두배가 되어버리면, CPI 가 순식간에 2배가 되어버린다.
    • 왜냐면 Fetch 에 두배나 더 많은 시간이 걸리기 때문.

Block Placement

  • 많이 봐온 그림이니까 간단하게만 정리하고 넘어가자
  • 일단 Associative 라는 것은 말 그대로 “연관” 된다는 것이다.
    • 따라서 Fully associative 는 어떤 block 이 cache 의 모든 entry 에 연관될 수 있다, 즉 모든 entry 에 들어갈 수 있다는 뜻이 되는거고,
    • Set associative 는 어떤 block 이 “Set” 이라는 단위 내에서 연관될 수 있다, 즉, “Set” 내에서의 모든 entry 에 들어갈 수 있다는 뜻이 된다.
  • 그리고 Set 이라는 것은 이것도 말 그대로 “집합” 이다.
    • 집합에는 원소가 순서와 중복이 없이 들어갈 수 있다는 점을 생각하면 된다.
    • 즉, Set 내에서는 중복되지 않은 block 에 대해, 어느 위치에도 (Set-associative) 갈 수 있다는 의미가 되는 것.
    • 이 set 의 크기에 따라 N-way 라는 말이 붙는다. 즉, set entry 가 N 일 때, N-way set 라고 부르는 것.

Fully-Associative Cache

  • 위에서 말한 대로, block 이 cache entry 의 어디든 갈 수 있는 경우.
  • Fully-associative cache 는 TLB 와 같은 특수한 경우가 아니라면 거의 사용되지 않는다.
    • 왜냐면 block 이 어디에든 갈 수 있기 때문에 원하는 block 이 있는지, 있다면 어디에 있는지 확인하기 위해 linear search 를 하거나 복잡한 회로를 사용해야 하기 때문이다.

Direct-Mapped Cache

  • 만약 Set 의 크기가 1 이라면, 그건 direct-mapped cache 가 된다. 즉, 어떤 block 이 들어갈 수 있는 위치는 하나의 cache entry 밖에 안되는 것.
    • 즉, 로 cache entry 가 결정된다.
  • 당연하게도 block 이 들어갈 수 있는 cache entry 가 하나밖에 없으므로 conflict 시에는 무조건 replacement 를 해줘야 하는 문제가 있다.
  • Direct-mapped cache 는 구현이 간편해서 종종 사용된다.
    • 이것은 단순히 “간편함” 과 연관되는 것만은 아니다. 구현이 간단하다는 말은 hit time 이 아주 빠르다는 말이 되기 때문에 average memory access latency 를 줄이는데 도움이 될 수 있다.
    • 또한 이때는 mem index 에 cache size 를 modulo 해서 어느 entry 에 있는지 찾는다.

N-Way Associative Cache

  • N 개의 cache entry 를 묶어 set 으로 관리하는 cache.
  • Direct-mapped 와는 다르게, block 이 들어갈 수 있는 cache entry 가 N 개이므로 conflict 가 발생하더라도 N 개 까지는 방어가 된다.
    • 즉, 로 cache entry 가 결정된다.
  • 이로 인해, N 번 까지는 conflict 가 안나므로 체감상 cache size 가 N 배 커진 것 같은 “환상” 을 만들어 준다.
    • 하지만 실제로 cache size 가 커지지는 않았으므로, 이러한 set-assiciative cache 의 효과가 좋은 것.
  • N-way associative cache 가 가장 많이 사용된다.
    • 이놈은 mem index 에 num. of set 을 modulo 해서 어느 set 에 있는지 찾고, 그 안에서 block 을 찾는 형태로 구현된다.

Miss Types

  • Compulsory: block 에 처음 접근할 때 발생하는 것
    • 이것은 prefetch 혹은 큰 cache line size 등으로 줄일 수 있다.
  • Capacity: cache size 가 부족해서 발생하는 것
    • 이것은 그냥 cache size 를 늘리는 수 밖에는 없다.
  • Conflict: 서로 다른 block 들이 같은 cache entry 에 들어가고자 해서 발생하는 것
    • 이것은 그냥 cache size 를 늘리거나 set size 를 늘려서 속일 수도 있다.
  • Coherence: write 로 인해 invalidate 되어서 miss 가 나는 것

Finding Block In a Cache

  • 우선 알다시피 memory address 을 위와 같은 layout 으로 나눠서 활용하게 된다.
    • Block offset (Data select, Byte select): block 안에서 원하는 byte 를 찾을 때 쓰는 것
      • 이놈의 크기가 bit 라면, block 의 size 는 byte 가 된다.
    • Block address: block 을 찾기 위한 address
      • 이놈은 어떤 cache 냐에 따라 TagIndex 로 나뉜다.
      • Tag 는 cache 에 있는 이 block 이 내가 찾는 놈이 맞는지 검사하는데 사용되고
      • Index 는 cache 내에서의 cache entry index 번호이다.
      • 이들이 어떻게 사용되냐는 cache 의 종류에 따라 다르다.

Direct-Mapped Cache

  • 보면, 우선 Cache index 로 cache entry 를 찾고,
  • Cache tag 를 이용해 이 cache entry 가 내가 찾는 block 이 맞는지 검사한 후,
  • 맞다면 block 안에서 byte 를 byte select 로 골라간다.

N-Way Associative Cache

  • N-way associative cache 는 위처럼 N 개의 direct-mapped cache 가 있는 형상을 하고 있다.
    • 그리고 이 N 개의 direct-mapped cache 에 걸쳐 있는 한 row 가 하나의 set 이 된다.
  • 다만 direct mapped cache 와의 차이점은 cache index 로 set entry 를 고른다는 것이다.
  • 그래서 우선 cache index 로 set entry 를 고른 후, set 내의 두 cache entry (예시의 그림은 2-way associative 이기 때문에) 에 대해 cache tag 를 parallel 하게 검사한다.
  • 그 다음 나온 cache block 에 대해, byte select 로 byte 를 가져가게 되는 것이다.
  • Address layout 과 관련해서, direct-mapped 와의 차이점은 저 cache index 의 크기가 다르다는 것이다.
    • Direct-mapped cache 에서 cache entry size 가 개였다면,
    • 2-way associative cache 에서는 이 cache entry 들이 2개씩 하나의 set 에 들어가게 되므로 set 의 개수는 개 인 것이다.
    • 따라서 direct-mapped cache 에 비해 2-way associative cache 의 cache index 의 크기가 1bit 더 작은 것이다.

Fully Associative Cache

  • Fully associative cache 에서는 cache index 없이 모든 cache address 부분을 tag 으로 사용한다.
  • 그래서 모든 cache entry 에 대한 cache tag 를 parallel 하게 한번에 비교하고, 그에 따라 cache entry 를 선택하여 byte select 로 값을 읽어가게 되는 것이다.

Replacement

  • 당연히 cache 에서는 자리가 부족하기 때문에 누군가는 evict 되어야 한다.
  • 이때, 누구를 뺄 것이냐.

Least Recently Used, LRU

  • LRU 를 사용하면 high-priority -> low priority 방향의 queue 에서 최근 접근된 tag 를 high-priority 에 넣는다.
    • 알다시피 LRU 는 잘 사용되지 않는다.
      • Scan 에서 hit rate 가 곱창나기도 하고
      • Mgmt 에 space overhead 가 크기 떄문

Re-Reference Interval Prediction, RRIP

  • 이건 접근되면 바로 MRU 에 넣지 않고 일단 queue 에서의 LRU 바로 앞에 (뒤에서 두번째) 에 넣은 다음
  • 한번 더 접근되면 그때 MRU 로 옮긴다.
  • 이렇게 하는 이유는 데이터가 단 한번만 접근될 때도 있기 때문에 한번 접근되었을 때 바로 최우선순위로 올리지 않고 한번 더 지켜보는 것이다.
  • 지금도 그런지는 모르겠는데 이전에는 intel 의 LLC 에서 이렇게 했다고 한다.

Write Policy

  • 간단히 복습만 하고 지나가면

  • Write through 는 말 그대로 write 시에 memory 에 까지 쓰는 것이고
  • Write back 은 cache 에다가만 쓰고, 나중에 evict 할 때 memory 에 쓰자는 정책이다.

Write Buffer

  • 알다시피 Write through 를 하게 되면 무조건 memory 로 가기 때문에 성능이 곱창날 수 있다.
  • 또한, Write back 을 한다고 해도 dirty block 에 대한 evict 가 빈번해지면 이때도 memory access 가 많아져 마찬가지로 곱창이 난다.
  • 이를 방지하기 위해 write buffer 를 활용할 수 있다:
    • Write through 에서는 write 시에 memory 까지 가지 않고 저 write buffer 에 쓰도록 하거나
    • Write back 에서는 dirty block 이 evict 될 때 저 write buffer 로 가게 한 다음
    • Write buffer 가 다 차면 그때 한꺼번에 flush 해버리는 것.
  • 그리고 이놈은 추가적인 cache 로도 작용해, cache miss 가 났을때 여기에서 가져갈 수도 있는 가능성을 만들어준다.

Block Size

  • 위 그래프에서 알 수 있다시피, block size 를 증가시키면서 실행해보면 32, 64B 에서 miss rate 가 가장 적게 나오고 이후로는 다시 증가한다.
  • 일단 block size 가 증가한다는 것은 spatial locality 가 좋아지기 때문에 일정 수준까지는 cache hit 이 늘어난다.
  • 하지만 block size 가 증가한다는 것은 cache 내의 block 개수가 줄어듦을 의미하기 때문에 conflict 가 더 많이 발생하게 된다.
    • 또한 block size 가 클수록 cache 로 올려야 하는 데이터의 양이 많아지기 때문에 miss penalty 가 크다.
  • 따라서 이 둘은 tradeoff 관계이다.

Sequential Access (Spatial Locality)

  • Sequential access 를 할 때는 당연히 block size 를 키워서 spatial locality 를 키우는 게 좋다.
  • 아래 그림을 보자.

  • 위 상황은 block 한개 당 cache entry 에 넣는 경우이다. 이때 8개의 block 에 sequential access 를 하면 8개 모두 compulsory miss 가 난다.

  • 하지만 만약에 cache entry size 를 키워서 block 을 두개씩 넣는다고 해보자. 그럼 8개의 block 에 sequential access 를 했을 때, compulsory miss 4번과 hit 4번이 발생하여 성능이 좋아지게 된다.

Random Access w/ Temporal Locality

  • 하지만, sequential 이 아닌 random access 를 하되 여러번 반복해서 접근하는 경우에는 상황이 달라진다.

  • 만약에 cache entry 에 block 하나씩이 들어간다면, 첫 8번의 접근은 compulsory miss 가 나지만 두번째 8번의 접근은 모두 hit 이 나게 된다.

  • 하지만 cache entry 에 block 을 두개씩 넣고, 만약 접근하는 애들이 conflict 관계에 있다면 위와 같은 상황이 발생할 수 있다.
    • 보면 첫 8번의 access 를 할 때는, 4번 정도는 동일하게 compulsory miss 가 난다.
    • 근데 다음 4 번의 경우에는 conflict miss 가 나더니
    • 두번째 8번의 access 를 할 때는 모두 conflict miss 가 나게 되는 것이다.

Conclusion

  • 따라서 정리해 보면 sequential access 일때는 spatial locality 가 높은 larger block size 가 유리하지만, temporal locality 가 중요할 때에는 conflict 가 적게 나야 하기 때문에 smaller block size 를 가져가서 더 많은 cache entry 를 갖는 것이 유리하다.
    • 이런 tradeoff 를 고려했을 때, 많은 CPU 들에서 block size 를 32byte 혹은 64byte 로 정한다고 한다.

Cache Size, Square-Root Rule

  • Cache size 가 커지는 것은 좋기는 하지만 그 영향은 그리 크지 않을 수 있다.
  • 보통 block size 가 4배 늘어나면, cache miss 는 2배 (4의 2제곱근) 만큼 줄어든다고 한다.
    • 따라서 이것을 Square-root rule 이라고 부른다.

Set Size, 2:1 Cache Rule

  • 그럼 set size 를 변화시키면 성능은 어떻게 변할까. 아래의 그래프를 보자.

  • 보면 (1) cache size 가 4KB 인 1-way cache 와 (2) cache size 가 2KB 인 2-way cache 의 miss rate 가 유사한 것을 볼 수 있다.
  • 따라서 같은 cache size 일 때, set size 를 늘리면 그에 맞게 miss rate 가 떨어진다.
    • 그래서 set size 를 늘리면 cache size 도 그만큼 늘어나는 것 같은 느낌을 준다고 (illusion) 표현하는 것이다.
  • 이것을 2:1 Cache Rule 이라고 한다.
    • 사이즈가 인 1-way cache 는 사이즈가 인 2-way cache 와 miss rate 가 동일하다는 것.
  • 하지만 위에서 말한 것처럼 miss rate 가 전부가 아니다.
    • 왜냐면 set size 가 커질수록 비교해야 하는 tag 의 수가 많아지기 때문에, hit time 이 커져 전체적인 access latency 가 오히려 안좋아지고 power consumption 도 커지게 되는 문제가 있다.
  • 아래의 표를 보자.

  • 이 표에서 빨간색 글자는 set size 가 커졌음에도 성능이 좋아지지 않은 놈을 표시하는 것이다.
  • 따라서 보면 cache size 가 4KB 를 넘어가면 오히려 2-way 일때부터 성능이 안좋아지는 것을 알 수 있다.
    • 물론 이것은 좀 옛날 데이터여서 지금이랑은 다르긴 하지만, 이러한 경향성은 동일하기 때문에 보통은 많아봤자 set size 를 8 보다 크게 하지는 않는다고 한다.

Victim Cache

  • 이것은 Direct-mapped cache 를 사용하되, 적은 양의 victim cache 를 두어서 evict 된 놈들을 잠깐 저장하는 방법이다.
    • 그래서 위의 예시를 보면, 원래는 conflict miss 가 났어야 하는 두 block 에 대해, 하나는 cache 에 있고 하나는 evict 되었지만 victim cache 에 저장되어 있는 덕분에 conflict miss 가 나지 않는 것을 볼 수 있다.
  • 이렇게 함으로써 direct-mapped cache 의 빠른 hit time 을 가져가면서, 2-way associative cache 와 유사한 효과를 낼 수 있다.
  • 근데 Pipeline 와 관련해서 문제가 있어서, 요즘은 잘 사용되지는 않는다고 한다.

Pseudo-Associative Cache

  • 얘는 Cuckoo Hash 와 유사한 전략을 취하는 방법이다.
  • Direct-mapped cache 를 사용하되, conflict 가 나면 modulo 가 아닌 다른 방법으로 새로운 cache index 를 계산해 거기에 비어있으면 넣는 것이다.
  • 그리고 다시 그 block 을 찾을 때는, 처음에는 modulo 로 접근했다가 tag 가 다르면 아까의 방법으로 새로운 cache index 를 계산해 그곳에 방문한다.
  • 그래서 이 두 방법을 다 시도해도 없으면 그때 cache miss 가 되는 것이다.
  • 따라서 이 두번째 접근은 첫 시도때 바로 hit 이 나는 것보다는 당연히 delay 가 있기 때문에 이 경우에 대해 Pseudo-hit (Slot hit) 이라고 부른다.
  • 그리고 direct-mapped cache 이지만 마치 2-way associative cache 와 같은 효과가 나기 때문에 이 방법을 Pseudo-associative cache 라고 부르는 것이다.
  • 하지만 이 방법도 잘 쓰이지는 않는다고 한다: 이놈도 뭐 pipeline 관련해서 문제가 있고, cuckoo hash 처럼 SW 로 구현하는 것이 아니고 HW 로 구현하기에는 까다롭다고 한다.

Inclusive, Exclusive Cache Policy

  • Inclusive Policy: 상위 레벨의 cache 에 있는 데이터를 하위 레벨의 cache 에도 동일하게 유지하는 정책이다.
  • Exclusive Policy: 반대로, 상위 레벨의 cache 에 데이터가 올라가면, 하위 레벨에서는 그것을 지워 둘 간의 중복된 데이터가 없게 하는 방법이다.
  • 이 둘은 tradeoff 가 있다:
    • Inclusive 를 하게 되면 데이터의 중복은 있지만, dirty 가 아닌 block 에 대해서는 그냥 해당 block 을 덮어씌우는 것으로 eviction 이 가능하다.
    • 반대로, Exclusive 를 하게 되면 데이터의 중복은 없지만, eviction 을 할 때 dirty 가 아니라 하더라도 하위 level 로 데이터를 옮겨줘야 하는 overhead 가 발생한다.
  • 따라서 보통 상위 레벨과 하위 레벨의 크기 차이가 많이 나는 경우에는 Inclusive 로 하고, 차이가 적은 경우에는 Exclusive 로 한다.
    • 요즘은 대부분 크기 차이가 많이 나기 때문에 Inclusive 를 사용한다.

Read, Write Priority

  • CPU 에는 Load unit 과 Store unit 이 따로 있기 때문에 read request 와 write request 가 동시에 들어올 수 있다.
  • 이런 경우에, read request 를 더 높은 우선순위로 처리한다.
    • 왜냐면 write request 의 경우에는 별도의 write buffer 를 사용하는 등의 방법으로 async 하게 할 수 있지만
    • Read request 의 경우에는 그런 것이 불가능하기 때문.

Virtually Indexed Physically Tagged (VIPT) Cache

  • Cache 에서는 어떤 memory address 를 사용하는게 좋을까. 다음의 세 선택지가 있다.

  • 우선 첫번째는 cache 에서는 무조건 physical address 를 사용하도록 하고, 따라서 cache 에 접근하기 전에 무조건 TLB 를 들르게 하는 방법이다.
    • 하지만 이 방법은 TLB 를 들르는 시간이 문제가 된다: 일단 TLB 에 들르는 것 자체의 latency 도 있거니와, 만약에 TLB miss 가 나버리면 multi-level page table 때문에 여러번 memory 를 왔다갔다 해야 하고, 따라서 latency 가 아주 커져버린다.
  • 그리고 두번째는 cache 에서는 무조건 Virtual Address 를 사용하도록 하는 것이다.
    • 이렇게 하면 저 TLB 에 의한 latency 는 사라진다. 하지만 VAS 는 각 process 마다 갖고 있는 것이기에, cache 에 어떤 process 의 virtual address 인지를 같이 명시해 줘야 한다 (aliasing problem).
  • 이것을 해결하기 위해 나온 방법이 저 세번째 이다.
    • Cache 와 TLB 에 parallel 하게 접근해버리는 것.

  • 그래서 전반적인 구조는 위와 같다. 이것을 TLB side 와 cache side 각각에 대해 알아보자.
    • 일단 TLB 는 Virtual Page Number (VPN) 을 Physical Page Number (PPN) 으로 바꿔주는 놈이란 것을 기억하자.
    • 만약 page size 가 4K 라면, 32bit 주소 체계에서 page offset 12bit 을 제외한 20bit 가 VPN 이 된다.
    • 그래서 이 VPN 을 TLB 에 넣으면, PPN 이 나오게 될 것이다.
  • 그리고 cache side 에서는, cache tag 은 PPN 과 동일한 20bit 로 하고 나머지 12bit 중에서 cache index 에 6bit, 나머지 byte select 에 6bit 을 주는 layout 을 사용한다.
    • 그렇게 하면 일단 입력받은 memory address 가 virtual address 인지 physical address 인지와 무관하게 cache index 로 cache entry 에 접근할 수 있을 것이다.
    • 그럼 이때 cache entry 에 적혀있는 tag 는 PPN 이 된다.
    • 따라서 cache entry 의 tag 에 있는 PPN 과 TLB 로부터 얻어온 PPN 을 비교하면 일단 이놈이 hit 인지 아닌지 알 수 있게 된다.
    • 그리고 나머지 6bit byte select 로 block 내에서 원하는 block 을 찾으면 되는 것이다.
  • 이렇게 하면 TLB 와 cache access 를 parallel 하게 처리할 수 있고, cache 에 불필요하게 process ID 같은 것도 넣을 필요가 없다.
  • 하지만 이 방법도 한계가 있다. 이 방법을 이용하면 cache size 가 4K 로 제한된다.
    • 왜인지를 살펴보면, cache index 와 byte offset 을 합친 크기가 무조건 12bit 이어야지 PPN 와 cache tag 를 동일한 크기로 만들 수 있기 때문에, 이 12bit 로 표현할 수 있는 byte 의 개수는 byte 로 제한된다.

32KB, 4-Way Set Associative Cache

  • 그래서 위와 같은 한계점을 돌파하기 위해, 다음과 같이 한다고 한다.

  • 우선 위에서는 direct-mapping 을 기준으로 cache size max 가 4K 였기 때문에, 일단 이것을 4-way associative 로 하면 cache size 를 16K 로 만들 수 있을 것이다.
  • 그리고 TLB + 4-way set associative 전체를 두개를 구비하고, virtual address 의 12번째 bit 을 이용해 이 둘 중 하나를 선택하도록 하면 총 사이즈가 32K 인 cache 를 구성할 수 있다.