Compare commits
16 commits
dec42f8c0f
...
400a8252bf
| Author | SHA1 | Date | |
|---|---|---|---|
| 400a8252bf | |||
| 1c028df397 | |||
| 951078f2c2 | |||
| 76a41aacdc | |||
| 0b25482bf1 | |||
| f52da82b3f | |||
| 886e81f0ea | |||
| 835f468e1b | |||
| 3f61b106cc | |||
| 15090bd997 | |||
| 4de78bc2a5 | |||
| 3f1eae2e17 | |||
| af4c3778a3 | |||
| f46f117eda | |||
| 7650da5d67 | |||
| 5068e04bd7 |
BIN
FrOSCon26/Talk-IOUring.odp
Normal file
BIN
FrOSCon26/demoCodeQR.png
Normal file
|
After Width: | Height: | Size: 609 B |
BIN
FrOSCon26/drawings/KleinVieh.jpg
Normal file
|
After Width: | Height: | Size: 103 KiB |
47
FrOSCon26/drawings/coherence.svg
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<?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="254.0mm" height="142.9mm" viewBox="0 0 254.0 142.9" version="1.1">
|
||||
<sodipodi:namedview inkscape:document-units="mm" units="mm"/>
|
||||
<title>Cache coherence and observability</title>
|
||||
<g inkscape:groupmode="layer" id="layer-1-scaffold" inkscape:label="1 - scaffold (x=0 everywhere)" style="display:inline">
|
||||
<defs><marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="#555555"/></marker></defs>
|
||||
<rect x="44.00" y="24.00" width="46.00" height="20.00" rx="2.0" fill="#ffffff" stroke="#222222" stroke-width="0.8"/>
|
||||
<text x="67.00" y="35.80" font-family="DejaVu Sans, Verdana, sans-serif" font-size="5.5" font-weight="600" fill="#222222" text-anchor="middle">Core</text>
|
||||
<rect x="164.00" y="24.00" width="46.00" height="20.00" rx="2.0" fill="#ffffff" stroke="#222222" stroke-width="0.8"/>
|
||||
<text x="187.00" y="35.80" font-family="DejaVu Sans, Verdana, sans-serif" font-size="5.5" font-weight="600" fill="#222222" text-anchor="middle">Core</text>
|
||||
<path d="M 67.00,44.00 L 67.00,58.00" stroke="#555555" stroke-width="0.6" fill="none" marker-end="url(#arrow)"/>
|
||||
<path d="M 187.00,44.00 L 187.00,58.00" stroke="#555555" stroke-width="0.6" fill="none" marker-end="url(#arrow)"/>
|
||||
<path d="M 67.00,80.00 L 67.00,110.00" stroke="#555555" stroke-width="0.6" fill="none"/>
|
||||
<path d="M 67.00,110.00 L 90.00,104.00" stroke="#555555" stroke-width="0.6" fill="none"/>
|
||||
<path d="M 187.00,80.00 L 187.00,110.00" stroke="#555555" stroke-width="0.6" fill="none"/>
|
||||
<path d="M 187.00,110.00 L 164.00,104.00" stroke="#555555" stroke-width="0.6" fill="none"/>
|
||||
<rect x="44.00" y="58.00" width="46.00" height="22.00" rx="2.0" fill="#f2f2f2" stroke="#222222" stroke-width="0.8"/>
|
||||
<rect x="164.00" y="58.00" width="46.00" height="22.00" rx="2.0" fill="#f2f2f2" stroke="#222222" stroke-width="0.8"/>
|
||||
<text x="67.00" y="64.00" font-family="DejaVu Sans, Verdana, sans-serif" font-size="4.2" font-weight="600" fill="#222222" text-anchor="middle">Cache</text>
|
||||
<text x="187.00" y="64.00" font-family="DejaVu Sans, Verdana, sans-serif" font-size="4.2" font-weight="600" fill="#222222" text-anchor="middle">Cache</text>
|
||||
<text x="67.00" y="74.00" font-family="DejaVu Sans, Verdana, sans-serif" font-size="6.5" font-weight="700" fill="#8a8a8a" text-anchor="middle">x = 0</text>
|
||||
<text x="187.00" y="74.00" font-family="DejaVu Sans, Verdana, sans-serif" font-size="6.5" font-weight="700" fill="#8a8a8a" text-anchor="middle">x = 0</text>
|
||||
<rect x="82.00" y="104.00" width="90.00" height="24.00" rx="2.0" fill="#f2f2f2" stroke="#222222" stroke-width="0.8"/>
|
||||
<text x="127.00" y="111.00" font-family="DejaVu Sans, Verdana, sans-serif" font-size="4.2" font-weight="600" fill="#222222" text-anchor="middle">DRAM</text>
|
||||
<text x="127.00" y="122.50" font-family="DejaVu Sans, Verdana, sans-serif" font-size="6.5" font-weight="700" fill="#8a8a8a" text-anchor="middle">x = 0</text>
|
||||
</g>
|
||||
<g inkscape:groupmode="layer" id="layer-2-core0-write" inkscape:label="2 - core0 writes x=1" style="display:inline">
|
||||
<rect x="44.00" y="58.00" width="46.00" height="22.00" rx="2.0" fill="#dce8f2" stroke="#14507d" stroke-width="1.1"/>
|
||||
<text x="67.00" y="64.00" font-family="DejaVu Sans, Verdana, sans-serif" font-size="4.2" font-weight="600" fill="#222222" text-anchor="middle">Cache</text>
|
||||
<text x="67.00" y="74.00" font-family="DejaVu Sans, Verdana, sans-serif" font-size="6.5" font-weight="700" fill="#14507d" text-anchor="middle">x = 1</text>
|
||||
</g>
|
||||
<g inkscape:groupmode="layer" id="layer-3-core1-read" inkscape:label="3 - core1 reads: still x=0" style="display:inline">
|
||||
<path d="M 184.00,45.50 C 179.00,51.00 179.00,51.00 184.00,56.50" stroke="#8a8a8a" stroke-width="0.6" fill="none" marker-end="url(#arrow)"/>
|
||||
<rect x="164.00" y="58.00" width="46.00" height="22.00" rx="2.0" fill="#f2f2f2" stroke="#8a8a8a" stroke-width="1.1"/>
|
||||
<text x="187.00" y="64.00" font-family="DejaVu Sans, Verdana, sans-serif" font-size="4.2" font-weight="600" fill="#222222" text-anchor="middle">Cache</text>
|
||||
<text x="187.00" y="74.00" font-family="DejaVu Sans, Verdana, sans-serif" font-size="6.5" font-weight="700" fill="#8a8a8a" text-anchor="middle">x = 0</text>
|
||||
</g>
|
||||
<g inkscape:groupmode="layer" id="layer-4-propagate" inkscape:label="4 - later: DRAM sees x=1, core1 still stale" style="display:inline">
|
||||
<circle cx="119.0" cy="95.0" r="0.7" fill="#8a8a8a"/><circle cx="123.0" cy="95.0" r="0.7" fill="#8a8a8a"/><circle cx="127.0" cy="95.0" r="0.7" fill="#8a8a8a"/><circle cx="131.0" cy="95.0" r="0.7" fill="#8a8a8a"/><circle cx="135.0" cy="95.0" r="0.7" fill="#8a8a8a"/>
|
||||
<rect x="82.00" y="104.00" width="90.00" height="24.00" rx="2.0" fill="#dce8f2" stroke="#14507d" stroke-width="1.1"/>
|
||||
<text x="127.00" y="111.00" font-family="DejaVu Sans, Verdana, sans-serif" font-size="4.2" font-weight="600" fill="#222222" text-anchor="middle">DRAM</text>
|
||||
<text x="127.00" y="122.50" font-family="DejaVu Sans, Verdana, sans-serif" font-size="6.5" font-weight="700" fill="#14507d" text-anchor="middle">x = 1</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.2 KiB |
222
FrOSCon26/drawings/gen-cache-coherence-diagram.py
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Cache coherence / observability diagram.
|
||||
|
||||
Four Inkscape layers, meant to be exported one by one (Export > Page) and
|
||||
stacked as transparent overlays in Impress, same workflow as the time-scales
|
||||
diagram. Layer content is INCREMENTAL: each layer only draws what is new at
|
||||
that step; earlier layers stay visible underneath once stacked.
|
||||
|
||||
Story:
|
||||
1 scaffold - two cores, two caches, one shared DRAM, all agree: x=0
|
||||
2 core0-write - core0's cache changes to x=1; nothing else does
|
||||
3 core1-read - core1 reads its own cache: still x=0 (stale, not wrong)
|
||||
4 propagate - some time later, x=1 reaches DRAM on its own;
|
||||
core1's cache is untouched and still says x=0
|
||||
"""
|
||||
|
||||
W, H = 254.0, 142.9
|
||||
|
||||
# ---- palette (kept consistent with time-scales.svg) -----------------------
|
||||
C_LINE = "#555555"
|
||||
C_BOX = "#222222"
|
||||
C_MACH = "#14507d" # "new" value / updated state
|
||||
C_STALE = "#8a8a8a" # stale / unchanged value
|
||||
C_FILL0 = "#f2f2f2" # box fill, neutral state
|
||||
C_FILL1 = "#dce8f2" # box fill, updated state
|
||||
C_WHITE = "#ffffff"
|
||||
FONT = "DejaVu Sans, Verdana, sans-serif"
|
||||
|
||||
F_LABEL, F_VALUE, F_TAG = 5.5, 6.5, 4.2
|
||||
|
||||
# ---- geometry ---------------------------------------------------------
|
||||
CORE_W, CORE_H = 46.0, 20.0
|
||||
CACHE_W, CACHE_H = 46.0, 22.0
|
||||
DRAM_W, DRAM_H = 90.0, 24.0
|
||||
|
||||
CORE0_X, CORE1_X = 44.0, 164.0
|
||||
CORE_Y = 24.0
|
||||
CACHE_Y = 58.0
|
||||
DRAM_X = (W - DRAM_W) / 2.0
|
||||
DRAM_Y = 104.0
|
||||
|
||||
C0_CX = CORE0_X + CORE_W / 2.0
|
||||
C1_CX = CORE1_X + CORE_W / 2.0
|
||||
DRAM_CX = DRAM_X + DRAM_W / 2.0
|
||||
|
||||
|
||||
def esc(s):
|
||||
return s.replace("&", "&").replace("<", "<").replace(">", ">")
|
||||
|
||||
|
||||
def text(x, y, s, size, fill, anchor="middle", weight="normal", family=FONT):
|
||||
return (f'<text x="{x:.2f}" y="{y:.2f}" font-family="{family}" '
|
||||
f'font-size="{size}" font-weight="{weight}" fill="{fill}" '
|
||||
f'text-anchor="{anchor}">{esc(s)}</text>')
|
||||
|
||||
|
||||
def rect(x, y, w, h, fill, stroke, sw=0.6, rx=2.0):
|
||||
return (f'<rect x="{x:.2f}" y="{y:.2f}" width="{w:.2f}" height="{h:.2f}" '
|
||||
f'rx="{rx}" fill="{fill}" stroke="{stroke}" stroke-width="{sw}"/>')
|
||||
|
||||
|
||||
def line(x1, y1, x2, y2, stroke=C_LINE, w=0.6, dash=None, marker=False):
|
||||
d = f' stroke-dasharray="{dash}"' if dash else ""
|
||||
m = ' marker-end="url(#arrow)"' if marker else ""
|
||||
return (f'<path d="M {x1:.2f},{y1:.2f} L {x2:.2f},{y2:.2f}" '
|
||||
f'stroke="{stroke}" stroke-width="{w}" fill="none"{d}{m}/>')
|
||||
|
||||
|
||||
def defs():
|
||||
return ('<defs><marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" '
|
||||
'markerWidth="6" markerHeight="6" orient="auto-start-reverse">'
|
||||
f'<path d="M0,0 L10,5 L0,10 z" fill="{C_LINE}"/></marker></defs>')
|
||||
|
||||
|
||||
# ==========================================================================
|
||||
# static structural pieces, reused across layers where needed
|
||||
# ==========================================================================
|
||||
def core_box(cx_left, label_suffix=""):
|
||||
cx = cx_left + CORE_W / 2.0
|
||||
o = [rect(cx_left, CORE_Y, CORE_W, CORE_H, C_WHITE, C_BOX, 0.8)]
|
||||
o.append(text(cx, CORE_Y + CORE_H / 2.0 + 1.8, "Core", F_LABEL, C_BOX,
|
||||
weight="600"))
|
||||
return o
|
||||
|
||||
|
||||
def cache_box_frame(cx_left):
|
||||
return [rect(cx_left, CACHE_Y, CACHE_W, CACHE_H, C_FILL0, C_BOX, 0.8)]
|
||||
|
||||
|
||||
def cache_tag(cx_left):
|
||||
cx = cx_left + CACHE_W / 2.0
|
||||
return [text(cx, CACHE_Y + 6.0, "Cache", F_TAG, C_BOX, weight="600")]
|
||||
|
||||
|
||||
def cache_value(cx_left, value_str, colour):
|
||||
cx = cx_left + CACHE_W / 2.0
|
||||
return [text(cx, CACHE_Y + CACHE_H / 2.0 + 5.0, value_str, F_VALUE, colour,
|
||||
weight="700")]
|
||||
|
||||
|
||||
def dram_box_frame():
|
||||
return [rect(DRAM_X, DRAM_Y, DRAM_W, DRAM_H, C_FILL0, C_BOX, 0.8)]
|
||||
|
||||
|
||||
def dram_tag():
|
||||
return [text(DRAM_CX, DRAM_Y + 7.0, "DRAM", F_TAG, C_BOX, weight="600")]
|
||||
|
||||
|
||||
def dram_value(value_str, colour):
|
||||
return [text(DRAM_CX, DRAM_Y + DRAM_H / 2.0 + 6.5, value_str, F_VALUE,
|
||||
colour, weight="700")]
|
||||
|
||||
|
||||
def bus_lines():
|
||||
o = []
|
||||
o.append(line(C0_CX, CORE_Y + CORE_H, C0_CX, CACHE_Y, marker=True))
|
||||
o.append(line(C1_CX, CORE_Y + CORE_H, C1_CX, CACHE_Y, marker=True))
|
||||
o.append(line(C0_CX, CACHE_Y + CACHE_H, C0_CX, DRAM_Y + 6.0, C_LINE, 0.6))
|
||||
o.append(line(C0_CX, DRAM_Y + 6.0, DRAM_X + 8.0, DRAM_Y, C_LINE, 0.6))
|
||||
o.append(line(C1_CX, CACHE_Y + CACHE_H, C1_CX, DRAM_Y + 6.0, C_LINE, 0.6))
|
||||
o.append(line(C1_CX, DRAM_Y + 6.0, DRAM_X + DRAM_W - 8.0, DRAM_Y, C_LINE, 0.6))
|
||||
return o
|
||||
|
||||
|
||||
# ==========================================================================
|
||||
# layer 1 - scaffold: full structure, everyone agrees x = 0
|
||||
# ==========================================================================
|
||||
def layer1():
|
||||
o = [defs()]
|
||||
o += core_box(CORE0_X)
|
||||
o += core_box(CORE1_X)
|
||||
o += bus_lines()
|
||||
o += cache_box_frame(CORE0_X)
|
||||
o += cache_box_frame(CORE1_X)
|
||||
o += cache_tag(CORE0_X)
|
||||
o += cache_tag(CORE1_X)
|
||||
o += cache_value(CORE0_X, "x = 0", C_STALE)
|
||||
o += cache_value(CORE1_X, "x = 0", C_STALE)
|
||||
o += dram_box_frame()
|
||||
o += dram_tag()
|
||||
o += dram_value("x = 0", C_STALE)
|
||||
return o
|
||||
|
||||
|
||||
# ==========================================================================
|
||||
# layer 2 - core0 writes: only its own cache changes
|
||||
# ==========================================================================
|
||||
def layer2():
|
||||
o = []
|
||||
# cover old value + box fill with an updated-state box, then new value
|
||||
o.append(rect(CORE0_X, CACHE_Y, CACHE_W, CACHE_H, C_FILL1, C_MACH, 1.1))
|
||||
o += cache_tag(CORE0_X)
|
||||
o += cache_value(CORE0_X, "x = 1", C_MACH)
|
||||
return o
|
||||
|
||||
|
||||
# ==========================================================================
|
||||
# layer 3 - core1 reads its own cache: emphasise the access, value unchanged
|
||||
# ==========================================================================
|
||||
def layer3():
|
||||
o = []
|
||||
cx = CORE1_X + CACHE_W / 2.0
|
||||
# small self-loop "read" arrow from core1 down into its own cache
|
||||
top = CORE_Y + CORE_H + 1.5
|
||||
bot = CACHE_Y - 1.5
|
||||
r = 5.0
|
||||
o.append(f'<path d="M {cx-3:.2f},{top:.2f} '
|
||||
f'C {cx-3-r:.2f},{(top+bot)/2:.2f} {cx-3-r:.2f},{(top+bot)/2:.2f} '
|
||||
f'{cx-3:.2f},{bot:.2f}" stroke="{C_STALE}" stroke-width="0.6" '
|
||||
f'fill="none" marker-end="url(#arrow)"/>')
|
||||
o.append(rect(CORE1_X, CACHE_Y, CACHE_W, CACHE_H, C_FILL0, C_STALE, 1.1))
|
||||
o += cache_tag(CORE1_X)
|
||||
o += cache_value(CORE1_X, "x = 0", C_STALE)
|
||||
return o
|
||||
|
||||
|
||||
# ==========================================================================
|
||||
# layer 4 - later, unobserved: x = 1 reaches DRAM; core1 still stale
|
||||
# ==========================================================================
|
||||
def layer4():
|
||||
o = []
|
||||
# a faint "time passing" mark between the structure and nothing else
|
||||
ty = DRAM_Y - 9.0
|
||||
dots = "".join(f'<circle cx="{DRAM_CX-8+4*i:.1f}" cy="{ty:.1f}" r="0.7" '
|
||||
f'fill="{C_STALE}"/>' for i in range(5))
|
||||
o.append(dots)
|
||||
o.append(rect(DRAM_X, DRAM_Y, DRAM_W, DRAM_H, C_FILL1, C_MACH, 1.1))
|
||||
o += dram_tag()
|
||||
o += dram_value("x = 1", C_MACH)
|
||||
return o
|
||||
|
||||
|
||||
# ==========================================================================
|
||||
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 = [
|
||||
layer("layer-1-scaffold", "1 - scaffold (x=0 everywhere)", layer1()),
|
||||
layer("layer-2-core0-write", "2 - core0 writes x=1", layer2()),
|
||||
layer("layer-3-core1-read", "3 - core1 reads: still x=0", layer3()),
|
||||
layer("layer-4-propagate", "4 - later: DRAM sees x=1, core1 still stale",
|
||||
layer4()),
|
||||
]
|
||||
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>Cache coherence and observability</title>
|
||||
{chr(10).join(layers)}
|
||||
</svg>
|
||||
'''
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
open("/mnt/user-data/outputs/coherence.svg", "w").write(build())
|
||||
print("written")
|
||||
245
FrOSCon26/drawings/gen-time-scales-diagram.py
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
#!/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")
|
||||
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
472
FrOSCon26/drawings/thread-handoff.svg
Normal file
|
|
@ -0,0 +1,472 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generated by graphviz version 2.42.4 (0)
|
||||
-->
|
||||
|
||||
<!-- Title: handoff Pages: 1 -->
|
||||
|
||||
<svg
|
||||
width="15.78cm"
|
||||
height="10.38cm"
|
||||
viewBox="0 0 760.11559 500"
|
||||
version="1.1"
|
||||
id="svg29"
|
||||
sodipodi:docname="thread-handoff.svg"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
inkscape:export-filename="../handoff.svg"
|
||||
inkscape:export-xdpi="90"
|
||||
inkscape:export-ydpi="90"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<title
|
||||
id="title33">Proactor data handoff</title>
|
||||
<defs
|
||||
id="defs29">
|
||||
<marker
|
||||
style="overflow:visible"
|
||||
id="marker29"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto-start-reverse"
|
||||
inkscape:stockid="Dart arrow"
|
||||
markerWidth="2"
|
||||
markerHeight="2"
|
||||
viewBox="0 0 1 1"
|
||||
inkscape:isstock="true"
|
||||
inkscape:collect="always"
|
||||
preserveAspectRatio="xMidYMid">
|
||||
<path
|
||||
style="fill:context-stroke;fill-rule:evenodd;stroke:none"
|
||||
d="M 0,0 5,-5 -12.5,0 5,5 Z"
|
||||
transform="scale(-0.5)"
|
||||
id="path29" />
|
||||
</marker>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="namedview29"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="pt"
|
||||
inkscape:zoom="1.3948107"
|
||||
inkscape:cx="323.34138"
|
||||
inkscape:cy="234.44042"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1061"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg29" />
|
||||
<g
|
||||
id="clust1"
|
||||
class="cluster"
|
||||
style="stroke-width:0.780137"
|
||||
transform="matrix(1.1565668,0,0,1.420652,-67.271267,489.33908)">
|
||||
<title
|
||||
id="title2">cluster_proactor</title>
|
||||
<path
|
||||
fill="#eef4ff"
|
||||
stroke="#7092c0"
|
||||
d="m 241.5,-143 c 0,0 446.5,0 446.5,0 6,0 12,-6 12,-12 0,0 0,-169 0,-169 0,-6 -6,-12 -12,-12 0,0 -446.5,0 -446.5,0 -6,0 -12,6 -12,12 0,0 0,169 0,169 0,6 6,12 12,12"
|
||||
id="path2"
|
||||
style="stroke-width:0.780137" />
|
||||
<text
|
||||
text-anchor="middle"
|
||||
x="361"
|
||||
y="-322.39999"
|
||||
font-family="sans-serif"
|
||||
font-size="12px"
|
||||
id="text2"
|
||||
style="font-size:13.257px;stroke-width:0.780137"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:13.257px;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans'"
|
||||
id="tspan29"> »Proactor« thread — playVideo()</tspan></text>
|
||||
</g>
|
||||
<g
|
||||
id="clust2"
|
||||
class="cluster"
|
||||
style="stroke-width:0.780137"
|
||||
transform="matrix(1.1565668,0,0,1.420652,-81.150069,489.33908)">
|
||||
<title
|
||||
id="title3">cluster_gui</title>
|
||||
<path
|
||||
fill="#eefaee"
|
||||
stroke="#6bab6b"
|
||||
d="m 205,-8 c 0,0 495,0 495,0 6,0 12,-6 12,-12 0,0 0,-49 0,-49 0,-6 -6,-12 -12,-12 0,0 -495,0 -495,0 -6,0 -12,6 -12,12 0,0 0,49 0,49 0,6 6,12 12,12"
|
||||
id="path3"
|
||||
style="stroke-width:0.780137;fill:#d7faf7;fill-opacity:1" />
|
||||
<text
|
||||
text-anchor="middle"
|
||||
x="272.72073"
|
||||
y="-68.043182"
|
||||
font-family="sans-serif"
|
||||
font-size="12px"
|
||||
id="text3"
|
||||
style="font-size:13.257px;stroke-width:0.780137"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans Bold'"
|
||||
id="tspan31"> GTK / GUI thread </tspan></text>
|
||||
</g>
|
||||
<g
|
||||
id="node1"
|
||||
class="node"
|
||||
style="stroke-width:1.03353"
|
||||
transform="matrix(0.85777003,0,0,1.0913897,4.0126228,416.88882)">
|
||||
<title
|
||||
id="title4">file</title>
|
||||
<path
|
||||
fill="#eee"
|
||||
stroke="black"
|
||||
d="m 101,-216.73 c 0,-1.8 -22.63,-3.27 -50.5,-3.27 -27.87,0 -50.5,1.47 -50.5,3.27 0,0 0,29.46 0,29.46 0,1.8 22.63,3.27 50.5,3.27 27.87,0 50.5,-1.47 50.5,-3.27 0,0 0,-29.46 0,-29.46"
|
||||
id="path4"
|
||||
style="stroke-width:1.03353" />
|
||||
<path
|
||||
fill="none"
|
||||
stroke="black"
|
||||
d="m 101,-216.73 c 0,1.81 -22.63,3.28 -50.5,3.28 -27.87,0 -50.5,-1.47 -50.5,-3.28"
|
||||
id="path5"
|
||||
style="stroke-width:1.03353" />
|
||||
<text
|
||||
text-anchor="middle"
|
||||
x="50.5"
|
||||
y="-199.2"
|
||||
font-family="sans-serif"
|
||||
font-size="11px"
|
||||
id="text5"
|
||||
style="stroke-width:1.03353">demovideo.raw</text>
|
||||
</g>
|
||||
<g
|
||||
id="node4"
|
||||
class="node"
|
||||
style="stroke-width:0.780137"
|
||||
transform="matrix(1.1565668,0,0,1.420652,-67.271267,489.33908)">
|
||||
<title
|
||||
id="title5">ring</title>
|
||||
<polygon
|
||||
fill="white"
|
||||
stroke="black"
|
||||
points="237.5,-252 379.5,-252 379.5,-152 237.5,-152 "
|
||||
id="polygon5"
|
||||
style="stroke-width:0.780137" />
|
||||
<text
|
||||
text-anchor="middle"
|
||||
x="308.5"
|
||||
y="-239.2"
|
||||
font-family="sans-serif"
|
||||
font-size="11px"
|
||||
id="text6"
|
||||
style="stroke-width:0.780137">ring buffers (RING_SIZ)</text>
|
||||
<polyline
|
||||
fill="none"
|
||||
stroke="black"
|
||||
points="237.5,-232 379.5,-232 "
|
||||
id="polyline6"
|
||||
style="stroke-width:0.780137" />
|
||||
<text
|
||||
text-anchor="middle"
|
||||
x="308.5"
|
||||
y="-219.2"
|
||||
font-family="sans-serif"
|
||||
font-size="11px"
|
||||
id="text7"
|
||||
style="stroke-width:0.780137">slot</text>
|
||||
<polyline
|
||||
fill="none"
|
||||
stroke="black"
|
||||
points="237.5,-212 379.5,-212 "
|
||||
id="polyline7"
|
||||
style="stroke-width:0.780137" />
|
||||
<text
|
||||
text-anchor="middle"
|
||||
x="308.5"
|
||||
y="-199.2"
|
||||
font-family="sans-serif"
|
||||
font-size="11px"
|
||||
id="text8"
|
||||
style="stroke-width:0.780137">slot</text>
|
||||
<polyline
|
||||
fill="none"
|
||||
stroke="black"
|
||||
points="237.5,-192 379.5,-192 "
|
||||
id="polyline8"
|
||||
style="stroke-width:0.780137" />
|
||||
<text
|
||||
text-anchor="middle"
|
||||
x="308.5"
|
||||
y="-179.2"
|
||||
font-family="sans-serif"
|
||||
font-size="11px"
|
||||
id="text9"
|
||||
style="stroke-width:0.780137">slot</text>
|
||||
<polyline
|
||||
fill="none"
|
||||
stroke="black"
|
||||
points="237.5,-172 379.5,-172 "
|
||||
id="polyline9"
|
||||
style="stroke-width:0.780137" />
|
||||
<text
|
||||
text-anchor="middle"
|
||||
x="308.5"
|
||||
y="-159.2"
|
||||
font-family="sans-serif"
|
||||
font-size="11px"
|
||||
id="text10"
|
||||
style="stroke-width:0.780137">…</text>
|
||||
</g>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="black"
|
||||
d="M 90.647396,202.36738 H 193.58071"
|
||||
id="path10"
|
||||
style="stroke-width:1;marker-end:url(#marker29)"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<text
|
||||
text-anchor="middle"
|
||||
x="162.54471"
|
||||
y="178.74663"
|
||||
font-family="sans-serif"
|
||||
font-size="12.8183px"
|
||||
id="text11"
|
||||
style="stroke-width:1"
|
||||
transform="scale(0.90228035,1.108303)">io_uring read</text>
|
||||
<g
|
||||
id="node2"
|
||||
class="node"
|
||||
style="stroke-width:0.780137"
|
||||
transform="matrix(1.1565668,0,0,1.420652,-67.271267,489.33908)">
|
||||
<title
|
||||
id="title11">wait</title>
|
||||
<path
|
||||
fill="white"
|
||||
stroke="black"
|
||||
d="m 680,-307 c 0,0 -98,0 -98,0 -6,0 -12,6 -12,12 0,0 0,12 0,12 0,6 6,12 12,12 0,0 98,0 98,0 6,0 12,-6 12,-12 0,0 0,-12 0,-12 0,-6 -6,-12 -12,-12"
|
||||
id="path11"
|
||||
style="stroke-width:0.780137" />
|
||||
<text
|
||||
text-anchor="middle"
|
||||
x="631"
|
||||
y="-292.20001"
|
||||
font-family="sans-serif"
|
||||
font-size="11px"
|
||||
id="text12"
|
||||
style="stroke-width:0.780137">io_uring_wait_cqe()</text>
|
||||
<text
|
||||
text-anchor="middle"
|
||||
x="631"
|
||||
y="-280.20001"
|
||||
font-family="sans-serif"
|
||||
font-size="11px"
|
||||
id="text13"
|
||||
style="stroke-width:0.780137">(blocking)</text>
|
||||
</g>
|
||||
<g
|
||||
id="node3"
|
||||
class="node"
|
||||
style="stroke-width:0.780137"
|
||||
transform="matrix(1.1565668,0,0,1.420652,-67.271267,489.33908)">
|
||||
<title
|
||||
id="title13">timer</title>
|
||||
<path
|
||||
fill="#ffedcc"
|
||||
stroke="black"
|
||||
d="m 353,-307 c 0,0 -89,0 -89,0 -6,0 -12,6 -12,12 0,0 0,12 0,12 0,6 6,12 12,12 0,0 89,0 89,0 6,0 12,-6 12,-12 0,0 0,-12 0,-12 0,-6 -6,-12 -12,-12"
|
||||
id="path13"
|
||||
style="stroke-width:0.780137" />
|
||||
<text
|
||||
text-anchor="middle"
|
||||
x="308.5"
|
||||
y="-292.20001"
|
||||
font-family="sans-serif"
|
||||
font-size="11px"
|
||||
id="text14"
|
||||
style="stroke-width:0.780137">frame-beat timer</text>
|
||||
<text
|
||||
text-anchor="middle"
|
||||
x="308.5"
|
||||
y="-280.20001"
|
||||
font-family="sans-serif"
|
||||
font-size="11px"
|
||||
id="text15"
|
||||
style="stroke-width:0.780137">20 ms · multishot</text>
|
||||
</g>
|
||||
<g
|
||||
id="edge2"
|
||||
class="edge"
|
||||
style="stroke-width:0.780137"
|
||||
transform="matrix(1.1565668,0,0,1.420652,-67.271267,489.33908)">
|
||||
<title
|
||||
id="title15">timer->wait</title>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="black"
|
||||
stroke-dasharray="0.780137, 3.90068"
|
||||
d="m 365.33,-289 c 53.97,0 135.93,0 194.33,0"
|
||||
id="path15"
|
||||
style="stroke-width:0.780137" />
|
||||
<polygon
|
||||
fill="black"
|
||||
stroke="black"
|
||||
points="559.92,-285.5 559.92,-292.5 569.92,-289 "
|
||||
id="polygon15"
|
||||
style="stroke-width:0.780137" />
|
||||
</g>
|
||||
<g
|
||||
id="node7"
|
||||
class="node"
|
||||
style="stroke-width:0.780137"
|
||||
transform="matrix(1.1565668,0,0,1.420652,-67.271267,489.33908)">
|
||||
<title
|
||||
id="title16">baton</title>
|
||||
<polygon
|
||||
fill="#e7d8ff"
|
||||
stroke="black"
|
||||
stroke-width="1.24822"
|
||||
points="563.5,-131 563.5,-95 698.5,-95 698.5,-131 "
|
||||
id="polygon16"
|
||||
style="fill:#cdcaff;fill-opacity:1" />
|
||||
<text
|
||||
text-anchor="middle"
|
||||
x="631"
|
||||
y="-116.2"
|
||||
font-family="sans-serif"
|
||||
font-size="11px"
|
||||
id="text17"
|
||||
style="stroke-width:0.780137;stroke:#241bab;stroke-opacity:1">toDisplay_</text>
|
||||
<text
|
||||
text-anchor="middle"
|
||||
x="631"
|
||||
y="-104.2"
|
||||
font-family="sans-serif"
|
||||
font-size="11px"
|
||||
id="text18"
|
||||
style="stroke-width:0.780137;stroke:#241bab;stroke-opacity:1">std::atomic<Buffer*></text>
|
||||
</g>
|
||||
<g
|
||||
id="node5"
|
||||
class="node"
|
||||
style="stroke-width:0.780137"
|
||||
transform="matrix(1.1565668,0,0,1.420652,258.4525,489.33908)">
|
||||
<title
|
||||
id="title20">disp</title>
|
||||
<path
|
||||
fill="white"
|
||||
stroke="black"
|
||||
d="m 404,-52 c 0,0 -191,0 -191,0 -6,0 -12,6 -12,12 0,0 0,12 0,12 0,6 6,12 12,12 0,0 191,0 191,0 6,0 12,-6 12,-12 0,0 0,-12 0,-12 0,-6 -6,-12 -12,-12"
|
||||
id="path20"
|
||||
style="stroke-width:0.780137" />
|
||||
<text
|
||||
text-anchor="middle"
|
||||
x="308.5"
|
||||
y="-37.200001"
|
||||
font-family="sans-serif"
|
||||
font-size="11px"
|
||||
id="text21"
|
||||
style="stroke-width:0.780137">EglDisplay::displayFrame()</text>
|
||||
<text
|
||||
text-anchor="middle"
|
||||
x="308.5"
|
||||
y="-25.200001"
|
||||
font-family="sans-serif"
|
||||
font-size="11px"
|
||||
id="text22"
|
||||
style="stroke-width:0.780137">retrieveFrame() / completedFrame()</text>
|
||||
</g>
|
||||
<g
|
||||
id="node6"
|
||||
class="node"
|
||||
style="stroke-width:0.780137"
|
||||
transform="matrix(1.1565668,0,0,1.420652,-493.129,489.33908)">
|
||||
<title
|
||||
id="title22">gpu</title>
|
||||
<path
|
||||
fill="white"
|
||||
stroke="black"
|
||||
d="m 692,-52 c 0,0 -122,0 -122,0 -6,0 -12,6 -12,12 0,0 0,12 0,12 0,6 6,12 12,12 0,0 122,0 122,0 6,0 12,-6 12,-12 0,0 0,-12 0,-12 0,-6 -6,-12 -12,-12"
|
||||
id="path22"
|
||||
style="stroke-width:0.780137" />
|
||||
<text
|
||||
text-anchor="middle"
|
||||
x="631"
|
||||
y="-37.200001"
|
||||
font-family="sans-serif"
|
||||
font-size="11px"
|
||||
id="text23"
|
||||
style="stroke-width:0.780137">GPU texture</text>
|
||||
<text
|
||||
text-anchor="middle"
|
||||
x="631"
|
||||
y="-25.200001"
|
||||
font-family="sans-serif"
|
||||
font-size="11px"
|
||||
id="text24"
|
||||
style="stroke-width:0.780137">(glTexImage2D → draw)</text>
|
||||
</g>
|
||||
<g
|
||||
id="edge5"
|
||||
class="edge"
|
||||
style="stroke-width:0.780137"
|
||||
transform="matrix(1.1565668,0,0,1.420652,-157.48563,489.33908)">
|
||||
<title
|
||||
id="title24">disp->gpu</title>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="black"
|
||||
d="m 558.76077,-34 c -42.72,0 -91.17,0 -131.13,0"
|
||||
id="path24"
|
||||
style="stroke-width:0.780137" />
|
||||
<polygon
|
||||
fill="black"
|
||||
stroke="black"
|
||||
points="557.58,-34 547.58,-30.5 547.58,-37.5 "
|
||||
id="polygon24"
|
||||
style="stroke-width:0.780137"
|
||||
transform="matrix(-1,0,0,1,974.98077,0)" />
|
||||
<text
|
||||
text-anchor="middle"
|
||||
x="487"
|
||||
y="-37"
|
||||
font-family="sans-serif"
|
||||
font-size="10px"
|
||||
id="text25"
|
||||
style="stroke-width:0.780137">upload + draw</text>
|
||||
</g>
|
||||
<metadata
|
||||
id="metadata33">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<cc:license
|
||||
rdf:resource="http://creativecommons.org/licenses/by-nc-sa/4.0/" />
|
||||
<dc:title>Proactor data handoff</dc:title>
|
||||
<dc:creator>
|
||||
<cc:Agent>
|
||||
<dc:title>Ichthyostega</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:creator>
|
||||
</cc:Work>
|
||||
<cc:License
|
||||
rdf:about="http://creativecommons.org/licenses/by-nc-sa/4.0/">
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Reproduction" />
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Distribution" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#Notice" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#Attribution" />
|
||||
<cc:prohibits
|
||||
rdf:resource="http://creativecommons.org/ns#CommercialUse" />
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
|
||||
</cc:License>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 14 KiB |
1558
FrOSCon26/drawings/time-scales.svg
Normal file
|
After Width: | Height: | Size: 37 KiB |
|
|
@ -203,6 +203,9 @@ UringVideoPlayer::addNextReadSubmissions (io_uring& uRing, int inputFD)
|
|||
{
|
||||
size_t srcOffset = calcSrcFrame (nextRead_);
|
||||
io_uring_sqe* readSubmission = io_uring_get_sqe (&uRing);
|
||||
if (nullptr == readSubmission)
|
||||
__FAIL ("URing slot allocation failed");
|
||||
|
||||
io_uring_prep_read (readSubmission
|
||||
,inputFD
|
||||
,& buffer_[nextRead_ % RING_SIZ]
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ class EglDisplay
|
|||
using Buffer = std::array<PIX, WIDTH*HEIGHT * 3 >;
|
||||
|
||||
/**
|
||||
* Interface retrieve pixel data for video frames to display.
|
||||
* Interface: retrieve pixel data for video frames to display.
|
||||
* - this is a Glib::Dispatcher, and the attached slot shall be
|
||||
* triggered whenever data for a new frame is available
|
||||
* - pixel data is accessed (threadsafe) by invoking #retrieveFrame()
|
||||
|
|
@ -85,7 +85,7 @@ class EglDisplay
|
|||
float scaleX_{1};
|
||||
float scaleY_{1};
|
||||
|
||||
/** Callback functor to access current pixel data */
|
||||
/** Callback to retrieve pixel data */
|
||||
PixelFeed& pixelFeed_{};
|
||||
|
||||
void openDisplay (Gtk::Window&);
|
||||
|
|
@ -183,9 +183,10 @@ EglDisplay<PIX,WIDTH,HEIGHT>::openDisplay (Gtk::Window& appWindow)
|
|||
|
||||
// use lib-GLEW to manage OpenGL extensions (notably GL Shader Language)
|
||||
GLenum glewErr = glewInit();
|
||||
if (GLEW_OK != glewErr)
|
||||
__FAIL ("could not bind OpenGL extensions through lib-GLEW:\n"
|
||||
+string{reinterpret_cast<const char*> (glewGetErrorString(glewErr))});
|
||||
////////////////////////////////////////////////////////////////////////////////////TODO for some reason I get glewErr == 4 ("unknown error"), but it works non the less
|
||||
//if (GLEW_OK != glewErr)
|
||||
// __FAIL ("could not bind OpenGL extensions through lib-GLEW:\n"
|
||||
// +string{reinterpret_cast<const char*> (glewGetErrorString(glewErr))});
|
||||
|
||||
|
||||
auto compileShader = [](GLenum kind, string code)
|
||||
|
|
|
|||
264
FrOSCon26/research/TimeScales.txt
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
Time Scales
|
||||
===========
|
||||
:Author: Lumiera Project
|
||||
:toc:
|
||||
|
||||
Anyone reasoning about I/O, threading or real-time media processing eventually
|
||||
needs a feeling for _how long things take_. The trouble is that the relevant
|
||||
durations span more than twelve orders of magnitude -- from a single CPU clock
|
||||
cycle up to the point where a human gives up waiting. Numbers on that scale
|
||||
resist intuition, and so we tend to reason about ``fast'' and ``slow'' without
|
||||
noticing that the two words can be separated by a factor of a million.
|
||||
|
||||
The following table collects the figures that matter in this context. It is
|
||||
meant as a rough orientation, not as a benchmark result.
|
||||
|
||||
NOTE: All values are order-of-magnitude figures for contemporary desktop and
|
||||
laptop x86 hardware. Expect a deviation of 2--3{nbsp}x depending on
|
||||
microarchitecture, memory configuration, kernel version and load. The
|
||||
_ratios_ are the point, not the digits.
|
||||
|
||||
The last column rescales everything by a single constant factor, chosen so
|
||||
that one CPU clock cycle (≙ 0.3{nbsp}ns at 3.3{nbsp}GHz) becomes one second
|
||||
of human time. This is the most effective way we know to convey what a cache
|
||||
miss or a disk seek actually costs from the machine's point of view.
|
||||
|
||||
|
||||
Overview Table
|
||||
--------------
|
||||
|
||||
[width="100%",cols="12%,26%,40%,22%",options="header"]
|
||||
|=========================================================================
|
||||
| Figure | Item | Remarks | Felt as (1{nbsp}cycle{nbsp}≙{nbsp}1{nbsp}s)
|
||||
|
||||
4+^s| Nanoseconds -- inside the CPU
|
||||
|
||||
| 0.3{nbsp}ns | one clock cycle | at 3.3{nbsp}GHz; the machine's own time quantum | *1 second*
|
||||
| ~1{nbsp}ns | L1 cache hit | about 4 cycles, fully pipelined | ≈{nbsp}3 seconds
|
||||
| ~1.5{nbsp}ns | `sfence` | store fence, ≈{nbsp}5 cycles; rarely needed on x86 | ≈{nbsp}5 seconds
|
||||
| ~2.7{nbsp}ns | `lfence` | ≈{nbsp}9 cycles; no ordering semantics on x86 beyond blocking speculation | ≈{nbsp}9 seconds
|
||||
| ~4{nbsp}ns | L2 cache hit | ≈{nbsp}12--15 cycles | ≈{nbsp}13 seconds
|
||||
| ~5{nbsp}ns | `lock add $0,(%rsp)` | the actual `smp_mb()` on Linux; ≈{nbsp}half the cost of `mfence` footnoteref:[lkml,LKML thread "[PATCH 3/4] x86,asm: Re-work smp_store_mb()", Nov 2015. Davidlohr Bueso measures XCHG against MFENCE on Ivy Bridge; Linus Torvalds reports that on Haswell "lock addq" costs pretty much exactly half of MFENCE, and explains that locked operations were the ones CPU designers had reason to optimise -- https://lkml.iu.edu/hypermail/linux/kernel/1511.0/01052.html] | ≈{nbsp}17 seconds
|
||||
| ~5{nbsp}ns | branch misprediction | pipeline refill | ≈{nbsp}17 seconds
|
||||
| ~10{nbsp}ns | `mfence` / full barrier | ≈{nbsp}23--33 cycles; comparable to a `lock`-prefixed RMW footnoteref:[downs,Travis Downs, "A Concurrency Cost Hierarchy", 6 July 2020, see the discussion of "vanilla" versus "non-vanilla" instructions: on x86 plain accesses and non-atomic read-modify-writes are cheap while LOCK-prefixed RMWs are an order of magnitude slower, with MFENCE comparable to a locked instruction -- https://travisdowns.github.io/blog/2020/07/06/concurrency-costs.html] | ≈{nbsp}33 seconds
|
||||
| ~15{nbsp}ns | L3 cache hit | shared, hence variable | ≈{nbsp}50 seconds
|
||||
| ~13{nbsp}ns | DRAM, open row | a plain load that happens to hit an already-activated row footnoteref:[cxl,Measured values in Table III of: Performance Characterizations and Usage Guidelines of Samsung CXL Memory Module Hybrid Prototype, arXiv:2503.22017. Local DDR5 median latency 122.9 ns under Intel MLC pointer chasing versus 13.1 ns for a plain load; methodology (rdtsc, 10 000 repetitions, median) described in section III-C -- https://arxiv.org/pdf/2503.22017] | ≈{nbsp}43 seconds
|
||||
| ~14{nbsp}ns | DRAM, first access | device-level figure; essentially unchanged from DDR4 to DDR5 footnote:[Measurement-oriented analysis of DDR5 sub-channel architecture; the relevant passage is in section "Latency Implications", noting that both DDR4 and DDR5 sit at approximately 14 ns first-access latency at their respective standard speeds. arXiv:2605.08725 -- https://arxiv.org/pdf/2605.08725]
|
||||
| ≈{nbsp}47 seconds
|
||||
| 40--100{nbsp}ns | contended cache line | core-to-core transfer; strongly topology dependent (same CCX / cross CCX / cross socket) | 2--5 minutes
|
||||
| ~100{nbsp}ns | DRAM random access | full miss through the whole hierarchy; 122.9{nbsp}ns measured by pointer chasing on DDR5 footnoteref:[cxl] | *≈{nbsp}5 minutes*
|
||||
|
||||
4+^s| Nanoseconds to microseconds -- entering the kernel
|
||||
|
||||
| ~25{nbsp}ns | syscall via vDSO | not a real trap; almost as cheap as a function call footnoteref:[arkanis,"Measurements of system call performance and overhead", Arkanis Development, 5 Jan 2017 -- compares a plain function call against getpid() issued via the syscall instruction and via the vDSO -- https://arkanis.de/weblog/2017-01-05-measurements-of-system-call-performance-and-overhead/] | ≈{nbsp}1.5 minutes
|
||||
| 70--100{nbsp}ns | syscall trap, bare | `getpid()` on a mitigation-free host footnoteref:[gms,Georg Sauthoff, "On the Costs of Syscalls", 30 Aug 2021. Median of 100 repetitions across a heterogeneous set of hosts; the conclusion is that user-kernel mode switches cost on the order of a few hundred nanoseconds on all hosts, with the fastest host switching modes in under 100 ns, and that the higher figures are explained by CPU-bug mitigations being enabled by default -- https://gms.tf/on-the-costs-of-syscalls.html] | 4--6 minutes
|
||||
| 200--600{nbsp}ns | syscall trap, mitigated | ``a few hundred nanoseconds'' on typical hosts with CPU-bug mitigations enabled footnoteref:[gms] | 11--33 minutes
|
||||
| ~1{nbsp}µs | syscall doing real work | the round figure we use for the diagram | *≈{nbsp}1 hour*
|
||||
| ~2{nbsp}µs | context switch | scheduler plus cold cache on the new task | ≈{nbsp}2 hours
|
||||
|
||||
4+^s| Microseconds -- the operating system
|
||||
|
||||
| 10--30{nbsp}µs | thread wake-up | IRQ handling, `try_to_wake_up()`, scheduling onto a CPU footnoteref:[lkmlwake,Michal Schmidt, reply in the thread "nanosleep() accuracy", linux-kernel mailing list, 17 Aug 2007: the point being that a process always carries non-zero wake-up latency covering interrupt processing, waking and scheduling, and that 10-30 us is not unreasonable -- https://lkml.iu.edu/hypermail/linux/kernel/0708.2/0423.html] | 9{nbsp}hours -- 1{nbsp}day
|
||||
| ~31--57{nbsp}µs | scheduling latency, avg | `cyclictest` at `SCHED_FIFO` 95 with memory locked footnoteref:[rhel,Sample cyclictest output and interpretation in: Finding Realtime Linux Kernel Latencies, Clark Williams (Red Hat), section "Using cyclictest" -- https://people.redhat.com/williams/latency-howto/rt-latency-howto.txt] | 1--2 days
|
||||
| ~59--90{nbsp}µs | scheduling latency, max | same run; the tail is what hurts footnoteref:[rhel] | 2--3.5 days
|
||||
| *50{nbsp}µs* | *OS timer slack, default* | the kernel may delay a timed wake-up by this much, deliberately, to coalesce wakeups footnoteref:[slack,Primary documentation: `man 7 time`, section "High-resolution timers", and `man 2 prctl`, description of PR_SET_TIMERSLACK. The 50 us default for non-realtime threads is stated explicitly in: Metronome -- adaptive and precise intermittent packet retrieval in DPDK, section II (discussion of nanosleep limits), arXiv:2103.13263 -- https://arxiv.org/pdf/2103.13263 ; a practical encounter with the same default, including the /proc interface, is documented in OpenJDK issue JDK-8307766 -- https://bugs.openjdk.org/browse/JDK-8307766] | *≈{nbsp}2 days*
|
||||
| 1{nbsp}ns | OS timer slack, minimum | via `prctl(PR_SET_TIMERSLACK)` or `/proc/PID/timerslack_ns` footnoteref:[slack] | ≈{nbsp}3 seconds
|
||||
| ~9{nbsp}µs | SSD read, best case | Optane / Z-NAND class at QD1 footnoteref:[anand,AnandTech, review of the Samsung 983 ZET (Z-NAND), page 3, "QD1 Random Read Performance" -- explains why queue depth 1 exposes best-case access latency, and reports an average of just under 9 us for an Optane drive against 16 us for the Z-NAND based 983 ZET -- https://at-web1.www.anandtech.com/show/13951/the-samsung-983-zet-znand-ssd-review/3] | ≈{nbsp}8 hours
|
||||
| 20--70{nbsp}µs | NVMe SSD 4K random read | typical contemporary drive footnoteref:[simply,Summary of the storage latency hierarchy: a modern NVMe SSD serves a random 4K read in roughly 20-70 us, a SATA SSD in 100-200 us, and a spinning disk in 5-10 ms. simplyblock glossary entry "NVMe Latency" -- https://simplyblock.io/glossary/nvme-latency/] | 1--3 days
|
||||
| 50--100{nbsp}µs | NVMe SSD, interrupt path | what you see without polling | *2--4 days*
|
||||
| 100--200{nbsp}µs | SATA SSD 4K random read | the interface costs, not just the medium footnoteref:[simply] | 4--8 days
|
||||
| 100{nbsp}µs -- 1{nbsp}ms | deep C-state exit | wake from C6 and below; worse when many cores wake at once | 4{nbsp}days -- 5{nbsp}weeks
|
||||
|
||||
4+^s| Milliseconds -- where machine and human meet
|
||||
|
||||
| 5--10{nbsp}ms | rotating disk, seek + rotation | 7200{nbsp}rpm; the number that has not improved in decades footnoteref:[simply] | *6{nbsp}months -- 1{nbsp}year*
|
||||
| ~10{nbsp}ms | barely noticeable latency | the threshold below which a human stops perceiving delay | ≈{nbsp}1 year
|
||||
| 20{nbsp}ms | one frame @{nbsp}50{nbsp}fps | | ≈{nbsp}2 years
|
||||
| 40{nbsp}ms | one frame @{nbsp}25{nbsp}fps | our actual deadline | *≈{nbsp}4 years*
|
||||
| ~100{nbsp}ms | UI starts to feel sluggish | | ≈{nbsp}11 years
|
||||
|
||||
4+^s| Seconds and beyond -- the human
|
||||
|
||||
| ~1{nbsp}s | ``the software takes time to respond'' | acceptable, but noticed | *≈{nbsp}1 century*
|
||||
| ~3{nbsp}min | ``time for another cup of coffee'' | | ≈{nbsp}19 000 years
|
||||
| ~15{nbsp}min | ``can't wait, I'll do something else'' | ≙{nbsp}1000{nbsp}s, which conveniently closes the fourth decade block | ≈{nbsp}95 000 years
|
||||
|=========================================================================
|
||||
|
||||
|
||||
The Memory Wall
|
||||
---------------
|
||||
|
||||
For roughly two decades, CPU clock rates have hovered around 3{nbsp}GHz while
|
||||
DRAM access latency has stayed near 60--100{nbsp}ns. The consequence is
|
||||
arithmetic: a cache miss that cost a handful of cycles on early-1990s hardware
|
||||
now costs several hundred. The processor did not merely get faster than
|
||||
memory -- it got faster than memory _by a growing margin_, and the entire
|
||||
cache hierarchy exists to paper over that gap. Note in the table above that
|
||||
the raw device-level latency of DDR5 is barely distinguishable from DDR4;
|
||||
the generational improvements went into bandwidth, not into latency.
|
||||
|
||||
Once several cores share one memory subsystem, a single uniform path to memory
|
||||
becomes the bottleneck. The answer was to move the memory controller onto the
|
||||
CPU die and attach memory per socket -- and, with chiplet designs, effectively
|
||||
per die group. This is what NUMA (non-uniform memory access) describes: memory
|
||||
that ``belongs'' to the core you are running on is reached quickly, while
|
||||
memory belonging to a neighbour costs noticeably more. The practical upshot is
|
||||
that thread placement and memory placement are no longer independent concerns,
|
||||
and that a scheduler which migrates a worker between cores may silently convert
|
||||
local accesses into remote ones.
|
||||
|
||||
Cores also keep private caches, and a store performed on one core does not
|
||||
become visible to another core at the moment the instruction retires: it first
|
||||
lands in that core's store buffer. Cache coherence guarantees that the cores
|
||||
eventually agree on the content of a cache line, but it says nothing about
|
||||
_when_, nor about the order in which several independent writes become visible.
|
||||
This is what memory fences are for -- they constrain the order, and thereby
|
||||
allow one core to reason about what an adjacent core has already done. The cost
|
||||
shows up in the table twice: an uncontended fence is roughly the price of an
|
||||
L3 hit, whereas a cache line actually bouncing between two cores costs about as
|
||||
much as going to DRAM. It is worth internalising that the expensive part is
|
||||
usually not the synchronisation primitive itself, but the coherence traffic it
|
||||
provokes.
|
||||
|
||||
x86 implements a comparatively strong model, _Total Store Order_ (TSO): of the
|
||||
four possible reorderings, only StoreLoad is permitted. Acquire and release
|
||||
semantics are therefore free on x86 -- they compile to plain `MOV` -- and only
|
||||
sequential consistency requires an actual instruction. ARM and POWER are
|
||||
weakly ordered and consequently offer a more differentiated set of barriers:
|
||||
ARM distinguishes `DMB` from the heavier `DSB`, and provides graded load
|
||||
acquire flavours such as `LDAR` and `LDAPR`; POWER separates the heavyweight
|
||||
`sync` from the cheaper `lwsync`. The trap for the unwary is that C++
|
||||
`memory_order` distinctions which cost nothing on x86 have a real price on
|
||||
ARM -- and, worse, that under-synchronised code can appear perfectly correct
|
||||
on x86 and fail on ARM.
|
||||
|
||||
|
||||
Crossing into the Kernel
|
||||
------------------------
|
||||
|
||||
A system call is not a function call: it is a controlled trap. The `syscall`
|
||||
instruction switches the processor to privilege level 0, and the kernel entry
|
||||
path then swaps to the kernel `GS` base, switches stacks, and saves the user
|
||||
register state. Since the Meltdown/Spectre family of vulnerabilities, that
|
||||
sequence also includes a page-table switch (KPTI writes `CR3`, with the
|
||||
attendant TLB consequences) and one or more branch-prediction barriers --
|
||||
retpolines, IBPB or IBRS depending on hardware and configuration. On return,
|
||||
the whole dance runs in reverse.
|
||||
|
||||
The direct cost is measurable: a minimal syscall such as `getpid()` amounts to
|
||||
little more than two mode switches, and lands in the region of a hundred
|
||||
nanoseconds on a fast host without mitigations, rising to several hundred once
|
||||
mitigations are enabled.footnoteref:[gms] The indirect cost is harder to see
|
||||
and frequently larger. Kernel entry code and the syscall handler evict
|
||||
user-space data from L1 and L2, and disturb the branch predictors; the
|
||||
application resumes with a cold working set and pays for it in misses that no
|
||||
syscall benchmark will attribute to the syscall. This is precisely why the
|
||||
interesting question is rarely ``how expensive is one syscall'' but rather
|
||||
``how many syscalls does this design require per unit of work'' -- which is
|
||||
the question `io_uring` was built to answer.
|
||||
|
||||
|
||||
Operating System Slack
|
||||
----------------------
|
||||
|
||||
A thread that asks to sleep for a specific duration will not, in general, wake
|
||||
at that instant. Part of this is unavoidable: an interrupt must be handled, the
|
||||
sleeping task must be moved to the run queue, and the scheduler must actually
|
||||
place it on a CPU, which together account for something in the range of
|
||||
10--30{nbsp}µs.footnoteref:[lkmlwake]
|
||||
|
||||
The larger part, however, is deliberate. Linux maintains a per-thread _timer
|
||||
slack_ value, and the kernel is free to delay a timed wake-up by up to that
|
||||
amount in order to coalesce several wake-ups into one and thereby let the CPU
|
||||
stay in a low-power state longer. For every thread not running under a
|
||||
real-time scheduling policy, the default is 50{nbsp}µs.footnoteref:[slack]
|
||||
This is not imprecision; it is a power-versus-precision trade that the kernel
|
||||
makes on the application's behalf, and it can be adjusted -- down to
|
||||
1{nbsp}ns -- via `prctl(PR_SET_TIMERSLACK)` or the per-process
|
||||
`/proc/PID/timerslack_ns` interface.
|
||||
|
||||
The distinction worth carrying away is between _resolution_ and _accuracy_.
|
||||
Since Linux 2.6.21 the high-resolution timer subsystem reports nanosecond
|
||||
resolution, and `clock_getres()` will happily confirm it. The accuracy with
|
||||
which a `nanosleep()` actually returns is a different quantity altogether, two
|
||||
to three orders of magnitude coarser, and dominated by slack, wake-up latency
|
||||
and -- if the core had gone idle -- C-state exit time. Any design that assumes
|
||||
it can steer a worker thread with microsecond-scale sleeps should be checked
|
||||
against these figures first.
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Footnote definitions are attached at first use above via footnoteref.
|
||||
// Repeated references reuse the id; see AsciiDoc footnoteref macro.
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
Sources
|
||||
-------
|
||||
|
||||
The footnotes above carry the precise locations. Collected here as a reading
|
||||
list, roughly in order of usefulness for further work.
|
||||
|
||||
- Travis Downs, *A Concurrency Cost Hierarchy* --
|
||||
https://travisdowns.github.io/blog/2020/07/06/concurrency-costs.html +
|
||||
The best single entry point on why synchronisation costs what it costs.
|
||||
|
||||
- LKML thread, *[PATCH 3/4] x86,asm: Re-work smp_store_mb()* (Nov 2015) --
|
||||
https://lkml.iu.edu/hypermail/linux/kernel/1511.0/01052.html +
|
||||
Kernel developers measuring `MFENCE` against `XCHG` and `lock addq`.
|
||||
|
||||
- Georg Sauthoff, *On the Costs of Syscalls* (Aug 2021) --
|
||||
https://gms.tf/on-the-costs-of-syscalls.html +
|
||||
Careful cross-host microbenchmark of the mode-switch cost.
|
||||
|
||||
- *Measurements of system call performance and overhead* (Jan 2017) --
|
||||
https://arkanis.de/weblog/2017-01-05-measurements-of-system-call-performance-and-overhead/ +
|
||||
Function call versus `syscall` instruction versus vDSO.
|
||||
|
||||
- Brendan Gregg, *KPTI/KAISER Meltdown Initial Performance Regressions* --
|
||||
https://www.brendangregg.com/blog/2018-02-09/kpti-kaiser-meltdown-performance.html +
|
||||
Relates mitigation overhead to syscall rate, context switch rate and working
|
||||
set size -- the systemic view rather than the per-call view.
|
||||
|
||||
- Clark Williams (Red Hat), *Finding Realtime Linux Kernel Latencies* --
|
||||
https://people.redhat.com/williams/latency-howto/rt-latency-howto.txt +
|
||||
Walks through a `cyclictest` run and how to read its output.
|
||||
|
||||
- `man 7 time` and `man 2 prctl` (PR_SET_TIMERSLACK) +
|
||||
Primary documentation for timer slack. The 50{nbsp}µs default is stated in
|
||||
https://arxiv.org/pdf/2103.13263 (Metronome, DPDK packet retrieval) and
|
||||
encountered in practice in https://bugs.openjdk.org/browse/JDK-8307766
|
||||
|
||||
- *Performance Characterizations ... Samsung CXL Memory Module Hybrid
|
||||
Prototype*, arXiv:2503.22017 -- https://arxiv.org/pdf/2503.22017 +
|
||||
Table III contains a well-documented measurement of plain local DDR5.
|
||||
|
||||
- *Single 32-bit Sub-Channel DDR5 DIMMs*, arXiv:2605.08725 --
|
||||
https://arxiv.org/pdf/2605.08725 +
|
||||
Useful for the point that DDR5 did not improve absolute latency over DDR4.
|
||||
|
||||
- simplyblock glossary, *NVMe Latency* --
|
||||
https://simplyblock.io/glossary/nvme-latency/ +
|
||||
Compact summary of the NVMe / SATA SSD / rotating disk hierarchy.
|
||||
|
||||
- AnandTech, *Samsung 983 ZET (Z-NAND) review*, page 3 --
|
||||
https://at-web1.www.anandtech.com/show/13951/the-samsung-983-zet-znand-ssd-review/3 +
|
||||
Explains why queue depth 1 exposes best-case access latency.
|
||||
|
||||
|
||||
A Note on Microbenchmarks
|
||||
-------------------------
|
||||
|
||||
One caveat deserves to be stated separately, because it explains why published
|
||||
figures for synchronisation primitives vary so wildly. A hot microbenchmark
|
||||
keeps every thread runnable and every core out of idle by construction. Under
|
||||
those conditions an uncontended atomic operation or a mutex acquisition costs
|
||||
single-digit to low-three-digit nanoseconds, and a benchmark will faithfully
|
||||
report that. What such a benchmark structurally cannot observe is the cost of
|
||||
_blocking_ -- of a thread actually parking and later being woken -- because
|
||||
pressing the benchmark is precisely what prevents that from happening.
|
||||
|
||||
The instruction-level cost of a synchronisation primitive is what a hot
|
||||
microbenchmark measures. Its systemic cost -- coherence traffic under
|
||||
contention, and blocking and wake-up latency when it is not contended -- is
|
||||
what production tail latency measures. The two can differ by two to four orders
|
||||
of magnitude, and the table above spans both regimes for exactly that reason.
|
||||
244
FrOSCon26/research/core2core.cpp
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
/*
|
||||
* core2core.cpp — measure core-to-core latency by cache-line ping-pong.
|
||||
*
|
||||
* Code generated 8/2026 by Claude -- prompted by Ichthyostega
|
||||
* Context: FrOSCon-26 talk on IO_URING
|
||||
*
|
||||
* Two threads are pinned to two specific logical CPUs and share a single
|
||||
* cache line holding two atomic counters. Thread A stores i, then spins
|
||||
* until thread B has answered with i; thread B does the mirror image.
|
||||
* Each round trip therefore forces the line to migrate twice: A -> B and
|
||||
* back. The one-way latency reported is half the measured round trip.
|
||||
*
|
||||
* What this actually measures is the cost of a *contended* cache line —
|
||||
* the coherence traffic, not the atomic instruction. An uncontended
|
||||
* atomic on a line already held in M state costs a couple of nanoseconds;
|
||||
* the numbers here are one to two orders of magnitude larger, and that
|
||||
* difference is the whole point.
|
||||
*
|
||||
* build: g++ -O2 -std=c++17 -pthread -o core2core core2core.cpp
|
||||
* run: ./core2core # full matrix over all allowed CPUs
|
||||
* ./core2core 3 9 # just the pair (3,9)
|
||||
*
|
||||
* Linux only (uses pthread_setaffinity_np / sched_getaffinity).
|
||||
*
|
||||
* Reading the results
|
||||
* -------------------
|
||||
* * SMT siblings of one physical core: lowest of all, often 5–20 ns.
|
||||
* They share L1/L2, so almost nothing migrates. Cross-check which
|
||||
* CPUs are siblings with lscpu -e or lscpu -p=CPU,CORE,SOCKET.
|
||||
* * Cores on the same die / CCX / cluster: typically 25–50 ns.
|
||||
* * Across an AMD CCX or CCD boundary, or across sockets: 70–150 ns
|
||||
* and upwards. On a multi-socket machine the cross-socket figure
|
||||
* can exceed local DRAM latency.
|
||||
*
|
||||
* Getting numbers you can trust
|
||||
* -----------------------------
|
||||
* * Set the CPU governor to performance; on an idle laptop the cores
|
||||
* will otherwise be clocked down and the figures inflate.
|
||||
* sudo cpupower frequency-set -g performance
|
||||
* * Run on an otherwise idle machine. The tool reports the minimum
|
||||
* over several repetitions, which filters most interference, but it
|
||||
* cannot filter a busy system.
|
||||
* * Do not read more than two significant digits into the result.
|
||||
*/
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <sched.h>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::size_t CACHELINE = 64; // deliberately hard-coded; see note below
|
||||
|
||||
/** the two counters live in one cache line, so a store by either side
|
||||
* invalidates the other side's copy. `alignas` keeps neighbouring data
|
||||
* out of that line, so we measure our own ping-pong and nothing else. */
|
||||
struct alignas(CACHELINE) PingPong
|
||||
{
|
||||
std::atomic<std::uint64_t> a{0};
|
||||
std::atomic<std::uint64_t> b{0};
|
||||
char pad[CACHELINE - 2 * sizeof(std::atomic<std::uint64_t>)]{};
|
||||
};
|
||||
|
||||
static_assert (sizeof(PingPong) == CACHELINE, "PingPong must occupy exactly one line");
|
||||
|
||||
|
||||
bool pin_to (int cpu)
|
||||
{
|
||||
cpu_set_t set;
|
||||
CPU_ZERO (&set);
|
||||
CPU_SET (cpu, &set);
|
||||
return 0 == pthread_setaffinity_np (pthread_self(), sizeof(set), &set);
|
||||
}
|
||||
|
||||
|
||||
std::vector<int> available_cpus()
|
||||
{
|
||||
std::vector<int> cpus;
|
||||
cpu_set_t set;
|
||||
CPU_ZERO (&set);
|
||||
if (0 == sched_getaffinity (0, sizeof(set), &set))
|
||||
for (int i = 0; i < CPU_SETSIZE; ++i)
|
||||
if (CPU_ISSET (i, &set))
|
||||
cpus.push_back (i);
|
||||
return cpus;
|
||||
}
|
||||
|
||||
|
||||
/** one ping-pong run between two pinned threads.
|
||||
* @return round-trip time per iteration in nanoseconds, or -1 on failure */
|
||||
double one_run (PingPong& pp, int cpuA, int cpuB,
|
||||
std::uint64_t warmup, std::uint64_t iters)
|
||||
{
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
pp.a.store (0, std::memory_order_relaxed);
|
||||
pp.b.store (0, std::memory_order_relaxed);
|
||||
|
||||
std::atomic<int> ready {0};
|
||||
std::atomic<bool> go {false};
|
||||
std::atomic<bool> ok {true};
|
||||
|
||||
const std::uint64_t total = warmup + iters;
|
||||
|
||||
std::thread responder{[&]
|
||||
{
|
||||
if (not pin_to (cpuB)) { ok.store (false); }
|
||||
ready.fetch_add (1, std::memory_order_release);
|
||||
while (not go.load (std::memory_order_acquire))
|
||||
;
|
||||
for (std::uint64_t i = 1; i <= total; ++i)
|
||||
{
|
||||
while (pp.a.load (std::memory_order_acquire) != i)
|
||||
;
|
||||
pp.b.store (i, std::memory_order_release);
|
||||
}
|
||||
}};
|
||||
|
||||
Clock::time_point t0, t1;
|
||||
|
||||
std::thread initiator{[&]
|
||||
{
|
||||
if (not pin_to (cpuA)) { ok.store (false); }
|
||||
ready.fetch_add (1, std::memory_order_release);
|
||||
while (not go.load (std::memory_order_acquire))
|
||||
;
|
||||
for (std::uint64_t i = 1; i <= total; ++i)
|
||||
{
|
||||
if (i == warmup + 1) // caches and predictors are warm now
|
||||
t0 = Clock::now();
|
||||
pp.a.store (i, std::memory_order_release);
|
||||
while (pp.b.load (std::memory_order_acquire) != i)
|
||||
;
|
||||
}
|
||||
t1 = Clock::now();
|
||||
}};
|
||||
|
||||
while (ready.load (std::memory_order_acquire) < 2)
|
||||
;
|
||||
go.store (true, std::memory_order_release);
|
||||
|
||||
initiator.join();
|
||||
responder.join();
|
||||
|
||||
if (not ok.load())
|
||||
return -1.0;
|
||||
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::nanoseconds> (t1 - t0);
|
||||
return double(elapsed.count()) / double(iters);
|
||||
}
|
||||
|
||||
|
||||
/** @return best (minimum) one-way latency in ns over @p reps repetitions */
|
||||
double measure_pair (int cpuA, int cpuB, int reps,
|
||||
std::uint64_t warmup, std::uint64_t iters)
|
||||
{
|
||||
PingPong pp;
|
||||
double best = std::numeric_limits<double>::infinity();
|
||||
for (int r = 0; r < reps; ++r)
|
||||
{
|
||||
double rtt = one_run (pp, cpuA, cpuB, warmup, iters);
|
||||
if (rtt < 0) return -1.0;
|
||||
if (rtt < best) best = rtt;
|
||||
}
|
||||
return best / 2.0; // round trip covers two line migrations
|
||||
}
|
||||
|
||||
} // namespace (anon)
|
||||
|
||||
|
||||
|
||||
int main (int argc, char** argv)
|
||||
{
|
||||
constexpr int REPS = 5;
|
||||
constexpr std::uint64_t WARMUP = 2000;
|
||||
constexpr std::uint64_t ITERS = 20000;
|
||||
|
||||
if (argc == 3)
|
||||
{ // single pair
|
||||
int a = std::atoi (argv[1]);
|
||||
int b = std::atoi (argv[2]);
|
||||
if (a == b)
|
||||
{
|
||||
std::fprintf (stderr, "the two CPUs must differ "
|
||||
"(both threads spin, they cannot share one core)\n");
|
||||
return 1;
|
||||
}
|
||||
double ns = measure_pair (a, b, REPS, WARMUP, ITERS);
|
||||
if (ns < 0)
|
||||
{
|
||||
std::fprintf (stderr, "could not pin to CPU %d / %d\n", a, b);
|
||||
return 1;
|
||||
}
|
||||
std::printf ("CPU %d <-> CPU %d : %.1f ns one-way (%.1f ns round trip)\n",
|
||||
a, b, ns, 2 * ns);
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto cpus = available_cpus();
|
||||
if (cpus.size() < 2)
|
||||
{
|
||||
std::fprintf (stderr,
|
||||
"need at least two available CPUs, found %zu.\n"
|
||||
"(inside a restricted container, check the cpuset)\n",
|
||||
cpus.size());
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::printf ("core-to-core latency, one-way, nanoseconds\n");
|
||||
std::printf ("minimum of %d runs x %llu iterations; %zu CPUs available\n\n",
|
||||
REPS, (unsigned long long)ITERS, cpus.size());
|
||||
|
||||
// header
|
||||
std::printf (" ");
|
||||
for (std::size_t j = 0; j + 1 < cpus.size(); ++j)
|
||||
std::printf ("%6d", cpus[j]);
|
||||
std::printf ("\n");
|
||||
|
||||
for (std::size_t i = 1; i < cpus.size(); ++i)
|
||||
{
|
||||
std::printf ("%5d ", cpus[i]);
|
||||
for (std::size_t j = 0; j < i; ++j)
|
||||
{
|
||||
double ns = measure_pair (cpus[i], cpus[j], REPS, WARMUP, ITERS);
|
||||
if (ns < 0) std::printf (" -");
|
||||
else std::printf ("%6.0f", ns);
|
||||
std::fflush (stdout);
|
||||
}
|
||||
std::printf ("\n");
|
||||
}
|
||||
|
||||
std::printf ("\nlowest values are usually SMT siblings; check with lscpu -e\n");
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 2.42.4 (0)
|
||||
-->
|
||||
<!-- Title: handoff Pages: 1 -->
|
||||
<svg width="728pt" height="352pt"
|
||||
viewBox="0.00 0.00 728.00 352.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 348)">
|
||||
<title>handoff</title>
|
||||
<g id="clust1" class="cluster">
|
||||
<title>cluster_proactor</title>
|
||||
<path fill="#eef4ff" stroke="#7092c0" d="M241.5,-143C241.5,-143 688,-143 688,-143 694,-143 700,-149 700,-155 700,-155 700,-324 700,-324 700,-330 694,-336 688,-336 688,-336 241.5,-336 241.5,-336 235.5,-336 229.5,-330 229.5,-324 229.5,-324 229.5,-155 229.5,-155 229.5,-149 235.5,-143 241.5,-143"/>
|
||||
<text text-anchor="middle" x="361" y="-322.4" font-family="sans-serif" font-size="12.00">  »Proactor« thread  —  playVideo() loop  </text>
|
||||
</g>
|
||||
<g id="clust2" class="cluster">
|
||||
<title>cluster_gui</title>
|
||||
<path fill="#eefaee" stroke="#6bab6b" d="M205,-8C205,-8 700,-8 700,-8 706,-8 712,-14 712,-20 712,-20 712,-69 712,-69 712,-75 706,-81 700,-81 700,-81 205,-81 205,-81 199,-81 193,-75 193,-69 193,-69 193,-20 193,-20 193,-14 199,-8 205,-8"/>
|
||||
<text text-anchor="middle" x="258.5" y="-67.4" font-family="sans-serif" font-size="12.00">  GTK / GUI thread  </text>
|
||||
</g>
|
||||
<!-- file -->
|
||||
<g id="node1" class="node">
|
||||
<title>file</title>
|
||||
<path fill="#eeeeee" stroke="black" d="M101,-216.73C101,-218.53 78.37,-220 50.5,-220 22.63,-220 0,-218.53 0,-216.73 0,-216.73 0,-187.27 0,-187.27 0,-185.47 22.63,-184 50.5,-184 78.37,-184 101,-185.47 101,-187.27 101,-187.27 101,-216.73 101,-216.73"/>
|
||||
<path fill="none" stroke="black" d="M101,-216.73C101,-214.92 78.37,-213.45 50.5,-213.45 22.63,-213.45 0,-214.92 0,-216.73"/>
|
||||
<text text-anchor="middle" x="50.5" y="-199.2" font-family="sans-serif" font-size="11.00">demovideo.raw</text>
|
||||
</g>
|
||||
<!-- ring -->
|
||||
<g id="node4" class="node">
|
||||
<title>ring</title>
|
||||
<polygon fill="#ffffff" stroke="black" points="237.5,-152 237.5,-252 379.5,-252 379.5,-152 237.5,-152"/>
|
||||
<text text-anchor="middle" x="308.5" y="-239.2" font-family="sans-serif" font-size="11.00">ring buffers (RING_SIZ)</text>
|
||||
<polyline fill="none" stroke="black" points="237.5,-232 379.5,-232 "/>
|
||||
<text text-anchor="middle" x="308.5" y="-219.2" font-family="sans-serif" font-size="11.00">slot</text>
|
||||
<polyline fill="none" stroke="black" points="237.5,-212 379.5,-212 "/>
|
||||
<text text-anchor="middle" x="308.5" y="-199.2" font-family="sans-serif" font-size="11.00">slot</text>
|
||||
<polyline fill="none" stroke="black" points="237.5,-192 379.5,-192 "/>
|
||||
<text text-anchor="middle" x="308.5" y="-179.2" font-family="sans-serif" font-size="11.00">slot</text>
|
||||
<polyline fill="none" stroke="black" points="237.5,-172 379.5,-172 "/>
|
||||
<text text-anchor="middle" x="308.5" y="-159.2" font-family="sans-serif" font-size="11.00">…</text>
|
||||
</g>
|
||||
<!-- file->ring -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>file->ring</title>
|
||||
<path fill="none" stroke="black" d="M101.16,-202C136.89,-202 185.86,-202 227.12,-202"/>
|
||||
<polygon fill="black" stroke="black" points="219.5,-205.5 229.5,-202 219.5,-198.5 219.5,-205.5"/>
|
||||
<text text-anchor="middle" x="151" y="-205" font-family="sans-serif" font-size="10.00">io_uring read</text>
|
||||
</g>
|
||||
<!-- wait -->
|
||||
<g id="node2" class="node">
|
||||
<title>wait</title>
|
||||
<path fill="#ffffff" stroke="black" d="M680,-307C680,-307 582,-307 582,-307 576,-307 570,-301 570,-295 570,-295 570,-283 570,-283 570,-277 576,-271 582,-271 582,-271 680,-271 680,-271 686,-271 692,-277 692,-283 692,-283 692,-295 692,-295 692,-301 686,-307 680,-307"/>
|
||||
<text text-anchor="middle" x="631" y="-292.2" font-family="sans-serif" font-size="11.00">io_uring_wait_cqe()</text>
|
||||
<text text-anchor="middle" x="631" y="-280.2" font-family="sans-serif" font-size="11.00">(blocking)</text>
|
||||
</g>
|
||||
<!-- timer -->
|
||||
<g id="node3" class="node">
|
||||
<title>timer</title>
|
||||
<path fill="#ffedcc" stroke="black" d="M353,-307C353,-307 264,-307 264,-307 258,-307 252,-301 252,-295 252,-295 252,-283 252,-283 252,-277 258,-271 264,-271 264,-271 353,-271 353,-271 359,-271 365,-277 365,-283 365,-283 365,-295 365,-295 365,-301 359,-307 353,-307"/>
|
||||
<text text-anchor="middle" x="308.5" y="-292.2" font-family="sans-serif" font-size="11.00">frame-beat timer</text>
|
||||
<text text-anchor="middle" x="308.5" y="-280.2" font-family="sans-serif" font-size="11.00">20 ms · multishot</text>
|
||||
</g>
|
||||
<!-- timer->wait -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>timer->wait</title>
|
||||
<path fill="none" stroke="black" stroke-dasharray="1,5" d="M365.33,-289C419.3,-289 501.26,-289 559.66,-289"/>
|
||||
<polygon fill="black" stroke="black" points="559.92,-292.5 569.92,-289 559.92,-285.5 559.92,-292.5"/>
|
||||
<text text-anchor="middle" x="487" y="-292" font-family="sans-serif" font-size="10.00">CQE (TICK_TAG)</text>
|
||||
</g>
|
||||
<!-- baton -->
|
||||
<g id="node7" class="node">
|
||||
<title>baton</title>
|
||||
<polygon fill="#e7d8ff" stroke="black" stroke-width="1.6" points="698.5,-131 563.5,-131 563.5,-95 698.5,-95 698.5,-131"/>
|
||||
<text text-anchor="middle" x="631" y="-116.2" font-family="sans-serif" font-size="11.00">toDisplay_</text>
|
||||
<text text-anchor="middle" x="631" y="-104.2" font-family="sans-serif" font-size="11.00">std::atomic<Buffer*></text>
|
||||
</g>
|
||||
<!-- ring->baton -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>ring->baton</title>
|
||||
<path fill="none" stroke="#5a2ca0" d="M379.84,-182.47C431.79,-168.04 502.39,-148.44 555.34,-133.73"/>
|
||||
<polygon fill="#5a2ca0" stroke="#5a2ca0" points="556.31,-137.09 565.01,-131.05 554.44,-130.35 556.31,-137.09"/>
|
||||
<text text-anchor="middle" x="487" y="-179" font-family="sans-serif" font-size="10.00" fill="#5a2ca0">publish</text>
|
||||
<text text-anchor="middle" x="487" y="-168" font-family="sans-serif" font-size="10.00" fill="#5a2ca0">store(release)</text>
|
||||
</g>
|
||||
<!-- disp -->
|
||||
<g id="node5" class="node">
|
||||
<title>disp</title>
|
||||
<path fill="#ffffff" stroke="black" d="M404,-52C404,-52 213,-52 213,-52 207,-52 201,-46 201,-40 201,-40 201,-28 201,-28 201,-22 207,-16 213,-16 213,-16 404,-16 404,-16 410,-16 416,-22 416,-28 416,-28 416,-40 416,-40 416,-46 410,-52 404,-52"/>
|
||||
<text text-anchor="middle" x="308.5" y="-37.2" font-family="sans-serif" font-size="11.00">EglDisplay::displayFrame()</text>
|
||||
<text text-anchor="middle" x="308.5" y="-25.2" font-family="sans-serif" font-size="11.00">retrieveFrame() / completedFrame()</text>
|
||||
</g>
|
||||
<!-- gpu -->
|
||||
<g id="node6" class="node">
|
||||
<title>gpu</title>
|
||||
<path fill="#ffffff" stroke="black" d="M692,-52C692,-52 570,-52 570,-52 564,-52 558,-46 558,-40 558,-40 558,-28 558,-28 558,-22 564,-16 570,-16 570,-16 692,-16 692,-16 698,-16 704,-22 704,-28 704,-28 704,-40 704,-40 704,-46 698,-52 692,-52"/>
|
||||
<text text-anchor="middle" x="631" y="-37.2" font-family="sans-serif" font-size="11.00">GPU texture</text>
|
||||
<text text-anchor="middle" x="631" y="-25.2" font-family="sans-serif" font-size="11.00">(glTexImage2D → draw)</text>
|
||||
</g>
|
||||
<!-- disp->gpu -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>disp->gpu</title>
|
||||
<path fill="none" stroke="black" d="M416.22,-34C458.94,-34 507.39,-34 547.35,-34"/>
|
||||
<polygon fill="black" stroke="black" points="547.58,-37.5 557.58,-34 547.58,-30.5 547.58,-37.5"/>
|
||||
<text text-anchor="middle" x="487" y="-37" font-family="sans-serif" font-size="10.00">upload + draw</text>
|
||||
</g>
|
||||
<!-- disp->baton -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>disp->baton</title>
|
||||
<path fill="none" stroke="#3a7a3a" stroke-dasharray="5,2" d="M341.45,-52.07C365.99,-65.08 401.13,-81.88 434,-91 472.45,-101.67 516.52,-107.18 553.11,-110.02"/>
|
||||
<polygon fill="#3a7a3a" stroke="#3a7a3a" points="553,-113.52 563.22,-110.76 553.5,-106.54 553,-113.52"/>
|
||||
<text text-anchor="middle" x="487" y="-122" font-family="sans-serif" font-size="10.00" fill="#3a7a3a">completedFrame</text>
|
||||
<text text-anchor="middle" x="487" y="-111" font-family="sans-serif" font-size="10.00" fill="#3a7a3a">store(nullptr, release)</text>
|
||||
</g>
|
||||
<!-- baton->disp -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>baton->disp</title>
|
||||
<path fill="none" stroke="#5a2ca0" d="M577.04,-94.93C570.45,-91.95 563.95,-88.64 558,-85 548.87,-79.42 549.69,-73.55 540,-69 519.39,-59.33 472.38,-51.4 426.16,-45.55"/>
|
||||
<polygon fill="#5a2ca0" stroke="#5a2ca0" points="426.36,-42.05 416.01,-44.29 425.5,-48.99 426.36,-42.05"/>
|
||||
<text text-anchor="middle" x="487" y="-83" font-family="sans-serif" font-size="10.00" fill="#5a2ca0">retrieveFrame</text>
|
||||
<text text-anchor="middle" x="487" y="-72" font-family="sans-serif" font-size="10.00" fill="#5a2ca0">load(acquire)</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 8.4 KiB |