Modules/Sketch2D/bench
Шурупов Илья Викторович 0216aa3f98 Keep worker threads off the UI thread's physical core
ThreadPlacement reserves the physical core the UI thread starts on,
including its SMT sibling, and pins the UI thread to it. Every worker
the app starts (engine, stroke painter lanes, parallel compositing and
the update checker) places itself on the remaining cores, so painting
and level work can no longer compete with input handling and drawing
for the same core. The worker count follows the cores left to workers.

Placement uses the scheduler affinity on Linux and Android and reads
SMT siblings from sysfs; elsewhere it is a no-op and threads run
unpinned as before.
2026-10-21 06:00:57 +03:00
..
BenchInput.cpp Rename the code name to Sketch2D 2026-10-17 19:47:01 +03:00
BenchInput.hpp Rename the code name to Sketch2D 2026-10-17 19:47:01 +03:00
BenchMain.cpp Keep worker threads off the UI thread's physical core 2026-10-21 06:00:57 +03:00
BenchReport.cpp Rename the code name to Sketch2D 2026-10-17 19:47:01 +03:00
BenchReport.hpp Rename the code name to Sketch2D 2026-10-17 19:47:01 +03:00
BenchRunner.cpp Rename the code name to Sketch2D 2026-10-17 19:47:01 +03:00
BenchRunner.hpp Rename the code name to Sketch2D 2026-10-17 19:47:01 +03:00
BenchScenario.cpp Rename the code name to Sketch2D 2026-10-17 19:47:01 +03:00
BenchScenario.hpp Rename the code name to Sketch2D 2026-10-17 19:47:01 +03:00
README.md Rename the code name to Sketch2D 2026-10-17 19:47:01 +03:00

Sketch2D benchmark

sketch2dBench replays scripted drawing input through the real Sketch2D application (window, Vulkan, compositor pacing) and reports where the time goes.

Running

tools/sketch2d-bench.sh                       # Release build, all brush and eraser scenarios
tools/sketch2d-bench.sh --baseline 1a2b3c4    # compare with a stored revision
tools/sketch2d-bench.sh --filter eraser       # only matching scenarios
tools/sketch2d-bench.sh --repeats 10          # tighter statistics

Results go to bench/history/<revision>/ (report.md for reading, results.tsv for tools and later comparisons). Without --baseline the newest earlier revision in bench/history is used. A revision with uncommitted changes is stored as <revision>-dirty.

Recording a session in the app

In Sketch2D open Debug ▸ Record session. A red ● REC 0:12 badge appears in the toolbar; draw normally. Stop with Debug ▸ Stop recording and save or by clicking the badge. The session is written to ~/sketch2d-sessions/<date_time>/ (or $SKETCH2D_SESSION_DIR/<date_time>/) and the path is shown in a notice:

file contents
session.txt revision, GPU, date, window size and scale, zoom and pan, the full brush (type, preset, colour, all 26 parameters, pressure flags, size limits, stabilizer, marker / linear / eraser modes), view settings, background, frame, every layer's properties and order, active layer
layers/layer-N.tiles the exact pixels of every layer when recording started
input.txt every raw window event before coalescing, with its time: pen tool, pressure, tilt, rotation, distance, device timestamp, positions, buttons, keys, scroll
recorded-frames.tsv what the user actually experienced: per frame wall and CPU time, every profiled section, GPU times and counters

Replay it:

tools/sketch2d-bench.sh --session ~/sketch2d-sessions/2026-10-15_20-49-31

This restores the document, view, brush and settings exactly, replays the input with its original timing through the real window path, and writes replay/report.md next to the recording. Besides the normal statistics the report has a Live session versus replay table: the frame, stage and GPU timings the user had while drawing beside the replayed ones. Add --matrix to run the brush and eraser scenarios in the same report.

Text input recordings in bench/recordings/*.input (the input.txt format) are also replayed as extra inputs for the whole matrix.

What is measured

Per frame: wall time, thread CPU time, every profiled section (inclusive) and GPU timestamp durations of the canvas render pass and the window frame. Per input event: latency from its recorded time to the end of the frame that consumed it. Per run: process CPU, peak memory, dabs, tiles, uploads.

Frames follow compositor frame callbacks like the real main loop; use --unpaced to render back to back and expose raw throughput instead.

Comparison

Each scenario is replayed --repeats times (default 5). Every key metric (input latency p95, frame wall median/p95, frame CPU p95, process p95, draw p95, total dab time, canvas GPU p95) is computed per repeat, so a comparison has a sample on each side, not a single number. The report shows, for every scenario and key metric:

  • median and coefficient of variation before and after (4.000 ±2%)
  • the relative change of the medians
  • the two-sided Mann-Whitney U p-value (exact for small tie-free samples)
  • q, the Benjamini-Hochberg false-discovery-adjusted p over the whole run
  • a verdict: slower / faster when p < 0.01, the medians differ by more than 5 % and 0.05 ms, and the difference exceeds twice the run-to-run spread; otherwise within noise; unmeasured when a large difference has only one repeat to test

With 200 metrics compared, about two flags are expected by chance; the report prints that number next to the verdict counts. Each scenario group is also summarised as the geometric mean of after/before ratios with counts of slower and faster cases, so broad shifts are visible even when no single case is decisive. --fail-on-regression returns exit status 3 when any metric is significantly slower.

Two stored runs can be compared again without rerunning:

sketch2dBench --baseline bench/history/A/results.tsv --compare bench/history/B/results.tsv --out /tmp/ab

Measured on this machine (Radeon 760M, headless mutter), the same build run twice gave 0 slower and 1 faster of 200 metrics (expected by chance: 2). An artificial 10 µs cost per dab was reported as 32 slower metrics with q = 0.02, largest where dabs are densest (+1579 % dab time at size 4, +10 % at size 80).