codexarray-traversal-spatial-locality.py
array-traversal-spatial-locality.py
"""Draw two access orders over an unchanged row-major array.
Tested with Python 3.14.4, NumPy 2.3.5, Matplotlib 3.10.7.
Run from _media; outputs this script's PNG basename to the current directory.
Each coloured row is one physical 16-byte cache block of four 4-byte items.
"""
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
import numpy as np
def main():
plt.rcParams.update({"font.family": "DejaVu Sans", "font.size": 11})
colours = ["#dceaf8", "#fce4cc", "#ddedda", "#ebe0f4"]
addresses = 4 * np.arange(16).reshape(4, 4)
fig, axes = plt.subplots(1, 2, figsize=(10.4, 5.9), dpi=120)
fig.patch.set_facecolor("white")
fig.suptitle("Same storage, different traversal", fontsize=19, fontweight="bold", y=0.96)
fig.text(0.5, 0.885, "4 × 4 row-major array • 4 bytes per item • 16 bytes per cache line • aligned base 0",
ha="center", fontsize=11)
orders = [[(i, j) for i in range(4) for j in range(4)],
[(i, j) for j in range(4) for i in range(4)]]
for ax, order, title in zip(axes, orders, ["By rows: column j varies fastest", "By columns: row i varies fastest"]):
for i in range(4):
for j in range(4):
ax.add_patch(Rectangle((j - 0.5, i - 0.5), 1, 1,
facecolor=colours[i], edgecolor="white", linewidth=2))
ax.text(j, i + 0.28, f"addr {addresses[i, j]}", ha="center", va="center", fontsize=9, color="#333333",
bbox={"facecolor": colours[i], "edgecolor": "none", "pad": 0.1}, zorder=5)
ax.text(3.67, i, f"line {i}", va="center", fontsize=10)
for step, (i, j) in enumerate(order, start=1):
ax.text(j, i - 0.18, str(step), ha="center", va="center", fontweight="bold", fontsize=13,
bbox={"boxstyle": "circle,pad=0.14", "facecolor": "white", "edgecolor": "#233e55"}, zorder=4)
for (i, j), (next_i, next_j) in zip(order[:-1], order[1:]):
reset = (next_j < j) if title.startswith("By rows") else (next_i < i)
ax.annotate("", xy=(next_j, next_i - 0.18), xytext=(j, i - 0.18),
arrowprops={"arrowstyle": "->", "color": "#233e55", "lw": 1.6 if not reset else 1.1,
"linestyle": "--" if reset else "-", "shrinkA": 15, "shrinkB": 15,
"connectionstyle": "arc3,rad=0"}, zorder=3)
ax.set(xlim=(-0.6, 4.2), ylim=(3.6, -0.65), aspect="equal", xticks=range(4), yticks=range(4),
xlabel="Column j", ylabel="Row i")
ax.set_title(title, fontsize=12, pad=12, fontweight="bold")
for spine in ax.spines.values():
spine.set_visible(False)
ax.tick_params(length=0)
fig.text(0.5, 0.04, "Circled numbers = access steps 1–16; arrows follow time (dashed = next row / column).\n"
"Row colours identify physical cache lines in BOTH panels; storage never moves.",
ha="center", fontsize=11, linespacing=1.7)
fig.subplots_adjust(left=0.06, right=0.99, top=0.80, bottom=0.24, wspace=0.15)
fig.savefig(Path(__file__).stem + ".png", facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()