3

私は C++ と Boost Spirit X3 の初心者です。私のプロジェクトでは、次の構造を持つ 2 つのファイルから地社会グラフを解析し、ブースト スピリット X3 をブースト グラフに入れます。

私は実用的な実装を持っています。私はライブラリの経験がないので、このアプローチについてどう思うか、別のアプローチを取ることをお勧めしますか.

グラフ ファイルには、エッジごとに 1 つの行があります。ノードが以前に見られなかった場合に備えて、エッジを解析している間、グラフのノードを作成する必要があります。そのノードがすでにグラフにあるかどうか、ノード ID に遭遇するたびにチェックするセマンティック アクションを使用します。行全体を読んだ後、エッジを追加するセマンティック アクションを使用します。

位置ファイルには、特定の時点でのノードの既知の位置ごとに 1 つの行があります。ノードの既知の最初の位置をグラフに保存します (カスタム ブースト グラフ プロパティを使用)。

具体的な質問が必要ですが、ご意見やご提案をいただければ幸いです。

  • グラフ ファイルに対して行っているように、ネストされたセマンティック アクションを使用しても問題ありませんか? これはパフォーマンスに影響しますか?
  • Spirit X3 で一度にファイル全体を解析することをお勧めしますか、それとも、Spirit X3 ですべての行を個別に解析する必要がありますか?

グラフ (グラフのエッジを示す)

[user1]     [user2]
0           3

場所

[user]  [check-in time]         [latitude]      [longitude]     [location id]
0       2010-10-19T23:55:27Z    30.2359091167   -97.7951395833      22847

Spirit X3 解析コード

// Parse the gowalla edge file
boost::spirit::istream_iterator file_iterator(edge_file), eof;

x3::phrase_parse(file_iterator, eof,
        // Begin grammar
        (
         *((x3::int_[add_vertex] >> x3::int_[add_vertex])[add_edge])
        ),
        // End grammar
        x3::space
        );

// Fail if we couldn't parse the whole edges file
if (file_iterator != eof) {
    std::cerr << "Couldn't parse whole edges file" << std::endl;
}

// Parse the gowalla location file
file_iterator = boost::spirit::istream_iterator(location_file);

x3::phrase_parse(file_iterator, eof,
        // Begin grammar
        (
         // vertex_id   time of checkin       latitude  longitude             location id
         *((x3::int_ >> x3::lexeme[*x3::graph] >> x3::double_ >> x3::double_)[add_location] >> x3::int_ >> x3::eol)
        ),
        // End grammar
        x3::blank
        );

// Fail if we couldn't parse the whole location file
if (file_iterator != eof) {
    std::cerr << "Couldn't parse whole location file" << std::endl;
}

X3 によって呼び出されるセマンティック アクション

// Lambda function that adds vertex to graph if not already added
auto add_vertex = [&](auto& ctx){
    // Return if the vertex is already known
    if (vertices.find(x3::_attr(ctx)) != vertices.end())    {
        return false;
    }

    // Otherwise add vertex to graph
    auto v = boost::add_vertex(g);

    // And add vertex descriptor to map
    vertices[x3::_attr(ctx)] = v;
};

// Lambda function that adds edge to graph
auto add_edge = [&](auto& ctx){
    // _attr(ctx) returns a boost fusion tuple
    auto attr = x3::_attr(ctx);

    // Add edge from the vertices returned from context
    boost::add_edge(vertices[fusion::at_c<0>(attr)],
            vertices[fusion::at_c<1>(attr)], g);
};

// Lambda function that adds locations to vertices in the graph
auto add_location = [&](auto& ctx){
    // _attr(ctx) returns a boost fusion tuple
    auto attr = x3::_attr(ctx);
    auto vertex_id = fusion::at_c<0>(attr);

    if (location_already_added.find(vertex_id) != location_already_added.end()) {
        // Exit, as we already stored the location for this vertex
        return true;
    }
    location_already_added.insert(vertex_id);

    // Test if vertex is in our graph
    // We are parsing locations from a different file than the graph,
    // so there might be inconsistencies
    if (vertices.find(vertex_id) == vertices.end()) {
        std::cerr << "Tried to add location to vertex " << vertex_id << ", but this vertex is not in our graph" << std::endl;
        return false;
    }

    auto vertex = vertices[vertex_id];

    // Add location to the vertex
    g[vertex].latitude = fusion::at_c<2>(attr);
    g[vertex].longitude = fusion::at_c<3>(attr);

    return true;
};

ブーストグラフ

struct vertex_property {
    double longitude;
    double latitude;
};

// Define our graph
// We use setS to enforce our graph not to become a multigraph
typedef boost::adjacency_list<boost::setS, boost::vecS, boost::undirectedS, vertex_property, edge_property > graph;
4

1 に答える 1

6

Q.グラフ ファイルに対して行っているように、ネストされたセマンティック アクションを使用しても問題ありませんか? これはパフォーマンスに影響しますか?

私はそれをしません。エッジのホールセールを追加する方がおそらくはるかに簡単です。

x3::parse(file_iterator, eof,
        *((x3::int_ >> '\t' >> x3::int_ >> x3::eol)[add_edge])
        );

次のように簡単add_egeにできます。

auto add_edge = [&](auto& ctx){
    // Add edge from from context
    vertex_decriptor source, target;
    auto tup = std::tie(source, target);

    fusion::copy(x3::_attr(ctx), tup);

    boost::add_edge(map_vertex(source), map_vertex(target), g);
};

Q. Spirit X3 で一度にファイル全体を解析することをお勧めしますか、それとも、Spirit X3 ですべての行を個別に解析する必要がありますか?

スピリットが推奨するものではないと思います。ファイル全体を一度に実行します。また、メモリ マップト ファイルを使用して効率を高めることをお勧めします (multi_passイテレータの適応なしのランダム アクセス イテレーション)。

総論:

  1. スペース認識パーサーを使用しようとしていますが istream_iterators で使用しています。その後、ストリームのフラグをリセットすることを忘れないでください。skipws

  2. verticesマップはリソースの無駄遣いのようです。[user]に変換する代わりに、 thing( vertex_id) を直接使用できるかどうかを検討してくださいvertex_descriptor

これは、 https: //snap.stanford.edu/data/loc-gowalla.html からのファイルを約 19 秒で問題なく解析する、クリーンアップされたバージョンです(すでにかなり高速です)。

Live On Coliru

#include <boost/fusion/adapted/std_tuple.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/include/support_istream_iterator.hpp>
#include <fstream>
#include <iostream>

namespace x3 = boost::spirit::x3;
namespace fusion = boost::fusion;

struct vertex_property {
    double longitude;
    double latitude;
};

struct edge_property { };

struct Reader {
    bool read_edges(std::string fname) {
        // Lambda function that adds edge to graph
        auto add_edge = [this](auto& ctx){
            // Add edge from from context
            vertex_decriptor source, target;
            auto tup = std::tie(source, target);

            fusion::copy(x3::_attr(ctx), tup);

            boost::add_edge(this->map_vertex(source), this->map_vertex(target), g);
        };

        // Parse the gowalla edge file
        std::ifstream edge_file(fname);
        if (!edge_file) return false;

        boost::spirit::istream_iterator file_iterator(edge_file >> std::noskipws), eof;

        x3::parse(file_iterator, eof, *((x3::int_ >> '\t' >> x3::int_ >> x3::eol)[add_edge]));

        // Fail if we couldn't parse the whole edges file
        return (file_iterator == eof);
    }

    bool read_locations(std::string fname) {
        // Lambda function that adds locations to vertices in the graph
        auto add_location = [&](auto& ctx){
            // _attr(ctx) returns a boost fusion tuple
            auto attr = x3::_attr(ctx);
            auto vertex_id = fusion::at_c<0>(attr);

            if (!location_already_added.insert(vertex_id).second)
                return true; // Exit, as we already stored the location for this vertex

            // Test if vertex is in our graph
            // We are parsing locations from a different file than the graph, so
            // there might be inconsistencies
            auto mapped = mapped_vertices.find(vertex_id);
            if (mapped == mapped_vertices.end()) {
                std::cerr << "Tried to add location to vertex " << vertex_id << ", but this vertex is not in our graph" << std::endl;
                return false;
            }

            // Add location to the vertex
            auto& props = g[mapped->second];
            props.latitude  = fusion::at_c<1>(attr);
            props.longitude = fusion::at_c<2>(attr);

            return true;
        };

        // Parse the gowalla location file
        std::ifstream location_file(fname);
        if (!location_file) return false;

        boost::spirit::istream_iterator file_iterator(location_file >> std::noskipws), eof;

        x3::parse(file_iterator, eof,
                // [vertex_id]   [time of checkin]       [latitude]  [longitude]             [location] id
                *((x3::int_ >> '\t' >> x3::omit[*x3::graph] >> '\t' >> x3::double_ >> '\t' >> x3::double_)[add_location] >> '\t' >> x3::int_ >> x3::eol)
                );

        // Fail if we couldn't parse the whole location file
        return (file_iterator == eof);
    }

  private:
    // We use setS to enforce our graph not to become a multigraph
    typedef boost::adjacency_list<boost::setS, boost::vecS, boost::undirectedS, vertex_property, edge_property> graph;
    using vertex_decriptor = graph::vertex_descriptor;

    std::map<int, vertex_decriptor> mapped_vertices;
    std::set<int> location_already_added;
    graph g;

    // Lambda function that adds vertex to graph if not already added
    vertex_decriptor map_vertex(int id) {
        auto match = mapped_vertices.find(id);

        if (match != mapped_vertices.end())
            return match->second; // vertex already known
        else                      // Otherwise add vertex
            return mapped_vertices[id] = boost::add_vertex(g);
    };
};

int main() {
    Reader reader;
    if (!reader.read_edges("loc-gowalla_edges.txt"))
        std::cerr << "Couldn't parse whole edges file" << std::endl;

    if (!reader.read_locations("loc-gowalla_totalCheckins.txt"))
        std::cerr << "Couldn't parse whole location file" << std::endl;
}

マップされたファイル

比較のために、メモリ マップされたファイルに置き換えると、はるかに高速になります。3秒で完了します (これも 6 倍以上高速です)。

Live On Coliru

変更されたフラグメントの例:

    boost::iostreams::mapped_file_source mm(fname);
    auto f = mm.begin(), l = mm.end();
    x3::parse(f, l, *((x3::int_ >> '\t' >> x3::int_ >> x3::eol)[add_edge]));

メモリのオーバーヘッド

プロファイリング後。マップ/セットを持っていることはおそらくそれほど悪くないようです:

ここに画像の説明を入力

私が見たところ、このプログラムは 152MiB を使用しており、location_already_added一見しただけで 4.1 しか表示されません。

メモリ使用量と時間の削減

それでも、set<int> location_already_addedを動的ビットセットに置き換えて を削除すると、メモリ使用量プログラムの実行時間map<int, vertex_descriptor>がさらに削減されます。

今回は2 秒未満で完了します (さらに 33% オフ)。

明らかな理由で、必要なメモリが約10% 少なくなります: 138.7 MiB。

Live On Coliru

変更点:

#include <boost/fusion/adapted/std_tuple.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/spirit/home/x3.hpp>
#include <boost/iostreams/device/mapped_file.hpp>
#include <boost/dynamic_bitset.hpp>
#include <fstream>
#include <iostream>

namespace x3 = boost::spirit::x3;
namespace fusion = boost::fusion;

struct vertex_property {
    double longitude;
    double latitude;
};

struct edge_property { };

struct Reader {
    Reader() {
        g.m_vertices.reserve(1024);
    }

    bool read_edges(std::string fname) {
        // Lambda function that adds edge to graph
        auto add_edge = [this](auto& ctx){
            // Add edge from from context
            vertex_decriptor source, target;
            auto tup = std::tie(source, target);

            fusion::copy(x3::_attr(ctx), tup);

            boost::add_edge(this->map_vertex(source), this->map_vertex(target), g);
        };

        // Parse the gowalla edge file
        boost::iostreams::mapped_file_source mm(fname);

        auto f = mm.begin(), l = mm.end();

        x3::parse(f, l, *((x3::int_ >> '\t' >> x3::int_ >> x3::eol)[add_edge]));

        // Fail if we couldn't parse the whole edges file
        return f == l;
    }

    bool read_locations(std::string fname) {
        boost::dynamic_bitset<> location_already_added(num_vertices(g));

        // Lambda function that adds locations to vertices in the graph
        auto add_location = [&](auto& ctx){
            // _attr(ctx) returns a boost fusion tuple
            auto const& attr = x3::_attr(ctx);
            auto vertex_id = fusion::at_c<0>(attr);

            if (location_already_added.test(vertex_id))
                return true; // Exit, as we already stored the location for this vertex
            location_already_added.set(vertex_id);

            // Test if vertex is in our graph
            // We are parsing locations from a different file than the graph, so
            // there might be inconsistencies
            auto mapped = this->mapped_vertex(vertex_id);
            if (graph::null_vertex() == mapped) {
                std::cerr << "Tried to add location to vertex " << vertex_id << ", but this vertex is not in our graph" << std::endl;
                return false;
            }

            // Add location to the vertex
            auto& props = g[mapped];
            props.latitude  = fusion::at_c<1>(attr);
            props.longitude = fusion::at_c<2>(attr);

            return true;
        };

        // Parse the gowalla location file
        std::ifstream location_file(fname);
        if (!location_file) return false;

        boost::iostreams::mapped_file_source mm(fname);

        auto f = mm.begin(), l = mm.end();

        x3::parse(f, l,
                // [vertex_id]   [time of checkin]       [latitude]  [longitude]             [location] id
                *((x3::int_ >> '\t' >> x3::omit[*x3::graph] >> '\t' >> x3::double_ >> '\t' >> x3::double_)[add_location] >> '\t' >> x3::int_ >> x3::eol)
                );

        // Fail if we couldn't parse the whole location file
        return f == l;
    }

    typedef boost::adjacency_list<boost::setS, boost::vecS, boost::undirectedS, vertex_property, edge_property> graph;
  private:
    // We use setS to enforce our graph not to become a multigraph
    using vertex_decriptor = graph::vertex_descriptor;

    graph g;

#if USE_VERTEX_DESCRIPTOR_MAPPING
    std::map<int, vertex_decriptor> mapped_vertices;

    vertex_decriptor map_vertex(int id) {
        auto match = mapped_vertices.find(id);

        if (match != mapped_vertices.end())
            return match->second; // vertex already known
        else                      // Otherwise add vertex
            return mapped_vertices[id] = boost::add_vertex(g);
    };

    vertex_decriptor mapped_vertex(int id) const {
        auto mapped = mapped_vertices.find(id);

        return mapped == mapped_vertices.end()
            ? return graph::null_vertex() 
            : mapped->second;
    }
#else
    static vertex_decriptor map_vertex(int id) { return id; }
    static vertex_decriptor mapped_vertex(int id) { return id; }
#endif
};

int main() {
    Reader reader;
    if (!reader.read_edges("loc-gowalla_edges.txt"))
        std::cerr << "Couldn't parse whole edges file" << std::endl;

    if (!reader.read_locations("loc-gowalla_totalCheckins.txt"))
        std::cerr << "Couldn't parse whole location file" << std::endl;
}
于 2016-05-16T23:11:41.677 に答える