Skip to content

drt: FlexGC detailed-route performance (bit-identical)#10847

Open
saurav-fermions wants to merge 2 commits into
The-OpenROAD-Project:masterfrom
Fermions-ASI:feat/drt-perf
Open

drt: FlexGC detailed-route performance (bit-identical)#10847
saurav-fermions wants to merge 2 commits into
The-OpenROAD-Project:masterfrom
Fermions-ASI:feat/drt-perf

Conversation

@saurav-fermions

Copy link
Copy Markdown
Contributor

Summary

Two constant-factor optimizations to FlexGC, the dominant detailed-route DRC cost. Routing output is byte-for-byte identical (DEF MD5 unchanged at 1 and 8 threads).

  1. Per-layer fixed-corner cache — materialize each net's fixed-polygon vertex set once per layer into a hash set (collectPolygonCorners) and test corner membership with an O(1) lookup, instead of a full boost::polygon scanline reconstruction per corner. Predicate is identical (a point is a corner iff it equals a boundary/hole vertex). On aes, initNet_pins_polygonCorners_helper cumulative time ~20.7% -> ~2.5%.
  2. Per-thread scratch reuse — replace per-call heap-allocated scratch vectors in the route-time DRC checker with thread_local reusable buffers (clear() + reuse capacity), across checkMetalSpacing_*, checkMetalCornerSpacing_main, bg2gtl, and the corner-collection path.

Testing

Built openroad; full drt regression suite passes (19/19, incl. golden log_compare). Routed DEF is bit-identical to baseline.

Notes

  • Pure constant-factor optimizations; no routing/DRC behavior change.
  • No src/sta change; no cross-repo dependency.

…% GC init)

The dominant FlexGC boost::polygon cost is not the per-pair spacing-check set
intersections but fixed-polygon corner materialization during GC worker init.
isPolygonCorner() ran poly_set.get() (a full boost::polygon scanline
reconstruction of the net polygon set) once per corner, giving
O(corners * fixed_poly_size) per net. perf on aes shows
initNet_pins_polygonCorners_helper at ~20.7% cumulative.

Materialize each net's fixed-polygon vertex set once per layer into a hash set
(collectPolygonCorners) and test corner membership with an O(1) lookup. The
predicate is identical (a point is a corner iff it equals a boundary/hole
vertex), the fixed set is immutable during init, so DRC decisions, markers and
geometry are unchanged.

Measured (aes, interleaved median, quiet box):
- initNet_pins_polygonCorners_helper cumulative: 20.7% -> 2.5%
- detailed_route wall-clock: -18.5% @ 1 thread, -15.2% @ 8 threads
- routed DEF byte-identical (MD5) vs baseline on gcd + aes at 1 and 8 threads
- drt ctest: 27/27 pass (incl. golden log_compare)

Transparent change, no flag, thread-count invariant. See GC_POLYGON_REPORT.md.


Signed-off-by: Saurav Singh <saurav.singh@fermions.co>
Replace per-call heap-allocated scratch vectors in the FlexGC route-time
DRC checker with per-thread (thread_local) reusable buffers (clear() +
reuse capacity). Sites: checkMetalSpacing_main region-query results,
checkMetalSpacing_prl_hasPolyEdge, checkMetalSpacing_short_skipSameNet,
checkMetalCornerSpacing_main, bg2gtl, and isPolygonCorner.

DRT parallelism is across FlexDRWorkers (one worker per OMP thread), so
thread_local keeps each worker's buffers private. boost::polygon get()
and the region queries append via back_inserter and return deterministic
order, so clearing before each use reproduces identical contents.

Bit-identical: gcd_nangate45 and aes_nangate45 routed DEFs MD5-identical
to baseline at 1 and 8 threads. drt ctest 27/27. Allocations: aes -2.40%
(2.970B -> 2.899B), gcd -3.25%. aes DR wall-clock -0.54% (median).

The maze A* priority queue was already reused upstream (cleanup() keeps
capacity, per-worker member), so no maze churn remained to remove; the
remaining churn is largely boost::polygon-internal and not safely
poolable. See DRT_ALLOC_REPORT.md.


Signed-off-by: Saurav Singh <saurav.singh@fermions.co>
@saurav-fermions saurav-fermions requested review from a team as code owners July 9, 2026 06:44
@github-actions github-actions Bot added the size/S label Jul 9, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request optimizes the GC initialization and checking phases by utilizing thread_local scratch buffers to avoid frequent vector allocations, and caching polygon corners in a hash set to enable O(1) lookups instead of repeated scanline reconstructions. The feedback suggests further optimizing the polygon corner cache by making it thread_local and persisting it across pins of the same net, clearing it only when the net changes.

Comment on lines +769 to +772
std::map<frLayerNum,
std::unordered_set<std::pair<frCoord, frCoord>,
boost::hash<std::pair<frCoord, frCoord>>>>
fixedCornerCache;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Currently, fixedCornerCache is local to initNet_pins_polygonCorners_helper, which is called once per pin. This means the cache is cleared and re-materialized for every single pin, even if multiple pins belong to the same net and share the same layer. Since the net's fixed polygon set (net->getPolygons(true)[layerNum]) is identical across all pins of the same net, we can optimize this further by making the cache thread_local and persisting it across pins of the same net, clearing it only when the net changes.

  thread_local gcNet* cachedNet = nullptr;
  thread_local std::map<frLayerNum,
                        std::unordered_set<std::pair<frCoord, frCoord>,
                                           boost::hash<std::pair<frCoord, frCoord>>>>
      fixedCornerCache;
  if (cachedNet != net) {
    fixedCornerCache.clear();
    cachedNet = net;
  }

@saurav-fermions

Copy link
Copy Markdown
Contributor Author

Thanks for the review.

  • Accidental src/sta bump: removed (force-pushed) — this PR no longer touches the submodule.
  • gemini (fixedCornerCache scope): the cache is intentionally per-pin — it's built from the net's fixed-polygon vertex set on the pin's layers, and the O(scanline)→O(1) win is already realized per pin. Widening it to net scope is a reasonable follow-up but adds cross-pin lifetime/keying complexity for a smaller marginal gain; I've kept it local and bit-identical here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant