Content generated by Knowledge-Technology - collect information with the help of Claude - validate my own measurements with sources from the net - find a suitable form for the presentation - instructed Claude to build a SVG with a logarithmic time scale - instructed Claude to summarise the findings as Asciidoc table
245 lines
10 KiB
Python
245 lines
10 KiB
Python
#!/usr/bin/env python3
|
|
"""Generate the FrOSCon io_uring talk 'time scales' wrapped-log-scale diagram.
|
|
|
|
Page: 254 x 142.9 mm (== LibreOffice Impress 16:9 slide, 25.4 x 14.29 cm)
|
|
User unit == 1 mm.
|
|
|
|
Structure: four bands, each spanning exactly three decades (1 -> 1000 of its
|
|
unit), all at identical horizontal scale. Each band is a separate Inkscape
|
|
layer so the whole thing can be exported step by step (Export > Page) and
|
|
stacked as transparent overlays in Impress.
|
|
|
|
@note Code generated 8/2026 by Claude -- prompted by Ichthyostega
|
|
"""
|
|
import math
|
|
|
|
W, H = 254.0, 142.9
|
|
|
|
X0, X1 = 34.0, 240.0 # axis: value 1 .. value 1000
|
|
DEC = (X1 - X0) / 3.0 # mm per decade
|
|
AXIS_END = 243.5 # slight overshoot + chevron
|
|
|
|
BANDS_Y = [40.0, 75.0, 102.0, 120.0]
|
|
|
|
# ---- colours -------------------------------------------------------------
|
|
C_AXIS = "#555555"
|
|
C_MINOR = "#b3b3b3"
|
|
C_MACH = "#14507d"
|
|
C_HUMAN = "#b4530a"
|
|
C_VAL_M = "#4a7ba6"
|
|
C_VAL_H = "#c98342"
|
|
C_MUTED = "#8a8a8a"
|
|
C_TITLE = "#222222"
|
|
|
|
# ---- type ----------------------------------------------------------------
|
|
F_NAME, F_VAL, F_BAND, F_TITLE, F_NOTE, F_DISC = 3.8, 3.2, 5.5, 8.0, 3.6, 3.0
|
|
FONT = "DejaVu Sans, Verdana, sans-serif"
|
|
|
|
# tier offsets relative to band axis Y
|
|
UP1_NAME, UP1_VAL, UP1_STUB = -10.3, -6.5, -5.0
|
|
UP2_NAME, UP2_VAL, UP2_STUB = -18.3, -14.5, -13.0
|
|
LO1_NAME, LO1_VAL, LO1_STUB = 11.2, 7.5, 5.0
|
|
BRACKET_Y = 4.0
|
|
|
|
|
|
def X(v):
|
|
return X0 + DEC * math.log10(v)
|
|
|
|
|
|
def esc(s):
|
|
return (s.replace("&", "&").replace("<", "<").replace(">", ">"))
|
|
|
|
|
|
def text(x, y, s, size, fill, anchor="middle", weight="normal"):
|
|
return (f'<text x="{x:.2f}" y="{y:.2f}" font-family="{FONT}" '
|
|
f'font-size="{size}" font-weight="{weight}" fill="{fill}" '
|
|
f'text-anchor="{anchor}">{esc(s)}</text>')
|
|
|
|
|
|
def line(x1, y1, x2, y2, stroke, w, dash=None):
|
|
d = f' stroke-dasharray="{dash}"' if dash else ""
|
|
return (f'<path d="M {x1:.2f},{y1:.2f} L {x2:.2f},{y2:.2f}" '
|
|
f'stroke="{stroke}" stroke-width="{w}" fill="none"{d}/>')
|
|
|
|
|
|
# ==========================================================================
|
|
# entry data
|
|
# ==========================================================================
|
|
# tier: 'up1' | 'up2' | 'lo1'
|
|
# kind: 'machine' | 'human'
|
|
# span: (v_lo, v_hi) -> draw a bracket instead of a dot
|
|
BANDS = [
|
|
dict(unit="1 ns", ay=BANDS_Y[0], layer="ns", entries=[
|
|
dict(v=1, name="L1 cache", val="1 ns", tier="up1", kind="machine"),
|
|
dict(v=4, name="L2 cache", val="4 ns", tier="up1", kind="machine"),
|
|
dict(v=10, name="full memory barrier", val="10 ns", tier="up2", kind="machine"),
|
|
dict(v=15, name="L3 cache", val="15 ns", tier="up1", kind="machine"),
|
|
dict(v=100, name="DRAM access", val="100 ns", tier="up1", kind="machine"),
|
|
dict(span=(50, 100), name="contended cache line", val="50-100 ns",
|
|
tier="lo1", kind="machine"),
|
|
dict(offscale=True, name="1 clock cycle", val="\u2248 0.3 ns",
|
|
tier="lo1", kind="machine"),
|
|
]),
|
|
dict(unit="1 \u00b5s", ay=BANDS_Y[1], layer="us", entries=[
|
|
dict(v=1, name="system call", val="1 \u00b5s", tier="up1", kind="machine"),
|
|
dict(v=2, name="context switch", val="2 \u00b5s", tier="up2", kind="machine"),
|
|
dict(v=50, name="OS timer slack", val="50 \u00b5s", tier="up1", kind="machine"),
|
|
dict(span=(10, 30), name="thread wake-up", val="10-30 \u00b5s",
|
|
tier="lo1", kind="machine"),
|
|
dict(span=(50, 100), name="SSD read", val="50-100 \u00b5s",
|
|
tier="lo1", kind="machine"),
|
|
]),
|
|
dict(unit="1 ms", ay=BANDS_Y[2], layer="ms", entries=[
|
|
dict(v=10, name="disk seek", val="10 ms", tier="up1", kind="machine"),
|
|
dict(v=40, name="one frame @ 25 fps", val="40 ms", tier="up1", kind="machine"),
|
|
dict(v=10, name="barely noticeable", val="10 ms", tier="lo1", kind="human"),
|
|
dict(v=100, name="UI feels sluggish", val="100 ms", tier="lo1", kind="human"),
|
|
]),
|
|
dict(unit="1 s", ay=BANDS_Y[3], layer="s", entries=[
|
|
dict(v=1, name="\u201cit\u2019s thinking\u2026\u201d", val="1 s",
|
|
tier="lo1", kind="human"),
|
|
dict(v=180, name="another coffee", val="3 min", tier="lo1", kind="human"),
|
|
dict(v=900, name="go do something else", val="\u2248 15 min",
|
|
tier="lo1", kind="human", anchor="end", ax=250.0),
|
|
]),
|
|
]
|
|
|
|
|
|
# ==========================================================================
|
|
# scaffold
|
|
# ==========================================================================
|
|
def band_axis(ay, unit):
|
|
o = []
|
|
o.append(line(X0, ay, AXIS_END, ay, C_AXIS, 0.6))
|
|
# continuation chevron
|
|
o.append(f'<path d="M {AXIS_END-2.4:.2f},{ay-1.6:.2f} L {AXIS_END:.2f},{ay:.2f} '
|
|
f'L {AXIS_END-2.4:.2f},{ay+1.6:.2f}" stroke="{C_AXIS}" '
|
|
f'stroke-width="0.6" fill="none"/>')
|
|
# ticks
|
|
for d in range(3):
|
|
base = 10 ** d
|
|
for m in range(1, 10):
|
|
v = base * m
|
|
x = X(v)
|
|
if m == 1:
|
|
o.append(line(x, ay - 3.0, x, ay + 3.0, C_AXIS, 0.6))
|
|
else:
|
|
o.append(line(x, ay - 1.6, x, ay + 1.6, C_MINOR, 0.25))
|
|
o.append(line(X(1000), ay - 3.0, X(1000), ay + 3.0, C_AXIS, 0.6))
|
|
o.append(text(32.0, ay + 2.0, unit, F_BAND, C_MUTED, "end", "600"))
|
|
return o
|
|
|
|
|
|
def scaffold():
|
|
o = []
|
|
o.append(text(10.0, 15.0, "Time scales", F_TITLE, C_TITLE, "start", "600"))
|
|
o.append(text(244.0, 12.5, "each row spans three decades", F_NOTE, C_MUTED, "end"))
|
|
o.append(text(244.0, 17.0, "one row down = \u00d7 1000", F_NOTE, C_MUTED, "end"))
|
|
for b in BANDS:
|
|
o += band_axis(b["ay"], b["unit"])
|
|
o.append(text(10.0, 137.0,
|
|
"Order of magnitude only \u2014 contemporary desktop / laptop x86. "
|
|
"Expect \u00b1 2\u20133\u00d7; the ratios are the point.",
|
|
F_DISC, C_MUTED, "start"))
|
|
return o
|
|
|
|
|
|
# ==========================================================================
|
|
# entries
|
|
# ==========================================================================
|
|
def render_entry(e, ay):
|
|
kind = e.get("kind", "machine")
|
|
cn = C_MACH if kind == "machine" else C_HUMAN
|
|
cv = C_VAL_M if kind == "machine" else C_VAL_H
|
|
tier = e["tier"]
|
|
anchor = e.get("anchor", "middle")
|
|
o = []
|
|
|
|
if tier == "up1":
|
|
ny, vy, sy = ay + UP1_NAME, ay + UP1_VAL, ay + UP1_STUB
|
|
elif tier == "up2":
|
|
ny, vy, sy = ay + UP2_NAME, ay + UP2_VAL, ay + UP2_STUB
|
|
else:
|
|
ny, vy, sy = ay + LO1_NAME, ay + LO1_VAL, ay + LO1_STUB
|
|
|
|
if e.get("offscale"):
|
|
# dashed stub running off the left-hand end of the scale
|
|
y = ay + BRACKET_Y + 0.5
|
|
o.append(line(X0, y, 22.5, y, cn, 0.5, dash="1.6,1.2"))
|
|
o.append(f'<path d="M 25.4,{y-1.6:.2f} L 22.0,{y:.2f} L 25.4,{y+1.6:.2f}" '
|
|
f'stroke="{cn}" stroke-width="0.6" fill="none"/>')
|
|
ax = 22.0
|
|
o.append(text(ax, vy, e["val"], F_VAL, cv, "start"))
|
|
o.append(text(ax, ny, e["name"], F_NAME, cn, "start", "600"))
|
|
return o
|
|
|
|
if "span" in e:
|
|
x1, x2 = X(e["span"][0]), X(e["span"][1])
|
|
by = ay + BRACKET_Y
|
|
o.append(line(x1, by, x2, by, cn, 0.7))
|
|
o.append(line(x1, by, x1, ay, cn, 0.7))
|
|
o.append(line(x2, by, x2, ay, cn, 0.7))
|
|
ax = e.get("ax", (x1 + x2) / 2.0)
|
|
else:
|
|
x = X(e["v"])
|
|
o.append(f'<circle cx="{x:.2f}" cy="{ay:.2f}" r="1.3" fill="{cn}"/>')
|
|
stub_from = ay - 2.0 if tier.startswith("up") else ay + 2.0
|
|
o.append(line(x, stub_from, x, sy, cn, 0.4))
|
|
ax = e.get("ax", x)
|
|
|
|
o.append(text(ax, vy, e["val"], F_VAL, cv, anchor))
|
|
o.append(text(ax, ny, e["name"], F_NAME, cn, anchor, "600"))
|
|
return o
|
|
|
|
|
|
# ==========================================================================
|
|
# assemble
|
|
# ==========================================================================
|
|
def layer(lid, label, body, display="inline"):
|
|
return (f'<g inkscape:groupmode="layer" id="{lid}" '
|
|
f'inkscape:label="{label}" style="display:{display}">\n '
|
|
+ "\n ".join(body) + "\n</g>")
|
|
|
|
|
|
def build():
|
|
layers = []
|
|
layers.append(layer("layer-1-scaffold", "1 - scaffold", scaffold()))
|
|
|
|
for i, b in enumerate(BANDS):
|
|
if b["layer"] == "ms":
|
|
mach = [x for e in b["entries"] if e["kind"] == "machine"
|
|
for x in render_entry(e, b["ay"])]
|
|
hum = [x for e in b["entries"] if e["kind"] == "human"
|
|
for x in render_entry(e, b["ay"])]
|
|
layers.append(layer("layer-4-ms-machine", "4 - ms band (machine)", mach))
|
|
layers.append(layer("layer-5-ms-human", "5 - ms band (human)", hum))
|
|
elif b["layer"] == "ns":
|
|
body = [x for e in b["entries"] for x in render_entry(e, b["ay"])]
|
|
layers.append(layer("layer-2-ns", "2 - ns band", body))
|
|
elif b["layer"] == "us":
|
|
body = [x for e in b["entries"] for x in render_entry(e, b["ay"])]
|
|
layers.append(layer("layer-3-us", "3 - us band", body))
|
|
else:
|
|
body = [x for e in b["entries"] for x in render_entry(e, b["ay"])]
|
|
layers.append(layer("layer-6-s", "6 - s band (human)", body))
|
|
|
|
dim = [f'<rect x="0" y="22" width="{W}" height="67" fill="#ffffff" '
|
|
f'fill-opacity="0.74"/>']
|
|
layers.append(layer("layer-7-dim", "7 - dim ns+us (optional)", dim,
|
|
display="none"))
|
|
|
|
return f'''<?xml version="1.0" encoding="UTF-8"?>
|
|
<svg xmlns="http://www.w3.org/2000/svg"
|
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
width="{W}mm" height="{H}mm" viewBox="0 0 {W} {H}" version="1.1">
|
|
<sodipodi:namedview inkscape:document-units="mm" units="mm"/>
|
|
<title>Time scales - ns to minutes, wrapped logarithmic</title>
|
|
{chr(10).join(layers)}
|
|
</svg>
|
|
'''
|
|
|
|
|
|
if __name__ == "__main__":
|
|
open("/mnt/user-data/outputs/time-scales.svg", "w").write(build())
|
|
print("written")
|