Created
July 24, 2026 01:16
-
-
Save dustinhartlyn/798c6d0514e03146306524911b497435 to your computer and use it in GitHub Desktop.
Benchmark for FreeCAD PR: analytic line/line intersection in WireJoiner.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """ | |
| Benchmark for FreeCAD PR: analytic line/line intersection in WireJoiner. | |
| It builds a sketch of crossing straight lines (N horizontal + N vertical, so | |
| N*N interior crossings) and times a recompute. That recompute runs the shape | |
| build, which is where WireJoiner::splitEdges() does its work, and a grid of | |
| crossing lines is the case that path is most expensive for. | |
| How to run | |
| ---------- | |
| Linux / macOS: | |
| freecadcmd gist_bench_wirejoiner.py | |
| (results print to the console) | |
| Windows: | |
| FreeCADCmd's stdout is detached, so set an output file and read it: | |
| set BENCH_OUT=C:\\path\\to\\results.txt | |
| FreeCADCmd.exe gist_bench_wirejoiner.py | |
| type C:\\path\\to\\results.txt | |
| How I measured | |
| -------------- | |
| Two builds of the same commit, release (-O2), same machine, differing only in | |
| WireJoiner.cpp. Baseline was FreeCAD main; the other build added the change. | |
| Median of 9 runs per size. Note this times a whole recompute, so splitEdges' | |
| own share of the improvement is larger than the recompute-level number. | |
| """ | |
| import os | |
| import sys | |
| import time | |
| import statistics | |
| import FreeCAD as App | |
| import Part | |
| import Sketcher | |
| _out_path = os.environ.get("BENCH_OUT") | |
| _out_file = open(_out_path, "w") if _out_path else None | |
| def emit(line): | |
| print(line) | |
| if _out_file: | |
| _out_file.write(line + "\n") | |
| _out_file.flush() | |
| def V(x, y): | |
| return App.Vector(x, y, 0) | |
| def build_grid(n, span=100.0): | |
| doc = App.newDocument("WJ%d" % n) | |
| sk = doc.addObject("Sketcher::SketchObject", "S") | |
| geo = [] | |
| step = span / (n + 1) | |
| for i in range(1, n + 1): | |
| geo.append(Part.LineSegment(V(0.0, i * step), V(span, i * step))) | |
| for i in range(1, n + 1): | |
| geo.append(Part.LineSegment(V(i * step, 0.0), V(i * step, span))) | |
| sk.addGeometry(geo, False) | |
| return doc, sk | |
| def median_ms(fn, reps): | |
| ts = [] | |
| for _ in range(reps): | |
| t0 = time.perf_counter() | |
| fn() | |
| ts.append((time.perf_counter() - t0) * 1000.0) | |
| return statistics.median(ts), min(ts), max(ts) | |
| emit("FreeCAD " + " ".join(App.Version()[:4])) | |
| emit("") | |
| emit("%-6s %-10s %-12s %-12s %-12s" % ("N", "edges", "median_ms", "min_ms", "max_ms")) | |
| emit("-" * 56) | |
| for n in [10, 15, 20, 25]: | |
| doc, sk = build_grid(n) | |
| doc.recompute() # warm | |
| def touch_recompute(): | |
| sk.touch() | |
| doc.recompute() | |
| med, lo, hi = median_ms(touch_recompute, 9) | |
| emit("%-6d %-10d %-12.1f %-12.1f %-12.1f" % (n, 2 * n, med, lo, hi)) | |
| App.closeDocument(doc.Name) | |
| emit("") | |
| emit("DONE") | |
| if _out_file: | |
| _out_file.close() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment