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
具体实现方法:设计一个DSL(Domain-Specific language),用户利用Green Marl描述算法,而无需考虑在具体机器上的实现,同时需要指出其中的秉性部分。提供一个Green Marl的编译器,能将Green Marl编译成优化、并行化后的目标语言(不是机器语言,提供的例子是C++的后端)
对于G=(V,E)以及Π={P1,…,Pn} Green Marl主要用于完成以下的图形分析:
根据(G,Π)计算一个标量(如计算Conductance)
根据(G,Π)计算一个新的属性Pn+1 (如PagerRank)
从原来的图中选出一个子图,如强连接图
根据上面的描述,Green-Marl做出了两个假设:
图是固定不变的,不能够被修改
图中的元素都是没有别名的
总的来说,Green-Marl会将图看成一个静态的对象。
包含了能够暗示可并行数据的语言特性
可以明确的标出可并行的部分(如利用foreach)
对于第二种可并行部分,使用的并行方式是
Green-Marl使用静态范围规则(在并行化中的变量作用域)
Green-Marl的内存一致性模型与OpenMP类似:
对于共享变量的写不能保证对其他并行线程可见。
对于共享变量的写在当前的遍历(并行)结束后才能保证可以被看见。
如果有多个并行的线程对同一个变量写,只有一个可以被看见。
Green-Marl保证所有的写都是原子的。
如下代码就会存在违背上面一致性的问题:
其中出现了写写冲突与读写冲突。
两种图类型:DGraph, UGRaph.
点与边:Node, Edge. 总是与图绑定,如下面的n1与n2
属性:属性也需要类型,如下面的
三种集合类型:Set, Order, Sequence.
Set:无序,不重复的
Order: 有序,不重复的
Sequence: 有序,不是唯一的(可能有重复)
这三种类型也需要绑定图,如下面的S。
集合之间的操作支持如下表所示:
其中要注意的是:
对collection的赋值其实是创建了collection的一个副本,在并行操作中,对于共享的collection的赋值是不被允许的。
并行的向Order中push数据是被允许的。push的顺序不是确定的,且push的数据不能保证立马被其他并行线程看见。Pop是否支持与编译器相关。
每种collection都可以被串行或者并行的被遍历,但是并行的在Order或者Sequence中遍历会出现遗失collection的顺序。
在遍历collection的时候禁止修改collection。
在并行处理的时候,一个collection只能并行的扩展或者并行的收缩,不能同时扩展与收缩。
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串行的遍历才能体现出来其中的顺序,逆序遍历的例子如下:
Green-Marl也提供了两种图的遍历模式:BFS和DFS。示例如下:
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没有设置的节点才继续向后遍历:
BFS有两个body_statement块,其中第一块是在向前进行BFS遍历是执行(从近到远),第二个块是可选的,其是逆序BFS遍历时执行的,即从最远的到最近的节点。
对应的DFS中正序和逆序的遍历为
对于DFS和BFS的具体实现,DFS是串行执行的,BFS是层次并行执行的,也就是说,离根节点的相同距离的节点会被并行的访问到,当访问完一个层次的所有节点后,会经历一次同步,然后再访问下一层的节点。所以以下代码没有数据冲突:
Green-Marl所支持的归约如下表,其中
其中Min,Max比较特别的一点是,其可以记录下来最大最小对应的节点的值,如下代码所示:
同样的,归约也可以利用
如果不手动指出,编译器会自己去找,若找不到会报错。
在最后一句,由于之前对sum进行了写操作,而这里在同一个
主要依赖有:
gcc (version >= 4.2), which supports OpenMP
g++
GNU flex and bison
最新版本的源码
配置好环境跑示例程序如下:
书写程序
对于图每一个节点的属性A,简单的求和。
书写
编译后头文件如下:
函数实现如下:
可以看出其并行部分是利用
继承项目提供的main函数框架,书写主函数如下:
在双核虚拟机下跑的结果如下:
此版本执行文件为:gxx-mp-5。
现在可以在.bashrc中将gcc链接到此执行文件,也可以修改Makefile避免影响其他项目。这里修改了Makefile中的CC,可以正常在macOS下编译成功。
本系列将结合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下编译成功。
相关文章推荐
- Green Marl 入门 Part2:编译器概要
- R 语言 相关入门资料
- R语言入门心得(3) -- 向量相关
- C 语言相关函数入门
- 【个人笔记】002-PHP基础-01-PHP快速入门-02-PHP语言相关介绍输
- 汇编语言入门介绍
- Android Studio NDK 入门教程(1)--来自C 语言的String
- MS SQL入门基础:SQL 语言
- 网站数据统计分析相关入门
- EM算法入门相关文章翻译与总结-3
- 嵌入式面试题总结-C语言知识点相关
- linux操作系统下c语言编程入门
- Shader编程学习笔记(十)—— Cg语言入门2 - profile和基本数据类型
- 写给步入网络门槛中入门级别工程师中相关Vlan、trunk、hybrid的理解
- go 语言入门
- C#语言初级入门(1)_C#教程
- 破解入门--提高汇编语言阅读能力
- 高级着色语言HLSL入门(7)
- 汇编语言入门教程
- Unix环境高级程序设计入门--文件系统的相关编程(上)