java-topology/defects/opencv/patch/opencv-0001-timvx-conflict-map-find.md

3.1 KiB
Raw Blame History

UNDF: UNDF-2026-000000485

opencv-0001: tvUpdateConfictMap — O(C²×G) std::find on graphConflictMap vectors inside recursive DFS

Location

modules/dnn/src/op_timvx.cpp lines 2741, line 869875 Repository: https://github.com/opencv/opencv

Severity

HIGH — Called during DNN network initialization when partitioning layers across TimVX sub-graphs. A DNN with C consumer edges and G TimVX graphs incurs O(C²×G) work per call site due to recursive DFS with linear conflict-set membership at every node. For transformer architectures with many attention layers (hundreds of consumer connections), this is a serious bottleneck.

Complexity

  • Before: O(C × G) per recursive call, O(C²×G) over full DFS traversal
  • After: O(C + G) over full DFS traversal using unordered_set

Defective Code

// op_timvx.cpp:23-41
void Net::Impl::tvUpdateConfictMap(int graphIndex, LayerData& ld,
                                   std::vector<std::vector<int>>& graphConflictMap)
{
    if (ld.consumers.empty()) return;
    for (int i = 0; i < ld.consumers.size(); i++)
    {
        LayerData &consumerld = layers[ld.consumers[i].lid];
        std::vector<int>::iterator it = std::find(
            graphConflictMap[ld.consumers[i].lid].begin(),  // O(G) linear scan
            graphConflictMap[ld.consumers[i].lid].end(),
            graphIndex);

        if (it == graphConflictMap[ld.consumers[i].lid].end())
        {
            graphConflictMap[ld.consumers[i].lid].push_back(graphIndex);
            tvUpdateConfictMap(graphIndex, consumerld, graphConflictMap);  // recursive
        }
    }
}

// op_timvx.cpp:864-875
bool TimVXInfo::isConflict(int layerId, int graphIndex)
{
    if (graphConflictMap[layerId].empty()) return false;
    std::vector<int>::iterator it = std::find(  // O(G) linear scan
        graphConflictMap[layerId].begin(),
        graphConflictMap[layerId].end(), graphIndex);
    return it != graphConflictMap[layerId].end();
}

Problem: graphConflictMap stores each layer's conflicting graph indices as a vector<int>. Every std::find call is O(G) where G = number of TimVX sub-graphs. The recursive DFS visits every consumer of every layer making the total O(C × G). Since this is recursive over the consumer graph (depth up to C nodes), total work is O(C²×G).

Fixed Code

// Change graphConflictMap type from vector<vector<int>> to vector<unordered_set<int>>

void Net::Impl::tvUpdateConfictMap(int graphIndex, LayerData& ld,
                                   std::vector<std::unordered_set<int>>& graphConflictMap)
{
    if (ld.consumers.empty()) return;
    for (int i = 0; i < ld.consumers.size(); i++)
    {
        int cid = ld.consumers[i].lid;
        LayerData &consumerld = layers[cid];
        if (graphConflictMap[cid].insert(graphIndex).second)  // O(1) insert+dedup
        {
            tvUpdateConfictMap(graphIndex, consumerld, graphConflictMap);
        }
    }
}

bool TimVXInfo::isConflict(int layerId, int graphIndex)
{
    return graphConflictMap[layerId].count(graphIndex) > 0;  // O(1)
}

CWE

CWE-407: Inefficient Algorithmic Complexity — O(C²×G) → O(C+G)