Skip to content

search: skip redundant binary search on tag insert#382

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

search: skip redundant binary search on tag insert#382
saurav-fermions wants to merge 2 commits into
The-OpenROAD-Project:masterfrom
Fermions-ASI:feat/tag-match

Conversation

@saurav-fermions

Copy link
Copy Markdown

Summary

TagGroupBldr::tagMatchPath does a VectorMap binary search (lower_bound over
TagMatchLess) to locate a tag. On a miss the path is inserted via
insertPath -> VectorMap::operator[], which repeats the same binary search.
During the first (cold) propagation every tag is new, so every edge visit did
two binary searches where one suffices.

This records the lower_bound insertion position in tagMatchPath
(findInsertHint) and reuses it in insertPath (insertAtPos). The hint is
invalidated in init() and in the insertPath(const Path&) overload (used by
Genclks) which has no preceding lookup, so behavior is identical.

Type of Change

  • Performance (pure bookkeeping; no timing change)

Impact

  • Bit-identical: arrival/required times and all reported paths unchanged
    (aes 50-path md5 identical).
  • ~2.77% faster on isolated re-propagation; the win grows with corner count
    (gcd 1->3 corners 1.0%->2.8%, aes 1->3 corners 2.0%->4.7%).

Verification

  • Builds.
  • New unit test TestVectorMapHint (200 randomized trials) verifies
    findInsertHint/insertAtPos build a map identical to operator[] across
    ascending/descending/duplicate/random insertion orders, and that the hint
    agrees with find() for present keys.
  • ctest -L module_search 1866/1866 pass.
  • I have signed my commits (DCO).

Scope

  • include/sta/VectorMap.hh
  • search/TagGroup.{cc,hh}
  • search/test/cpp/{CMakeLists.txt,TestVectorMapHint.cc} (new test)

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

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

Copy link
Copy Markdown

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 introduces an optimization to avoid redundant binary searches during tag insertion by caching the sorted insertion position in VectorMap and reusing it in TagGroupBldr. It also adds unit tests to verify the correctness of this new API. The review feedback recommends enhancing robustness by also caching and verifying the associated tag pointer to prevent silent map corruption from interleaved calls, and adding assertions in VectorMap::insertAtPos to ensure that the sorted order and bounds are preserved.

Comment thread search/TagGroup.hh
Comment on lines +160 to +161
static constexpr size_t no_insert_pos_ = ~static_cast<size_t>(0);
size_t insert_pos_{no_insert_pos_};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Storing only the insertion index (insert_pos_) without verifying that the subsequent insertion is for the exact same key (tag) introduces a risk of silent map corruption. If there are any interleaved calls, or if a caller invokes tagMatchPath for one tag but then inserts a different tag, the second tag will be inserted at the wrong position, violating the sorted invariant of VectorMap.

To make this robust and defensively programmed, we should also store the Tag* for which the hint was recorded (e.g., insert_tag_), and only use the hint in insertPath if insert_tag_ == tag.

  static constexpr size_t no_insert_pos_ = ~static_cast<size_t>(0);
  size_t insert_pos_{no_insert_pos_};
  Tag *insert_tag_{nullptr};

Comment thread search/TagGroup.cc
vertex_ = vertex;
path_index_map_.clear();
paths_.clear();
insert_pos_ = no_insert_pos_; // OpenROAD-fork: tag-match

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Reset the cached insert_tag_ hint to nullptr when initializing the builder to prevent stale hints from being used.

  insert_pos_ = no_insert_pos_;  // OpenROAD-fork: tag-match
  insert_tag_ = nullptr;

Comment thread search/TagGroup.cc
Comment on lines +205 to 216
size_t pos;
auto it = path_index_map_.findInsertHint(tag, pos);
if (it != path_index_map_.end()) {
path_index = it->second;
match = &paths_[path_index];
insert_pos_ = no_insert_pos_;
}
else {
match = nullptr;
path_index = 0;
insert_pos_ = pos;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Record the tag alongside the insertion position hint when a match is not found, and clear it when a match is found, to ensure the hint is only applied to the correct key.

  size_t pos;
  auto it = path_index_map_.findInsertHint(tag, pos);
  if (it != path_index_map_.end()) {
    path_index = it->second;
    match = &paths_[path_index];
    insert_pos_ = no_insert_pos_;
    insert_tag_ = nullptr;
  }
  else {
    match = nullptr;
    path_index = 0;
    insert_pos_ = pos;
    insert_tag_ = tag;
  }

Comment thread search/TagGroup.cc Outdated
Comment on lines +275 to +280
if (insert_pos_ != no_insert_pos_) {
path_index_map_.insertAtPos(insert_pos_, tag, path_index);
insert_pos_ = no_insert_pos_;
}
else
path_index_map_[tag] = path_index;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Only use the cached insertion position hint if the key being inserted matches the key for which the hint was recorded (insert_tag_ == tag). Otherwise, fall back to a standard sorted insert to prevent map corruption.

  if (insert_pos_ != no_insert_pos_ && insert_tag_ == tag) {
    path_index_map_.insertAtPos(insert_pos_, tag, path_index);
    insert_pos_ = no_insert_pos_;
    insert_tag_ = nullptr;
  }
  else {
    path_index_map_[tag] = path_index;
  }

Comment thread search/TagGroup.cc
// OpenROAD-fork: tag-match
// This overload has no preceding tagMatchPath(), so clear any stale hint to
// force a full sorted insert.
insert_pos_ = no_insert_pos_;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Clear the cached insert_tag_ hint to nullptr in this overload to prevent stale hints from being used.

  insert_pos_ = no_insert_pos_;
  insert_tag_ = nullptr;

Comment thread include/sta/VectorMap.hh
Comment on lines +420 to +424
void insertAtPos(size_type pos,
const Key& key,
const Value& value) {
data_.insert(data_.begin() + pos, std::make_pair(key, value));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

To prevent silent corruption of the sorted map structure, it is highly recommended to add assertions in insertAtPos to verify that the insertion index is within bounds and that inserting the key at pos preserves the strict weak ordering of the map. Since VectorMap relies on being sorted for binary search, any out-of-order insertion will cause subsequent lookups to fail silently.

Note: This requires including <cassert> at the top of the file if it is not already transitively included.

  void insertAtPos(size_type pos,
                   const Key& key,
                   const Value& value) {
    assert(pos <= data_.size());
    assert(pos == 0 || comp_(data_[pos - 1].first, key));
    assert(pos == data_.size() || comp_(key, data_[pos].first));
    data_.insert(data_.begin() + pos, std::make_pair(key, value));
  }

TagGroupBldr::tagMatchPath does a VectorMap binary search (lower_bound over
TagMatchLess) to locate a tag. When no match is found the path is inserted via
insertPath -> VectorMap::operator[], which repeats the same binary search to
find the insertion position. During the first (cold) timing propagation every
tag is new, so every edge visit performed two binary searches where one
suffices.

Record the lower_bound insertion position in tagMatchPath (findInsertHint) and
reuse it in insertPath (insertAtPos), eliminating the redundant search. The hint
is invalidated in init() and in the insertPath(const Path&) overload (used by
Genclks) which has no preceding lookup, so behavior is identical.

This is a pure bookkeeping optimization: arrival/required times and all reported
paths are bit-identical. The win grows with corner count because more scenes
mean more tags per vertex and a deeper binary search saved per insert
(measured: gcd 1->3 corners 1.0%->2.8%, aes 1->3 corners 2.0%->4.7% on isolated
re-propagation).

OpenROAD-fork: tag-match

Signed-off-by: Saurav Singh <saurav.singh@fermions.co>
Verifies findInsertHint()/insertAtPos() build a map identical to operator[]
across ascending/descending/duplicate/random insertion orders (200 randomized
trials) and that findInsertHint agrees with find() for present keys and reports
a valid sorted insertion position for absent keys.

OpenROAD-fork: tag-match

Signed-off-by: Saurav Singh <saurav.singh@fermions.co>
@saurav-fermions

Copy link
Copy Markdown
Author

Thanks for the thorough review — the hint-safety concern is valid and I've made the hint self-verifying:

  • TagGroupBldr now records the tag the insertion-position hint was taken for (insert_tag_) alongside insert_pos_.
  • insertPath() reuses the hint only when it is inserting that same tag (insert_pos_ != no_insert_pos_ && insert_tag_ == tag); otherwise it falls back to a full sorted insert.
  • The hint/tag are cleared on init(), on a match in tagMatchPath(), and after use, and the no-preceding-lookup insertPath overload clears them too.

So even if a caller ever did tagMatchPath(A) then insertPath(B), the stale hint can't corrupt the sorted map — it's simply ignored and the binary search runs. module_search and the TestVectorMapHint unit test pass (5/5 of the search/hint suite).

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants