RowScope: DRAM Row Buffer Locality Analyzer

Quantifying how memory access patterns drive DRAM row buffer hit rates

01Problem

DRAM performance is shaped by how an access pattern lands on banks and rows, not only by how much bandwidth a program asks for. An access to the row already open in a bank is cheap; an access to a different row in the same bank forces a precharge and an activate first. Profilers report cache misses and bandwidth, but not that distinction, and the hardware counters that would report it need elevated privilege and differ between CPU generations. RowScope answers one question for a known access pattern: what fraction of its accesses find the row already open?

02What I built

  • A five-stage pipeline — C benchmarks, address traces, DRAM address mapper, per-bank row buffer state machine, results aggregator — that labels every access as a row hit, a row miss, or a row conflict.
  • A closed-form model, hit rate = 1 − stride × 4 / 8192, checked against the simulated result at 11 stride values from 1 to 1024 elements.
  • A 250x locality gap between sequential access (99.95% hit rate) and random access on a 16 MB array (0.4%), and nine log-spaced working sets from 512 KB to 128 MB showing that sequential locality does not depend on array size.

03How it works

RowScope never touches a real memory controller. A C benchmark writes the virtual addresses it touches into a trace file with a metadata header. A Python mapper decomposes each address into a bank, a row, and a column offset under a configurable geometry — 8 KB rows and 8 banks by default — using a bit-interleaved scheme. The trace is then replayed through one row buffer state machine per bank, which is what turns an address sequence into hit, miss, and conflict counts.

1Generate tracesFour C benchmarks (sequential_access.c, random_access.c, stride_access.c, working_set_sweep.c) execute a controlled access pattern and write the virtual addresses they touch, with a metadata header, to a .trace file.
2Map addressesdram_mapping.py decomposes each address into (bank_id, row_id, col_offset) for a configurable row size and bank count, defaulting to 8 KB and 8 banks.
3Model the row bufferrow_buffer_model.py keeps an OPEN or EMPTY state per bank and classifies each access as a row hit, a row miss, or a row conflict in constant time.
4AggregatePer-benchmark hit, miss, and conflict rates plus a locality score are written to summary.csv, and six figures are generated from it with Matplotlib.
5Reportreport/final_report.md is filled from a template with those tables and figures, so the whole experiment reruns from one shell script.

04Results

99.95%
Row hit rate, sequential access
0.4%
Row hit rate, random access on 16 MB
250x
Locality gap between the two
rowscope-plot.png — simulated row hit rate (left axis) and row conflict rate (right axis) against stride, for strides of 1 to 1024 elements with an 8 KB row and 4-byte elements. Hit rate stays above 87% up to stride 256 and collapses beyond it.
Key takeaway The access pattern decides row buffer locality, and the working set size barely matters: sequential access holds 99.95% from 512 KB to 128 MB, while random access on 16 MB drops to 0.4% with a 99.6% conflict rate. The stride sweep follows 1 − stride × 4 / 8192 closely enough that the row size can be read back off the curve.

05Engineering decisions

  • Simulate in user space instead of reading hardware counters. Counters such as UNC_M_CAS_COUNT need elevated privilege and change between CPU generations, and simulation makes the DRAM geometry a parameter rather than a property of the test machine.
  • Treat malloc virtual addresses as if they were physical. Page allocation for large arrays is not deterministic, but every workload is analysed under the same mapping, so the systematic error cancels in a comparison between patterns — it would not cancel in an absolute prediction.
  • Model the open-page policy only. It is the common DDR4 policy and the only one that produces the three-way hit, miss, and conflict split this project is about; closed-page and adaptive policies are left out rather than approximated.

06Stack & artifacts