Gemini关键使用总结
1.下载
https://github.com/thu-pacman/GeminiGraph
代码有两处错误会导致段错误,需要修改:
2.搭建mpi集群
有两个博客参考,
这个是主要搭建步骤,但是比较简单,ubuntu和centOS系统都行,我用的是centOS
(小白向)MPI集群环境搭建–ubuntu 18.04_mpi ubuntu 18.04_Jasscical的博客-CSDN博客
这个是搭建hadoop的,主要参考内容是配置hadoop之前的东西,和mpi集群搭建有相似的地方。
Hadoop——集群搭建(更新版)(步骤图文超详细版)_hadoop环境搭建_何壹时的博客-CSDN博客
搭建mpi集群,并会编译和运行mpi程序,网上可以学到,资料比较多,运行gemini之前看一下readme,readme里面也比较有用,这三个基本可以解决mpi集群的搭建。
c编译: mpicc -o mpiProgram mpiProgram.c
c++编译:mpicxx -o mpiProgram mpiProgram.cpp
mpirun -np 3 -f ./mpi_config ./first.o
这个就是mpi程序的编译和运行命令。-f不能用的情况下使用–machinefile也是可以的。下面是mpi介绍的相关文章,可以了解,有助于看懂gemini core文件夹下面的源码
高性能计算–mpi - 知乎 (zhihu.com)
3.使用
主要看例子,core里面的代码对使用没有影响。编程时主要靠下面的函数,和例子里面的编程模式。
VertexId * parent = graph->alloc_vertex_array();
第一个重要数据结构顶点性质数组
就是每个节点所对应的特征的数组,VertexId是他自定义的类型,其实就是整数类型,这里可以替换,c++的模板而已,如pagerank计算里面的使用double。我认为甚至可以替换为数组,但是应该用不到。
对应操作如下:
graph->fill_vertex_array(parent, graph->vertices);
//为root节点设置属性值
parent[root] = root;
graph->dealloc_vertex_array(parent);
主要是通过graph类中的方法进行操作,
VertexSubset * visited = graph->alloc_vertex_subset();
第二个重要数据结构节点集合
主要有clear() set_bit() get_bit() 清空集合、加入集合、判断是否在集合
visited->clear();
//记录root为访问过
visited->set_bit(root);
active_in->clear();
//设置root为激活节点
active_in->set_bit(root);
//设置parent全为最大值
cas(&parent[dst], graph->vertices, msg)
write_add(&next[dst], msg);
write_min(&distance[dst], relax_dist)
比较写入函数:第一个变量为写入地址、第二个为旧值、第三个为新值,主要是防止写错误
这些原子操作都在atomic.hpp里面,他们的主要作用是保证节点属性写入的一致性,在操作节点属性数据时,需要用。
graph->gather_vertex_array(parent, 0);
聚集节点数据,由于每个处理器都有负责的顶点,需要聚合函数把每个节点负责的数据进行合并,以此得到准确顶点数据。
graph->transpose();
边翻转,把边的出入转化一下。
编程的核心模式如下:
主要是调用边处理函数处理主要更新,
点处理函数更新一下节点属性,
更新激活顶点。这个例子是bfs
for (int i_i=0;active_vertices>0;i_i++) {
//活跃节点数输出
if (graph->partition_id==0) {
printf(“active(%d)>=%u\n”, i_i, active_vertices);
}
active_out->clear();
//边处理
active_vertices = graph->process_edges<VertexId,VertexId>(
//空闲信号函数
[&](VertexId src){
//发送空闲信号,第一个参数为发送源,第二个为发送的信息
graph->emit(src, src);
},
//空闲信号的槽函数,信号源、信号内容、信号源的邻居
[&](VertexId src, VertexId msg, VertexAdjList outgoing_adj){
//激活的节点数
VertexId activated = 0;
//遍历出度邻居节点
for (AdjUnit * ptr=outgoing_adj.begin;ptr!=outgoing_adj.end;ptr++) {
VertexId dst = ptr->neighbour;
//没有访问的节点进行访问,并修改parent和下一轮迭代的激活
if (parent[dst]==graph->vertices && cas(&parent[dst], graph->vertices, src)) {
active_out->set_bit(dst);
activated += 1;
}
}
//返回本轮激活的节点数
return activated;
},
//这里的dst就是活跃顶点的邻居,而且应该是去重后的,去重邻居与活跃节点构成活跃边
//即,边处理的对象
[&](VertexId dst, VertexAdjList incoming_adj) {
if (visited->get_bit(dst)) return;
//如果没有访问过,寻找其邻居节点,当邻居节点为激活节点时发送信号,并退出
for (AdjUnit * ptr=incoming_adj.begin;ptr!=incoming_adj.end;ptr++) {
VertexId src = ptr->neighbour;
if (active_in->get_bit(src)) {
graph->emit(dst, src);
break;
}
}
},
[&](VertexId dst, VertexId msg) {
//设置为激活节点
if (cas(&parent[dst], graph->vertices, msg)) {
active_out->set_bit(dst);
return 1;
}
return 0;
},
active_in, visited
);
//修改visited 这里的激活节点数和边处理的返回值应该是一样的吧?
active_vertices = graph->process_vertices(
[&](VertexId vtx) {
visited->set_bit(vtx);
return 1;
},
active_out
);
//更新激活节点
std::swap(active_in, active_out);
}
边处理函数
graph->process_edges<VertexId,VertexId>()
边处理函数主要有6个参数,分别是空闲信号函数、空闲信号槽函数、忙信号函数、忙信号槽函数、活跃节点集合、节点集合。这里的信号和槽函数是对应的,和qt类似,信号激活槽函数,就像按按钮执行操作,信号函数是按钮,槽函数是操作。
第六个参数节点目前还不是很会,好像用的不多。
在讲解之前首先要说一下关键数据
激活顶点就是本轮的active_in,而src是激活顶点中的每一个,相当于遍历一下。而dst并不是激活顶点,而是他们的出度邻居,如果1、2、3是激活顶点,那么4、5、6就是dst。
这里需要注意的是src与dst并不是按照边一一对应的,它只是把所有活跃顶点的出邻居给收集成一个集合,没有重复元素,dst是{4,5,6}而不是[4,5,5,6]。
空闲信号实际就是推信号,就是把活跃顶点的信息发给槽函数。其中有个重要函数,如下:
graph->emit(src, src);
emit()里面第一个参数是发送源,第二个是发送的内容,对应槽函数的src和msg,而outgong_adj对应的是src的出度。
忙信号实际就是拉信号,就是操作上图中4、5、6进行信号发送,获得活跃顶点信息。并由曹函数处理。同样的忙信号里的dst 和msg与忙槽函数是对应的。Incoming_adj也是dst的邻居。
另外,两个槽函数都有返回值,在一次迭代中只会调用一种槽函数,槽函数的返回值之和就是边处理函数的返回值之和。
节点处理函数就比较简单了
第一个参数是对激活节点进行操作的函数,第二个参数是激活节点集。
第一个参数中的函数对每一个激活节点操作,返回值之和为边处理函数返回值。