The Mobile Architecture Workbench
Every tool this course needs, standing up in one sitting on any laptop — with a browser-only path for a locked-down machine and an optional route for students who own an Android device
Appendix A — The Mobile Architecture Workbench
This course measures things. Not in Week 6, not once, not as a garnish on an essay — every week, on your own machine, with a repetition count and a dispersion figure. Three of The Four Questions — performance, energy, thermals — are answered with numbers you take yourself, and the fourth, placement, is decided from them. That means the bench has to be standing before the first lab, and it means the bench has to be trustworthy, which is a harder and much less discussed requirement. A compiler that runs is not a workbench. A compiler that runs on a laptop quietly rescaling its own clocks halfway through your benchmark is a machine for generating confident, reproducible, wrong numbers.
So this appendix does two jobs. Most of it gets you from nothing to a working toolchain in one sitting. The last part — A.8 — is the one almost nobody writes down, and it separates a graduate measurement from an undergraduate one: two runs that prove your bench is measuring what you think it is measuring.
There are three workbenches, and the names are used throughout the book exactly as defined here.
| Name | What it is | Status | |
|---|---|---|---|
| L | Workbench L | A laptop you control, with a C++17 compiler and Python 3 | The default if you have admin rights |
| B | Workbench B | A cloud development environment in a browser tab. Nothing installed, no administrator rights, no device | A first-class citizen, not a consolation prize |
| D | Workbench D | Workbench L plus an Android device with developer options and the debug bridge | Always optional. Never required by anything graded at Normal tier |
Read A.1, pick one, build it in the sitting described there, then run the calibration pass in A.8 before you write down your first number.
Two things run through every section.
Every version, allowance, menu path, and package name below is a snapshot, not a law. Operating systems rename their settings, cloud vendors change their tiers, and phone manufacturers move developer options somewhere new every couple of releases. Where this appendix cannot be certain, it tells you the shape of the thing and sends you to the vendor’s own documentation — the same discipline the course grades in every report you write.
Do the setup yourself. Asking an assistant to explain a linker error is a good use of it. Pasting a setup script you do not understand into a terminal is not setup. And never take a package name, a flag, a free-tier limit, or a menu path from a model without checking it — those are exactly the facts a language model invents most fluently, in exactly the tone of a manual.
A.1 — The Decision Table: Which Workbench Is Yours
Be honest about the machine and the permissions you actually have, not the ones you wish you had. Find your row.
| What you actually have | Take | Where to go |
|---|---|---|
| A school- or work-managed laptop with no administrator rights; a Chromebook; a borrowed, shared, or library machine | Workbench B | A.4 |
| A macOS machine you own and can install on | Workbench L | A.3.1 |
| A Linux machine you own and can install on | Workbench L | A.3.2 |
| A Windows machine you own, with rights to enable the Windows Subsystem for Linux | Workbench L via WSL | A.3.3 |
| A Windows machine you own but cannot enable WSL on (policy, virtualization disabled in firmware, an edition without it) | Try the native compiler route; if that fails, Workbench B | A.3.3 |
| A very old or low-memory machine you own | Workbench B, and do not fight it | A.4 |
| Any of the above plus an Android phone or tablet you own | Your workbench from above, plus Workbench D | A.5 |
| Any of the above plus an iPhone or iPad | Your workbench from above. The phone is not a workbench in this course | A.5 |
| Admin rights at home, none on the machine you use between classes | Build both. Workbench L at home, Workbench B for everywhere else | A.3 and A.4 |
| You are not sure what rights you have | Open a terminal and run sudo -v (macOS/Linux). If it refuses or you have no password, you do not have admin. Take Workbench B | A.4 |
The guarantee
Read this once and then stop worrying about it:
Every Normal-tier requirement in this book is completable on Workbench B alone. No lab, no exam, no quiz, and no part of the capstone requires a phone, a power meter, an oscilloscope, a paid tool, a GPU, or administrator rights on anything. A student working on a locked-down Chromebook with an iPhone in their pocket can earn 100 in this course.
That is a design constraint on the book, not a courtesy. The two places where a physical device would genuinely tell you something more — a real thermal soak on mobile silicon in Lab 5, a system trace in Lab 6 — are marked optional, sit in the Medium or Hard tiers, and carry a complete non-device path worth identical points. A.4 lists every one of them by name so you can verify the claim rather than take it on faith.
The full weighting is in Appendix D and it does not change:
| Component | Weight |
|---|---|
| Six weekly labs (Weeks 1–3, 5–7), 7% each | 42% |
| Week 4 Placement Practical | 8% |
| Eight weekly quizzes, 1.5% each | 12% |
| Week 4 auto-graded checkpoint (cumulative, Weeks 1–4) | 10% |
| Capstone SoC Investigation | 20% |
| Capstone Technical Briefing (12 min, recorded) | 8% |
| Total | 100% |
Nothing in that table has a hardware prerequisite beyond a browser.
The sitting
Do it in this order. Each row assumes the one above it exists.
| # | What you are doing | Minutes |
|---|---|---|
| 1 | Read A.1, find your row, commit to a workbench | 5 |
| 2 | Install the toolchain (A.3) or stand up the cloud environment (A.4) | 20 |
| 3 | Run the two --version checks and read the output properly | 5 |
| 4 | The Week-1 acceptance test: compile one program, run one script (A.3.4) | 10 |
| 5 | Type calibrate.cpp from A.8 and build it | 15 |
| 6 | Run calibration Pass 1 and read the verdict | 2 |
| 7 | Run calibration Pass 2 for sixty seconds and read the verdict | 3 |
| 8 | Paste both verdict lines into your measurements.xlsx with today’s date | 5 |
| 9 | Optional: enable developer options on a device you own (A.5) | 10 |
| Total (without row 9) | 65 |
One hour. If you find yourself in hour three, you have almost certainly wandered onto Workbench L without the rights to finish it — go back to A.1, take the Workbench B row, and lose nothing.
Coach’s Note — Do not skip rows 5 through 8 because they look like extras. They are the only rows in this table that will change a grade. Rows 1–4 tell you the compiler exists; rows 5–8 tell you whether the compiler is attached to a machine that holds still. The single most common cause of a wrong student result is never a wrong program. It is a right program run on a laptop that was on battery, thermally soaked, or stepping between operating points — and the student had no way to know, because nobody had told them to look.
A.2 — What This Course Actually Requires of a Machine
Four things. That is the whole list.
| Piece | Why the course needs it | Used from |
|---|---|---|
A C++17 compiler — g++ or clang++ | Every measurement program. Compiled, for the reasons in A.6 | Week 1 |
| Python 3 — standard library only | Every analysis and model program | Week 1 |
| A text editor and a shell | Typing programs, running them, reading CSV | Week 1 |
| A browser and somewhere to publish a repository | Vendor specification pages, the interactive labs, submission | Week 1 |
What you do not need, at any point, for any tier: Android Studio. The Android SDK or NDK. A phone. A tablet. A GPU. numpy, pandas, matplotlib, or any third-party package whatsoever — every plot in this book is ASCII, printed by a program using nothing but the standard library, precisely so it renders on a locked-down machine. A paid editor, a paid profiler, a paid cloud plan, a license for anything. An oscilloscope, a power meter, a USB current monitor. Administrator rights on the machine you are reading this on.
If somebody tells you a graduate architecture course requires you to buy hardware, hand them this section.
Every file you will run, and what runs it
This is the complete inventory. Nothing outside it is needed, and the right-hand column is the only dependency any of it has.
| Week | Chapter’s code/ files | Runs under |
|---|---|---|
| 1 | soc-blocks.csv, block_budget.py, isa_compare.cpp, regwalk.s | Python 3 · C++17 · (regwalk.s is a reading exercise — nothing assembles it) |
| 2 | core-profiles.csv, placement_model.py, amdahl.py, migrate.cpp | Python 3 · C++17 with -pthread |
| 3 | cache_walk.cpp, pointer_chase.cpp, matmul_order.cpp, hierarchy_plot.py, model-memory.csv | C++17 · Python 3 |
| 4 | placement-bench.csv, partition.py, quantize.py, tile_cost.cpp | Python 3 · C++17 |
| 5 | thermal-soak.csv, throttle_analysis.py, energy_model.cpp, battery.py | Python 3 · C++17 |
| 6 | bus-traffic.csv, qos_arbiter.py, irq_vs_poll.cpp, radio_energy.py | Python 3 · C++17 |
| 7 | boot-chain.csv, chain_check.py, pqc_budget.py, pqc-sizes-example.csv, timing_leak.cpp | Python 3 · C++17 |
| 8 | bench_harness.cpp, counters.py, compare.py, soc-compare.csv, sample-perf-stat.txt | C++17 · Python 3 |
Plus the files you type yourself in the reps — the reps say “type it, do not paste it,” and they mean it: sumfn.cpp and branch.cpp (Ch 1, Reps 6 and 8), aos_soa.cpp (Ch 3, Rep 5), soak.py (Ch 5, Rep 8 — Python on purpose, see A.6), nobar.cpp (Ch 8, Rep 2), and calibrate.cpp from this appendix. Same two dependencies; nothing new.
The build line
One canonical form, used everywhere in the book:
g++ -O2 -std=c++17 -o <program> code/<program>.cpp
Two exceptions, both of which will bite you if you do not know them now:
| File | The line | Why |
|---|---|---|
migrate.cpp | g++ -O2 -std=c++17 -pthread migrate.cpp -o migrate | It spawns threads. Without -pthread some toolchains link but throw std::system_error at run time |
matmul_order.cpp | Build at -O2, not -O3 | At -O3 some compilers interchange the loops for you and erase the very effect you are measuring. Try both and report what yours did — that is Rep 4 |
And one rule with no exception: never -O0. At -O0 you are timing the compiler’s un-optimized scaffolding — register spills, unelided temporaries, a function call for everything — not the machine. Chapter 8 Rep 2 makes you build at -O0 deliberately, once, so you can see how badly it misleads, and then never again.
If your compiler is clang++ rather than g++, substitute the name and change nothing else. Every flag above means the same thing to both.
A.3 — Workbench L: The Local Path
Faster than the browser, works on a plane, consumes nobody’s allowance, and gives you the operating-system interfaces that make Chapter 5 richer. It needs administrator rights. If you cannot get them, take Workbench B (A.4) and stop negotiating with your IT desk — a week spent on that is an eighth of this course, and you lose nothing graded.
A.3.1 — macOS
You do not need Xcode. You need the command line developer tools, which are a fraction of the size and install from the terminal:
xcode-select --install
A dialog appears; accept it and wait. If it reports the tools are already installed, they are, and you are done. Python 3 arrives with the same package on current macOS versions — verify rather than assume, and if python3 is missing, install a current Python 3 from python.org.
One thing to understand before it confuses you. On macOS, g++ is not GNU g++. It is a wrapper that invokes Apple’s clang, which is why g++ --version prints something with the word clang in it. That is correct and expected — every program in this book is standard C++17 and builds identically under either. When a rep says g++, type g++.
The second thing matters in Week 1 Rep 6: Apple’s assembler prints vector lane shapes on the mnemonic (saddw.2d v1, v1, v16) where the GNU assembler prints them on the operand (add v1.4s, v1.4s, v2.4s). Both mean the same thing, and a student grepping for v0.4s on a Mac concludes “not vectorized” about code that plainly is.
A.3.2 — Linux
Pick the line for your distribution family. All of them want the same two things: a C++ compiler and Python 3.
| Family | Command |
|---|---|
| Debian, Ubuntu, and derivatives | sudo apt update && sudo apt install -y build-essential python3 |
| Fedora, RHEL, and derivatives | sudo dnf install -y gcc-c++ python3 |
| Arch and derivatives | sudo pacman -S --needed base-devel python |
| openSUSE | sudo zypper install -y gcc-c++ python3 |
| Alpine | sudo apk add build-base python3 |
build-essential and base-devel are meta-packages that pull in the compiler, the linker, and the standard headers together, which is why they are preferred over installing g++ alone.
Linux is the richest workbench for this course, and not because of the compiler. It is the only mainstream platform that exposes the operating system’s own power and frequency machinery as ordinary files:
| Path | What it tells you |
|---|---|
/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor | Which governor is deciding your clocks right now |
.../cpufreq/scaling_available_frequencies | The operating-point menu the hardware offers |
/sys/devices/system/cpu/cpu0/cpuidle/state*/ | The idle-state ladder: exit latency, required residency, use count |
/sys/class/power_supply/BAT*/power_now | Instantaneous draw, in microwatts, if the machine has a battery |
/sys/class/thermal/thermal_zone*/temp | Whatever thermal zones the platform chose to expose |
Chapter 5 Rep 3 walks all of these, and A.8 uses the first. Several are absent inside containers and virtual machines — that absence is a finding you report, not a failure to work around.
A.3.3 — Windows
Two routes that work and one that does not.
Route 1 — the Windows Subsystem for Linux. This is the recommended path. You get a real Linux userspace, the exact commands printed in every rep, a POSIX shell for the loops the reps use, and — for the /sys interfaces above — at least a Linux-shaped view of the world. Enable it from an administrator PowerShell:
wsl --install
Reboot, let the default distribution finish its first-run setup, then open the Linux shell and follow A.3.2 exactly as written. The command name, the default distribution, and the exact prerequisites vary by Windows version and edition, and hardware virtualization has to be enabled in firmware — if wsl --install fails, read Microsoft’s own documentation for your version rather than guessing. Some managed machines have this disabled by policy.
Route 2 — a native Windows toolchain. If WSL is unavailable but you can still install software, a MinGW-w64 distribution (MSYS2 is the common way to get one) gives you a real g++ on Windows, and Python 3 installs from python.org. Every program in the book compiles under it. Two friction points: the reps’ shell loops (for s in 64 128 256; do ...; done) are POSIX shell and need Git Bash or the MSYS2 shell rather than PowerShell, and Windows path separators occasionally surprise a redirect.
The route that does not work: Microsoft Visual C++ (cl.exe) as a drop-in. The compiler is fine; the command lines in this book are not portable to it. -O2 -std=c++17 -o foo is GCC/Clang syntax, and every rep, project, and troubleshooting entry here assumes it. Choosing MSVC means translating flags yourself for eight weeks, unsupported, for no gain.
No administrator rights on the Windows machine? Take Workbench B, A.4. It is one browser tab and it is genuinely fine.
A.3.4 — Verify Workbench L
Three checks, in this order. Do not skip to the third.
Check 1 — the compiler answers.
g++ --version
Expected shape on Linux (GNU):
g++ (<packager's build string>) <major>.<minor>.<patch>
Copyright (C) <year> Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Expected shape on macOS (Apple clang wearing the name):
Apple clang version <version> (clang-<build>)
Target: arm64-apple-darwin<version> <- or x86_64-apple-darwin<version>
Thread model: posix
InstalledDir: <an Xcode or CommandLineTools path>
What you are checking: a version line appears at all, and the Target: triple (on clang) names an architecture you recognize. Write that architecture down — arm64 or x86_64 — because Chapter 1 Rep 6 has you read your compiler’s assembly output, and which instruction set you are reading is the point of that rep.
Check 2 — Python answers.
python3 --version
Expected shape: Python 3.<minor>.<patch>. Any Python 3 currently shipping with a supported operating system is fine; the programs use formatted string literals and nothing more exotic, and they import only sys, os, csv, re, random, and argparse — all standard library. If the command is not found but python --version reports a 3.x, use python everywhere the book says python3; if it reports a 2.x, install Python 3.
Check 3 — the acceptance test. This is the one that counts, because it exercises the compiler, the linker, the standard library, the shell, and Python in one pass. Run it from Chapter 1’s directory:
g++ -O2 -std=c++17 -o isa_compare code/isa_compare.cpp && ./isa_compare
python3 code/block_budget.py
The first command should print a small table: a header naming the array size and repetition count, then one row per spelling of the kernel — scalar, unrolled x4, lane block x4 — each with a median in seconds, a throughput figure, a ratio against scalar, and a result value. The numbers are yours and will not match anyone else’s. What proves the bench works is the shape: three rows, three medians, three ratios, and a note at the bottom about the last digits of the three results differing.
The second command should print a bar chart of die-area share by block, a rollup by category, and a closing block answering how much of the chip is not the CPU.
If both worked, Workbench L is real. Go to A.6 and then to the calibration pass in A.8. If either failed, A.10 almost certainly has your symptom.
A.4 — Workbench B: The Browser Path (a first-class citizen)
Nothing installed. No administrator rights. No download. One browser tab on a machine you do not own, and every graded requirement in this book within reach.
Be precise about the word “first-class,” because students read a browser path as a fallback and then quietly under-invest in it. Workbench B is not a reduced version of the course. It is the same course, with two capabilities removed that affect a handful of clearly marked optional steps — and those removals are, awkwardly for the consolation-prize reading, also a teaching opportunity, because “I could not measure this, and here is exactly what I would have needed” is a sentence Chapter 8 grades you on writing well.
What it is, generically
A cloud development environment is a Linux container running somewhere else, with a full editor and a real terminal delivered to your browser. You are the administrator inside the container; no rights on your laptop are involved at any point. You reach it from a repository, it starts in about a minute, and it dies when you delete it.
The default recommendation is the cloud development environment built into GitHub, because it launches from the repository you are submitting anyway and needs nothing but a free account. Alternatives that also work: a browser IDE that executes real code in a tab, a hosted notebook service if you are comfortable driving g++ from a shell cell, or a small cloud virtual machine reached through the vendor’s browser-based shell.
This book deliberately prints no price, free-tier size, or monthly allowance for any of them. Those change without notice and cannot be verified for the day you read this. Read the provider’s own current documentation, write the number down with the date you checked it, and treat it as a fact with an expiry — the same habit every lab in this course grades.
Two things bite students on every provider:
- Storage is usually billed on stopped environments, not just running ones. Delete an environment you are finished with; do not merely stop it.
- The container’s disk is not backup. Commit and push at the end of every session. Anything uncommitted can vanish when the environment is reclaimed.
Standing it up
Create a free account and a repository for this course, using an address that survives graduation. Launch a development environment from that repository and wait for the terminal. Run the verification below. Commit one line to a README and confirm it appears in the repository from a browser tab. Then delete the environment — you now know the path works, on today’s network, from today’s machine, which you want to have learned in Week 1 rather than Week 5.
Verify Workbench B
Exactly the same three checks as Workbench L, run in the browser terminal. That is the point: the commands do not change, so the book does not need two sets of instructions.
g++ --version
python3 --version
Expected shape: the GNU form shown in A.3.4 for the compiler, and Python 3.<minor>.<patch> for Python. Most cloud images ship both preinstalled. If the compiler is missing, you are root inside the container, so install it with the Debian/Ubuntu line from A.3.2 — a permitted sudo inside a container you own, which is not the same thing as needing admin on your laptop.
Then the acceptance test, unchanged:
g++ -O2 -std=c++17 -o isa_compare code/isa_compare.cpp && ./isa_compare
python3 code/block_budget.py
Same expected shapes. If both work, go do the calibration pass in A.8 — which matters more on Workbench B than on Workbench L, because a shared host is exactly the environment where a benchmark drifts for reasons that have nothing to do with your program.
What Workbench B cannot do
Two capabilities, stated plainly.
1. It cannot give you a thermal environment you own. You are on server-class hardware in a data centre, sharing it with tenants you cannot see. Three consequences:
- The host may have real cooling and never throttle at all, so Chapter 5’s sustained-load run may show a flat line where a laptop shows a falling one.
- Or it may drift — and you cannot tell whether that drift was heat, a scheduler, or a neighbour compiling something enormous on the next core.
- The thermal transient the course cares about — a device absorbing a burst it cannot sustain because the chassis has heat capacity — is a property of a small sealed object in your hand. A data-centre host does not have it.
2. It cannot give you physical-device counters or platform power. No hardware performance counters (unprivileged access to the performance monitoring unit is normally unavailable inside a container, and often absent in the virtual machine underneath it). No adb, no device. No battery, so no /sys/class/power_supply. Frequently a truncated cpufreq and cpuidle view, or none at all.
Exactly which steps that affects — and their tier
Here is the complete list. Check it against the labs yourself; that is why it is a table.
| Where | The step | Tier | What you do instead |
|---|---|---|---|
| Ch 5, Rep 3 | Read the governor, the frequency menu, and the idle-state ladder | Rep (ungraded) | You may see one governor and a truncated ladder. The rep already tells you that is a finding — report what you see and say why it is truncated |
| Ch 5, Rep 4 | Convert seconds into joules using a platform power figure | Rep (ungraded) | Report the time, state that power was not measurable, and use time as a stated relative energy proxy at fixed configuration — Chapter 8 §8.7 licenses exactly this |
| Ch 5, Rep 8 | A ten-minute soak on your own machine | Rep (ungraded) | Runs fine. If nothing throttles, that is a legitimate result — say so and give the two most likely reasons |
| Lab 5, Part 4 | A real sustained-load run of ten minutes or more | Normal | Runs on Workbench B. The rubric explicitly credits “a negative result handled correctly.” No throttling is an answer, if you say why |
| Ch 5, Rep 10 | adb shell dumpsys thermalservice | Rep, marked optional | Skip. The rep’s graded reasoning is about the headroom ladder, which is on the page |
| Ch 8, Rep 6 | Produce your own perf stat counter set | Rep (ungraded) | Use the shipped sample-perf-stat.txt, which exists for this reason and is labeled synthetic on its first line |
| Lab 8 (capstone) | Counter analysis | Normal | The capstone’s required measurements are timing measurements. Counter analysis may be done on the supplied sample with the limitation stated — Chapter 8 §8.8 says so in as many words |
| Lab 2 | Pin a thread to a specific core | Medium | migrate.cpp has a portable fallback and reports the cold-cache cost without pinning. On Linux hosts pinning often works anyway — try it |
| Lab 2, optional path | Device battery-usage attribution | Optional, zero additional points | Skip |
| Lab 5, H2 | Device thermal capture | Hard, and marked “never required” | H1 is the graded Hard piece and is scored identically without H2 |
| Lab 6, optional | A system trace on a device | Optional bonus, any tier | Skip |
Read down the tier column. Two rows say Normal, and both run on Workbench B — one because a negative result is explicitly creditable, the other because the book says outright that counter analysis on the supplied sample is acceptable with the limitation stated. Everything else is a rep, an optional step, or an extra-credit tier. No Normal-tier requirement anywhere in this book depends on a capability Workbench B lacks. The guarantee in A.1 holds.
The one thing you must do that Workbench L students need not
Say that you were on Workbench B, every time. A shared cloud host is a legitimate measurement condition and naming it is worth points — Lab 3 says so explicitly, and the conditions block in Appendix C has a field for it. Expect wider dispersion than a quiet laptop. Report the median with its interquartile range, note the host was shared, and you have written a better measurement than a Workbench L student who reported one clean number without conditions.
Coach’s Note — The browser student’s disadvantage is smaller than they think and their advantage is bigger than they think. A cloud environment is defined by a file in your repository, so a grader can reproduce it exactly — your numbers are checkable in a way a laptop’s never are. In Week 8, when the capstone asks what a stranger would need to reproduce your measurement, the honest answer for most laptops is “my laptop, in the state it was in that afternoon.” Yours is a container definition. That is not a consolation. That is the better answer.
A.5 — Workbench D: The Optional Device
Skipping this section costs you nothing. Not a point, not a tier, not a rubric line. Read that again if you were about to buy a used phone for this course: do not. Every graded item in the book has a complete, identically-scored path that does not touch a device, and A.4 lists every device step by name and tier so you can check.
Workbench D is for the student who already owns an Android phone or tablet and wants to see the platform they have been reasoning about actually answer. There is a real difference between reading that the thermal ladder exists and watching THERMAL_STATUS_LIGHT appear on a device warming up in your hand. That difference is worth an hour if you have the hardware, and nothing at all if you have to acquire it.
Only do this on a device you own. Not a work phone, not a family member’s, not a device under a management policy — enabling developer options there may violate a policy you signed, and the debug bridge is a genuinely powerful interface.
An iPhone or iPad is not Workbench D. Apple’s platform does not offer an equivalent path for this course’s purposes, and nothing in the book asks for one. If your phone is an iPhone you are on Workbench L or B, like most of the class, with no penalty whatsoever.
Enabling it, in the general shape
Menu paths vary by manufacturer, by Android version, and sometimes by carrier build. This book will not print a specific sequence and claim it is yours — that is precisely the kind of confident, plausible, wrong instruction the course spends eight weeks teaching you to distrust. The shape is stable even when the path is not:
- Find the build-number entry in your device’s settings, usually somewhere under an “About phone” or “About device” screen, occasionally one level deeper under a “Software information” sub-screen.
- Tap it repeatedly until the device tells you developer options are enabled. It will count down at you.
- Open the new developer options screen, which appears somewhere in settings — often under a system or additional-settings group.
- Enable USB debugging there.
- Connect the device by cable and accept the authorization prompt on the device screen. The prompt appears once per computer; if it never appears, the cable is charge-only, which is by far the most common cause.
For the current, vendor-neutral version of that sequence — and for everything else in this section — go to https://developer.android.com/ and search for developer options and the Android Debug Bridge. Read the platform’s own page, not a forum post and not a model’s summary of one.
The debug bridge
adb is the Android Debug Bridge, part of the platform tools. Install it from Android’s own distribution rather than from a package that repackages it. Then the only two commands the book actually uses:
adb devices # is the device visible and authorized?
adb shell dumpsys thermalservice # the platform's own thermal view
adb devices should list one device with the word device beside it. If it says unauthorized, look at the phone — the authorization dialog is waiting. If it lists nothing, try another cable before you try anything else.
What Workbench D unlocks, week by week
| Week | Step | Tier | What it adds | Points if you skip |
|---|---|---|---|---|
| 2 | Battery-usage attribution across foreground scrolling and background sync, compared to your model | Optional path | Real attribution data against a synthetic model — the gap is instructive | None lost |
| 5 | adb shell dumpsys thermalservice before, during, and after a sustained load | Rep, optional | Watching the status ladder actually climb | None lost |
| 5 | Lab 5 H2 — a device soak compared against thermal-soak.csv | Hard, marked “never required” | One place where the modeled curve and a real one disagree, and why | None lost — H1 is scored identically |
| 6 | A system trace during a real use case, to check one claim on your integration map | Optional bonus | One claim on your map checked against the platform’s own trace | None lost |
| 8 | simpleperf stat on a debuggable build, parsed with counters.py | Optional | Counters from mobile silicon rather than a laptop | None lost |
Every row says the same thing in the last column. That is not an accident.
Three rules if you do use it
- One device, one set of conditions, one measurement. A number off your phone is a fact about your unit, in your case, at your ambient temperature, at that charge level — not about that product, as Lab 5 says explicitly. Log the device, the ambient temperature, whether it was charging, whether it was in a case, and how long you soaked it, or the number is not usable.
- Turn USB debugging off when you are done. It is a standing interface into your device.
- A device result never replaces the non-device analysis. It is an addition. Every rubric is written that way, so a Workbench D student who skips the modeled work to show a phone trace scores lower, not higher.
A.6 — Why the Measurement Code Is Compiled
Students ask this in Week 1 every time, and it deserves a real answer rather than “because C++ is fast.”
The effects this course measures are small in absolute time. A first-level cache hit and a DRAM access differ by roughly two orders of magnitude in cycles — Chapter 3 hangs an entire week on your ability to see that staircase on your own machine. A branch misprediction costs on the order of the pipeline depth. A cold cache after a migration costs a handful of misses. These are events measured in single-digit to triple-digit nanoseconds, and to see them your measurement apparatus has to cost much less than the thing being measured.
An interpreted language puts its own machinery in between, in four ways, every one of them fatal to the specific experiments in this book:
1. The per-element overhead swamps the signal. In a mainstream Python implementation, acc += data[i] is not one load and one add. It is bytecode dispatch through an interpreter loop, a bounds check, an object unbox, arithmetic on boxed values, an allocation for the result, and reference-count updates — dozens to hundreds of machine instructions with their own dependent memory accesses. If the difference you want to observe is 3 ns against 100 ns and your instrument costs several hundred nanoseconds per element, your instrument is the measurement. The staircase does not shrink; it flattens into a line.
2. The interpreter’s working set competes with yours for the cache. Chapter 3’s experiments work by controlling working-set size precisely — 4 KiB, then 8, then 16, until the data stops fitting in a level and the cost steps up. That control is meaningless if a large interpreter, its bytecode, its object tables, and its allocator share the cache with you and evict your array between iterations. You would be measuring the interpreter’s locality.
3. The data layout is not the layout you wrote. A Python list of floats is an array of pointers to heap-allocated objects. Walking it sequentially in source is a pointer chase in hardware — the exact phenomenon pointer_chase.cpp exists to isolate separately. You cannot demonstrate the difference between sequential access and pointer chasing in a language where sequential access already is pointer chasing. Rep 5’s array-of-structs against struct-of-arrays makes the same point in miniature: the layout is the experiment.
4. You do not control when the runtime does work. Allocation, garbage collection, and dynamic dispatch inject non-determinism inside your timed region. Chapter 8 grades you on dispersion, and a distribution whose tail is somebody else’s garbage collector is not a distribution about your program.
Compiled C++17 removes all four. std::vector<float> is a contiguous array of raw floats; a for loop over it is loads and adds; the timed region contains what you put in it and nothing else. That is what makes a 4 KiB-to-64 MiB sweep produce a readable staircase on an ordinary laptop.
The exception that proves the rule
Notice what the book does not do. Chapter 5 Rep 8 — the ten-minute sustained-load soak — is Python, deliberately, and the rep even tells you to calibrate its chunk size because interpreter speed varies between versions.
That is fine because the quantity being measured is different in scale and in kind. You are not timing a nanosecond-scale memory event; you are asking whether delivered throughput over ten minutes falls as the machine heats up. Interpreter overhead is a large constant factor, and a constant factor cancels out of a ratio — opening rate against closing rate is unaffected by how slow each chunk was in absolute terms. Python is also practical there: no build step, and one copy per core from a shell loop on any machine in the class.
So the rule, stated properly: compile when the measurement’s resolution requirement is near the cost of the instrument; interpret when it is not. Every .cpp file in this book takes a measurement at hardware timescales or evaluates a model whose arithmetic is exact. Every .py file reads data somebody else produced, evaluates a model, or drives a long-timescale experiment. Learn to make that call yourself — it is the same judgment as choosing between a microbenchmark and an application benchmark.
One more thing the compiler will do to you
A good optimizer will notice you never used the result of your benchmark loop and delete the loop. Your kernel then runs in zero time and your speedup is infinite. This is the single most common way a student microbenchmark lies, and it is why bench_harness.cpp ships with a do_not_optimize barrier and why Chapter 8 Rep 2 makes you delete that barrier on purpose and watch the numbers become impossible. Any program you write for this course either consumes its result or passes it through a barrier. There is no third option, and “the number looked great” is not evidence that there is.
A.7 — Timing and Counters, Platform by Platform
What you have everywhere
Portable C++ std::chrono::steady_clock is the floor of this course, and a higher floor than students expect. It gives you monotonic elapsed time for a region you delimit, a time_point with nanosecond-representable resolution on mainstream implementations, a clock that never jumps backwards or gets adjusted by a time service (unlike system_clock), and identical semantics on every platform here with no #ifdef. It does not give you any statement about why the time was spent, any per-core or per-block attribution, any energy figure, or a guarantee that the hardware resolves nanoseconds — that last is exactly what A.8 Pass 1 measures.
Everything the capstone requires you to measure is a timing measurement. That is deliberate, and it is what makes the Workbench B guarantee possible. Counters make an analysis richer; they never make it possible.
What each platform adds
| Platform | Tool | What it adds | The honest caveat |
|---|---|---|---|
| Linux | perf stat, perf record | Real hardware counters: cycles, instructions, cache misses per level, TLB misses, branch misses, stalls. IPC and MPKI come straight out of it | Unprivileged access is restricted by a kernel setting and usually needs an administrator to relax. Counters are frequently absent inside containers and virtual machines regardless of privilege |
| Linux | /sys/devices/system/cpu/... | Governor, operating-point menu, idle-state ladder, sometimes battery draw and thermal zones | Also often absent or truncated in a container |
| macOS | Instruments, and its driver xctrace | The supported profiling path on Apple platforms, including counter-based instruments | The output format differs from perf’s, so counters.py will not parse it. Enter the numbers by hand rather than pretending you ran something you did not. Documentation: https://developer.apple.com/ |
| macOS | sudo powermetrics --samplers cpu_power | A package-level power and frequency estimate while you measure | Needs administrator rights. It is a whole-package figure; it does not attribute watts to a block |
| Windows | The Windows Performance Toolkit (WPR/WPA) and vendor profilers | OS-level tracing and sampled profiling | Direct architectural-counter access on Windows varies by CPU vendor, driver, virtualization state, and OS build. Verify rather than assume. Under WSL you inherit the host’s power management and a virtualized counter path — treat Windows as a timing-only platform for this course unless you have proven otherwise on your machine |
| Android (Workbench D) | simpleperf, Perfetto, Android Studio Profiler, Android GPU Inspector, the Jetpack benchmark libraries | Counters on mobile silicon, system-wide traces, GPU frame analysis | Needs a debuggable build for some paths. https://developer.android.com/ and https://perfetto.dev/ |
| Arm targets | Arm Performance Studio — Streamline, Mali Offline Compiler, Frame Advisor, Performance Advisor | The vendor’s own system profiler and GPU tooling | https://developer.arm.com/ |
Where counters are simply unavailable — and what to do instead
On Workbench B you will not have them. On macOS you have a different tool with a different format. On many managed Linux machines the kernel setting is locked. This is normal, and it is planned for.
First: use the sample. sample-perf-stat.txt ships in Chapter 8’s code/ folder, contains three complete runs, is labeled synthetic on its first line, and is parsed by counters.py. Every counter rep in the book works against it. That was designed in, not bolted on.
Second — the professional move — substitute a differential experiment for a counter. When you cannot count an event, construct two runs that differ in nothing but that event and time both. The whole book is built on this technique:
| You cannot count | So instead you run | The difference isolates |
|---|---|---|
| Cache misses | cache_walk.cpp at a working set inside a level and just outside it | The capacity boundary of that level |
| Cache-line utilization | Stride 64 against stride 128, same total accesses | Your machine’s line size |
| Prefetcher effectiveness | cache_walk.cpp (strided, predictable) against pointer_chase.cpp (random, dependent) at the same working set | What the prefetcher was buying you — this is the ratio Chapter 3 Rep 3 calls the most examinable idea in the chapter |
| Branch mispredictions | The same conditional loop over sorted and unsorted data | The predictor’s contribution, and nothing else |
| Migration cost | The same kernel pinned and free to move (migrate.cpp), plus a cold-cache control | Cache warmth, separated from thread-creation overhead |
| Instruction-count effects | matmul_order.cpp in ijk against ikj order — identical multiply-accumulate count | Purely the access pattern |
Each row is a controlled experiment that yields a number you can defend without a single counter. Then state the limitation: “counters were unavailable on this workbench; the mechanism is inferred from a controlled A/B timing comparison, described in measurements.xlsx.” That sentence is worth more on a rubric than a counter dump nobody interpreted.
Third: never invent the number you could not measure. Not the wattage, not the miss rate, not the cache size. Chapter 8 §8.7 is blunt about the specific failure — multiplying a benchmark score by a plausible-looking wattage you did not measure and calling the quotient performance-per-watt is worse than a guess, because the arithmetic makes it look derived. A fabricated figure is an integrity failure in this course, not a deduction; Appendix D has the policy.
A.8 — The Calibration Pass (the part nobody else writes)
Everything above tells you the bench exists. This section tells you whether it can be trusted, and it takes about five minutes.
The two failures it catches are the two that silently corrupt every subsequent measurement in the course:
- Your clock is too coarse or too expensive to see what you are timing. You will get numbers. They will be reproducible. They will be about the clock.
- Your machine did not hold one operating point while you measured. A laptop that starts at a boost frequency and falls back thirty seconds later has given you two different machines and one filename. Compare a “before” run to an “after” run on it and you have measured the power management, with every downstream conclusion contaminated.
Neither failure announces itself. Both produce clean-looking output. That is why this pass exists.
The program
Type it into calibrate.cpp at the top of your course folder. Type it; do not paste it. The shape of a timing harness is something your hands should know before Week 8 asks you to build one under time pressure.
// calibrate.cpp — the workbench calibration pass for Mobile Computer Architecture.
// Build: g++ -O2 -std=c++17 -o calibrate calibrate.cpp
// Run: ./calibrate # both passes
// ./calibrate --seconds 120 # a longer drift pass
//
// Pass 1 asks what your clock can even see. Pass 2 asks whether your machine
// held one operating point while you were measuring. Run it before Week 1's
// first measurement and again the day you start the capstone.
#include <algorithm>
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
using Clock = std::chrono::steady_clock;
static inline double ns(Clock::duration d) {
return std::chrono::duration<double, std::nano>(d).count();
}
// A dependent integer chain: one long carried dependency, no memory traffic,
// nothing the optimizer can fold away because the result is printed.
static std::uint64_t kernel(std::uint64_t n, std::uint64_t seed) {
std::uint64_t x = seed;
for (std::uint64_t i = 0; i < n; ++i) x = x * 6364136223846793005ULL + 1442695040888963407ULL;
return x;
}
static double median(std::vector<double> v) {
if (v.empty()) return 0.0;
std::sort(v.begin(), v.end());
const std::size_t m = v.size() / 2;
return v.size() % 2 ? v[m] : 0.5 * (v[m - 1] + v[m]);
}
int main(int argc, char **argv) {
double seconds = 60.0;
for (int i = 1; i < argc - 1; ++i)
if (!std::strcmp(argv[i], "--seconds")) seconds = std::atof(argv[i + 1]);
// ---------------- Pass 1: what can the clock see? ----------------
std::printf("PASS 1 — timer resolution and overhead\n");
std::printf(" steady_clock is_steady : %s\n", Clock::is_steady ? "true" : "false");
std::printf(" steady_clock tick period : %lld/%lld s\n",
(long long)Clock::period::num, (long long)Clock::period::den);
// Overhead: the cost of one now() call, measured over a long run of them.
const int kOver = 200000;
auto o0 = Clock::now();
Clock::time_point t;
for (int i = 0; i < kOver; ++i) t = Clock::now();
auto o1 = Clock::now();
(void)t;
const double overhead_ns = ns(o1 - o0) / kOver;
// Granularity: the smallest non-zero difference two back-to-back calls report.
double gran_ns = 1e18;
for (int trial = 0; trial < 2000; ++trial) {
auto a = Clock::now(), b = a;
while (b == a) b = Clock::now();
gran_ns = std::min(gran_ns, ns(b - a));
}
std::printf(" cost of one now() call : %8.1f ns\n", overhead_ns);
std::printf(" smallest visible tick : %8.1f ns\n", gran_ns);
std::printf(" do not time any region shorter than ~%.0f ns (1000x the call cost)\n",
overhead_ns * 1000.0);
// ---------------- Pass 2: did the machine hold still? ----------------
// Size one work quantum to about 50 ms so each sample is far above the floor above.
std::uint64_t n = 1 << 16, sink = 12345;
for (;;) {
auto a = Clock::now();
sink ^= kernel(n, sink);
const double ms = ns(Clock::now() - a) / 1e6;
if (ms > 50.0 || n > (1ULL << 34)) break;
n *= 2;
}
std::printf("\nPASS 2 — operating-point stability over %.0f s (quantum = %llu iterations)\n",
seconds, (unsigned long long)n);
std::vector<double> ms;
const auto start = Clock::now();
while (ns(Clock::now() - start) / 1e9 < seconds) {
auto a = Clock::now();
sink ^= kernel(n, sink);
ms.push_back(ns(Clock::now() - a) / 1e6);
}
const std::size_t k = ms.size(), fifth = k / 5 ? k / 5 : 1;
std::vector<double> first(ms.begin(), ms.begin() + fifth), last(ms.end() - fifth, ms.end());
const double med = median(ms), mf = median(first), ml = median(last);
std::vector<double> sorted = ms;
std::sort(sorted.begin(), sorted.end());
const double iqr = sorted[3 * k / 4] - sorted[k / 4];
std::printf(" samples : %zu\n", k);
std::printf(" median / min / max (ms) : %8.2f %8.2f %8.2f\n", med,
*std::min_element(ms.begin(), ms.end()), *std::max_element(ms.begin(), ms.end()));
std::printf(" interquartile spread : %8.2f ms (%.1f%% of median)\n", iqr, 100.0 * iqr / med);
std::printf(" first fifth / last fifth : %8.2f %8.2f -> drift %+.1f%%\n",
mf, ml, 100.0 * (ml / mf - 1.0));
std::printf(" VERDICT: %s\n",
(ml / mf > 1.10) ? "CLOCKS FELL during the run — see A.8, do not measure yet"
: (ml / mf < 0.90) ? "CLOCKS ROSE during the run — you measured a cold machine"
: (iqr > 0.05 * med) ? "NOISY — spread too wide to defend a 10% claim, see A.8"
: "STABLE ENOUGH to measure on");
std::printf(" (sink %llu — printed so nothing above can be optimized away)\n",
(unsigned long long)sink);
return 0;
}
Build and run it:
g++ -O2 -std=c++17 -o calibrate calibrate.cpp
./calibrate
It prints two blocks. Read them in order.
Pass 1 — timer resolution and overhead
The output has this shape (your numbers will differ; the shape is what matters):
PASS 1 — timer resolution and overhead
steady_clock is_steady : true
steady_clock tick period : 1/1000000000 s
cost of one now() call : <n> ns
smallest visible tick : <n> ns
do not time any region shorter than ~<n> ns (1000x the call cost)
Four things are being reported, and each has a failure mode.
| Line | Good | Bad, and what it means | What to do |
|---|---|---|---|
is_steady | true | false — the clock can be adjusted or can jump backwards | Do not measure on it. On mainstream implementations this is true; if yours is not, you are on an unusual runtime and should move to another workbench |
tick period | A small fraction, typically 1/1000000000 s | A coarse period | This is the representation, not the hardware. The next two lines are the truth |
cost of one now() call | Tens of nanoseconds | Hundreds of nanoseconds, or into the microseconds | Common inside some virtual machines where the clock read traps to the hypervisor. Not fatal — it just moves the floor up |
smallest visible tick | Tens of nanoseconds | Hundreds of nanoseconds, or a microsecond-plus quantum | Your clock cannot resolve anything shorter. Every timed region must be far larger than this |
The rule this pass gives you, and it is the whole point: never time a region shorter than about a thousand times the cost of a clock read. The program prints that threshold. If your floor comes out at twenty microseconds and you were about to time a single 200-nanosecond kernel iteration, you would have measured the clock and called it a cache effect.
The fix is never “get a better clock.” It is to batch the work inside the timed region: put a loop of many iterations between the two now() calls and divide at the end. Every measurement program in this book already does this — that is what the repetition counts inside cache_walk.cpp, pointer_chase.cpp, and bench_harness.cpp are for. Knowing why they are there is what stops you writing a naive harness in Week 8 and reporting nonsense. If your floor is unusually high, raise the repetition counts the programs expose (--reps, and the loop constants the reps tell you to edit) until each timed region is comfortably above it, and record that you did.
Pass 2 — the power-governor and turbo check
This is the run that catches a laptop silently rescaling its own clocks mid-benchmark. It runs a fixed work quantum over and over for sixty seconds and asks one question: was the last fifth of the run the same speed as the first fifth?
PASS 2 — operating-point stability over 60 s (quantum = <n> iterations)
samples : <n>
median / min / max (ms) : <n> <n> <n>
interquartile spread : <n> ms (<n>% of median)
first fifth / last fifth : <n> <n> -> drift <±n>%
VERDICT: <one of four>
The four verdicts, what each means, and what to do:
| Verdict | What actually happened | Why it corrupts everything downstream |
|---|---|---|
| STABLE ENOUGH to measure on | Drift within ±10%, interquartile spread within 5% of the median | Nothing. Go measure |
| CLOCKS FELL during the run | The machine started at a boost operating point and dropped back — thermal limit reached, or a power/turbo budget expired | Your first runs are systematically faster than your last ones. Any A/B comparison run in that order will “prove” that A is faster than B when the only difference was when you ran it |
| CLOCKS ROSE during the run | You started on a cold, idle, down-clocked machine and the governor ramped up | Your warm-up was too short. Your early samples undercount and your medians are polluted by a transient. Chapter 8 Rep 3 is exactly this failure, deliberately induced |
| NOISY | Wide spread with no systematic direction — the governor stepping between discrete operating points, or another process (or another tenant) taking the core | You cannot defend a 10% difference between two builds when your own repeated measurement of the same build spreads wider than 10% |
What to do about a bad verdict, by platform:
| Platform | Actions, in order |
|---|---|
| Linux (Workbench L) | 1. Plug the machine in — battery power routinely changes the policy. 2. Read /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor. If it is powersave or an on-demand policy and you have admin, set performance (via cpupower frequency-set -g performance, or by writing to scaling_governor) for the session only. 3. Quit browsers, editors, indexers, and syncing clients. 4. Where your platform exposes a turbo control — on Intel P-state systems, /sys/devices/system/cpu/intel_pstate/no_turbo — you may disable boost to trade peak speed for a flat curve. Not every platform has an equivalent; if yours does not, say so instead of inventing one. 5. Re-run. Put the governor back afterwards — leaving performance pinned on a laptop you use on battery is a real cost you did not intend to pay |
| macOS (Workbench L) | You cannot set a governor; the tunables are not exposed, which is the same philosophy as Chapter 2’s point about Apple refusing to expose CPU affinity and offering quality-of-service classes instead. What you can control: plug in, turn off Low Power Mode, quit background work, let the machine idle before you start, keep it on a hard surface rather than a soft one, and observe with sudo powermetrics --samplers cpu_power while you measure. Then report the condition rather than controlling it |
| Windows (Workbench L) | Plug in and select a high-performance power plan. Under WSL you inherit the host’s power management entirely, so set it on the Windows side, not inside the Linux shell. Then re-run |
| Workbench B | You have neither the knob nor the visibility, and pretending otherwise is the mistake. Instead: raise repetition counts, report the median with its interquartile range, run your A and B conditions interleaved rather than in blocks so drift affects both equally, and state in measurements.xlsx that the host was shared and drift could not be attributed. That is a complete, professional answer |
Two disciplines that apply on every platform, and which cost nothing:
- Interleave, do not block. If you must compare A against B on a machine that drifts, run
A B A B A Brather thanAAA BBB. Drift then contaminates both conditions equally instead of masquerading as a difference between them. This one habit rescues more student comparisons than any tuning knob. - Re-run the pass, do not re-run until you like it. Running Pass 2 six times and reporting the run that said STABLE is exactly the failure Chapter 8 is about.
What to record
Paste both verdict lines into measurements.xlsx with the date, the machine, and the power state, before your first real measurement of the term. Then do it again at the start of the capstone, because a laptop in Week 8 is not the laptop it was in Week 1.
The conditions block, the median-and-dispersion format, the repetition counts, and the sustained-load soak protocol that all of this feeds into are in Appendix C. This appendix proves the instrument works; Appendix C is the methodology built on top of it. Read them in that order, once, in Week 1.
Coach’s Note — This can sound like fussiness. It is not. A calibration pass is the difference between “my optimization was 12% faster” and “my optimization was 12% faster, on a bench whose repeated measurement of an unchanged binary varies by 2%.” The first is an assertion. The second is evidence. In a design review the person who can say the second sentence wins the argument even as the junior engineer in the room — because everyone else there has been burned by the first one.
A.9 — Milestone → Minimum Workbench
Read this in Week 1, not Week 5. The point of the table is that nobody discovers a missing capability the week it is needed.
| Wk | Graded item | Minimum workbench | What Workbench L adds | What Workbench D adds |
|---|---|---|---|---|
| 1 | Lab 1 — The SoC Teardown (7%) + the week’s quiz | B. It is a reading, citation, and reasoning lab; the project says so outright | isa_compare.cpp, and reading your own compiler’s assembly in Rep 6 | Nothing |
| 2 | Lab 2 — The Placement Study (7%) + quiz | B | A quieter migrate.cpp measurement; on Linux, real thread pinning | An optional battery-attribution observation — no points |
| 3 | Lab 3 — The Memory Wall Lab (7%) + quiz | B. The project explicitly accepts a shared cloud host as a stated condition | A cleaner staircase with lower dispersion | Nothing |
| 4 | Placement Practical (8%) + cumulative checkpoint (10%) + the week’s quiz | B. The practical says every requirement is completable on Workbench B, with no phone and no admin rights | Nothing required | Nothing |
| 5 | Lab 5 — The Energy and Thermal Study (7%) + quiz | B. Part 4’s real sustained-load run works, and a negative result is explicitly creditable | The governor, the operating-point menu, the idle-state ladder, a battery, and a genuine thermal transient | H2’s device thermal capture — Hard tier, marked “never required,” H1 scores identically |
| 6 | Lab 6 — The Integration Map (7%) + quiz | B | Nothing required | An optional system trace to check one claim — bonus at any tier |
| 7 | Lab 7 — The Silicon Threat Model (7%) + quiz | B. The project states a physical device is never required, at any tier | A slightly cleaner timing_leak.cpp signal on a quiet machine | Nothing |
| 8 | Capstone SoC Investigation (20%) + Technical Briefing (8%) + quiz | B, plus something that records your screen and your voice — see below | perf/Instruments counters, if you have access | simpleperf counters from mobile silicon — optional |
Eight weekly quizzes at 1.5% each make up the remaining 12%; the full contract is Appendix D. Every row above costs nothing to satisfy.
The one Week-8 trap
The Technical Briefing is recorded. Twelve minutes plus a three-minute addendum, slides and voice, submitted as a video file or a link to one. It is the only requirement in the course that needs a capability the rest of the course does not, and every term somebody discovers it on the Friday.
Sort it out in Week 1, in ten minutes. Any conferencing tool you already use will record a meeting with one participant — you — sharing a screen, and that needs nothing installed on a locked-down machine. Your operating system very likely has a built-in recorder; browser-based ones exist too. Check that your microphone is actually captured — a silent twelve-minute slide deck is a resubmission. Record thirty seconds of yourself talking over one slide this week and play it back.
What you never need
No GPU. No phone. No paid tool. No hardware purchase of any kind. If a step in this book seems to require one, re-read the tier line — it is optional, and the non-device path is worth the same points.
A.10 — Troubleshooting: The Eight Failures That Will Actually Happen
| # | Symptom you will actually see | What it means | The fix |
|---|---|---|---|
| 1 | g++: command not found, or macOS says xcrun: error: invalid active developer path | The command line developer tools are missing or were invalidated by an OS upgrade (which routinely does this); no compiler package on Linux; or you are in PowerShell rather than a Linux shell | macOS: xcode-select --install, and re-run it after every major OS upgrade. Linux: the line for your distribution in A.3.2. Windows: open the WSL shell |
| 2 | Errors that look like the standard library is missing pieces — 'std::optional' file not found, complaints about structured bindings | You dropped -std=c++17, or your compiler defaults to an older standard | Paste the whole build line every time: g++ -O2 -std=c++17 -o <program> code/<program>.cpp. If the flag is present and it still fails, your compiler predates C++17 — update it, or use Workbench B |
| 3 | Only migrate misbehaves: undefined reference to pthread_create, or it builds and then dies with std::system_error | The missing -pthread flag. Some toolchains link without it and fail only at run time, which looks like a bug in the program | g++ -O2 -std=c++17 -pthread migrate.cpp -o migrate. It is the only file in the book needing an extra flag, and its own header comment says so |
| 4 | permission denied on a binary you just built, or ./cache_walk: No such file or directory after a clean build | Either you built into one directory and invoked from another (-o cache_walk writes to the current directory, not code/), or the filesystem is mounted noexec — real on some managed machines and container mounts | Build and run as one line so there is no ambiguity: g++ -O2 -std=c++17 -o cache_walk code/cache_walk.cpp && ./cache_walk --max-mib 64 | head. If it is noexec, build into your home directory |
| 5 | The cache staircase is flat, or matmul_order shows almost no difference between loop orders | In order of likelihood: you built at -O0 and are timing the compiler’s scaffolding; you built at -O3 and the compiler interchanged the loops for you; or your working set never left a very large last-level cache | Build at -O2 — not -O0, not -O3. Push further out with ./cache_walk --max-mib 256. Then record what happened: “my compiler interchanged the loops at -O3, and here is the evidence” is a correct answer to Rep 4, not a failure |
| 6 | perf prints “Access to performance monitoring and observability operations is limited”, or <not supported> for every event | Unprivileged counter access is restricted by a kernel setting, or the performance monitoring unit is not exposed inside your container or virtual machine. On Workbench B this is normal | With admin on your own machine, relax the kernel setting for the session. Otherwise do not fight it: use sample-perf-stat.txt, get the mechanism evidence from the differential-experiment table in A.7, and state the limitation. That is a complete answer at every tier |
| 7 | The same program gives numbers 30% apart on consecutive runs | Almost never the program. In order: you are on battery; the machine is thermally soaked from the previous run; you are sharing a host; a browser or indexer woke up; or your timed region is below the timer floor | Run A.8 and read the verdict — it usually names the cause. Then plug in, quit background work, raise repetitions, interleave conditions rather than blocking them, and report a median with an interquartile range plus the conditions block from Appendix C |
| 8 | hierarchy_plot.py or throttle_analysis.py prints nothing, or crashes on a CSV you just made | Usually the redirect: the build silently failed and you re-ran an old binary, or something non-CSV reached standard output. Occasionally you stripped the # comment lines because they looked like clutter | head -n 5 cache-walk.csv before you blame the tool — you should see a # line then a column header. Leave the # lines in. Every loader skips them by design, and the first line of every shipped dataset is its provenance statement |
And one that is not a failure at all. pqc_budget.py prints a refusal and reports nothing. That is the program working correctly. It ships knowing no key sizes whatsoever and declines to report until you fill in pqc-sizes-example.csv with sizes you looked up in the primary standard and cited — the project pages at https://csrc.nist.gov/projects/post-quantum-cryptography. A tool that refuses to invent numbers is the tool this course wants you to become.
A.11 — Before You Close This Page
Nine checks. If all of them pass, stop thinking about tooling and go do Chapter 1.
- You know which workbench you are on, and you can say why in one sentence.
g++ --version(orclang++ --version) prints a version block, and you know which architecture the target triple names.python3 --versionprints a3.x.isa_comparebuilt and printed three rows.block_budget.pyprinted a bar chart.calibrate.cppbuilt and ran.- Pass 1’s verdict is recorded, and you know your minimum trustworthy timed region in nanoseconds.
- Pass 2’s verdict is recorded, with the machine, the power state, and the date.
- You have proved you can record your screen and your voice — ten minutes now, or a disaster in Week 8.
Two habits to carry out of this appendix, because they outlive the course.
An uncalibrated bench is worse than no bench. A student with no measurement writes “I could not measure this, and here is what I would need” — honest, and creditable. A student with an uncalibrated bench writes a number, the number is wrong, and nothing in the write-up reveals it. The whole discipline this book is training — The Four Questions asked at once, over hardware you did not design, with every figure either cited or measured — rests on the instrument being trustworthy. Five minutes in Week 1 buys that.
Write down the condition, always. Which workbench, plugged in or not, how long the machine had been idle, which consecutive run, which compiler and flags. Not because a rubric demands it, though several do — because a number without its conditions is an anecdote with a decimal point, and in Week 8 somebody competent and busy is going to ask you measured how, for how long, on what.
Build the bench. Calibrate it. Then leave it alone and go measure something.
See you in Chapter 1.
Up next: Chapter 1 — The Machine in Your Pocket and Lab 1. Learn to read a block diagram and a specification table without being sold to in Appendix B, which also documents the provenance of every dataset in code/. The methodology this appendix’s calibration pass feeds into — repetitions, medians, dispersion, the conditions block, the sustained-load soak protocol, and every report template you are graded against — is Appendix C. The grading contract and the AI-use policy are Appendix D. The vocabulary is Appendix E. The whole eight-week arc is the lab arc.