Level 5 — MasteryMemory Hierarchy & Caches

Memory Hierarchy & Caches

90 minutes60 marksprintable — key stays hidden on paper

Level 5 — Mastery (cross-domain: architecture + math + coding + proof) Time limit: 90 minutes Total marks: 60

Instructions: Show all reasoning. Use ....../...... for mathematics. Byte addresses assume a byte-addressable memory unless stated otherwise. log\log is base 2 for address-field derivations.


Question 1 — Cache geometry, miss classification & AMAT modelling (24 marks)

A single-core processor has a two-level cache hierarchy over a 32-bit byte-addressable physical address space.

  • L1 (data): 32 KiB, 8-way set-associative, 64-byte lines, LRU, write-back + write-allocate. Hit time 1 cycle.
  • L2 (unified): 512 KiB, 16-way set-associative, 64-byte lines. Local hit time 12 cycles.
  • DRAM: 200-cycle access.

(a) For the L1 cache, derive the number of sets and the exact bit-widths of the offset, index, and tag fields. Show the arithmetic. (4)

(b) Repeat the field derivation for L2. Then compute the total tag storage overhead (in bits) of L1 alone, including one valid bit and one dirty bit per line. (4)

(c) Consider the loop below over double A[N] (8 bytes each), int N = 4096, arrays 64-byte aligned, and a cold cache:

for (int r = 0; r < 10; r++)
    for (int i = 0; i < N; i++)
        sum += A[i];

Classify every L1 miss produced by this code into compulsory / capacity / conflict, giving the count of each type across the full run. Justify using the working-set size versus L1 capacity. (6)

(d) The system's L1 local miss rate is measured at 4% and L2 local miss rate at 25%. Using AMAT, compute the average memory access time in cycles. Then prove algebraically that halving the L1 miss rate yields a larger AMAT improvement than halving the L2 local miss rate, for these numbers, and state the general condition (in terms of the miss/penalty parameters) under which improving L1 dominates. (6)

(e) The designer proposes switching L1 to write-through + no-write-allocate. Give one workload characteristic that makes this better and one that makes it worse than the original policy, each justified by a concrete access-pattern argument. (4)


Question 2 — Virtual memory, TLB reach, and multi-level paging (18 marks)

A 64-bit machine uses 48-bit virtual addresses, 4 KiB pages, and a 4-level page table where each level indexes with 9 bits and each page-table entry (PTE) is 8 bytes.

(a) Verify that the address decomposition 48=4×9+1248 = 4\times 9 + 12 is consistent, and state the number of entries per page-table page and why it equals one 4 KiB frame's worth of PTEs. (3)

(b) A program touches a fully-populated contiguous 2 GiB region. Compute the total memory consumed by all levels of its page table (assume minimal but complete tables covering exactly that region). Show the count of tables at each level. (6)

(c) A 4-way set-associative TLB has 512 entries. Define TLB reach and compute it for 4 KiB pages. If the OS additionally supports 2 MiB huge pages, compute the reach when all TLB entries map huge pages, and state the miss-type consequence for a program with an 800 MiB working set. (5)

(d) Give the AMAT-style formula for effective memory access time with a TLB, given TLB hit rate hh, TLB access tt, a page-table walk cost WW (on TLB miss), and physical memory access mm (assume all pages resident, no cache). Then compute the effective time for h=0.98h=0.98, t=1t=1, W=100W=100, m=100m=100 cycles. (4)


Question 3 — Cache coherence, MESI, and a consistency proof (18 marks)

Two cores (C0, C1) share memory via a snooping bus with write-back, write-allocate private L1 caches running the MESI protocol. Block BB starts in memory only (Invalid in both caches).

(a) Trace the MESI state of BB in both caches after each of the following operations, and note every bus transaction generated (e.g., BusRd, BusRdX, BusUpgr, Flush):

  1. C0 reads BB
  2. C1 reads BB
  3. C0 writes BB
  4. C1 writes BB
  5. C0 reads BB

Present as a 5-row table. (8)

(b) Explain precisely why the E (Exclusive) state exists as distinct from S (Shared): give a two-operation sequence where having E saves a bus transaction that a protocol with only M/S/I would incur. (4)

(c) Consider Dekker-style shared flags, initially x = y = 0:

Core 0:          Core 1:
x = 1;           y = 1;
r0 = y;          r1 = x;

Prove that under sequential consistency the outcome r0=r1=0r0 = r1 = 0 is impossible, but show it is permitted under a store-buffer (TSO-like) relaxed model. Frame the impossibility as a contradiction over a total global order of the four memory operations. (6)

Answer keyMark scheme & solutions

Question 1

(a) L1 = 32 KiB = 2152^{15} B. Line = 64 B \Rightarrow offset =log264=6= \log_2 64 = 6 bits. (1) Number of lines =215/26=512= 2^{15}/2^6 = 512. Ways = 8 \Rightarrow sets =512/8=64=26= 512/8 = 64 = 2^6, index =6= 6 bits. (2) Tag =3266=20= 32 - 6 - 6 = 20 bits. (1)

(b) L2 = 512 KiB = 2192^{19} B. offset =6=6. Lines =219/26=8192=2^{19}/2^6=8192. Ways=16 \Rightarrow sets =8192/16=512=29=8192/16=512=2^9, index =9=9. Tag =3269=17=32-6-9=17 bits. (2) L1 tag storage: 512 lines ×(20 tag+1 valid+1 dirty)=512×22=11264\times(20\text{ tag}+1\text{ valid}+1\text{ dirty})=512\times22=11264 bits. (2)

(c) Working set =4096×8=32768=4096\times8=32768 B =32=32 KiB == exactly L1 capacity. Because A is contiguous and fully-associative-enough here (8-way, 64 sets, and 32 KiB fills all lines evenly with a linear stride), the whole array just fits. Lines spanned =32768/64=512=32768/64=512 lines = exactly all 512 L1 lines. (2)

  • Compulsory: first pass (r=0r=0) touches each of the 512 distinct lines once for the first time \Rightarrow 512 compulsory misses. (2)
  • Capacity/Conflict: working set (32 KiB) \le capacity (32 KiB), and a linear scan maps the 512 lines onto 64 sets, 8 per set, exactly filling each set with no set over-subscribed under LRU. Hence on reuse passes r=1..9r=1..9 the whole array is still resident \Rightarrow 0 capacity, 0 conflict misses. Total misses =512=512. (2) (Note the array exactly equals capacity; had N been slightly larger, passes would thrash → capacity misses.)

(d) AMAT=tL1+mL1(tL2+mL2tDRAM)\text{AMAT}=t_{L1}+m_{L1}\big(t_{L2}+m_{L2}\cdot t_{DRAM}\big). (1) =1+0.04(12+0.25×200)=1+0.04(12+50)=1+0.04×62=1+2.48=3.48=1+0.04(12+0.25\times200)=1+0.04(12+50)=1+0.04\times62=1+2.48=3.48 cycles. (2)

  • Halve L1 miss to 2%: 1+0.02×62=1+1.24=2.241+0.02\times62=1+1.24=2.24 → improvement 1.241.24 cycles.
  • Halve L2 local miss to 12.5%: 1+0.04(12+0.125×200)=1+0.04(12+25)=1+0.04×37=1+1.48=2.481+0.04(12+0.125\times200)=1+0.04(12+25)=1+0.04\times37=1+1.48=2.48 → improvement 1.001.00 cycle. L1 halving (1.24-1.24) beats L2 halving (1.00-1.00). (2) General condition: L1-rate halving improvement =12mL1(tL2+mL2tDRAM)=\tfrac{1}{2}m_{L1}(t_{L2}+m_{L2}t_{DRAM}); L2-rate halving improvement =12mL1mL2tDRAM=\tfrac12 m_{L1}m_{L2}t_{DRAM}. L1 dominates iff tL2+mL2tDRAM>mL2tDRAMt_{L2}+m_{L2}t_{DRAM} > m_{L2}t_{DRAM}, i.e. iff tL2>0t_{L2}>0always true here (the L1-miss penalty includes the full L2 access chain, so reducing L1 misses removes the L2 hit cost too). (1)

(e) Better: streaming/write-once workloads writing data never re-read soon (e.g. memset of large buffer): no-write-allocate avoids polluting L1 by fetching a line to be fully overwritten; write-through with a write buffer hides latency. (2) Worse: workloads with high write locality / read-after-write reuse (e.g. accumulating into a hot variable in a tight loop): write-through generates a bus/L2 write on every store (bandwidth blowup) and no-allocate keeps the line out of L1, causing repeated misses. (2)

Question 2

(a) 4 KiB page \Rightarrow offset =log24096=12=\log_2 4096=12 bits. Index bits =4812=36=4×9=48-12=36=4\times9. ✓ Consistent. (1) Entries per PT page =29=512=2^9=512; each PTE =8=8 B 512×8=4096\Rightarrow 512\times8=4096 B == one frame. ✓ (2)

(b) 2 GiB =231=2^{31} B. Pages =231/212=219=524288=2^{31}/2^{12}=2^{19}=524288 leaf pages (data). (1)

  • L4 (PTE/leaf tables): each maps 512 pages 219/29=210=1024\Rightarrow 2^{19}/2^9=2^{10}=1024 tables. (1)
  • L3: each maps 512 L4 tables 1024/512=2\Rightarrow 1024/512=2 tables. (1)
  • L2: maps 2 L3 tables 1\Rightarrow 1 table. (1)
  • L1 (root): 11 table. (1) Total tables =1024+2+1+1=1028=1024+2+1+1=1028; each =4=4 KiB 1028×4096=4,210,688\Rightarrow 1028\times4096=4{,}210{,}688 B 4.02\approx 4.02 MiB. (1)

(c) TLB reach = amount of memory mappable by the TLB's entries simultaneously == (entries)×\times(page size). (1) 4 KiB pages: 512×4 KiB=2048 KiB=2512\times4\text{ KiB}=2048\text{ KiB}=2 MiB. (2) 2 MiB huge pages: 512×2 MiB=1024512\times2\text{ MiB}=1024 MiB =1=1 GiB. (1) An 800 MiB working set exceeds 2 MiB (base pages) → heavy TLB capacity misses; but 800<1024800<1024 MiB fits in huge-page reach → TLB misses become rare (compulsory only). (1)

(d) Teff=h(t+m)+(1h)(t+W+m)=t+m+(1h)WT_{eff}=h(t+m)+(1-h)(t+W+m)=t+m+(1-h)W. (2) =1+100+0.02×100=101+2=103=1+100+0.02\times100=101+2=103 cycles. (2)

Question 3

(a) (8 marks; ~1.5 per correct row incl. bus txn)

Op Bus txn C0 state C1 state
C0 read B BusRd (no sharer) E I
C1 read B BusRd, C0 flushes/downgrades S S
C0 write B BusUpgr (invalidate C1) M I
C1 write B BusRdX, C0 Flush (writeback) then invalidate I M
C0 read B BusRd, C1 Flush → both share S S

(b) After a lone read that misses with no other cache holding the block, state E means the line is clean and privately owned. If the same core then writes it, it can silently transition EME\to M with no bus transaction (no other copy to invalidate). A protocol with only M/S/I would have loaded it as S, forcing a BusUpgr/BusRdX on the write to invalidate phantom sharers. So E saves one bus transaction on the read-then-write sequence. (4)

(c) Under SC, all four ops (2 stores, 2 loads) appear in one total order consistent with each core's program order: x=1x{=}1 before r0=yr0{=}y, and y=1y{=}1 before r1=xr1{=}x. (2) Suppose r0=r1=0r0=r1=0. r0=0r0=0 means r0=yr0{=}y read before y=1y{=}1 in the global order. r1=0r1=0 means r1=xr1{=}x read before x=1x{=}1. Program order: x=1r0=yy=1x{=}1 \prec r0{=}y \prec y{=}1 (last from r0r0 reading old y) and y=1r1=xx=1y{=}1 \prec r1{=}x \prec x{=}1. Chaining: x=1y=1x=1x{=}1 \prec \dots \prec y{=}1 \prec \dots \prec x{=}1 — a cycle, contradicting a total order. Hence r0=r1=0r0=r1=0 impossible. (2) Under TSO/store-buffer: each store sits in the core's local store buffer; the load r0=yr0{=}y and r1=xr1{=}x execute (bypass buffer / read memory) before the buffered stores drain to memory. Both loads see the old value 0. Store→load reordering is allowed (a load may complete before an earlier store in program order becomes globally visible), so r0=r1=0r0=r1=0 is permitted. (2)

[
{"claim":"L1 tag=20 bits, sets=64, offset=6","code":"offset=log(64,2); sets=(2**15//2**6)//8; idx=log(sets,2); tag=32-offset-idx; result=(offset==6 and sets==64 and idx==6 and tag==20)"},
{"claim":"L1 tag storage = 11264 bits","code":"result=(512*(20+1+1)==11264)"},
{"claim":"AMAT baseline = 3.48 cycles","code":"amat=1+Rational(4,100)*(12+Rational(25,100)*200); result=(amat==Rational(348,100))"},
{"claim":"Halving L1 improves 1.24, halving L2 improves 1.00","code":"base=1+Rational(4,100)*62; a=1+Rational(2,100)*62; b=1+Rational(4,100)*(12+Rational(125,1000)*200); result=(base-a==Rational(124,100) and base-b==1 and (base-a)>(base-b))"},
{"claim":"Page table total = 1028 tables","code":"leaf=2**19//2**9; l3=leaf//512; l2=1; l1=1; result=(leaf==1024 and leaf+l3+l2+l1==1028)"},
{"claim":"TLB reach 2MiB (4K) and 1GiB (2M huge)","code":"r4=512*4096; rh=512*2*1024*1024; result=(r4==2*1024*1024 and rh==1024*1024*1024)"},
{"claim":"Effective time with TLB = 103 cycles","code":"result=(1+100+(1-Rational(98,100))*100==103)"}
]