3.8 KiB
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
// 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
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)