Chunk vertices in BFS parallel iteration for dynamic load balancing

Dispatch fixed-size chunks (8 vertices per task) instead of one
contiguous slice per thread, so threads that finish early pick up more
work. Chunk size vs. runtime is U-shaped; 8 is the smallest size at the
bottom of the curve.

report_checks on a 32-core machine (min of 5 runs):
- gcd_sky130hd, 32 threads: 1845 us -> 1255 us (-32%)
- gcd_sky130hd,  8 threads: 1254 us -> 1117 us (-11%)
- aes_nangate45 (~17k inst): neutral; single-threaded path unchanged.

To keep upstream merges clean, the fork logic lives in a new
BfsIterator::visitLevelChunked() wrapped in "OpenROAD fork: BFS chunked
dispatch" markers, called from one marked line in visitParallel's
existing else branch. All other upstream lines are byte-identical. This
is a stop-gap until the upstream BFS rework lands; on merge conflict
take upstream and drop it.

Signed-off-by: Drew <[email protected]>
This commit is contained in:
Drew
2026-08-27 08:50:21 -04:00
parent d1b2b3962c
commit 65bd9df5f7
2 changed files with 45 additions and 19 deletions
+9
View File
@@ -109,6 +109,15 @@ protected:
Level level);
void findNext(Level to_level);
// ---- OpenROAD fork: BFS chunked dispatch (begin) ----
// Dispatch level_vertices to the worker pool in fixed-size chunks so
// idle threads pick up remaining work. Stop-gap until upstream BFS
// rework lands; on merge conflict take upstream and drop this.
void visitLevelChunked(VertexSeq &level_vertices,
Level level,
std::vector<VertexVisitor *> &visitors);
// ---- OpenROAD fork: BFS chunked dispatch (end) ----
BfsIndex bfs_index_;
Level level_min_;
Level level_max_;
+36 -19
View File
@@ -182,25 +182,9 @@ BfsIterator::visitParallel(Level to_level,
}
}
else {
size_t from = 0;
size_t chunk_size = vertex_count / thread_count;
BfsIndex bfs_index = bfs_index_;
for (size_t k = 0; k < thread_count; k++) {
// Last thread gets the left overs.
size_t to = (k == thread_count - 1) ? vertex_count : from + chunk_size;
dispatch_queue_->dispatch([=, this](size_t) {
for (size_t i = from; i < to; i++) {
Vertex *vertex = level_vertices[i];
if (vertex) {
checkLevel(vertex, level);
vertex->setBfsInQueue(bfs_index, false);
visitors[k]->visit(vertex);
}
}
});
from = to;
}
dispatch_queue_->finishTasks();
// ---- OpenROAD fork: BFS chunked dispatch (begin) ----
visitLevelChunked(level_vertices, level, visitors);
// ---- OpenROAD fork: BFS chunked dispatch (end) ----
}
level_vertices.clear();
visit_count += vertex_count;
@@ -336,6 +320,39 @@ BfsIterator::findNext(Level to_level)
}
}
// ---- OpenROAD fork: BFS chunked dispatch (begin) ----
void
BfsIterator::visitLevelChunked(VertexSeq &level_vertices,
Level level,
std::vector<VertexVisitor *> &visitors)
{
// Tasks read level_vertices in place and unlocked. This relies on
// visitors never enqueuing at the current level (they only enqueue
// fanout/fanin, which levelize places at a higher/lower level).
// Chunk size vs. runtime is U-shaped; 8 is the smallest size at the
// bottom of the curve.
constexpr size_t chunk_size = 8;
size_t vertex_count = level_vertices.size();
BfsIndex bfs_index = bfs_index_;
for (size_t from = 0; from < vertex_count; from += chunk_size) {
size_t to = (from + chunk_size < vertex_count) ? from + chunk_size : vertex_count;
dispatch_queue_->dispatch([this, &level_vertices, from, to, level,
bfs_index, &visitors](int thread_id) {
VertexVisitor *thread_visitor = visitors[thread_id];
for (size_t i = from; i < to; i++) {
Vertex *vertex = level_vertices[i];
if (vertex) {
checkLevel(vertex, level);
vertex->setBfsInQueue(bfs_index, false);
thread_visitor->visit(vertex);
}
}
});
}
dispatch_queue_->finishTasks();
}
// ---- OpenROAD fork: BFS chunked dispatch (end) ----
////////////////////////////////////////////////////////////////
BfsFwdIterator::BfsFwdIterator(BfsIndex bfs_index,