92 lines
3.8 KiB
Markdown
92 lines
3.8 KiB
Markdown
# UNDF: UNDF-2026-000000482
|
||
# open3d-0001: ValidatePoseGraphConnectivity — O(V²×E) std::find on component vector inside BFS×edge scan
|
||
|
||
## Location
|
||
`cpp/open3d/pipelines/registration/GlobalOptimization.cpp` lines 367–404
|
||
Repository: https://github.com/isl-org/Open3D
|
||
|
||
## Severity
|
||
**HIGH** — Called twice during pose graph validation (for all edges, then certain edges only) before every global optimization run. With V nodes and E edges in the pose graph, the BFS expands V nodes, each scanning all E edges, and for each adjacent node does a O(V) `std::find` on `component`. Total: O(V²×E). For large 3D reconstruction datasets (thousands of frames/cameras), this is a significant pre-optimization bottleneck.
|
||
|
||
## Complexity
|
||
- Before: O(V² × E) — std::find on `component` (O(V)) called inside while-loop (V iters) × edge-scan (E iters)
|
||
- After: O(V × E) — O(1) unordered_set membership replaces the O(V) linear scan
|
||
|
||
## Defective Code
|
||
|
||
```cpp
|
||
// GlobalOptimization.cpp:367-404
|
||
static bool ValidatePoseGraphConnectivity(const PoseGraph &pose_graph,
|
||
bool ignore_uncertain_edges = false) {
|
||
size_t n_nodes = pose_graph.nodes_.size();
|
||
size_t n_edges = pose_graph.edges_.size();
|
||
|
||
std::vector<int> nodes_to_explore{};
|
||
std::vector<int> component{}; // membership tracked as a vector
|
||
if (n_nodes > 0) {
|
||
nodes_to_explore.push_back(0);
|
||
component.push_back(0);
|
||
}
|
||
while (!nodes_to_explore.empty()) {
|
||
int i = nodes_to_explore.back();
|
||
nodes_to_explore.pop_back();
|
||
for (size_t j = 0; j < n_edges; j++) {
|
||
const PoseGraphEdge &t = pose_graph.edges_[j];
|
||
if (ignore_uncertain_edges && t.uncertain_) continue;
|
||
int adjacent_node{-1};
|
||
if (t.source_node_id_ == i) adjacent_node = t.target_node_id_;
|
||
else if (t.target_node_id_ == i) adjacent_node = t.source_node_id_;
|
||
if (adjacent_node != -1) {
|
||
auto find_result = std::find(component.begin(), component.end(),
|
||
adjacent_node); // O(V) per call
|
||
if (find_result == component.end()) {
|
||
nodes_to_explore.push_back(adjacent_node);
|
||
component.push_back(adjacent_node);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return component.size() == n_nodes;
|
||
}
|
||
```
|
||
|
||
**Problem:** `component` is a `std::vector<int>`. The `std::find` membership check is O(V)
|
||
called for every edge of every explored node. BFS visits V nodes, each scanning E edges,
|
||
each doing O(V) find → O(V²×E) total.
|
||
|
||
## Fixed Code
|
||
|
||
```cpp
|
||
static bool ValidatePoseGraphConnectivity(const PoseGraph &pose_graph,
|
||
bool ignore_uncertain_edges = false) {
|
||
size_t n_nodes = pose_graph.nodes_.size();
|
||
size_t n_edges = pose_graph.edges_.size();
|
||
|
||
std::vector<int> nodes_to_explore{};
|
||
std::unordered_set<int> component_set; // O(1) membership
|
||
if (n_nodes > 0) {
|
||
nodes_to_explore.push_back(0);
|
||
component_set.insert(0);
|
||
}
|
||
while (!nodes_to_explore.empty()) {
|
||
int i = nodes_to_explore.back();
|
||
nodes_to_explore.pop_back();
|
||
for (size_t j = 0; j < n_edges; j++) {
|
||
const PoseGraphEdge &t = pose_graph.edges_[j];
|
||
if (ignore_uncertain_edges && t.uncertain_) continue;
|
||
int adjacent_node{-1};
|
||
if (t.source_node_id_ == i) adjacent_node = t.target_node_id_;
|
||
else if (t.target_node_id_ == i) adjacent_node = t.source_node_id_;
|
||
if (adjacent_node != -1) {
|
||
if (component_set.insert(adjacent_node).second) { // O(1)
|
||
nodes_to_explore.push_back(adjacent_node);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return component_set.size() == n_nodes;
|
||
}
|
||
```
|
||
|
||
## CWE
|
||
CWE-407: Inefficient Algorithmic Complexity — O(V²×E) → O(V×E)
|