[Rename diff] Change the terminology to match the rest.

Reviewers: sasa.stanko

Reviewed By: sasa.stanko

Subscribers: buda, lion

Differential Revision: https://phabricator.memgraph.io/D672
This commit is contained in:
Goran Zuzic 2017-08-17 15:36:58 +02:00
parent 061b8933a7
commit 58453794a6
7 changed files with 243 additions and 213 deletions

View File

@ -1,7 +1,18 @@
# distributed memgraph
This subdirectory structure implements distributed infrastructure of Memgraph.
## terminology
* Memgraph Node Id (mnid): a machine (processs) that runs a (distributed) Memgraph program.
* Node: a computer that performs (distributed) work.
* Vertex: an abstract graph concept.
* Reactor: a unit of concurrent execution, lives on its own thread.
* Connector: a communication abstraction between Reactors. The reactors can be on the same machine or on different processes.
* EventStream: read-end of a connector, is owned by exactly one Reactor/thread.
* Channel: write-end of a connector, can be owned (wrote into) by multiple threads.
## conventions
1. Locked: A method having a Locked... prefix indicates that you

View File

@ -8,155 +8,157 @@
enum class EdgeType { OUTGOING, INCOMING };
/** A node in the graph. Has incoming and outgoing edges which
* are defined as global addresses of other nodes */
class Node {
/** A vertex in the graph. Has incoming and outgoing edges which
* are defined as global addresses of other vertices */
class Vertex {
public:
Node(const GlobalId &id) : id_(id) {}
Vertex(const UniqueVid &id) : id_(id) {}
const auto &id() const { return id_; };
const auto &edges_out() const { return edges_out_; }
const auto &edges_in() const { return edges_in_; }
void AddConnection(EdgeType edge_type, const GlobalAddress &gad) {
void AddConnection(EdgeType edge_type, const GlobalVertAddress &gad) {
(edge_type == EdgeType::INCOMING ? edges_in_ : edges_out_)
.emplace_back(gad);
}
/** Changes all old_address edges to have the new_worker */
void RedirectEdges(const GlobalAddress old_address, size_t new_worker) {
/** Changes all old_address edges to have the new Memgraph node id */
void RedirectEdges(const GlobalVertAddress& old_address, int64_t new_mnid) {
for (auto &address : edges_in_)
if (address == old_address) address.worker_id_ = new_worker;
if (address == old_address) address.cur_mnid_ = new_mnid;
for (auto &address : edges_out_)
if (address == old_address) address.worker_id_ = new_worker;
if (address == old_address) address.cur_mnid_ = new_mnid;
}
private:
// TODO remove id_ from Node if not necessary
GlobalId id_;
UniqueVid id_;
// global addresses of nodes this node is connected to
std::vector<GlobalAddress> edges_out_;
std::vector<GlobalAddress> edges_in_;
// global addresses of vertices this vertex is connected to
std::vector<GlobalVertAddress> edges_out_;
std::vector<GlobalVertAddress> edges_in_;
};
/** A worker / shard in the distributed system */
class Worker {
/**
* A storage that doesn't assume everything is in-memory.
*/
class ShardedStorage {
public:
// unique worker ID. uniqueness is ensured by the worker
// owner (the Distributed class)
const int64_t id_;
// Unique Memgraph node ID. Uniqueness is ensured by the (distributed) system.
const int64_t mnid_;
Worker(int64_t id) : id_(id) {}
ShardedStorage(int64_t mnid) : mnid_(mnid) {}
int64_t NodeCount() const { return nodes_.size(); }
int64_t VertexCount() const { return vertices_.size(); }
/** Gets a node. */
Node &GetNode(const GlobalId &gid) {
auto found = nodes_.find(gid);
assert(found != nodes_.end());
/** Gets a vertex. */
Vertex &GetVertex(const UniqueVid &gid) {
auto found = vertices_.find(gid);
assert(found != vertices_.end());
return found->second;
}
/** Returns the number of edges that cross from this
* graph / worker into another one */
/**
* Returns the number of edges that cross from this
* node into another one
*/
int64_t BoundaryEdgeCount() const {
int64_t count = 0;
auto count_f = [this, &count](const auto &edges) {
for (const GlobalAddress &address : edges)
if (address.worker_id_ != id_) count++;
for (const GlobalVertAddress &address : edges)
if (address.cur_mnid_ != mnid_) count++;
};
for (const auto &node : nodes_) {
count_f(node.second.edges_out());
count_f(node.second.edges_in());
for (const auto &vertex : vertices_) {
count_f(vertex.second.edges_out());
count_f(vertex.second.edges_in());
}
return count;
}
/** Creates a new node on this worker. Returns it's global id */
const GlobalId &MakeNode() {
GlobalId new_id(id_, next_node_sequence_++);
auto new_node = nodes_.emplace(std::make_pair(new_id, Node(new_id)));
return new_node.first->first;
/** Creates a new vertex on this node. Returns its global id */
const UniqueVid &MakeVertex() {
UniqueVid new_id(mnid_, next_vertex_sequence_++);
auto new_vertex = vertices_.emplace(std::make_pair(new_id, Vertex(new_id)));
return new_vertex.first->first;
};
/** Places the existing node on this worker */
void PlaceNode(const GlobalId &gid, const Node &node) {
nodes_.emplace(gid, node);
/** Places the existing vertex on this node */
void PlaceVertex(const UniqueVid &gid, const Vertex &vertex) {
vertices_.emplace(gid, vertex);
}
/** Removes the node with the given ID from this worker */
void RemoveNode(const GlobalId &gid) { nodes_.erase(gid); }
/** Removes the vertex with the given ID from this node */
void RemoveVertex(const UniqueVid &gid) { vertices_.erase(gid); }
auto begin() const { return nodes_.begin(); }
auto begin() const { return vertices_.begin(); }
auto end() const { return nodes_.end(); }
auto end() const { return vertices_.end(); }
private:
// counter of sequences numbers of nodes created on this worker
int64_t next_node_sequence_{0};
// counter of sequences numbers of vertices created on this node
int64_t next_vertex_sequence_{0};
// node storage of this worker
std::unordered_map<GlobalId, Node> nodes_;
// vertex storage of this node
std::unordered_map<UniqueVid, Vertex> vertices_;
};
/**
* A distributed system consisting of mulitple workers.
* A distributed system consisting of mulitple nodes.
* For the time being it's not modelling a distributed
* system correctly in terms of message passing (as opposed
* to operating on workers and their data directly).
* to operating on nodes and their data directly).
*/
class Distributed {
public:
/** Creates a distributed with the given number of workers */
Distributed(int initial_worker_count = 0) {
for (int worker_id = 0; worker_id < initial_worker_count; worker_id++)
AddWorker();
/** Creates a distributed with the given number of nodes */
Distributed(int initial_mnode_count = 0) {
for (int mnode_id = 0; mnode_id < initial_mnode_count; mnode_id++)
AddMnode();
}
int64_t AddWorker() {
int64_t new_worker_id = workers_.size();
workers_.emplace_back(new_worker_id);
return new_worker_id;
int64_t AddMnode() {
int64_t new_mnode_id = mnodes_.size();
mnodes_.emplace_back(new_mnode_id);
return new_mnode_id;
}
int WorkerCount() const { return workers_.size(); }
int MnodeCount() const { return mnodes_.size(); }
auto &GetWorker(int64_t worker_id) { return workers_[worker_id]; }
auto &GetMnode(int64_t mnode_id) { return mnodes_[mnode_id]; }
GlobalAddress MakeNode(int64_t worker_id) {
return {worker_id, workers_[worker_id].MakeNode()};
GlobalVertAddress MakeVertex(int64_t mnid) {
return {mnid, mnodes_[mnid].MakeVertex()};
}
Node &GetNode(const GlobalAddress &address) {
return workers_[address.worker_id_].GetNode(address.id_);
Vertex &GetVertex(const GlobalVertAddress &address) {
return mnodes_[address.cur_mnid_].GetVertex(address.uvid_);
}
/** Moves a node with the given global id to the given worker */
void MoveNode(const GlobalAddress &gid, int64_t destination) {
const Node &node = GetNode(gid);
/** Moves a vertex with the given global id to the given mnode */
void MoveVertex(const GlobalVertAddress &gad, int64_t destination) {
const Vertex &vertex = GetVertex(gad);
// make sure that all edges to and from the node are updated
for (auto &edge : node.edges_in())
GetNode(edge).RedirectEdges(gid, destination);
for (auto &edge : node.edges_out())
GetNode(edge).RedirectEdges(gid, destination);
// make sure that all edges to and from the vertex are updated
for (auto &edge : vertex.edges_in())
GetVertex(edge).RedirectEdges(gad, destination);
for (auto &edge : vertex.edges_out())
GetVertex(edge).RedirectEdges(gad, destination);
// change node destination
workers_[destination].PlaceNode(gid.id_, node);
workers_[gid.worker_id_].RemoveNode(gid.id_);
// change vertex destination
mnodes_[destination].PlaceVertex(gad.uvid_, vertex);
mnodes_[gad.cur_mnid_].RemoveVertex(gad.uvid_);
}
void MakeEdge(const GlobalAddress &from, const GlobalAddress &to) {
GetNode(from).AddConnection(EdgeType::OUTGOING, to);
GetNode(to).AddConnection(EdgeType::INCOMING, from);
void MakeEdge(const GlobalVertAddress &from, const GlobalVertAddress &to) {
GetVertex(from).AddConnection(EdgeType::OUTGOING, to);
GetVertex(to).AddConnection(EdgeType::INCOMING, from);
}
auto begin() const { return workers_.begin(); }
auto begin() const { return mnodes_.begin(); }
auto end() const { return workers_.end(); }
auto end() const { return mnodes_.end(); }
private:
std::vector<Worker> workers_;
std::vector<ShardedStorage> mnodes_;
};

View File

@ -36,76 +36,76 @@ auto MaxRandom(const std::vector<double> &scores) {
}
/**
* Returns the index of the best (highest scored) worker
* for the given node. If there are multiple workers with
* the best score, node prefers to remain on the same worker
* Returns the index of the best (highest scored) mnode
* for the given vertex. If there are multiple mnodes with
* the best score, vertex prefers to remain on the same mnode
* (if among the best), or one is chosen at random.
*
* @param distributed - the distributed system.
* @param node - the node which is being evaluated.
* @param penalties - a vector of penalties (per worker).
* @param current_worker - the worker on which the given
* node is currently residing.
* @param vertex - the vertex which is being evaluated.
* @param penalties - a vector of penalties (per mnode).
* @param current_mnode - the mnode on which the given
* vertex is currently residing.
* @return - std::pair<int, std::vector<double>> which is a
* pair of (best worker, score_per_worker).
* pair of (best mnode, score_per_mnode).
*/
auto BestWorker(const Distributed &distributed, const Node &node,
const std::vector<double> &penalties, int current_worker) {
// scores per worker
std::vector<double> scores(distributed.WorkerCount(), 0.0);
auto BestMnode(const Distributed &distributed, const Vertex &vertex,
const std::vector<double> &penalties, int current_mnode) {
// scores per mnode
std::vector<double> scores(distributed.MnodeCount(), 0.0);
for (auto &edge : node.edges_in()) scores[edge.worker_id_] += 1.0;
for (auto &edge : node.edges_out()) scores[edge.worker_id_] += 1.0;
for (auto &edge : vertex.edges_in()) scores[edge.cur_mnid_] += 1.0;
for (auto &edge : vertex.edges_out()) scores[edge.cur_mnid_] += 1.0;
for (int worker = 0; worker < distributed.WorkerCount(); ++worker) {
// normalize contribution of worker over neighbourhood size
scores[worker] /= node.edges_out().size() + node.edges_in().size();
for (int mnode = 0; mnode < distributed.MnodeCount(); ++mnode) {
// normalize contribution of mnode over neighbourhood size
scores[mnode] /= vertex.edges_out().size() + vertex.edges_in().size();
// add balancing penalty
scores[worker] -= penalties[worker];
scores[mnode] -= penalties[mnode];
}
// pick the best destination, but prefer to stay if you can
size_t destination = MaxRandom(scores);
if (scores[current_worker] == scores[destination])
destination = current_worker;
if (scores[current_mnode] == scores[destination])
destination = current_mnode;
return std::make_pair(destination, scores);
}
/** Indication if Spinner worker penality is calculated based on
* vertex or edge worker cardinalities */
/** Indication if Spinner mnode penality is calculated based on
* vertex or edge mnode cardinalities */
enum class PenaltyType { Vertex, Edge };
/** Calcualtes Spinner penalties for workers in the given
/** Calcualtes Spinner penalties for mnodes in the given
* distributed system. */
auto Penalties(const Distributed &distributed,
PenaltyType penalty_type = PenaltyType::Edge) {
std::vector<double> penalties;
int64_t total_count{0};
for (const auto &worker : distributed) {
int64_t worker_count{0};
for (const auto &mnode : distributed) {
int64_t mnode_count{0};
switch (penalty_type) {
case PenaltyType::Vertex:
worker_count += worker.NodeCount();
mnode_count += mnode.VertexCount();
break;
case PenaltyType::Edge:
for (const auto &node_kv : worker) {
// Spinner counts the edges on a worker as the sum
// of degrees of nodes on that worker. In that sense
for (const auto &vertex_kv : mnode) {
// Spinner counts the edges on a mnode as the sum
// of degrees of vertices on that mnode. In that sense
// both incoming and outgoing edges are individually
// added...
worker_count += node_kv.second.edges_out().size();
worker_count += node_kv.second.edges_in().size();
mnode_count += vertex_kv.second.edges_out().size();
mnode_count += vertex_kv.second.edges_in().size();
}
break;
}
total_count += worker_count;
penalties.emplace_back(worker_count);
total_count += mnode_count;
penalties.emplace_back(mnode_count);
}
for (auto &penalty : penalties)
penalty /= c * total_count / distributed.WorkerCount();
penalty /= c * total_count / distributed.MnodeCount();
return penalties;
}
@ -117,31 +117,31 @@ void PerformSpinnerStep(Distributed &distributed) {
// here a strategy can be injected for limiting
// the number of movements performed in one step.
// limiting could be based on (for example):
// - limiting the number of movements per worker
// - limiting the number of movements per mnode
// - limiting only to movements that are above
// a treshold (score improvement or something)
// - not executing on all the workers (also prevents
// - not executing on all the mnodes (also prevents
// oscilations)
//
// in the first implementation just accumulate all
// the movements and execute together.
// relocation info: contains the address of the Node
// that needs to relocate and it's destination worker
std::vector<std::pair<GlobalAddress, int>> movements;
// relocation info: contains the address of the Vertex
// that needs to relocate and it's destination mnode
std::vector<std::pair<GlobalVertAddress, int>> movements;
for (const Worker &worker : distributed)
for (const auto &gid_node_pair : worker) {
// (best destination, scores) pair for node
for (const ShardedStorage &mnode : distributed)
for (const auto &gid_vertex_pair : mnode) {
// (best destination, scores) pair for vertex
std::pair<int, std::vector<double>> destination_scores =
BestWorker(distributed, gid_node_pair.second, penalties, worker.id_);
if (destination_scores.first != worker.id_)
movements.emplace_back(GlobalAddress(worker.id_, gid_node_pair.first),
BestMnode(distributed, gid_vertex_pair.second, penalties, mnode.mnid_);
if (destination_scores.first != mnode.mnid_)
movements.emplace_back(GlobalVertAddress(mnode.mnid_, gid_vertex_pair.first),
destination_scores.first);
}
// execute movements. it is likely that in the real system
// this will need to happen as a single db transaction
for (const auto &m : movements) distributed.MoveNode(m.first, m.second);
for (const auto &m : movements) distributed.MoveVertex(m.first, m.second);
}
} // namespace spinner

View File

@ -1,62 +1,66 @@
#pragma once
#include "utils/hashing/fnv.hpp"
#include <cstdint>
#include <vector>
/** A globally defined identifier. Defines a worker
* and the sequence number on that worker */
class GlobalId {
/**
* Globally unique id (in the entire distributed system) of a vertex.
*
* It is identified by a pair of a (original memgraph node, local vertex id)
*/
class UniqueVid {
public:
GlobalId(int64_t worker_id, int64_t sequence_number)
: worker_id_(worker_id), sequence_number_(sequence_number) {}
// TODO perhaps make members const and replace instead of changing
// when migrating nodes
int64_t worker_id_;
int64_t sequence_number_;
UniqueVid(int64_t orig_mnid, int64_t vid)
: orig_mnid_(orig_mnid), vid_(vid) {}
/** Original Memgraph node the vertex was created **/
int64_t orig_mnid_;
bool operator==(const GlobalId &other) const {
return worker_id_ == other.worker_id_ &&
sequence_number_ == other.sequence_number_;
/** Original vertex id it was assigned on creation. **/
int64_t vid_;
bool operator==(const UniqueVid &other) const {
return orig_mnid_ == other.orig_mnid_ &&
vid_ == other.vid_;
}
bool operator!=(const GlobalId &other) const { return !(*this == other); }
bool operator!=(const UniqueVid &other) const { return !(*this == other); }
};
/** Defines a location in the system where something can be found.
* Something can be found on some worker, for some Id */
class GlobalAddress {
/**
* Specifies where a vertex is in the distributed system.
*/
class GlobalVertAddress {
public:
GlobalAddress(int64_t worker_id, GlobalId id)
: worker_id_(worker_id), id_(id) {}
// TODO perhaps make members const and replace instead of changing
// when migrating nodes
int64_t worker_id_;
GlobalId id_;
GlobalVertAddress(int64_t cur_mnid, const UniqueVid &uvid)
: cur_mnid_(cur_mnid), uvid_(uvid) {}
bool operator==(const GlobalAddress &other) const {
return worker_id_ == other.worker_id_ && id_ == other.id_;
/** The current Memgraph node where the vertex is **/
int64_t cur_mnid_;
UniqueVid uvid_;
bool operator==(const GlobalVertAddress &other) const {
return cur_mnid_ == other.cur_mnid_ && uvid_ == other.uvid_;
}
bool operator!=(const GlobalAddress &other) const {
bool operator!=(const GlobalVertAddress &other) const {
return !(*this == other);
}
};
namespace std {
template <>
struct hash<GlobalId> {
size_t operator()(const GlobalId &id) const {
return id.sequence_number_ << 4 ^ id.worker_id_;
struct hash<UniqueVid> {
size_t operator()(const UniqueVid &uid) const {
return HashCombine<decltype(uid.orig_mnid_), decltype(uid.vid_)>()(uid.orig_mnid_, uid.vid_);
}
};
template <>
struct hash<GlobalAddress> {
size_t operator()(const GlobalAddress &ga) const {
return gid_hash(ga.id_) << 4 ^ ga.worker_id_;
struct hash<GlobalVertAddress> {
size_t operator()(const GlobalVertAddress &ga) const {
return HashCombine<decltype(ga.cur_mnid_), decltype(ga.uvid_)>()(ga.cur_mnid_, ga.uvid_);
}
private:
std::hash<GlobalId> gid_hash{};
};
}

View File

@ -5,63 +5,63 @@
#include "graph.hpp"
void test_global_id() {
GlobalId a(1, 1);
assert(a == GlobalId(1, 1));
assert(a != GlobalId(1, 2));
assert(a != GlobalId(2, 1));
UniqueVid a(1, 1);
assert(a == UniqueVid(1, 1));
assert(a != UniqueVid(1, 2));
assert(a != UniqueVid(2, 1));
}
void test_global_address() {
GlobalAddress a(1, {1, 1});
assert(a == GlobalAddress(1, {1, 1}));
assert(a != GlobalAddress(2, {1, 1}));
assert(a != GlobalAddress(1, {2, 1}));
GlobalVertAddress a(1, {1, 1});
assert(a == GlobalVertAddress(1, {1, 1}));
assert(a != GlobalVertAddress(2, {1, 1}));
assert(a != GlobalVertAddress(1, {2, 1}));
}
void test_worker() {
Worker worker0{0};
assert(worker0.NodeCount() == 0);
GlobalId n0 = worker0.MakeNode();
assert(worker0.NodeCount() == 1);
void test_mnode() {
ShardedStorage mnode0{0};
assert(mnode0.VertexCount() == 0);
UniqueVid n0 = mnode0.MakeVertex();
assert(mnode0.VertexCount() == 1);
Worker worker1{1};
worker1.PlaceNode(n0, worker0.GetNode(n0));
worker0.RemoveNode(n0);
assert(worker0.NodeCount() == 0);
assert(worker1.NodeCount() == 1);
ShardedStorage mnode1{1};
mnode1.PlaceVertex(n0, mnode0.GetVertex(n0));
mnode0.RemoveVertex(n0);
assert(mnode0.VertexCount() == 0);
assert(mnode1.VertexCount() == 1);
worker1.MakeNode();
assert(worker1.NodeCount() == 2);
assert(std::distance(worker1.begin(), worker1.end()) == 2);
mnode1.MakeVertex();
assert(mnode1.VertexCount() == 2);
assert(std::distance(mnode1.begin(), mnode1.end()) == 2);
}
void test_distributed() {
Distributed d;
assert(d.WorkerCount() == 0);
auto w0 = d.AddWorker();
assert(d.WorkerCount() == 1);
auto w1 = d.AddWorker();
assert(d.WorkerCount() == 2);
assert(d.MnodeCount() == 0);
auto w0 = d.AddMnode();
assert(d.MnodeCount() == 1);
auto w1 = d.AddMnode();
assert(d.MnodeCount() == 2);
GlobalAddress n0 = d.MakeNode(w0);
assert(d.GetWorker(w0).NodeCount() == 1);
GlobalAddress n1 = d.MakeNode(w1);
GlobalVertAddress n0 = d.MakeVertex(w0);
assert(d.GetMnode(w0).VertexCount() == 1);
GlobalVertAddress n1 = d.MakeVertex(w1);
assert(d.GetNode(n0).edges_out().size() == 0);
assert(d.GetNode(n0).edges_in().size() == 0);
assert(d.GetNode(n1).edges_out().size() == 0);
assert(d.GetNode(n1).edges_in().size() == 0);
assert(d.GetVertex(n0).edges_out().size() == 0);
assert(d.GetVertex(n0).edges_in().size() == 0);
assert(d.GetVertex(n1).edges_out().size() == 0);
assert(d.GetVertex(n1).edges_in().size() == 0);
d.MakeEdge(n0, n1);
assert(d.GetNode(n0).edges_out().size() == 1);
assert(d.GetNode(n0).edges_in().size() == 0);
assert(d.GetNode(n1).edges_out().size() == 0);
assert(d.GetNode(n1).edges_in().size() == 1);
assert(d.GetVertex(n0).edges_out().size() == 1);
assert(d.GetVertex(n0).edges_in().size() == 0);
assert(d.GetVertex(n1).edges_out().size() == 0);
assert(d.GetVertex(n1).edges_in().size() == 1);
}
int main() {
test_global_id();
test_global_address();
test_worker();
test_mnode();
test_distributed();
std::cout << "All tests passed" << std::endl;
}

View File

@ -12,16 +12,16 @@
void PrintStatistics(const Distributed &distributed) {
using std::cout;
using std::endl;
for (const Worker &worker : distributed) {
cout << " Worker " << worker.id_ << ":";
cout << " #nodes = " << worker.NodeCount();
for (const ShardedStorage &mnode : distributed) {
cout << " ShardedStorage " << mnode.mnid_ << ":";
cout << " #vertices = " << mnode.VertexCount();
int64_t edge_count{0};
for (const auto &gid_node_pair : worker) {
edge_count += gid_node_pair.second.edges_in().size();
edge_count += gid_node_pair.second.edges_out().size();
for (const auto &gid_vertex_pair : mnode) {
edge_count += gid_vertex_pair.second.edges_in().size();
edge_count += gid_vertex_pair.second.edges_out().size();
}
cout << ", #edges = " << edge_count;
cout << ", #cuts = " << worker.BoundaryEdgeCount() << endl;
cout << ", #cuts = " << mnode.BoundaryEdgeCount() << endl;
}
}
@ -33,8 +33,8 @@ void PrintStatistics(const Distributed &distributed) {
* https://snap.stanford.edu/data/facebook_combined.txt.gz
* add number of vertices and edges in the first line of that file
*/
Distributed ReadGraph(std::string filename, int worker_count) {
Distributed distributed(worker_count);
Distributed ReadGraph(std::string filename, int mnode_count) {
Distributed distributed(mnode_count);
std::fstream fs;
fs.open(filename, std::fstream::in);
@ -43,17 +43,17 @@ Distributed ReadGraph(std::string filename, int worker_count) {
int vertex_count, edge_count;
fs >> vertex_count >> edge_count;
// assign vertices to random workers
std::vector<GlobalAddress> nodes;
// assign vertices to random mnodes
std::vector<GlobalVertAddress> vertices;
for (int i = 0; i < vertex_count; ++i)
nodes.emplace_back(distributed.MakeNode(rand() % worker_count));
vertices.emplace_back(distributed.MakeVertex(rand() % mnode_count));
// add edges
for (int i = 0; i < edge_count; ++i) {
size_t u, v;
fs >> u >> v;
assert(u < nodes.size() && v < nodes.size());
distributed.MakeEdge(nodes[u], nodes[v]);
assert(u < vertices.size() && v < vertices.size());
distributed.MakeEdge(vertices[u], vertices[v]);
}
fs.close();
return distributed;

View File

@ -65,3 +65,16 @@ struct FnvCollection {
private:
static const uint64_t fnv_prime = 1099511628211u;
};
template<typename TA, typename TB>
struct HashCombine {
size_t operator()(const TA& a, const TB& b) const {
constexpr size_t fnv_prime = 1099511628211UL;
constexpr size_t fnv_offset = 14695981039346656037UL;
size_t ret = fnv_offset;
ret ^= std::hash<TA>()(a);
ret *= fnv_prime;
ret ^= std::hash<TB>()(b);
return ret;
}
};