Computing Foundations › Inside the Machine › Day 3
Hands-on lab — Day 3: Memory Hierarchy: Registers, RAM, and Storage
- ← Back to the Day 3 lesson
- Open the hands-on files on GitHub — clone or download them from the public labs repository
- Local path in your clone:
labs/sections/computing-foundations/day-003-memory-hierarchy-registers-ram-and-storage/
Commands
Setup
cd labs/sections/computing-foundations/day-003-memory-hierarchy-registers-ram-and-storage Run
bash examples/measure_read_speed.sh
bash starter/measure_read_speed.sh Test
bash tests/run_tests.sh File tree
examples/measure_read_speed.sh expected-output/FIELDS.md expected-output/sample-macos.txt expected-output/sample-test-run.txt metadata.yml README.md requirements/README.md security.md starter/hierarchy-worksheet.md starter/measure_read_speed.sh tests/run_tests.sh troubleshooting.md
Lab README
Day 003 lab — Feel the Hierarchy: Measuring Your Machine's Memory Levels
Lesson
- Lesson title: Memory Hierarchy: Registers, RAM, and Storage
- Day number: 3 of 365
- Lesson article: https://ai-roadmap-365.github.io/day-003-memory-hierarchy-registers-ram-and-storage
- Lab files: everything you need is in this directory — follow “How to run” below.
- Browse the course locally: from the repository root, this lab also appears in the course website at
/labs/day-003-memory-hierarchy-registers-ram-and-storagewhen the site is running.
Purpose
Day 3's lesson explained the memory hierarchy — registers, caches, RAM, storage — and the speed/size/cost trade-off that creates it. This lab makes two of those levels measurable on your own machine: it reads your CPU's L1 and L2 cache sizes and your RAM, then times a cold versus warm read of a large file so you can watch the operating system's page cache at work.
Learning objectives
- Read your machine's L1 data cache, L2 cache, and RAM sizes from the OS.
- Explain, in bytes and in KiB/MiB/GiB, how far apart those levels are.
- Measure a cold read versus a warm (cached) read and interpret the result.
- Connect the page-cache effect you observe to why "loading" something the second time is faster.
Prerequisites
- The Day 3 lesson (it explains every level this lab measures).
- The Day 1 lab's machine profile (you extend it here).
- A terminal. No programming experience required.
Supported operating systems
- macOS — fully supported (Apple Silicon and Intel).
- Linux — fully supported where the kernel exposes cache descriptors (most bare-metal and full VMs; minimal containers may not — handled gracefully).
- Windows — run under WSL, or read cache sizes with PowerShell (see
troubleshooting.md) and fill the worksheet by hand.
Hardware requirements
Any computer with at least ~500 MB of free disk (for the temporary test file, which is deleted afterwards). No GPU or minimum RAM needed.
Required software
bash plus standard OS utilities (sysctl/sw_vers on macOS; /proc and
/sys on Linux; dd, date, df). All preinstalled — see
requirements/README.md.
Free and open-source options
Everything here is free and ships with your OS. No accounts, keys, or purchases.
Installation
None. Change into this directory:
cd labs/sections/computing-foundations/day-003-memory-hierarchy-registers-ram-and-storage
File structure
day-003-.../
├── README.md
├── metadata.yml
├── starter/
│ ├── measure_read_speed.sh ← YOUR working file (numbered exercises)
│ └── hierarchy-worksheet.md ← record your machine's real numbers
├── examples/
│ └── measure_read_speed.sh ← completed reference implementation
├── tests/
│ └── run_tests.sh
├── expected-output/
│ ├── sample-macos.txt ← real captured run
│ ├── sample-test-run.txt ← real captured test run
│ └── FIELDS.md ← required fields on every platform
├── requirements/README.md
├── troubleshooting.md
└── security.md
How to run
## 1. See the finished result first
bash examples/measure_read_speed.sh
## 2. Complete the exercises in the starter, then run it
bash starter/measure_read_speed.sh
## 3. Check your work
bash tests/run_tests.sh
What the commands do
bash examples/measure_read_speed.sh— reads your L1/L2 cache and RAM sizes (viasysctlon macOS or/sysand/proc/meminfoon Linux), then creates a ~200 MB file inside the lab directory withdd, times a first (cold) read and a second (warm) read of it, prints both speeds and their ratio, and deletes the file on exit.bash starter/measure_read_speed.sh— the same skeleton with numbered exercises for you to complete; each names the exact command to use.bash tests/run_tests.sh— runs the reference (and your starter) and checks the report has every required field and exits cleanly.
Expected output
See expected-output/sample-macos.txt
for a real captured run and expected-output/FIELDS.md
for the exact fields required on every platform. Your cache sizes, RAM, and
timings will differ — that is the point.
Validation steps
bash starter/measure_read_speed.shexits with no error.- No field reads
unknownor is left blank (except a level your OS genuinely does not expose, which printsnot exposed by this environment). - The RAM figure matches your Day 1 profile.
- The tests pass.
Tests
bash tests/run_tests.sh
Expected final line: 19 checks, 0 failure(s). (exit 0 on success, non-zero
on any failure — CI-ready).
Cleanup
The scripts delete their temporary file automatically. To reset your edited
starter: git checkout -- starter/measure_read_speed.sh. If a hard kill left
read-speed-testfile.bin behind, delete it manually.
Troubleshooting
See troubleshooting.md — cache names per platform, empty cache values on minimal Linux, warm-vs-cold ratios, disk space, WSL.
Security notes
See security.md. The scripts read hardware facts and write one temporary file inside the lab directory (auto-deleted); no network, no sudo.
Extension exercises
- Raise
size_mbtoward (but below) your free RAM and observe how the cold/warm gap changes. - Add L3 cache to the report (macOS:
sysctl -a | grep -i l3; Linux: theindex3cache descriptor) and note how much larger it is than L2. - Estimate how many float32 model weights (4 bytes each) fit in your L2 cache versus your RAM, and relate that to why AI models stream weights from RAM rather than holding them all in cache.
Navigation
- Previous day: Day 2 — The CPU: Fetch, Decode, Execute.
- Next day: Day 4 — Binary and Data Representation: Bits, Bytes, and Numbers.
Expected output
FIELDS.md
# Required fields in the measurements report
Both `examples/measure_read_speed.sh` and a completed
`starter/measure_read_speed.sh` must print, on any supported platform:
| Line prefix | Meaning | Example (real macOS run) |
| --- | --- | --- |
| `=== Memory Hierarchy Measurements ===` | report header | — |
| `Generated on:` | run date | `Generated on: 2026-07-12` |
| `Operating system kernel:` | `Darwin` (macOS) or `Linux` | `Operating system kernel: Darwin` |
| `L1 data cache:` | bytes and KiB | `L1 data cache: 131072 bytes (128 KiB)` |
| `L2 cache:` | bytes and MiB | `L2 cache: 16777216 bytes (16 MiB)` |
| `RAM:` | bytes and GiB | `RAM: 38654705664 bytes (36 GiB)` |
| `Cold read (first pass):` | seconds and MB/s | `Cold read (first pass): 0.023 s (8695 MB/s)` |
| `Warm read (second pass):` | seconds and MB/s | `Warm read (second pass): 0.022 s (9090 MB/s)` |
| `Warm read speed-up over cold:` | ratio | `Warm read speed-up over cold: 1.0x` |
| `=== End of measurements ===` | report footer | — |
| `Test file removed.` | cleanup confirmation (printed by the EXIT trap) | — |
Notes on interpreting your own run:
- The cache and RAM values differ per machine; what must hold everywhere is
the *shape*: L1 in KiB, L2 in MiB, RAM in GiB.
- On Apple Silicon the script reports the performance-core caches
(`hw.perflevel0.*`); on Intel Macs the plain `hw.l1dcachesize` /
`hw.l2cachesize` names; on Linux, cpu0's sysfs cache descriptors.
- A speed-up near 1.0x is normal on machines with plenty of free RAM: the
test file is still in the page cache from being written, so even the
"cold" read is served from RAM. See `troubleshooting.md` for how to
observe a truly cold read.
- The warm read must never be much slower than the cold read; the automated
test allows a 1.5x noise tolerance.
sample-macos.txt
=== Memory Hierarchy Measurements ===
Generated on: 2026-07-12
Operating system kernel: Darwin
L1 data cache: 131072 bytes (128 KiB)
L2 cache: 16777216 bytes (16 MiB)
RAM: 38654705664 bytes (36 GiB)
Creating a 200 MB test file with dd (inside this lab directory) ...
Cold read (first pass): 0.023 s (8695 MB/s)
Warm read (second pass): 0.022 s (9090 MB/s)
Warm read speed-up over cold: 1.0x
If the two times are close, the file was already in the page cache from being written — see troubleshooting.md.
=== End of measurements ===
Test file removed.
sample-test-run.txt
Testing ./examples/measure_read_speed.sh ...
ok: script exits successfully
ok: prints report header
ok: prints report footer
ok: prints 'L1 data cache:'
ok: prints 'L2 cache:'
ok: prints 'RAM:'
ok: prints 'Cold read (first pass):'
ok: prints 'Warm read (second pass):'
ok: no field is left 'unknown'
ok: warm read <= 1.5x cold read (page-cache effect, with tolerance)
ok: test file cleaned up after run
Note: starter/measure_read_speed.sh still has unfilled exercises — testing structure only.
Testing ./starter/measure_read_speed.sh ...
ok: script exits successfully
ok: prints report header
ok: prints report footer
ok: prints 'L1 data cache:'
ok: prints 'L2 cache:'
ok: prints 'RAM:'
ok: prints 'Cold read (first pass):'
ok: prints 'Warm read (second pass):'
19 checks, 0 failure(s).
Source files
examples/measure_read_speed.sh (3747 bytes)
#!/usr/bin/env bash
# Day 003 lab — completed reference: measure your machine's memory levels.
#
# Part 1 reads the L1/L2 cache and RAM sizes from the OS.
# Part 2 creates a ~200 MB test file INSIDE THIS LAB DIRECTORY with dd,
# times a first (cold) and second (warm) read of it to show the OS
# page-cache effect, and always deletes the file afterwards (trap on EXIT).
#
# No network access, no elevated privileges, no writes outside this directory.
set -euo pipefail
export LC_ALL=C
lab_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
test_file="${lab_dir}/read-speed-testfile.bin"
size_mb=200
cleanup() {
if [ -f "${test_file}" ]; then
rm -f "${test_file}"
echo "Test file removed."
fi
}
trap cleanup EXIT
os="$(uname -s)"
# Convert a Linux sysfs cache size such as "48K" or "2048K" to bytes.
sysfs_cache_bytes() {
local raw
raw="$(cat "$1" 2>/dev/null || echo '')"
case "${raw}" in
*K) echo $(( ${raw%K} * 1024 )) ;;
*M) echo $(( ${raw%M} * 1048576 )) ;;
*[0-9]) echo "${raw}" ;;
*) echo 0 ;;
esac
}
echo "=== Memory Hierarchy Measurements ==="
echo "Generated on: $(date '+%Y-%m-%d')"
echo "Operating system kernel: ${os}"
if [ "${os}" = "Darwin" ]; then
# Apple Silicon reports per-cluster sizes; prefer the performance cores
# (hw.perflevel0.*) and fall back to the plain names on Intel Macs.
l1d_bytes="$(sysctl -n hw.perflevel0.l1dcachesize 2>/dev/null || sysctl -n hw.l1dcachesize)"
l2_bytes="$(sysctl -n hw.perflevel0.l2cachesize 2>/dev/null || sysctl -n hw.l2cachesize)"
ram_bytes="$(sysctl -n hw.memsize)"
elif [ "${os}" = "Linux" ]; then
# Walk cpu0's cache descriptors: level 1 Data cache and level 2 cache.
l1d_bytes=0
l2_bytes=0
for idx in /sys/devices/system/cpu/cpu0/cache/index*; do
[ -d "${idx}" ] || continue
level="$(cat "${idx}/level" 2>/dev/null || echo 0)"
ctype="$(cat "${idx}/type" 2>/dev/null || echo '')"
if [ "${level}" = "1" ] && [ "${ctype}" != "Instruction" ] && [ "${l1d_bytes}" = "0" ]; then
l1d_bytes="$(sysfs_cache_bytes "${idx}/size")"
fi
if [ "${level}" = "2" ] && [ "${l2_bytes}" = "0" ]; then
l2_bytes="$(sysfs_cache_bytes "${idx}/size")"
fi
done
mem_kb="$(awk '/^MemTotal:/ {print $2}' /proc/meminfo)"
ram_bytes=$(( mem_kb * 1024 ))
else
echo "Unsupported OS for this script: ${os} (Windows users: run it inside WSL)" >&2
exit 1
fi
echo "L1 data cache: ${l1d_bytes} bytes ($(( l1d_bytes / 1024 )) KiB)"
echo "L2 cache: ${l2_bytes} bytes ($(( l2_bytes / 1048576 )) MiB)"
echo "RAM: ${ram_bytes} bytes ($(( ram_bytes / 1073741824 )) GiB)"
echo "Creating a ${size_mb} MB test file with dd (inside this lab directory) ..."
if [ "${os}" = "Darwin" ]; then dd_bs="1m"; else dd_bs="1M"; fi
dd if=/dev/zero of="${test_file}" bs="${dd_bs}" count="${size_mb}" 2>/dev/null
sync
# TIMEFORMAT='%R' makes bash's built-in `time` print just the elapsed
# seconds; `cat` streams the file to /dev/null so only reading is measured.
TIMEFORMAT='%R'
cold_s=$( { time cat "${test_file}" > /dev/null; } 2>&1 )
warm_s=$( { time cat "${test_file}" > /dev/null; } 2>&1 )
rate_of() { awk -v mb="${size_mb}" -v s="$1" 'BEGIN { if (s + 0 <= 0) print "n/a"; else printf "%d", mb / s }'; }
cold_rate="$(rate_of "${cold_s}")"
warm_rate="$(rate_of "${warm_s}")"
speedup="$(awk -v c="${cold_s}" -v w="${warm_s}" 'BEGIN { if (w + 0 <= 0) print "n/a"; else printf "%.1f", c / w }')"
echo "Cold read (first pass): ${cold_s} s (${cold_rate} MB/s)"
echo "Warm read (second pass): ${warm_s} s (${warm_rate} MB/s)"
echo "Warm read speed-up over cold: ${speedup}x"
echo "If the two times are close, the file was already in the page cache from being written — see troubleshooting.md."
echo "=== End of measurements ==="
metadata.yml (602 bytes)
lesson_id: D003
day: 3
kind: command-line-inspection
languages: [bash]
setup_commands:
- cd labs/sections/computing-foundations/day-003-memory-hierarchy-registers-ram-and-storage
run_commands:
- bash examples/measure_read_speed.sh
- bash starter/measure_read_speed.sh
test_commands:
- bash tests/run_tests.sh
cleanup_commands:
- 'git checkout -- starter/measure_read_speed.sh # optional: reset your work'
requires_network: false
requires_api_key: false
estimated_minutes: 30
last_executed: '2026-07-12'
executed_on: 'macOS (Apple Silicon), bash tests/run_tests.sh → 19 checks, 0 failures'
requirements/README.md (487 bytes)
# Dependencies — Day 003 lab
**None beyond a POSIX shell.** Everything this lab uses ships with the OS:
- `bash` ≥ 3.2 (preinstalled on macOS and Linux)
- `sysctl` and `sw_vers` (macOS) or the `/sys/devices/system/cpu/.../cache/`
and `/proc/meminfo` files (Linux) for cache and RAM sizes
- `dd`, `date`, `df` for the read-speed measurement
No installs, no package manager, no network. On Windows, run under WSL or
read cache sizes with PowerShell (see `../troubleshooting.md`).
starter/hierarchy-worksheet.md (1830 bytes)
# My Memory Hierarchy — Day 003 worksheet
This worksheet extends the Day 1 machine profile with the levels we could
not see then. Fill in every value from your own machine using the lab
script (`bash starter/measure_read_speed.sh` once completed, or
`bash examples/measure_read_speed.sh`) and, where noted, your Day 1
worksheet. Keep this file — Week 1's project (the Annotated Machine
Teardown) assembles it with the Day 1 profile.
| Level | Your machine's size | Approximate latency (from the lesson table) | Command / source you used |
| ----- | ------------------- | ------------------------------------------- | ------------------------- |
| Registers | ~1 KB total per core (not directly measurable) | | lesson table |
| L1 data cache | | | |
| L2 cache | | | |
| L3 / system-level cache (if reported) | | | |
| RAM | | | Day 1 worksheet or `sysctl -n hw.memsize` |
| Free disk on `/` | | | Day 1 worksheet or `df -h /` |
| Cold read of the 200 MB test file | (seconds and MB/s) | | lab script |
| Warm read of the 200 MB test file | (seconds and MB/s) | | lab script |
## Sanity checks (tick each)
- [ ] Each level's size is bigger than the one above it (L1 < L2 < RAM < disk).
- [ ] My cache sizes are in KiB/MiB, my RAM in GiB, my disk in GB/TB — three
different unit scales, exactly as the pyramid predicts.
- [ ] My warm read was at least as fast as my cold read, and I can say which
level of the hierarchy served it.
## My pyramid, in my machine's real numbers
Write one paragraph: starting at the registers and descending to your disk,
tell the story of *your* memory hierarchy with the sizes you measured and an
approximate latency for each level. End with one sentence on what would
happen if you opened a dataset larger than your RAM.
## One thing that surprised me
One or two sentences.
starter/measure_read_speed.sh (4539 bytes)
#!/usr/bin/env bash
# Day 003 lab — measure your machine's memory levels (YOUR working file).
#
# This starter already detects your OS, creates and cleans up the test file,
# and prints the report skeleton. Your job is the four numbered exercises
# below: replace each "unknown" assignment with the exact command named in
# the comment above it, wrapped in "$(...)". The completed reference is in
# examples/measure_read_speed.sh — run it first to see where you are headed.
#
# The script runs as-is (printing "unknown" for unfinished parts), makes no
# network connections, needs no elevated privileges, and writes only the
# temporary test file inside this lab directory.
set -euo pipefail
export LC_ALL=C
lab_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
test_file="${lab_dir}/read-speed-testfile.bin"
size_mb=200
cleanup() {
if [ -f "${test_file}" ]; then
rm -f "${test_file}"
echo "Test file removed."
fi
}
trap cleanup EXIT
os="$(uname -s)"
# Helper (Linux): convert a sysfs cache size such as "48K" or "2048K" to bytes.
sysfs_cache_bytes() {
local raw
raw="$(cat "$1" 2>/dev/null || echo '')"
case "${raw}" in
*K) echo $(( ${raw%K} * 1024 )) ;;
*M) echo $(( ${raw%M} * 1048576 )) ;;
*[0-9]) echo "${raw}" ;;
*) echo 0 ;;
esac
}
echo "=== Memory Hierarchy Measurements ==="
echo "Generated on: $(date '+%Y-%m-%d')"
echo "Operating system kernel: ${os}"
if [ "${os}" = "Darwin" ]; then
# Exercise 1 (macOS): set l1d_bytes using:
# sysctl -n hw.perflevel0.l1dcachesize 2>/dev/null || sysctl -n hw.l1dcachesize
# (performance-core figure on Apple Silicon; plain name on Intel Macs)
l1d_bytes="unknown"
# Exercise 2 (macOS): set l2_bytes using:
# sysctl -n hw.perflevel0.l2cachesize 2>/dev/null || sysctl -n hw.l2cachesize
l2_bytes="unknown"
# Exercise 3 (macOS): set ram_bytes using: sysctl -n hw.memsize
ram_bytes="unknown"
elif [ "${os}" = "Linux" ]; then
# Exercise 1 (Linux): set l1d_bytes using the provided helper on cpu0's
# level-1 Data cache descriptor:
# sysfs_cache_bytes /sys/devices/system/cpu/cpu0/cache/index0/size
# (index0 is the L1 data cache on almost all machines; confirm with
# `cat /sys/devices/system/cpu/cpu0/cache/index0/type` — it should say Data)
l1d_bytes="unknown"
# Exercise 2 (Linux): set l2_bytes using:
# sysfs_cache_bytes /sys/devices/system/cpu/cpu0/cache/index2/size
# (index2 is L2 on almost all machines; its `level` file should say 2)
l2_bytes="unknown"
# Exercise 3 (Linux): set ram_bytes by reading MemTotal (in kB) and
# multiplying by 1024:
# mem_kb="$(awk '/^MemTotal:/ {print $2}' /proc/meminfo)"; ram_bytes=$(( mem_kb * 1024 ))
ram_bytes="unknown"
else
echo "Unsupported OS for this script: ${os} (Windows users: run it inside WSL)" >&2
exit 1
fi
if [ "${l1d_bytes}" != "unknown" ]; then
echo "L1 data cache: ${l1d_bytes} bytes ($(( l1d_bytes / 1024 )) KiB)"
else
echo "L1 data cache: unknown"
fi
if [ "${l2_bytes}" != "unknown" ]; then
echo "L2 cache: ${l2_bytes} bytes ($(( l2_bytes / 1048576 )) MiB)"
else
echo "L2 cache: unknown"
fi
if [ "${ram_bytes}" != "unknown" ]; then
echo "RAM: ${ram_bytes} bytes ($(( ram_bytes / 1073741824 )) GiB)"
else
echo "RAM: unknown"
fi
echo "Creating a ${size_mb} MB test file with dd (inside this lab directory) ..."
if [ "${os}" = "Darwin" ]; then dd_bs="1m"; else dd_bs="1M"; fi
dd if=/dev/zero of="${test_file}" bs="${dd_bs}" count="${size_mb}" 2>/dev/null
sync
# Exercise 4 (both): time a cold and a warm read of the test file.
# Set cold_s and then warm_s, IN THAT ORDER, each using exactly:
# { time cat "${test_file}" > /dev/null; } 2>&1
# (TIMEFORMAT below makes bash's `time` print just the elapsed seconds;
# the first assignment measures the cold read, the second the warm one.)
TIMEFORMAT='%R'
cold_s="unknown"
warm_s="unknown"
if [ "${cold_s}" = "unknown" ] || [ "${warm_s}" = "unknown" ]; then
echo "Cold read (first pass): unknown"
echo "Warm read (second pass): unknown"
echo "(Exercise 4 not completed yet.)"
else
rate_of() { awk -v mb="${size_mb}" -v s="$1" 'BEGIN { if (s + 0 <= 0) print "n/a"; else printf "%d", mb / s }'; }
echo "Cold read (first pass): ${cold_s} s ($(rate_of "${cold_s}") MB/s)"
echo "Warm read (second pass): ${warm_s} s ($(rate_of "${warm_s}") MB/s)"
speedup="$(awk -v c="${cold_s}" -v w="${warm_s}" 'BEGIN { if (w + 0 <= 0) print "n/a"; else printf "%.1f", c / w }')"
echo "Warm read speed-up over cold: ${speedup}x"
fi
echo "=== End of measurements ==="
tests/run_tests.sh (3289 bytes)
#!/usr/bin/env bash
# Tests for the Day 003 lab. Run from the lab directory:
# bash tests/run_tests.sh
#
# Verifies that the completed reference script produces a well-formed
# measurements report (structure + behavior: warm read not slower than the
# cold read beyond a noise tolerance, test file cleaned up), and — once the
# learner has finished the starter script — checks their version the same way.
set -u
lab_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
failures=0
checks=0
check() {
local label="$1" ok="$2"
checks=$((checks + 1))
if [ "${ok}" = "yes" ]; then
echo " ok: ${label}"
else
echo " FAIL: ${label}"
failures=$((failures + 1))
fi
}
run_measurement_checks() {
local script="$1" strict="$2" output
echo "Testing ${script} ..."
if ! output="$(bash "${script}" 2>&1)"; then
check "script exits successfully" "no"
echo "${output}" | sed 's/^/ /'
return
fi
check "script exits successfully" "yes"
echo "${output}" | grep -q '^=== Memory Hierarchy Measurements ===$' && check "prints report header" "yes" || check "prints report header" "no"
echo "${output}" | grep -q '^=== End of measurements ===$' && check "prints report footer" "yes" || check "prints report footer" "no"
for field in "L1 data cache:" "L2 cache:" "RAM:" "Cold read (first pass):" "Warm read (second pass):"; do
echo "${output}" | grep -q "^${field}" && check "prints '${field}'" "yes" || check "prints '${field}'" "no"
done
if [ "${strict}" = "strict" ]; then
if echo "${output}" | grep -q "unknown"; then
check "no field is left 'unknown'" "no"
else
check "no field is left 'unknown'" "yes"
fi
# Behavior: the warm (page-cached) read must not be meaningfully slower
# than the cold read. Tolerance factor 1.5 absorbs timer noise on very
# fast machines where both reads are served from the page cache.
cold="$(echo "${output}" | sed -n 's/^Cold read (first pass):[[:space:]]*\([0-9.]*\) s.*/\1/p')"
warm="$(echo "${output}" | sed -n 's/^Warm read (second pass):[[:space:]]*\([0-9.]*\) s.*/\1/p')"
if [ -n "${cold}" ] && [ -n "${warm}" ] && awk -v c="${cold}" -v w="${warm}" 'BEGIN { exit !(c > 0 && w > 0 && w <= c * 1.5) }'; then
check "warm read <= 1.5x cold read (page-cache effect, with tolerance)" "yes"
else
check "warm read <= 1.5x cold read (page-cache effect, with tolerance)" "no"
fi
if [ ! -f "${lab_dir}/read-speed-testfile.bin" ]; then
check "test file cleaned up after run" "yes"
else
check "test file cleaned up after run" "no"
rm -f "${lab_dir}/read-speed-testfile.bin"
fi
fi
}
run_measurement_checks "${lab_dir}/examples/measure_read_speed.sh" strict
# The starter ships with 'unknown' values on purpose; once the learner has
# replaced them all, hold their script to the same strict standard.
if grep -q '"unknown"' "${lab_dir}/starter/measure_read_speed.sh"; then
echo "Note: starter/measure_read_speed.sh still has unfilled exercises — testing structure only."
run_measurement_checks "${lab_dir}/starter/measure_read_speed.sh" lenient
else
run_measurement_checks "${lab_dir}/starter/measure_read_speed.sh" strict
fi
echo
echo "${checks} checks, ${failures} failure(s)."
[ "${failures}" -eq 0 ]
Troubleshooting
Troubleshooting — Day 003 lab
sysctl: unknown oid 'hw.perflevel0.l1dcachesize'
That name exists only on Apple Silicon. The script already falls back to the
plain hw.l1dcachesize / hw.l2cachesize names on Intel Macs — if you are
running the commands by hand, use the plain names.
Cache sizes come back empty on Linux
Minimal or virtualized Linux (containers, some cloud VMs) may not expose
/sys/devices/system/cpu/cpu0/cache/. The script prints not exposed by this environment for any level it cannot read — that is honest and
expected; note it on your worksheet rather than inventing a number.
lscpu | grep -i cache is an alternative source when it is available.
Warm read is not faster than cold read
This is normal and the lab allows for it. On a fast SSD with a warm OS cache
already primed, or on a machine with plenty of free RAM, both passes can be
near-identical — the ratio may read 1.0x. The point is to observe the
page-cache mechanism, not to hit a target speed-up. If you want a clearer
gap, increase size_mb in the script to something close to (but below) your
free RAM, or on Linux drop caches between passes with
sync; echo 3 | sudo tee /proc/sys/vm/drop_caches (optional, needs sudo —
skip it if you would rather not).
dd: No space left on device
The script writes a ~200 MB temporary file inside the lab directory and
deletes it on exit. If your disk is nearly full, lower size_mb at the top
of the script (e.g. to 50) and rerun.
The test file was left behind after I pressed Ctrl+C
The cleanup runs on normal exit via a trap. A hard kill can occasionally
skip it; just delete read-speed-testfile.bin in the lab directory manually.
Windows
Run the scripts inside WSL (they work unchanged), or read your cache sizes
in PowerShell with Get-CimInstance Win32_CacheMemory | Select-Object Purpose, InstalledSize and record the numbers on the worksheet by hand.
Security notes
Security notes — Day 003 lab
- What the scripts do: read cache/RAM sizes from the OS, then write one
~200 MB temporary file (
read-speed-testfile.bin) inside this lab directory, time reading it twice, and delete it on exit via atrap. Nothing is written outside the lab directory; there is no network access. - Privileges: everything runs as your normal user. No
sudois required. The optional Linux "drop caches" tip in the troubleshooting file does need sudo — it is clearly marked optional and you can skip it. - Disk: the temporary file is the only footprint and is removed automatically. If a hard kill interrupts the script, delete the file manually.
- Privacy: cache and RAM sizes are generic hardware facts, safe to share. As with Day 1, avoid posting full profiles of employer-managed machines.
- Read before running: the scripts are short and commented — read them first, as with every lab in this course.