Skip to content

Commit 06b486f

Browse files
grt: O(1) coordinate dedup in setTreeNodesVariables (FastRoute)
setTreeNodesVariables() was the largest measured global-route hotspot (~41% of routing instructions). It deduplicated tree-node coordinates with an O(numpoints^2) linear scan, dominated by a few very large multi-pin nets. Instrumentation on medium03 showed ~12.4 billion inner iterations (~99.7% of the function's work) across only ~8.1K calls. Replace the linear scan with a reused hash-map keyed by the packed (x,y) grid position, mapping to the dcor index of the first node inserted at that position. This reproduces the original first-match aliasing exactly (identical stackAlias values and identical xcor_/ycor_/dcor_ contents), so routing results are unchanged. Results (timed region = global_route only, interleaved base vs opt): medium03 (98.6K cells): 20529 ms -> 10314 ms (1.99x, -49.8%) medium04 (135K cells): 268179 ms -> 84948 ms (3.16x, -68.3%) Correctness: all 131 grt ctest pass; wirelength / via count / routed nets / congestion / overflow byte-for-byte identical on both designs. Signed-off-by: Saurav Singh <saurav.singh@fermions.co>
1 parent ec10d06 commit 06b486f

2 files changed

Lines changed: 32 additions & 9 deletions

File tree

src/grt/src/fastroute/include/FastRoute.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,11 @@ class FastRouteCore
808808
std::vector<int> xcor_;
809809
std::vector<int> ycor_;
810810
std::vector<int> dcor_;
811+
// Scratch map for setTreeNodesVariables() coordinate deduplication.
812+
// Maps a packed (x,y) grid position to the dcor index of the first node
813+
// inserted at that position, replacing an O(numpoints^2) linear scan with
814+
// O(1) average lookups. Reused across calls to avoid per-call allocation.
815+
std::unordered_map<int32_t, int> tree_node_coord_dedup_;
811816

812817
std::vector<FrNet*> nets_;
813818
std::unordered_map<odb::dbNet*, int> db_net_id_map_; // db net -> net id

src/grt/src/fastroute/src/utility.cpp

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2996,6 +2996,18 @@ void FastRouteCore::setTreeNodesVariables(const int netID)
29962996
auto& treenodes = sttrees_[netID].nodes;
29972997

29982998
// Setting the values needed for each TreeNode
2999+
//
3000+
// Coordinate deduplication: non-terminal nodes that share a grid (x,y)
3001+
// position with an earlier node are aliased (stackAlias) to that earlier
3002+
// node. The original implementation found the earliest matching node with an
3003+
// O(numpoints) linear scan, making the whole loop O(numpoints^2). For large
3004+
// multi-pin nets this dominates global routing runtime. We replace the scan
3005+
// with a hash-map keyed by the packed (x,y) position, mapping to the dcor
3006+
// index of the FIRST node inserted at that position. This yields identical
3007+
// results (same first-match semantics, same xcor_/ycor_/dcor_ contents)
3008+
// with O(1) average lookups.
3009+
auto& coord_map = tree_node_coord_dedup_;
3010+
coord_map.clear();
29993011
for (int d = 0; d < sttrees_[netID].num_nodes(); d++) {
30003012
treenodes[d].topL = -1;
30013013
treenodes[d].botL = num_layers_;
@@ -3006,27 +3018,33 @@ void FastRouteCore::setTreeNodesVariables(const int netID)
30063018
treenodes[d].lID = BIG_INT;
30073019
treenodes[d].status = 0;
30083020

3021+
// Pack the int16_t grid coordinates into a single 32-bit key.
3022+
const int32_t key
3023+
= (static_cast<int32_t>(static_cast<uint16_t>(treenodes[d].x)) << 16)
3024+
| static_cast<int32_t>(static_cast<uint16_t>(treenodes[d].y));
3025+
30093026
if (d < num_terminals) {
30103027
const int pin_idx = sttrees_[netID].node_to_pin_idx[d];
30113028
treenodes[d].botL = nets_[netID]->getPinL()[pin_idx];
30123029
treenodes[d].topL = nets_[netID]->getPinL()[pin_idx];
30133030
treenodes[d].assigned = true;
30143031
treenodes[d].status = 1;
30153032

3033+
// Terminals are always appended (they are never aliased away), matching
3034+
// the original behavior. Record only the first dcor per position so
3035+
// later lookups resolve to the earliest insertion, as the linear scan
3036+
// did.
3037+
coord_map.try_emplace(key, d);
30163038
xcor_[numpoints] = treenodes[d].x;
30173039
ycor_[numpoints] = treenodes[d].y;
30183040
dcor_[numpoints] = d;
30193041
numpoints++;
30203042
} else {
3021-
bool redundant = false;
3022-
for (int k = 0; k < numpoints; k++) {
3023-
if ((treenodes[d].x == xcor_[k]) && (treenodes[d].y == ycor_[k])) {
3024-
treenodes[d].stackAlias = dcor_[k];
3025-
redundant = true;
3026-
break;
3027-
}
3028-
}
3029-
if (!redundant) {
3043+
const auto it = coord_map.find(key);
3044+
if (it != coord_map.end()) {
3045+
treenodes[d].stackAlias = it->second;
3046+
} else {
3047+
coord_map.emplace(key, d);
30303048
xcor_[numpoints] = treenodes[d].x;
30313049
ycor_[numpoints] = treenodes[d].y;
30323050
dcor_[numpoints] = d;

0 commit comments

Comments
 (0)