# Benchmarks
# Static execution and rendering
benchmarks/pipeline.py measures a Run and the window's drawing. Run it from the
repository root with the development environment installed:
venv/bin/python benchmarks/pipeline.py /path/to/part.py --output /tmp/part.json
venv/bin/python benchmarks/pipeline.py --instances 1000 --samples 20 --output /tmp/instances.json
The default synthetic scene is ten instances of one cylinder, shown once. Use
--no-viewer without a display. The optional --frames argument makes a small
synthetic animation; real scripts run unchanged. --timeout limits the child
execution (180 seconds by default).
The benchmark reads the model in place and redirects pycodecad's last-run report to a temporary directory. Like a normal Run, the model executes Python code and can have its own side effects. Run one benchmark at a time.
The JSON records:
- The initial build123d import, separately from subsequent Run time.
- Execution in the normal forked child, including time inside
show(), tessellation andframe(). These are nested timings: do not add them together. - Time until
Run.wait()returns, report writing, and the remainder for fork, result transfer and child exit. The remainder is not pure serialization time. - Unique mesh and triangle counts, mesh bytes and peak process RSS on Linux.
- Window creation, the first image, repeated draws of an unchanged scene and
sampled scenes.
bbox, upload and draw timings are nested too. - Python version, GPU, viewport and package source hashes.
source_unchangedchecks whether another process edited the package during measurement.
The viewer is the actual Viewer in a hidden GLFW window, without vsync.
Each draw waits for the GPU, so its timing includes completion rather than only
command submission. Window creation is separate from the first image; the
regular application's event scheduling is not part of this measurement.
# Reference measurements
Linux, Python 3.12.14, AMD Radeon Graphics (Rembrandt), 800×600. These are local measurements, not timing assertions or performance guarantees.
| Static case | Result |
|---|---|
| First image, 1,000 cylinder instances | 98 ms, 1,000 bounding-box calculations |
| CLI mesh preparation/upload, 1,000 cylinder instances | 1 shared mesh, 0.018 MiB, 2.5–2.8 ms |
| Report with more than 1,000 objects | Only the reported details are calculated; the full object count is kept |
A feeder model with 61 objects sharing 14 meshes executes in about 400 ms, of which
about 80 ms inside show(); writing its report takes about 20 ms, the rest of the
Run about 25 ms, and the first image about 40 ms. The initial build123d import
takes another 1.7 seconds. Redrawing the unchanged scene repeats no mesh uploads
and no bounding-box calculations.
What keeps these numbers low:
- CLI rendering uses the same mesh/placement representation as the window: each
different shape is uploaded once and instances are placements, instead of a
transformed copy (
obj.world()) uploaded per instance. The CLI preparation/upload row measures that representation before drawing; it excludes execution, PNG encoding and context creation. A regression test compares translated and rotated instances against the transformed geometry in three views. show()does not use build123d'scopy.copy(), which deep-copies the geometry. A small internal helper copies placements, labels, colors and assembly wrappers while sharing the BRep. Movement and STEP assembly metadata are checked by the export tests.