search: skip redundant binary search on tag insert#382
Conversation
|
|
There was a problem hiding this comment.
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.
| static constexpr size_t no_insert_pos_ = ~static_cast<size_t>(0); | ||
| size_t insert_pos_{no_insert_pos_}; |
There was a problem hiding this comment.
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};| vertex_ = vertex; | ||
| path_index_map_.clear(); | ||
| paths_.clear(); | ||
| insert_pos_ = no_insert_pos_; // OpenROAD-fork: tag-match |
| 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; | ||
| } |
There was a problem hiding this comment.
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;
}| 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; |
There was a problem hiding this comment.
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;
}| // 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_; |
| void insertAtPos(size_type pos, | ||
| const Key& key, | ||
| const Value& value) { | ||
| data_.insert(data_.begin() + pos, std::make_pair(key, value)); | ||
| } |
There was a problem hiding this comment.
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>
a6de601 to
64eb71f
Compare
|
Thanks for the thorough review — the hint-safety concern is valid and I've made the hint self-verifying:
So even if a caller ever did |
Summary
TagGroupBldr::tagMatchPathdoes aVectorMapbinary search (lower_boundoverTagMatchLess) to locate a tag. On a miss the path is inserted viainsertPath -> 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_boundinsertion position intagMatchPath(
findInsertHint) and reuses it ininsertPath(insertAtPos). The hint isinvalidated in
init()and in theinsertPath(const Path&)overload (used byGenclks) which has no preceding lookup, so behavior is identical.
Type of Change
Impact
(aes 50-path md5 identical).
(gcd 1->3 corners 1.0%->2.8%, aes 1->3 corners 2.0%->4.7%).
Verification
TestVectorMapHint(200 randomized trials) verifiesfindInsertHint/insertAtPosbuild a map identical tooperator[]acrossascending/descending/duplicate/random insertion orders, and that the hint
agrees with
find()for present keys.ctest -L module_search1866/1866 pass.Scope
include/sta/VectorMap.hhsearch/TagGroup.{cc,hh}search/test/cpp/{CMakeLists.txt,TestVectorMapHint.cc}(new test)