benchmarks
Numbers from one machine
Timings for version 0.9.4, measured on 2026-09-26, so that you can estimate whether an operation fits your budget before writing any code. They are not a comparison against anything, because we have not run anyone else's library carefully enough to publish the result.
Eight cores at 3.6 GHz, 32 GB of memory, Linux, GCC 13, -O2, asserts off. Each figure is the median of seven runs after one discarded warm-up. Wall time excludes reading and writing files, which are measured separately below. Peak memory is the high-water mark of resident size for the whole process, including the loaded mesh. Single thread unless a column says otherwise.
Test meshes
Five meshes, chosen because they fail in different ways rather than because they are pretty. None of them is distributed with the library; the shapes are described well enough to substitute your own.
| Name | Triangles | Vertices | Shape |
|---|---|---|---|
terrain-tile | 4 010 000 | 2 006 001 | heightfield, one open boundary, no sharp edges at all |
scan-torso | 2 004 320 | 1 003 112 | photogrammetry scan, 14 boundary loops, speckle components |
statue-scan | 1 198 600 | 599 302 | closed, genus 3, uniform triangle size |
turbine-blade | 620 400 | 310 202 | closed, genus 0, high curvature on two thin trailing edges |
bracket-cad | 148 220 | 74 112 | tessellated solid, genus 2, almost all vertices on an arris |
Decimation at a glance
The same five test meshes, charted straight from bench.json. Switch the metric; the table under the next heading holds every figure for reading without the chart.
Bars are relative within the chosen metric. “bracket-cad (ridges locked)” is the same part with sharp edges pinned, which stops decimation at 21 %.
Decimation to 10 %
target_ratio = 0.1, boundaries preserved, ridges not locked except where noted.
| Mesh | Out | 1 thread | 8 threads | Peak RSS | Collapses/s |
|---|---|---|---|---|---|
terrain-tile | 401 000 | 12.1 s | 9.4 s | 392 MB | 192 k |
scan-torso | 200 432 | 5.9 s | 4.6 s | 192 MB | 196 k |
statue-scan | 119 860 | 3.4 s | 2.7 s | 118 MB | 199 k |
turbine-blade | 62 040 | 1.72 s | 1.35 s | 62 MB | 207 k |
bracket-cad | 14 822 | 0.39 s | 0.31 s | 16 MB | 215 k |
bracket-cad, ridges locked | 31 440 | 0.28 s | 0.23 s | 16 MB | 214 k |
The last row is the interesting one. Locking sharp edges on a CAD part stops decimation at 21 % rather than the requested 10 %, and the run is shorter because it ends early - target_reached comes back false. Throughput per collapse is flat across all sizes, which is what you want from a heap-based algorithm and also means you can estimate a new mesh by multiplying.
Thread scaling
scan-torso, same work, varying threads:
| Threads | Decimate | Speed-up | Intersection search | Speed-up |
|---|---|---|---|---|
| 1 | 5.9 s | 1.00× | 2.42 s | 1.00× |
| 2 | 5.3 s | 1.11× | 1.27 s | 1.91× |
| 4 | 4.8 s | 1.23× | 0.69 s | 3.51× |
| 8 | 4.6 s | 1.28× | 0.51 s | 4.75× |
Decimation barely scales, and that is structural rather than a defect to be fixed later: only the initial quadric accumulation is parallel, and it is about a quarter of the run. The collapse loop is sequential because each accepted collapse changes the costs of its neighbours, and any scheme that collapses two edges at once either locks or produces a different mesh. The intersection search, by contrast, is an embarrassingly parallel tree descent and behaves accordingly.
Other operations
All on scan-torso, single thread except where the table says otherwise.
| Operation | Time | Peak RSS | Note |
|---|---|---|---|
Stats::of | 0.19 s | 52 MB | one edge table, discarded on return |
weld_vertices, tol 10⁻⁵ | 0.44 s | 68 MB | uniform grid of the tolerance cell size |
remove_degenerate | 0.07 s | 38 MB | one pass plus a compaction |
orient_consistently | 0.26 s | 60 MB | flood fill per component |
fill_holes, 14 loops | 0.11 s | 38 MB | largest loop here is 214 edges |
RidgeExtractor, 42° | 0.63 s | 74 MB | emitted 1 840 polylines |
| Hierarchy build | 0.31 s | 60 MB | median split, 8 triangles per leaf |
SelfIntersection::find | 2.42 s | 72 MB | found 2 pairs; 0.51 s on 8 threads |
Remesher, 5 iterations | 38 s | 148 MB | with projection; 23 s without |
Remeshing is the expensive one by an order of magnitude, and most of that is the projection step rebuilding nothing but querying the hierarchy once per moved vertex per iteration. If a remesh is in your pipeline, budget for it as a batch job rather than as an interactive step.
File input and output
| Format | File size | Read | Write | Read rate |
|---|---|---|---|---|
| PLY, binary | 36.1 MB | 0.21 s | 0.15 s | 172 MB/s |
| PLY, ascii | 148 MB | 3.9 s | 2.8 s | 38 MB/s |
| STL, binary | 100 MB | 0.48 s | 0.37 s | 208 MB/s |
| OBJ, ascii | 131 MB | 3.6 s | 3.2 s | 36 MB/s |
Ascii parsing is dominated by number conversion, which is why the two text formats land within ten percent of each other despite different syntax. Note the STL size: no vertex sharing means every triangle carries three full positions, so the file is three times the binary PLY while holding less information.
Predicted against measured memory
The per-element costs on the limits page are meant to be usable for planning. Checked against the measurements for scan-torso:
| Phase | Predicted | Measured peak | Difference |
|---|---|---|---|
| Mesh resident only | 36 MB | 38 MB | +5 % |
| Decimation | 184 MB | 192 MB | +4 % |
| Intersection search | 68 MB | 72 MB | +6 % |
The excess is the allocator's own bookkeeping and the growth slack in vectors that were not reserved exactly. Four to six percent is close enough that the arithmetic is worth doing before you discover a limit on a build machine.
What these numbers do not tell you
- Nothing about your mesh. Decimation cost tracks the number of collapses, and a mesh with many locked vertices does fewer. A million-triangle part made entirely of arrises can be faster than a hundred-thousand-triangle smooth blob.
- Nothing about other machines. The collapse loop is bound by random access into the heap and the adjacency arrays, so it follows memory latency more than clock speed.
- Nothing about quality. Timing a decimator is easy; measuring whether its output looks right needs a deviation metric this library does not provide.
- Nothing about previous versions. These are 0.9.4 figures. The changelog notes the two releases where decimation changed speed by more than a few percent.