您的位置:首页 > 编程语言 > C语言/C++

Green Marl 入门 Part1:语言相关

2017-11-27 11:35 281 查看
Green Marl是一种面向图的特定领域语言。用户可以在 Green-Marl 中使用高级的、图形特有的数据类型和运算符直观地编写自己的图形算法。

本系列将结合Green Marl的论文与文档,分析学习Green Marl语言。其中部分是对论文或文档的翻译。若有理解错误,请指出。

论文:Green-Marl: A DSL for Easy and Efficient Graph Analysis

Github Repo:Green-Marl

Green Marl 简介

Green Marl的两个目标:Performance 和 Implementation

具体实现方法:设计一个DSL(Domain-Specific language),用户利用Green Marl描述算法,而无需考虑在具体机器上的实现,同时需要指出其中的秉性部分。提供一个Green Marl的编译器,能将Green Marl编译成优化、并行化后的目标语言(不是机器语言,提供的例子是C++的后端)

Green Marl 语言设计

Scope of the Language

从数学上来说,G = (N,E) 其中N是所有的点集,而E是所有的边集。与图中每一个节点相关的数据可以看成一个从点N或者边E到值域的一个映射。作者将其称为
node property


对于G=(V,E)以及Π={P1,…,Pn} Green Marl主要用于完成以下的图形分析:

根据(G,Π)计算一个标量(如计算Conductance)

根据(G,Π)计算一个新的属性Pn+1 (如PagerRank)

从原来的图中选出一个子图,如强连接图

根据上面的描述,Green-Marl做出了两个假设:

图是固定不变的,不能够被修改

图中的元素都是没有别名的

总的来说,Green-Marl会将图看成一个静态的对象。

Parallelism in Green-Marl

Green-Marl不是一个完全独立的语言,而是基于一些现有语言的功能:

包含了能够暗示可并行数据的语言特性

可以明确的标出可并行的部分(如利用foreach)

对于第二种可并行部分,使用的并行方式是
fork-join
的风格,对于并行中的可并行部分
fork
一个新的进程,并在最后的一个
join-point
进行同步。

Green-Marl使用静态范围规则(在并行化中的变量作用域)

Green-Marl的内存一致性模型与OpenMP类似:

对于共享变量的写不能保证对其他并行线程可见。

对于共享变量的写在当前的遍历(并行)结束后才能保证可以被看见。

如果有多个并行的线程对同一个变量写,只有一个可以被看见。

Green-Marl保证所有的写都是原子的。

如下代码就会存在违背上面一致性的问题:

Foreach(s:G.Nodes)
Foreach(t:s.OutNbrs)
t.A = t.A + s.B


其中出现了写写冲突与读写冲突。

Language Constructs

数据类型

五种基本类型:Bool, Int, Long, Float, Double.

两种图类型:DGraph, UGRaph.

点与边:Node, Edge. 总是与图绑定,如下面的n1与n2

属性:属性也需要类型,如下面的
Node_Prop<Int>


三种集合类型:Set, Order, Sequence.

Set:无序,不重复的

Order: 有序,不重复的

Sequence: 有序,不是唯一的(可能有重复)

这三种类型也需要绑定图,如下面的S。

Procedure foo(G1, G2:Graph, n1:Node(G1)){
Node(G2) n2;            //a node of graph G2
n2 = n1;                //type error? n1 n2 bound to different graphs
Node_Prop<Int>(G1) A;   //integer node property for G1
n1.A = 0;
Node_Set(G1) S;         // A node set of G1
S.Add(n1);
}


​ 集合之间的操作支持如下表所示:



其中要注意的是:

对collection的赋值其实是创建了collection的一个副本,在并行操作中,对于共享的collection的赋值是不被允许的。

并行的向Order中push数据是被允许的。push的顺序不是确定的,且push的数据不能保证立马被其他并行线程看见。Pop是否支持与编译器相关。

每种collection都可以被串行或者并行的被遍历,但是并行的在Order或者Sequence中遍历会出现遗失collection的顺序。

在遍历collection的时候禁止修改collection。

在并行处理的时候,一个collection只能并行的扩展或者并行的收缩,不能同时扩展与收缩。

Iterations and Traversals

在Green-Marl中的遍历格式如下:

Foreach(iterator:source(-).range)(filter)
body_statement


Foreach代表着下面的部分可以并行执行,而如果使用For关键字,则代表着下面的操作将串行执行。iterator与source如字面意思。filter是一个可选项,其是一个bool表达式,决定当前的遍历是否执行body_statement。

与图有关的可遍历的source的range如下图所示:



可以通过Graph.Nodes来遍历节点,也可以通过collection来遍历。

可以通过以下几种方式来遍历一个节点的临接节点:

InNbrs与OutNbrs 用来通过入边或者出边连接的节点(有向图),在无向图中,InNbrs与OutNbrs = Nbrs。

UpNbrs和Down只有在从特定的点进行BFS时才有意义。

上面的表中Access列则表示的遍历的本质。线性遍历表示每一个iterator都指向一个唯一的元素,所有的元素都只会被访问到一次。而Random则可能会出现混淆(aliasing,应该是指多次访问同一个元素)。

对于Order和Set串行的遍历才能体现出来其中的顺序,逆序遍历的例子如下:

Node_Order(G) O;
For(o:O-.Items) //reverse order iteration on O
body_statement


Green-Marl也提供了两种图的遍历模式:BFS和DFS。示例如下:

InBFS (iter:src^.Nodes From root)[navigator](filter1)
forward_body_statement
InRBFS (filter2)
backward_body_statement


root defines the root node of BFS

^ means that we first create a transposed version of graph



navigator 表示哪些节点需要在遍历时被修剪

如果一个节点不满足navigator的条件,将不会在这个节点上进行扩展。

如果满足navigator但是不满足filter,依然会扩展,但是不会执行body_statement。

下面是一个实际利用BFS访问一个转置的图的例子,且只有在flag没有设置的节点才继续向后遍历:

Node_Prop<Bool>(G) flag;
InBFS(s: G^.Nodes From r)[!s.flag]{
//...
}


BFS有两个body_statement块,其中第一块是在向前进行BFS遍历是执行(从近到远),第二个块是可选的,其是逆序BFS遍历时执行的,即从最远的到最近的节点。

对应的DFS中正序和逆序的遍历为
InDFS
InPost
,同样也可以拥有两个body_statement块。

对于DFS和BFS的具体实现,DFS是串行执行的,BFS是层次并行执行的,也就是说,离根节点的相同距离的节点会被并行的访问到,当访问完一个层次的所有节点后,会经历一次同步,然后再访问下一层的节点。所以以下代码没有数据冲突:

InBFS(s:G.Nodes From r) {
Foreach(t:s.UpNbrs)
s.A += t.A //s.A does not conflict with t.A
}


Deferred Assignment 延后操作

Green-Marl支持延后赋值,如下面代码中的s.X就是通过延后赋值。延后赋值类似于RCU,读者可能会读到旧数据,而对于数据的写操作,会在绑定的(@表示绑定)遍历结束后可见。

Foreach(s:G.Nodes){
s.X <= Sum(t:s.Nbrs) {t.X} @ s
// no conflict t.X gives 'old' value
}
// 所有对于X的写在这里可见


Reductions 归约

Int x,y;

// expression form (in-place form)
x = Sum(t:G.Nodes) {t.A};

// assignment form (need initialization)
y = 0;
Foreach(t:G.Nodes)
y += t.A


Green-Marl所支持的归约如下表,其中
In-place
版本允许添加
filter
,而在
Assignment
版本则是在
foreach
中的
statement
做出判断。



其中Min,Max比较特别的一点是,其可以记录下来最大最小对应的节点的值,如下代码所示:

Int X = INF;
Node(G) from,to;
Foreach(t:G.Nodes)
Foreach(u:t.Nbrs)
X <from,to> min = (t.A + u.B)<t,u>;


同样的,归约也可以利用
@
来进行绑定:
x+=…@s
表示
x
在进行
s-iteration
的时候被
+
归约。这就表明
x
不能在
s-iteration
之内的其他地方进行读写操作。

如果不手动指出,编译器会自己去找,若找不到会报错。

Int sum = 0;
Foreach(s:G.Nodes){
Foreach(t:s.Nbrs)
sum += t.A @s;  //accumulate over s-iteration
if (s.A > THRESHOLD)
sum += s.B ;    //@s is implied
s.C = sum;           // this is an read-reduce conflict
}


在最后一句,由于之前对sum进行了写操作,而这里在同一个
s-iteration
内直接进行读操作,会导致
read-reduce conflict


GreenMarl 本地编译环境(C++实现)搭建

Github Repo : https://github.com/stanford-ppl/Green-Marl

主要依赖有:

gcc (version >= 4.2), which supports OpenMP

g++

GNU flex and bison

最新版本的源码
commit 4c0d62e67d431d535ca27140df60b25c234a808b
在语法文件中有小问题,改一下就好了。

配置好环境跑示例程序如下:

# liu @ liu in ~/Desktop/Green-Marl/apps/output_cpp/bin on git:master x [11:38:00]
$ ./graph_gen 1000000 8000000 ../data/u1m_8m.bin 0
Creating Graph, N =                                                          1000000, M =                                                          8000000 , Type = 0
creation time (ms) = 1674.369000
saving to file = ../data/u1m_8m.bin
storing time (ms) = 201.063000

# liu @ liu in ~/Desktop/Green-Marl/apps/output_cpp/bin on git:master x [11:38:34]
$ ./conduct ../data/u1m_8m.bin 1
running with 1 threads
N = 1000000, M = 8000000
graph loading time=2303.032000
reverse edge creation time=0.000000
running time=145.662000
sum C = 3.000369

# liu @ liu in ~/Desktop/Green-Marl/apps/output_cpp/bin on git:master x [11:38:39]
$ ./conduct ../data/u1m_8m.bin 8
running with 8 threads
N = 1000000, M = 8000000
graph loading time=1254.962000
reverse edge creation time=0.000000
running time=103.377000
sum C = 3.000369


第一个程序

搭建好了编译环境,现在就写一个简单的程序来试试这个编译器。

书写程序
foo.gm
如下:

Procedure foo(G:Graph,A:N_P<Int>) : Long
{
Long x;
x = Sum(t:G.Nodes) {t.A};
Return x;
}


对于图每一个节点的属性A,简单的求和。

书写
Makefile
如下:

GM_COMP = /home/liu/Desktop/Green-Marl/bin/gm_comp
all: foo.gm
$(GM_COMP) foo.gm


编译后头文件如下:

#ifndef GM_GENERATED_CPP_FOO_H
#define GM_GENERATED_CPP_FOO_H

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <float.h>
#include <limits.h>
#include <cmath>
#include <algorithm>
#include <omp.h>
#include "gm.h"

int64_t foo(gm_graph& G, int32_t* G_A);

#endif


函数实现如下:

#include "foo.h"

int64_t foo(gm_graph& G, int32_t* G_A)
{
//Initializations
gm_rt_initialize();
G.freeze();

int32_t __S0 = 0 ;

__S0 = 0 ;
#pragma omp parallel
{
int32_t __S0_prv = 0 ;

__S0_prv = 0 ;

#pragma omp for nowait
for (node_t t = 0; t < G.num_nodes(); t ++)
{
__S0_prv = __S0_prv + G_A[t] ;
}
ATOMIC_ADD<int32_t>(&__S0, __S0_prv);
}
return __S0;
}


可以看出其并行部分是利用
OpenMP
做的。

继承项目提供的main函数框架,书写主函数如下:

#include "common_main.h"
#include "foo.h"
class my_main: public main_t
{
public:
int* A;
long sum;

virtual ~my_main() {
delete[] A;
}

my_main() {
A = NULL;
sum = 0;
}

virtual bool prepare() {
A = new int[G.num_nodes()];
return true;
}

virtual bool run() {
printf("Graph has %d node\n",G.num_nodes());
for (int i = 0; i < G.num_nodes(); i++)
A[i] = i;
sum = foo(G, A);
return true;
}

virtual bool post_process() {
printf("Sum = %ld\n",sum);
return true;
}
};

int main(int argc, char** argv) {
my_main M;
M.main(argc, argv);
}


在双核虚拟机下跑的结果如下:

# liu @ liu in ~/Desktop/Green-Marl/apps on git:master x [13:14:53]
$ lscpu
Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                2
On-line CPU(s) list:   0,1
Thread(s) per core:    1
Core(s) per socket:    1
Socket(s):             2
NUMA node(s):          1
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 61
Model name:            Intel(R) Core(TM) i5-5250U CPU @ 1.60GHz
Stepping:              4
CPU MHz:               1599.289
BogoMIPS:              3198.57
Hypervisor vendor:     VMware
Virtualization type:   full
L1d cache:             32K
L1i cache:             32K
L2 cache:              256K
L3 cache:              3072K
NUMA node0 CPU(s):     0,1

# liu @ liu in ~/Desktop/Green-Marl/apps on git:master x [13:14:45]
$ ./output_cpp/bin/foo ./output_cpp/data/u1m_8m.bin 2
running with 2 threads
N = 1000000, M = 8000000
graph loading time=1346.469000
reverse edge creation time=0.000000
Graph has 1000000 node
running time=1.045000
Sum = 1783293664

# liu @ liu in ~/Desktop/Green-Marl/apps on git:master x [13:14:49]
$ ./output_cpp/bin/foo ./output_cpp/data/u1m_8m.bin 1
running with 1 threads
N = 1000000, M = 8000000
graph loading time=1731.271000
reverse edge creation time=0.000000
Graph has 1000000 node
running time=1.814000
Sum = 1783293664


Green-Marl 在macOS下搭建环境:

由于macOS原生的GCC版本太低为(4.2)版本,故使用port安装gcc5.0版本。

此版本执行文件为:gxx-mp-5。

现在可以在.bashrc中将gcc链接到此执行文件,也可以修改Makefile避免影响其他项目。这里修改了Makefile中的CC,可以正常在macOS下编译成功。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息