Efficient Geometry
How Laylight keeps large layouts fast, measured against gdsfactory, gdstk and KLayout.
A photonic chip easily holds tens of thousands of devices, each with curved geometry of hundreds of vertices, and test chips sweep device parameters so that hardly any two cells are the same. Laylight is built so that the time to compile a layout grows with its size and nothing else.
What makes it fast
A compiled evaluator. The compiler is written in Rust and evaluates cells directly, with no interpreter in the loop. Every distinct combination of cell parameters is evaluated once and becomes one cell; instances share it, and a regular grid of instances is written as a single array reference.
A geometry engine for layout data. Boolean operations, growing and shrinking run on the integer database grid. Edges are snap-rounded (Hobby's method), so results are exact to the grid and every predicate is computed in exact integer arithmetic: degenerate input such as touching, overlapping or collinear edges needs no tolerance and cannot break the result. Crossings and the grid pixels near each edge are found through a uniform grid of cells about as long as a typical edge; the pixels are then numbered, so that the rest of the work sorts and merges plain integers. The sweep that classifies the result keeps, with every edge it crosses, the winding number just above that edge, which stays valid as the sweep moves on, so each vertex costs a binary search and a local update. Polygons that rounding leaves as they are, such as keyholes, are not traced on their own, and the union of shapes that lie apart, the first step of growing or shrinking them, needs no tracing at all. Growing and shrinking trace the raw offset of the boundary, one ring per input ring, instead of a piece per edge.
All cores for large operations. The compiler and the language server run every stage of a large boolean operation in parallel, including the sweep, which is cut into slabs that each start from the edges crossing their left side, and get exactly the same result as on one thread.
A layout database that reads fast. The GDS reader and the hierarchical layout database behind Laylight, the same ones that power the Laylight viewer in the browser, are written for large files: they parse in a single pass over large reads, and flatten into exactly preallocated buffers with a branch-free inner loop that the compiler vectorizes.
Benchmarks
The benchmarks compare Laylight with gdsfactory, gdstk and KLayout's Python module on the same geometry, in four scenarios. The scripts are in bench/geometry in the repository.
Setup. Apple M3 Max with 128 GB of memory, macOS 26; Laylight built with Rust 1.90 in release mode; Python 3.12 with gdstk 1.0.1, KLayout 0.30.12 and gdsfactory 9.51.0. Every measurement runs in its own process; the median of three runs is reported (one run when it takes longer than 20 s). For Laylight, the time is the whole laylight build command: parsing, checking, evaluating and writing GDS. For the Python libraries it is building the layout and writing GDS, without interpreter start-up and imports. Rings use the same discretization everywhere (a chord error of at most 1 nm), and every output file is read back with KLayout to check that the tools wrote the same cells and comparable numbers of shapes and vertices. Runs are stopped after 10 minutes (shown as > 10 min). In the tables, the fastest time of a row is in bold and the others show how many times slower they are. Laylight runs its large boolean operations on all 16 cores, as it does by default; the Python libraries run on one thread, as they do by default.
Generating a parameter sweep
A test chip with an N × N array of all-pass ring resonators (a 500 nm bus waveguide and a ring), where every device has its own radius and gap, so that every device is a distinct cell. This is the typical workload of a layout generator: many small cells with curved geometry.
Time, lower is better · logarithmic scale
- Laylight
- gdstk, KLayout, gdsfactory
1,024 devices
- Laylight16 ms
- gdstk54 ms
- KLayout155 ms
- gdsfactory221 ms
10,000 devices
- Laylight119 ms
- gdstk523 ms
- KLayout1.53 s
- gdsfactory8.04 s
40,000 devices
- Laylight437 ms
- gdstk2.20 s
- KLayout6.50 s
- gdsfactory142.1 s
| Devices | Laylight | gdstk | KLayout | gdsfactory |
|---|---|---|---|---|
| 1,024 | 16 ms | 54 ms (3.5×) | 155 ms (9.9×) | 221 ms (14.1×) |
| 10,000 | 119 ms | 523 ms (4.4×) | 1.53 s (12.9×) | 8.04 s (67.7×) |
| 40,000 | 437 ms | 2.20 s (5.0×) | 6.50 s (14.9×) | 142.1 s (325.2×) |
Laylight builds and writes the chip 3.5 to 5 times faster than gdstk and 10 to 15 times faster than KLayout's Python API. gdsfactory is slowest and its time grows faster than the number of devices: over two minutes for 40,000 devices, about 325 times longer than Laylight.
Generating a parameter sweep with cladding
The same chip, where every device also gets a cladding: the bus and ring grown by 2 µm with mitered corners. Every device now runs a boolean operation on curved geometry. Laylight writes it as shape [bus, resonator] + 2um as cladding; gdstk uses gdstk.offset, KLayout and gdsfactory Region.sized.
Time, lower is better · logarithmic scale
- Laylight
- gdstk, KLayout, gdsfactory
1,024 devices
- Laylight87 ms
- gdstk271 ms
- KLayout1.06 s
- gdsfactory1.19 s
10,000 devices
- Laylight813 ms
- gdstk2.75 s
- KLayout10.4 s
- gdsfactory20.7 s
40,000 devices
- Laylight3.34 s
- gdstk11.1 s
- KLayout43.1 s
- gdsfactory232.3 s
| Devices | Laylight | gdstk | KLayout | gdsfactory |
|---|---|---|---|---|
| 1,024 | 87 ms | 271 ms (3.1×) | 1.06 s (12.1×) | 1.19 s (13.6×) |
| 10,000 | 813 ms | 2.75 s (3.4×) | 10.4 s (12.8×) | 20.7 s (25.4×) |
| 40,000 | 3.34 s | 11.1 s (3.3×) | 43.1 s (12.9×) | 232.3 s (69.7×) |
The boolean operation now dominates the work, and Laylight is still the fastest: 3.1 to 3.4 times faster than gdstk, whose offset runs on the Clipper library, and about 13 times faster than KLayout.
Boolean operations on large flat layouts
Two layers of shapes in memory, combined with AND and XOR, and the first layer grown by 100 nm (offset). The rectangles are 1 µm squares on a 1.5 µm pitch with the second layer shifted by half a square, the Manhattan case (up to 11.5 million vertices). The rings have a centerline radius of 5 µm, a width of 500 nm and 324 vertices each, on a 12 µm pitch with the second layer shifted by (3 µm, 2 µm), the curved case (up to 6.5 million vertices). gdsfactory runs its booleans on KLayout regions (through kfactory), which is why its times follow KLayout's.
Time, lower is better · logarithmic scale
- Laylight
- gdstk, KLayout, gdsfactory
90,000 rectangles per layer
- Laylight49 ms
- gdstk1.35 s
- KLayout311 ms
- gdsfactory335 ms
360,000 rectangles per layer
- Laylight164 ms
- gdstk17.6 s
- KLayout1.38 s
- gdsfactory1.45 s
1,440,000 rectangles per layer
- Laylight590 ms
- gdstk248.3 s
- KLayout5.98 s
- gdsfactory6.14 s
| Rectangles per layer | Operation | Laylight | gdstk | KLayout | gdsfactory |
|---|---|---|---|---|---|
| 90,000 | AND | 49 ms | 1.35 s (27.3×) | 311 ms (6.3×) | 335 ms (6.8×) |
| 90,000 | XOR | 55 ms | > 10 min | 455 ms (8.3×) | 431 ms (7.8×) |
| 90,000 | OFFSET | 31 ms | 574 ms (18.3×) | 268 ms (8.5×) | 290 ms (9.2×) |
| 360,000 | AND | 164 ms | 17.6 s (107.1×) | 1.38 s (8.4×) | 1.45 s (8.8×) |
| 360,000 | XOR | 191 ms | > 10 min | 1.97 s (10.3×) | 1.75 s (9.2×) |
| 360,000 | OFFSET | 110 ms | 5.37 s (48.6×) | 1.22 s (11.1×) | 1.23 s (11.1×) |
| 1,440,000 | AND | 590 ms | 248.3 s (421.1×) | 5.98 s (10.1×) | 6.14 s (10.4×) |
| 1,440,000 | XOR | 757 ms | > 10 min | 8.51 s (11.2×) | 7.42 s (9.8×) |
| 1,440,000 | OFFSET | 368 ms | 50.1 s (136.1×) | 5.04 s (13.7×) | 5.37 s (14.6×) |
Time, lower is better · logarithmic scale
- Laylight
- gdstk, KLayout, gdsfactory
900 rings per layer
- Laylight35 ms
- gdstk160 ms
- KLayout277 ms
- gdsfactory277 ms
3,600 rings per layer
- Laylight134 ms
- gdstk739 ms
- KLayout1.07 s
- gdsfactory1.11 s
10,000 rings per layer
- Laylight308 ms
- gdstk2.31 s
- KLayout3.10 s
- gdsfactory3.15 s
| Rings per layer | Operation | Laylight | gdstk | KLayout | gdsfactory |
|---|---|---|---|---|---|
| 900 | AND | 35 ms | 160 ms (4.5×) | 277 ms (7.8×) | 277 ms (7.8×) |
| 900 | XOR | 40 ms | 645 ms (16.0×) | 344 ms (8.5×) | 353 ms (8.8×) |
| 900 | OFFSET | 23 ms | 357 ms (15.7×) | 367 ms (16.1×) | 375 ms (16.5×) |
| 3,600 | AND | 134 ms | 739 ms (5.5×) | 1.07 s (8.0×) | 1.11 s (8.2×) |
| 3,600 | XOR | 155 ms | 7.96 s (51.5×) | 1.45 s (9.4×) | 1.49 s (9.7×) |
| 3,600 | OFFSET | 79 ms | 2.50 s (31.6×) | 1.52 s (19.2×) | 1.52 s (19.2×) |
| 10,000 | AND | 308 ms | 2.31 s (7.5×) | 3.10 s (10.1×) | 3.15 s (10.2×) |
| 10,000 | XOR | 367 ms | 81.5 s (222.0×) | 4.23 s (11.5×) | 4.24 s (11.5×) |
| 10,000 | OFFSET | 210 ms | 10.3 s (49.0×) | 4.22 s (20.1×) | 4.23 s (20.2×) |
Laylight is the fastest engine in every case. On Manhattan data it runs 6 to 14 times faster than KLayout's engine (which gdsfactory also uses), the reference for this kind of work, and 18 to 420 times faster than gdstk; gdstk's XOR did not finish within 10 minutes once the result merges into large polygons with many holes. On curved data it is 4.5 to 7.5 times faster than gdstk for AND, where gdstk's Clipper does best, and 8 to 20 times faster than the fastest of the others for XOR and offset. Results are exact on the integer grid in every case.
Reading and flattening a large GDS file
A synthetic 247 MB GDS file modeled on a real chip: 6,201 cells, 15 layers, many small rectangles fanned out through array references and large dense polygons; flattened, 2.36 million shapes with 101 million vertices. The file is read, timed from opening it, and its top cell flattened.
Time, lower is better · logarithmic scale
- Laylight
- gdstk, KLayout, gdsfactory
Read
- Laylight75 ms
- gdstk125 ms
- KLayout250 ms
- gdsfactory2.07 s
Flatten
- Laylight120 ms
- gdstk304 ms
- KLayout1.15 s
- gdsfactory1.01 s
Read and flatten
- Laylight195 ms
- gdstk429 ms
- KLayout1.40 s
- gdsfactory3.08 s
| Laylight | gdstk | KLayout | gdsfactory | |
|---|---|---|---|---|
| Read | 75 ms | 125 ms (1.7×) | 250 ms (3.3×) | 2.07 s (27.7×) |
| Flatten | 120 ms | 304 ms (2.5×) | 1.15 s (9.5×) | 1.01 s (8.4×) |
| Read and flatten | 195 ms | 429 ms (2.2×) | 1.40 s (7.2×) | 3.08 s (15.8×) |
Laylight is the fastest at every step: it reads the file 1.7 times faster than gdstk and 3.3 times faster than KLayout, and flattens it 2.5 times faster than gdstk. Reading and flattening together take under a fifth of a second, less than half of gdstk's time and a seventh of KLayout's. gdsfactory reads through KLayout and then converts the cells, which takes 2 seconds on its own.
Reproducing the results
uv run --no-project --python 3.12 \
--with gdstk==1.0.1 --with klayout==0.30.12 --with gdsfactory==9.51.0 \
python bench/geometry/run.py
python bench/geometry/report.py # the tables on this pageAuto Routing
Waveguides between devices as code. Cross-sections say what a waveguide is made of, ports say where it starts and ends, and routes are computed from them.
Efficient DRC
Why design rule checks struggle with curved layouts, and how Laylight checks them on the semantic geometry inside the compiler, pointing at the line of code, in a fraction of KLayout's time.