1

我需要从一个大数据集中创建一个很大的有向图。我肯定知道这些事情:

  • 每个节点最多有 K 个出边
  • 我有unordered_mapN >> K 个节点的列表 ( )
  • 该图是通过将所有节点相互比较来构建的(是的,不幸的是 O(N^2))

考虑一下,我会使用 并行化图形创建std::thread,我想知道这是否可以通过 Boost Graph Library 来完成。

如果我使用邻接矩阵,应该可以预先分配矩阵(K*N 个元素),因此插入所有相邻节点将是线程安全的。

我读过 BGL 可能是线程不安全的,但我发现的帖子已经三年了。

你知道是否可以按照我的想法去做吗?你建议不这样做吗?

干杯!

4

2 回答 2

3

几乎 BGL 中的任何图形算法都需要一个映射:vertex -> int,它为每个顶点分配一个在 [0, num_vertices(g) ) 范围内的唯一整数。此映射称为“vertex_index”,通常可作为 property_map 访问。

话虽如此,我可以假设您的顶点已经是整数或与某些整数相关联(例如,您的 unordered_map 在“mapped_type”中有一些额外的字段)。如果您的输入顶点存储在连续紧密数组中(例如std::vector),则更好(对于性能和内存),那么索引是自然的。

如果顶点与整数[关联],则内存紧密图的最佳选择是“压缩稀疏行图”。该图是不可变的,因此您需要在生成图之前填充边容器。

正如 ravenpoint 解释的那样,您最好的选择是为每个线程配备自己的本地结果容器,并仅在将本地结果合并到最终结果时锁定中央容器。这种策略由 TBB 模板tbb::parallel_reduce实现无锁。因此,您用于图形构建的完整代码大致如下所示:

#include "tbb/blocked_range2d.h"
#include "tbb/parallel_reduce.h"
#include "boost/graph/compressed_sparse_row_graph.hpp"

typedef something vertex; //e.g.something is integer giving index of a real data

class EdgeBuilder
{
public:
    typedef std::pair<int,int> edge;
    typedef std::vector<edge> Edges;
    typedef ActualStorage Input;

    EdgeBuilder(const Input & input):_input(input){} //OPTIONAL: reserve some space in _edges
    EdgeBuilder( EdgeBuilder& parent, tbb::split ): _input(parent.input){} // reserve something

    void operator()( const const tbb::blocked_range2d<size_t>& r ) 
    { 
        for( size_t i=r.rows().begin(); i!=r.rows().end(); ++i ){
            for( size_t j=r.cols().begin(); j!=r.cols().end(); ++j ) {
                //I assume you provide some function to compute existence
                if (my_func_edge_exist(_input,i, j))
                    m_edges.push_back(edge(i,j));
            }
        }        
    } 

    //merges local results from two TBB threads
    void join( EdgeBuilder& rhs ) 
    {
        m_edges.insert( m_edges.end(), rhs.m_edges.begin(), rhs.m_edges.end() ); 
    }

    Edges _edges; //for a given interval of vertices
    const Input & _input;
};

//full flow:  
boost::compressed_sparse_row_graph<>* build_graph( const Storage & vertices)
{
    EdgeBuilder builder(vertices);
    tbb::blocked_range2d<size_t,size_t> range(0,vertices.size(), 100, //row grain size 
                                              0,vertices.size(), 100); //col grain size
    tbb::parallel_reduce(range, builder);

    boost::compressed_sparse_row_graph<> 
      theGraph = new boost::compressed_sparse_row_graph<> 
                        (boost::edges_are_unsorted_multi_pass_t, 
                         builder._edges.begin(), builder._edges.end(), 
                         vertices.size() );
    return theGraph;
}
于 2013-10-26T20:01:25.673 回答
1

我认为您应该将目标分解为两个单独的子目标。

  1. 通过对节点对进行 N * ( N - 1 ) 次测试来创建节点之间的链接。您似乎知道如何将其分解为独立的线程。将结果存储在您知道是线程安全的数据结构中,而不必担心 boost:graph 的奥秘。

  2. 从您的节点和(刚刚创建的)链接创建 boost::graph。

关于存储在每个线程中创建的链接的注意事项:找到合适的线程安全数据结构并不容易。如果您使用 STL 动态分配的结构,那么您必须担心制作线程安全的分配器,这是一个挑战。如果您预先分配,那么有很多混乱的代码来处理分配。因此,我建议将每个线程创建的链接存储在单独的数据结构中,这样它们就不必是线程安全的。链接全部创建完成后,您可以逐个循环遍历每个线程创建的链接。

可以想象一个稍微更有效的设计,但需要大量关于线程安全的神秘知识。我提出的设计可以在没有神秘知识或复杂代码的情况下实现,因此将更快、更健壮地实现,并且更容易维护。

于 2013-10-22T15:15:29.553 回答