【无标题】

文章介绍了如何下载和修改GeminiGraph代码以避免段错误,详细讲述了基于CentOS的MPI集群搭建步骤,并提供了相关博客资源。接着,文章详细讨论了MPI程序的编译和运行,以及在Gemini中使用的数据结构和关键函数,如alloc_vertex_array、alloc_vertex_subset等。重点在于理解边处理函数process_edges的工作机制,包括空闲信号和忙信号的处理,以及如何利用atomic操作确保数据一致性。
摘要由CSDN通过智能技术生成

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的邻居。
另外,两个槽函数都有返回值,在一次迭代中只会调用一种槽函数,槽函数的返回值之和就是边处理函数的返回值之和。
节点处理函数就比较简单了
第一个参数是对激活节点进行操作的函数,第二个参数是激活节点集。
第一个参数中的函数对每一个激活节点操作,返回值之和为边处理函数返回值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值