Capstone
The capstone: a small CS-foundations toolkit
The capstone builds three small, independent, fully-runnable tools that each exercise a cluster of
concepts from across this topic's three tiers -- proof that the 55 individual worked examples add up
to genuinely reusable capability, not just isolated demonstrations. Every script lives under
learning/capstone/code/, standard-library-only, fully type-annotated (DD-39), and was actually run
against Python 3.13.12 to capture the output shown below.
- Step 1 --
represent.py: an int/float <-> binary/hex converter, plus an IEEE-754 float-bit inspector. Ties together co-02, co-03. - Step 2 --
automaton.py: a finite-automaton simulator, run against a hand-traced regular language. Ties together co-18, co-19. - Step 3 --
memory.py: times row-major vs. column-major traversal of a 2-D array, at a larger, capstone-grade scale than Example 28, then traces a recursive function's call-stack push/pop events over a slice of the same buffer. Ties together co-16, co-17.
Together, the three steps span representation (Beginner tier), automata theory (Intermediate tier), and machine organization (Intermediate tier) -- deliberately not just a rehash of the Advanced tier's Turing machines and complexity theory, since those concepts (co-22 through co-28) are already each individually demonstrated in Examples 41-55 and don't need a fourth capstone step to prove reusability.
Step 1: represent.py -- number-representation toolkit
Context: Examples 1, 3, and 6 each introduced one representation primitive in isolation -- binary/hex conversion (co-01), two's-complement encoding (co-02), and IEEE-754 float decoding (co-03). This step packages all three into one importable module, then re-runs each one's exact verification claim to prove the packaging didn't change the underlying behavior.
# learning/capstone/code/represent.py
"""Capstone Step 1: an int/float <-> binary/hex converter, plus an IEEE-754 float-bit inspector.
Ties together co-01 (positional number systems), co-02 (two's complement), and co-03 (IEEE-754
floats) into one small, reusable "representation toolkit" -- the same primitives Examples 1, 3,
and 6 introduced separately, now packaged as functions a caller can import and reuse directly.
""" # => co-01: this file's own restated purpose, doubling as its module __doc__
# => co-01: no runtime output beyond setting __doc__ -- the three paragraphs above just orient the reader
from __future__ import annotations # => DD-39 hygiene: postpones type-annotation evaluation, keeping this file interpreter-version-agnostic
import struct # => co-03: struct.pack/unpack -- the bridge between a Python float and its raw IEEE-754 bytes
from typing import NamedTuple # => co-03: typing import supporting the typed structures below
def int_to_binary(n: int) -> str: # => co-01: positional-system conversion, unsigned magnitude only
"""Convert a non-negative int to its binary string (no '0b' prefix).""" # => co-01: this file's own restated purpose, doubling as its module __doc__
if n < 0: # => co-01: this function's contract is unsigned -- negative ints need to_twos_complement below
raise ValueError("int_to_binary expects a non-negative int -- use to_twos_complement for negatives") # => co-01: continues the statement started above
return bin(n)[2:] if n else "0" # => co-01: bin() does the division-by-2 conversion; strip its "0b" prefix
def int_to_hex(n: int) -> str: # => co-01: the SAME value, rendered in base 16 instead of base 2
"""Convert a non-negative int to its hex string (no '0x' prefix).""" # => co-01: documents int_to_hex's contract -- no runtime output, just sets its __doc__
return hex(n)[2:] if n else "0" # => co-01: hex() does the division-by-16 conversion; strip its "0x" prefix
def to_twos_complement(n: int, bits: int = 8) -> str: # => co-02: signed int -> fixed-width two's-complement bits
"""Render a signed int in `bits`-wide two's complement.""" # => co-02: documents to_twos_complement's contract -- no runtime output, just sets its __doc__
mask = (1 << bits) - 1 # => co-02: keeps only the low `bits` bits -- Python's arbitrary-precision AND does the encoding
return format(n & mask, f"0{bits}b") # => co-02: zero-padded binary string, exactly `bits` characters wide
class Ieee754Fields(NamedTuple): # => co-03: the three fields IEEE 754-2019 binary64 packs into 64 bits
sign: int # => co-03: bit 63 -- 0 positive, 1 negative
exponent: int # => co-03: bits 62-52 (11 bits), BIASED by 1023
mantissa: int # => co-03: bits 51-0 (52 bits) -- the stored fractional significand
def inspect_float(x: float) -> Ieee754Fields: # => co-03: decomposes a float into its raw IEEE-754 bit fields
"""Decompose a float into its IEEE-754 binary64 sign/exponent/mantissa fields.""" # => co-03: documents inspect_float's contract -- no runtime output, just sets its __doc__
bits = int.from_bytes(struct.pack(">d", x), byteorder="big") # => co-03: 8 big-endian bytes -> one 64-bit int
return Ieee754Fields( # => co-03: mask/shift out each field from the packed 64-bit integer
sign=(bits >> 63) & 0x1, # => co-03: the top bit
exponent=(bits >> 52) & 0x7FF, # => co-03: the next 11 bits
mantissa=bits & 0xFFFFFFFFFFFFF, # => co-03: the low 52 bits
) # => co-03: closes the multi-line construct opened above
def float_bits_string(x: float) -> str: # => co-03: the full 64-bit pattern, grouped for readability
"""Render x's IEEE-754 binary64 bit pattern as sign|exponent|mantissa, space-separated.""" # => co-03: documents float_bits_string's contract -- no runtime output, just sets its __doc__
f = inspect_float(x) # => co-03: decode the three fields
return f"{f.sign:01b} {f.exponent:011b} {f.mantissa:052b}" # => co-03: 1 + 11 + 52 = 64 bits total
def demonstrate_rounding_error() -> tuple[float, bool]: # => co-03: the structural (not buggy) 0.1+0.2 fact
"""Return (0.1 + 0.2, whether it equals 0.3) -- IEEE-754 rounding is structural, not a bug.""" # => co-03: documents demonstrate_rounding_error's contract -- no runtime output, just sets its __doc__
total = 0.1 + 0.2 # => co-03: neither 0.1 nor 0.2 has an exact binary64 representation -- both already round
return total, total == 0.3 # => co-03: the sum and whether it happens to equal the (also-rounded) literal 0.3
if __name__ == "__main__": # => co-03: entry point -- this block runs only when the file executes directly, not on import
print("=== base conversion ===") # => co-01: section 1 of the toolkit demo
value = 156 # => co-01: the same fixed test value Example 1 used, for continuity
print(f"{value} -> binary {int_to_binary(value)}, hex {int_to_hex(value)}") # => co-01: both conversions shown
assert int_to_binary(value) == "10011100", "must match the known bit pattern for 156" # => co-01
assert int(int_to_binary(value), 2) == value == int(int_to_hex(value), 16), "round-trip must recover 156" # => co-01
print("\n=== two's complement ===") # => co-02: section 2 of the toolkit demo
negative = -42 # => co-02: the same fixed test value Example 3 used
print(f"{negative} in 8-bit two's complement = {to_twos_complement(negative)}") # => co-02
assert to_twos_complement(negative) == "11010110", "must match the known 8-bit pattern for -42" # => co-02
print("\n=== IEEE-754 float inspection ===") # => co-03: section 3 of the toolkit demo
known_value = 1.0 # => co-03: a float with a clean, easy-to-verify encoding
print(f"float_bits(1.0) = {float_bits_string(known_value)}") # => co-03: sign|exponent|mantissa, grouped
fields = inspect_float(known_value) # => co-03: decode 1.0's fields directly, for the exact-match assertions
assert fields == Ieee754Fields(sign=0, exponent=1023, mantissa=0), "1.0 must decode to sign=0 exp=1023 mant=0" # => co-03
print("\n=== 0.1 + 0.2 != 0.3 ===") # => co-03: section 4 -- the structural rounding-error demonstration
total, equals_point_three = demonstrate_rounding_error() # => co-03: the actual computed sum and its comparison
print(f"0.1 + 0.2 = {total!r}") # => co-03: prints the EXACT stored value, not a rounded display
print(f"0.1 + 0.2 == 0.3: {equals_point_three}") # => co-03: the headline claim -- expect False
assert repr(total) == "0.30000000000000004", "must print the syllabus's documented exact value" # => co-03
assert equals_point_three is False, "0.1 + 0.2 must NOT equal the literal 0.3" # => co-03: the structural fact
print("\nAll representation and IEEE-754 claims verified: True") # => co-01, co-02, co-03: every assert above passed
# => co-01: every assert above is this script's own regression check -- a clean exit means the claim held for these inputs
# => co-01, co-02, co-03: this module packages Examples 1, 3, and 6's standalone demonstrations as importable, reusable functions
# => co-03: Ieee754Fields is a NamedTuple so its three fields (sign/exponent/mantissa) compare by VALUE, enabling the exact-match assert below
# => co-02: to_twos_complement's bitmask-and-format approach generalizes Example 3's fixed 8-bit case to any bit width
# => co-01: int_to_binary and int_to_hex share the same shape -- both delegate to Python's own builtin, then strip its base-specific prefixRun: python3 represent.py
Output:
=== base conversion ===
156 -> binary 10011100, hex 9c
=== two's complement ===
-42 in 8-bit two's complement = 11010110
=== IEEE-754 float inspection ===
float_bits(1.0) = 0 01111111111 0000000000000000000000000000000000000000000000000000
=== 0.1 + 0.2 != 0.3 ===
0.1 + 0.2 = 0.30000000000000004
0.1 + 0.2 == 0.3: False
All representation and IEEE-754 claims verified: TrueAcceptance criteria: 156 must convert to binary 10011100 and hex 9c (co-01); -42 must
encode to 8-bit two's complement 11010110 (co-02); 1.0 must decode to sign=0, biased
exponent=1023 (01111111111 in binary), mantissa=0 (co-03); and 0.1 + 0.2 must print exactly
0.30000000000000004 and compare unequal to 0.3 (co-03). All four hold, verified by the asserts
inside the script itself and cross-checked against the captured output above.
Key takeaway: three representation primitives that Examples 1, 3, and 6 taught in isolation compose cleanly into one small module, and every one of their individual verification claims still holds identically once packaged together.
Why it matters: this is exactly the shape of "utility module" a real codebase accumulates -- small, focused, independently-testable functions for a shared low-level concern (in this case, number representation) that higher-level code imports and reuses without re-deriving the bit manipulation each time.
Step 2: automaton.py -- finite-automaton simulator on a new language
Context: Example 33 built a generic Dfa simulator and ran it against "ends in 1". This step
reuses that exact shape for a brand-new language -- binary strings ending in the substring "01" --
and cross-checks the hand-built DFA against an independent regex (co-19, Kleene's theorem) and a
plain str.endswith() check, then documents a full hand trace for one input string.
# learning/capstone/code/automaton.py
"""Capstone Step 2: a finite-automaton simulator, run against a hand-traced regular language.
Ties together co-18 (finite automata) and co-19 (regex-to-FA equivalence, Kleene's theorem) into
one reusable Dfa class -- the same shape Example 33's generic simulator introduced -- instantiated
here for a NEW language: binary strings ENDING in the substring "01".
""" # => co-18: this file's own restated purpose, doubling as its module __doc__
# => co-18: no runtime output beyond setting __doc__ -- the three paragraphs above just orient the reader
from __future__ import annotations # => DD-39 hygiene: postpones type-annotation evaluation, keeping this file interpreter-version-agnostic
import re # => co-19: Python's own regex engine -- the independent cross-check for Kleene's theorem
from dataclasses import dataclass # => co-19: stdlib-only import backing this example
# ex-capstone: L = { w in {0,1}* : w ends with "01" }. Hand-traced transition table below --
# state Si tracks "have we just seen the prefix of '01' needed to accept if the string ended here":
# S0 = "last symbol read was NOT a 0 that could start '01'" (or nothing read yet)
# S1 = "last symbol read was a 0" (a potential start of '01')
# S2 = "last two symbols read were exactly '0' then '1'" (ACCEPTING -- ends in "01" right now)
TRANSITIONS: dict[tuple[str, str], str] = { # => co-18: δ -- the full transition table for this DFA
("S0", "0"): "S1", # => co-18: from S0, a "0" moves toward acceptance (potential start of "01")
("S0", "1"): "S0", # => co-18: from S0, a "1" stays put -- can't start "01" with a "1"
("S1", "0"): "S1", # => co-18: from S1, another "0" stays (still "just saw a 0")
("S1", "1"): "S2", # => co-18: from S1, "1" completes "01" -- move to the accepting state
("S2", "0"): "S1", # => co-18: from S2, a NEW "0" restarts the watch for another "01"
("S2", "1"): "S0", # => co-18: from S2, "1" breaks the ending -- back to "not watching"
} # => co-18: closes the multi-line construct opened above
ACCEPT = {"S2"} # => co-18: F -- accepting iff the walk currently ends in S2 ("...01" just seen)
REGEX = re.compile(r".*01$") # => co-19: the SAME language, expressed as a regex -- Kleene's theorem says these must agree
@dataclass(frozen=True) # => co-18: a DFA is DATA -- reusable for any (states, alphabet, δ, start, accept) tuple
class Dfa: # => co-18: continues the statement started above
transitions: dict[tuple[str, str], str] # => co-18: δ
start: str # => co-18: q0
accept: set[str] # => co-18: F
def run(self, s: str) -> bool: # => co-18: feed s through δ symbol by symbol, from q0
state = self.start # => co-18: every run begins at q0, unconditionally
for symbol in s: # => co-18: one transition per symbol -- exactly one next state each step
state = self.transitions[(state, symbol)] # => co-18: δ(state, symbol)
return state in self.accept # => co-18: accepted iff the FINAL state is in F
ENDS_IN_01 = Dfa(transitions=TRANSITIONS, start="S0", accept=ACCEPT) # => co-18: the machine this step exercises
if __name__ == "__main__": # => co-18: entry point -- this block runs only when the file executes directly, not on import
# Hand trace for "001": S0 -0-> S1 -0-> S1 -1-> S2. Final state S2 is accepting, so "001" IS
# accepted -- and indeed "001" genuinely ends with the substring "01".
test_cases = ["", "0", "1", "01", "10", "001", "110", "0101", "0110", "111101"] # => co-18: a spread of strings
for s in test_cases: # => co-18, co-19: run every string through BOTH the DFA and the regex
dfa_verdict = ENDS_IN_01.run(s) # => co-18: the hand-built machine's own accept/reject verdict
regex_verdict = REGEX.fullmatch(s) is not None # => co-19: Kleene's theorem's independent cross-check
ends_with_01 = s.endswith("01") # => co-18: a THIRD, brute-force sanity check against plain string logic
print(f"{s!r:<8} dfa={dfa_verdict} regex={regex_verdict} ends_with_01={ends_with_01}") # => co-18, co-19
assert dfa_verdict == regex_verdict == ends_with_01, f"all three checks must agree for {s!r}" # => co-18, co-19
print("\nHand trace for '001': S0 -0-> S1 -0-> S1 -1-> S2 (accepting)") # => co-18: documents the trace by hand
assert ENDS_IN_01.run("001") is True, "the hand-traced walk for '001' must land in the accepting state S2" # => co-18
print("Every test string classified correctly against its hand-traced expectation: True") # => co-18, co-19
# => co-18: every assert above is this script's own regression check -- a clean exit means the claim held for these inputs
# => co-18: Dfa is declared frozen -- once built, a DFA's transition table never mutates mid-run, matching the formal (Q, Sigma, delta, q0, F) definition
# => co-19: REGEX and TRANSITIONS encode the identical language two different ways -- Kleene's theorem guarantees they must agree on every input
# => co-18: the hand-traced walk for "001" in the comment above is exactly what Dfa.run replays mechanically, symbol by symbolRun: python3 automaton.py
Output:
'' dfa=False regex=False ends_with_01=False
'0' dfa=False regex=False ends_with_01=False
'1' dfa=False regex=False ends_with_01=False
'01' dfa=True regex=True ends_with_01=True
'10' dfa=False regex=False ends_with_01=False
'001' dfa=True regex=True ends_with_01=True
'110' dfa=False regex=False ends_with_01=False
'0101' dfa=True regex=True ends_with_01=True
'0110' dfa=False regex=False ends_with_01=False
'111101' dfa=True regex=True ends_with_01=True
Hand trace for '001': S0 -0-> S1 -0-> S1 -1-> S2 (accepting)
Every test string classified correctly against its hand-traced expectation: True%% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73
stateDiagram-v2
[*] --> S0
S0 --> S1: 0
S0 --> S0: 1
S1 --> S1: 0
S1 --> S2: 1
S2 --> S1: 0
S2 --> S0: 1
S2 --> [*]
classDef start fill:#0173B2,stroke:#000000,color:#FFFFFF,stroke-width:2px
classDef mid fill:#DE8F05,stroke:#000000,color:#FFFFFF,stroke-width:2px
classDef accept fill:#029E73,stroke:#000000,color:#FFFFFF,stroke-width:2px
class S0 start
class S1 mid
class S2 accept
Figure: the hand-traced walk for "001" is S0 -0-> S1 -0-> S1 -1-> S2, ending in the accepting
state S2 -- matching the printed output above exactly.
Acceptance criteria: for every one of the 10 test strings, the hand-built DFA's verdict, the
independent regex .*01$'s verdict, and a plain str.endswith("01") check must all agree (co-18,
co-19). The hand-traced walk for "001" (S0 -0-> S1 -0-> S1 -1-> S2) must land in accepting state
S2, matching both the printed dfa=True result and the diagram above. All conditions hold.
Key takeaway: three independent implementations -- a hand-built DFA, Python's regex engine, and a one-line string method -- agree on every single test string for the language "ends in 01," demonstrating Kleene's theorem (co-19) concretely rather than asserting it abstractly.
Why it matters: this cross-checking pattern -- hand-built machine vs. regex vs. brute-force check -- is exactly how you'd validate a hand-rolled parser or lexer against a reference implementation before trusting it in production; agreement across independently-written checks is much stronger evidence of correctness than any single implementation's self-consistency.
Step 3: memory.py -- capstone-grade cache-locality measurement
Context: Example 28 measured row-major vs. column-major traversal at N=1400 with 3 trials, and Example 29 traced a recursive function's call-stack push/pop events. This step combines both techniques at a larger, capstone-grade scale: it reruns the cache-traversal experiment (N=1600, 5 trials) as the topic's closing proof that the memory hierarchy's latency gap (co-16) is not a one-off fluke, then runs a small recursive call-stack trace over a slice of the same underlying buffer (co-17) so the stack-frame/heap-buffer distinction is directly observable in one script.
# learning/capstone/code/memory.py
"""Capstone Step 3: timing row-major vs. column-major traversal of a 2-D array, plus a call-stack trace.
Ties together co-16 (memory-hierarchy intuition) and co-17 (stack-and-heap familiarity with how
Python objects are actually laid out) into two real, measured demonstrations run in this same
script: a larger-scale rerun of Example 28's cache-timing technique (co-16), and a small recursive
call-stack push/pop trace (co-17), the same logging pattern Example 29 introduced.
""" # => co-16: this file's own restated purpose, doubling as its module __doc__
# => co-16: no runtime output beyond setting __doc__ -- the three paragraphs above just orient the reader
from __future__ import annotations # => DD-39 hygiene: postpones type-annotation evaluation, keeping this file interpreter-version-agnostic
import array # => co-16: array.array packs real C doubles CONTIGUOUSLY -- unlike list-of-lists of boxed floats
import time # => co-16: perf_counter -- a monotonic, high-resolution clock, the right tool for timing code
N = 1600 # => co-16: N*N doubles (~20.5 MB) -- comfortably exceeds typical L2/L3 cache, keeping the run fast
TRIALS = 5 # => co-16: best-of-5 -- a capstone-grade measurement, more trials than Example 28's best-of-3
call_log: list[str] = [] # => co-17: records "push" and "pop" events, in the ACTUAL order they happen
def build_matrix(n: int) -> array.array[float]: # => co-16: one FLAT contiguous buffer -- row i lives at [i*n : i*n+n]
"""Build an n*n matrix as one flat, contiguous array.array of doubles (row-major layout).""" # => co-16: this file's own restated purpose, doubling as its module __doc__
flat = array.array("d", [0.0]) * (n * n) # => co-16: n*n contiguous 8-byte slots, zero-initialized
for k in range(n * n): # => co-16: fill with distinguishable, cheap-to-compute values
flat[k] = float(k % 97) # => co-16: content is irrelevant to the timing -- only the ACCESS PATTERN matters
return flat # => co-16: returns this computed value to the caller
def row_major_sum(flat: array.array[float], n: int) -> float: # => co-16: walks memory in STORAGE order -- stride 1
"""Sum every element visiting row 0 fully, then row 1 fully, etc. -- matches the storage layout.""" # => co-16: documents row_major_sum's contract -- no runtime output, just sets its __doc__
total = 0.0 # => co-16: the returned value only proves correctness, not speed
for i in range(n): # => co-16: outer loop over rows
base = i * n # => co-16: row i's starting flat index -- computed once per row, not once per element
for j in range(n): # => co-16: inner loop walks CONSECUTIVE flat indices -- sequential memory access
total += flat[base + j] # => co-16: stride-1 access -- exactly how a cache-line prefetcher likes it
return total # => co-16: returns this computed value to the caller
def col_major_sum(flat: array.array[float], n: int) -> float: # => co-16: walks memory AGAINST storage order -- stride n
"""Sum every element visiting column 0 fully, then column 1 fully, etc. -- fights the storage layout.""" # => co-16: documents col_major_sum's contract -- no runtime output, just sets its __doc__
total = 0.0 # => co-16: same arithmetic result as row_major_sum -- only the ACCESS ORDER differs
for j in range(n): # => co-16: outer loop over columns
for i in range(n): # => co-16: inner loop jumps n flat-indices apart on every single step
total += flat[i * n + j] # => co-16: stride-n access -- each step likely lands in a DIFFERENT cache line
return total # => co-16: returns this computed value to the caller
def recursive_frame_sum(flat: array.array[float], start: int, end: int, depth: int = 0) -> float: # => co-17: each call is a NEW STACK FRAME with its own start/end/depth
"""Recursively sum flat[start:end] by binary splitting, logging every frame's push/pop (co-17).""" # => co-17: documents recursive_frame_sum's contract -- no runtime output, just sets its __doc__
call_log.append(f"push depth={depth} range=[{start}:{end}]") # => co-17: a new frame is pushed onto the call stack HERE
if end - start <= 1: # => co-17: base case -- the deepest frame, popping immediately without recursing further
total = flat[start] if start < end else 0.0 # => co-17: a single element (or an empty slice) needs no further split
call_log.append(f"pop depth={depth} range=[{start}:{end}] returns={total:.1f}") # => co-17: this frame's automatic-lifetime storage ends
return total # => co-17: unwinds back to the caller -- the frame's local variables cease to exist
mid = (start + end) // 2 # => co-17: splits the range in half -- each half becomes its own recursive call
left = recursive_frame_sum(flat, start, mid, depth + 1) # => co-17: a NEW frame is pushed for the left half, one level deeper
right = recursive_frame_sum(flat, mid, end, depth + 1) # => co-17: a NEW frame is pushed for the right half, one level deeper
total = left + right # => co-17: this frame's own result, combining both children's returned sums
call_log.append(f"pop depth={depth} range=[{start}:{end}] returns={total:.1f}") # => co-17: THIS frame pops only after both children return
return total # => co-17: this frame's own local storage (start, end, depth, left, right, total) is reclaimed here
if __name__ == "__main__": # => co-16: entry point -- this block runs only when the file executes directly, not on import
matrix = build_matrix(N) # => co-16: one shared buffer -- both traversal orders read the SAME data
row_times: list[float] = [] # => co-16: one measured duration per trial, row-major
col_times: list[float] = [] # => co-16: one measured duration per trial, column-major
for trial in range(TRIALS): # => co-16: repeat both traversals, keeping the BEST (least-noisy) time of each
t0 = time.perf_counter() # => co-16: start of the row-major timing window
row_result = row_major_sum(matrix, N) # => co-16: the timed operation itself
t1 = time.perf_counter() # => co-16: end of the row-major window, start of the column-major window
col_result = col_major_sum(matrix, N) # => co-16: the timed operation itself
t2 = time.perf_counter() # => co-16: end of the column-major window
row_times.append(t1 - t0) # => co-16: this trial's row-major duration
col_times.append(t2 - t1) # => co-16: this trial's column-major duration
assert row_result == col_result, "both traversal orders must sum to the identical total" # => co-16
print(f"trial {trial}: row={row_times[-1]:.4f}s col={col_times[-1]:.4f}s") # => co-16: per-trial readout
best_row = min(row_times) # => co-16: best-of-5 -- the closest approximation to each method's true cost
best_col = min(col_times) # => co-16: same best-of-5 policy applied to the column-major trials
ratio = best_col / best_row # => co-16: how much slower column-major was, as a multiple
print(f"\nrow-major best of {TRIALS}: {best_row:.4f}s") # => co-16: final headline row-major measurement
print(f"col-major best of {TRIALS}: {best_col:.4f}s") # => co-16: final headline column-major measurement
print(
f"row-major completed in {best_row:.4f}s vs column-major {best_col:.4f}s, " # => co-16: the capstone claim
f"row-major faster by {ratio:.2f}x"
) # => co-16: phrased with the ACTUAL measured numbers, not fabricated ones
assert best_row < best_col, "row-major (sequential access) must be measurably faster than column-major" # => co-16
print(f"\nRow-major is measurably faster than column-major: True") # => co-16: reached only if the timing assert held
print( # => co-16: ties the measurement back to the theory -- WHY row-major wins
"Why: row-major visits array.array's underlying C doubles in the SAME order they sit in " # => co-16: one chunk of the multi-line literal, concatenated with its neighbors
"memory (stride 1), letting the CPU's cache-line prefetcher stay useful. Column-major jumps " # => co-16: one chunk of the multi-line literal, concatenated with its neighbors
"N doubles (a full row's width) between consecutive accesses (stride N), so almost every " # => co-16: one chunk of the multi-line literal, concatenated with its neighbors
"access lands in a DIFFERENT cache line -- the memory hierarchy's latency (co-16) becomes " # => co-16: one chunk of the multi-line literal, concatenated with its neighbors
"visible in wall-clock time, exactly as the register-to-disk latency survey predicted." # => co-16: one chunk of the multi-line literal, concatenated with its neighbors
) # => co-16: closes the multi-line construct opened above
# => co-16: every assert above is this script's own regression check -- a clean exit means the claim held for these inputs
# => co-16: array.array (not list) is the point -- it stores raw C doubles contiguously, the same layout a lower-level language would use
# => co-16: TRIALS=5 (vs. Example 28's 3) trades a longer run for a tighter best-of-N estimate, appropriate for a capstone-grade measurement
# => co-16: row_major_sum and col_major_sum read the SAME underlying buffer -- only the traversal ORDER differs between the two functions
# => co-16: the closing print above ties the measured ratio back to cache-line prefetching -- the theory Example 27's latency table introduced
call_log.clear() # => co-17: fresh log for this run
frame_count = 8 # => co-17: small enough to print every frame, big enough to show 4 levels of recursion depth
recursive_total = recursive_frame_sum(matrix, 0, frame_count) # => co-17: recursively sums the first 8 elements of row 0, one call-stack frame per split
iterative_total = sum(matrix[0:frame_count]) # => co-17: the SAME 8 elements, summed the plain iterative way, as a correctness cross-check
assert abs(recursive_total - iterative_total) < 1e-9, "recursive and iterative sums of the same slice must agree" # => co-17
pushes = [line for line in call_log if line.startswith("push")] # => co-17: all push events, in order
pops = [line for line in call_log if line.startswith("pop")] # => co-17: all pop events, in order
assert len(pushes) == len(pops) == 2 * frame_count - 1, "binary-splitting frame_count elements produces exactly 2*frame_count-1 frames" # => co-17
assert call_log[0].startswith("push depth=0"), "the outermost call must push FIRST" # => co-17: LIFO order
assert call_log[-1].startswith("pop depth=0"), "the outermost call must pop LAST" # => co-17: LIFO order
print(f"\nrecursive call-stack trace over matrix[0:{frame_count}] ({len(pushes)} frames):") # => co-17: heading for the trace readout
for line in call_log: # => co-17: prints the frame push/pop sequence, in the exact order it happened
print(f" {line}") # => co-17: every push must eventually be followed by a MATCHING pop
print( # => co-17: reports the cross-checked result
f"recursive sum: {recursive_total:.1f} (iterative cross-check: {iterative_total:.1f}, " # => co-17: one chunk of the multi-line literal, concatenated with its neighbors
f"match: {recursive_total == iterative_total})" # => co-17: one chunk of the multi-line literal, concatenated with its neighbors
) # => co-17: closes the multi-line construct opened above
print(f"Frames pushed then popped in correct LIFO order: True") # => co-17: reached only if the asserts above held
print( # => co-17: ties the trace back to the theory -- WHY this matters alongside co-16
"Why: every recursive call above pushed a NEW stack frame (its own start/end/depth) while the " # => co-17: one chunk of the multi-line literal, concatenated with its neighbors
"matrix itself stayed a single HEAP-allocated array.array buffer the whole time -- the stack " # => co-17: one chunk of the multi-line literal, concatenated with its neighbors
"(frames, LIFO, automatic lifetime) and the heap (the flat buffer, one long-lived allocation) " # => co-17: one chunk of the multi-line literal, concatenated with its neighbors
"are the two DIFFERENT memory regions co-17 asked you to keep straight." # => co-17: one chunk of the multi-line literal, concatenated with its neighbors
) # => co-17: closes the multi-line construct opened above
# => co-17: recursive_frame_sum's own asserts above ARE this section's test suite -- matching push/pop counts and LIFO order both held
# => co-17: the iterative sum() cross-check proves the recursive result is CORRECT, not just that recursion ran without error
# => co-17: this trace deliberately stays small (8 elements, 15 frames) so every push/pop line above can be read, unlike the co-16 timing loop above which only prints per-trial summariesRun: python3 memory.py
Output (best-of-5 wall-clock timings -- exact seconds vary run to run and machine to machine; the reproducible claim is that row-major consistently wins, matching Example 28's independently measured ratio):
trial 0: row=0.0763s col=0.1035s
trial 1: row=0.0808s col=0.1045s
trial 2: row=0.0813s col=0.1056s
trial 3: row=0.0804s col=0.1137s
trial 4: row=0.0862s col=0.1161s
row-major best of 5: 0.0763s
col-major best of 5: 0.1035s
row-major completed in 0.0763s vs column-major 0.1035s, row-major faster by 1.36x
Row-major is measurably faster than column-major: True
Why: row-major visits array.array's underlying C doubles in the SAME order they sit in memory (stride 1), letting the CPU's cache-line prefetcher stay useful. Column-major jumps N doubles (a full row's width) between consecutive accesses (stride N), so almost every access lands in a DIFFERENT cache line -- the memory hierarchy's latency (co-16) becomes visible in wall-clock time, exactly as the register-to-disk latency survey predicted.
recursive call-stack trace over matrix[0:8] (15 frames):
push depth=0 range=[0:8]
push depth=1 range=[0:4]
push depth=2 range=[0:2]
push depth=3 range=[0:1]
pop depth=3 range=[0:1] returns=0.0
push depth=3 range=[1:2]
pop depth=3 range=[1:2] returns=1.0
pop depth=2 range=[0:2] returns=1.0
push depth=2 range=[2:4]
push depth=3 range=[2:3]
pop depth=3 range=[2:3] returns=2.0
push depth=3 range=[3:4]
pop depth=3 range=[3:4] returns=3.0
pop depth=2 range=[2:4] returns=5.0
pop depth=1 range=[0:4] returns=6.0
push depth=1 range=[4:8]
push depth=2 range=[4:6]
push depth=3 range=[4:5]
pop depth=3 range=[4:5] returns=4.0
push depth=3 range=[5:6]
pop depth=3 range=[5:6] returns=5.0
pop depth=2 range=[4:6] returns=9.0
push depth=2 range=[6:8]
push depth=3 range=[6:7]
pop depth=3 range=[6:7] returns=6.0
push depth=3 range=[7:8]
pop depth=3 range=[7:8] returns=7.0
pop depth=2 range=[6:8] returns=13.0
pop depth=1 range=[4:8] returns=22.0
pop depth=0 range=[0:8] returns=28.0
recursive sum: 28.0 (iterative cross-check: 28.0, match: True)
Frames pushed then popped in correct LIFO order: True
Why: every recursive call above pushed a NEW stack frame (its own start/end/depth) while the matrix itself stayed a single HEAP-allocated array.array buffer the whole time -- the stack (frames, LIFO, automatic lifetime) and the heap (the flat buffer, one long-lived allocation) are the two DIFFERENT memory regions co-17 asked you to keep straight.Acceptance criteria: row_major_sum and col_major_sum must agree on the total (correctness,
verified by an in-script assert for every trial), and best_row (the minimum of 5 trials) must be
strictly less than best_col (co-16). Both hold: 0.0763s < 0.1035s, a 1.36x measured ratio --
directionally consistent with Example 28's independently measured 1.35x ratio at a smaller N,
confirming this is a reproducible effect rather than a one-off measurement. Separately (co-17),
recursive_frame_sum's recursive sum of matrix[0:8] must match a plain iterative sum of the same
slice, and the 15 logged push/pop events must be exactly balanced with the outermost call pushing
first and popping last (LIFO order). All hold: 28.0 == 28.0, 15 == 15 push/pop events, first
push at depth=0, last pop at depth=0.
Key takeaway: at a larger scale and with more trials than Example 28, the same row-major-beats-
column-major result reproduces -- 0.0763s vs. 0.1035s, a 1.36x gap -- confirming the memory
hierarchy's latency ordering (Example 27) has a directly observable, repeatable consequence in
ordinary Python code. Separately, the recursive call-stack trace makes co-17 just as concrete: 15
stack frames are pushed and popped in strict LIFO order while operating on a slice of the SAME
heap-resident array.array buffer the timing demo above just measured -- the stack (frames,
automatic lifetime) and the heap (the one long-lived buffer) are visibly two different memory
regions, not just textbook vocabulary.
Why it matters: this closing measurement is the topic's proof that "the memory hierarchy
matters" isn't an abstract claim from a textbook diagram -- it's something you can measure yourself,
in five lines of timing code, on any machine, any time you're unsure whether an access pattern is
cache-friendly. The recursive trace alongside it makes the same point for the call stack: every
function call allocates and frees a real stack frame, in a strict push-then-pop order -- not a
mental abstraction, but something this script's own call_log genuinely recorded and verified.
← Previous: Advanced Examples · Next: Drilling →
Last updated July 15, 2026