java-topology/defects/open3d/patch/open3d-0001-pose-graph-connectivity-find.md

3.8 KiB
Raw Blame History

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 367404 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)