Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

yu961549745/MapleParallel

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

10 Commits

Repository files navigation

MapleParallel

Maple Parallel Programming Study Note.

Maple具有两种并行编程模型:

  • Task : 单进程多线程并行,可以共享内存,但是需要注意共享变量的访问。并且要注意Maple内置函数可能不是线程安全的。需要在threadsafe页面中查看Maple内置函数是否线程安全。Maple线程安全的检查只更新到Maple18,Maple2016引入了CodeTools:-ThreadSafetyCheck来简单的检查函数是否安全,检查是否使用了global变量和lexical变量。
  • Grid : 多进程并行,不共享内存,共享变量需要通讯,传入变量和函数需要显式声明。

另外,并行程序的内部是并行执行的,但是并行程序的启动和其它代码是串行的,只有当并行任务完成之后,后续代码才会继续执行。

Threads————Maple多线程

Maple会自动指定线程到各个CPU执行。

  • 线程全局变量
    • global
    • kernerl options / interface variables
    • global module : module 的局部变量也是全局唯一的。
  • 线程局部变量
    • 局部变量,环境变量
    • module 中声明了 thread_local 的变量
  • 原子性操作
    • 变量赋值
    • interface 交互 : print printf interface
    • 文件操作
    • 调用外部函数

局限性:

  • 大部分Maple库函数都不是线程安全的。只有声明了threadsafe的函数才是线程安全的。(很不幸,sovle不是线程安全的)
  • 需要合理编程才能有效提高效率。

Task Model

  • Start 创建并执行task tree根节点。
  • Continue 创建 task tree 子节点。
  • Return 允许提前结束Task,不会中断正在运行的task,但是会阻止创建新的task。

Maple多线程包含两种实现:

  • Task模型:这是一个高级接口,一个task就是一个线程,用户只需关心task的内容而不需要关心线程的具体实现。
  • Threads包:正确有效的利用这个工具编写多线程程序是困难的,强烈建议直接使用Task模型。
  • 只有Maple主线程才能调用外部函数,

Start 基本用法

with(Threads:-Task):
printf("root exec\n");
Start(print,1,2,3);
printf("single task\n");
Start(null,Task=[print,1,2,3]);
printf("multi tasks\n");
Start(null,Task=[print,1],Task=[print,2],Task=[print,3]);
print("multi tasks with same function\n");
Start(null,Tasks=[print,[1],[2],[3]]);
print("complex parameters\n");
Start(passed,1,Task=[`+`,1,2,3],Tasks=[`*`,[2,3,3],[4,5,6]],x,y,z);

Start的模型为:

fc(arg1,arg2,...,argn) <==> Start(fc,arg1,...,argn);

其中 arg1..argn 既可以是参数,也可以是一个函数调用,如果是函数的话,将以多线程的形式调用。

Maple的Start内置了两个根函数 nullpassed ,前者不范围任何值,后者返回所有输入参数。

总结输入参数具有以下4种类型:

  • 第一个参数是根函数
  • 普通参数
  • Task=[fcn,arg1,...,argn] :单个task
  • Tasks=[fcn,[args1],[args2],...,[argsn]] : 多个task

Continue

Continue的用法和Start类似,但是区别在于Continue只能作为Start的子节点存在,Start和Continue共同构造了一颗task tree.

任务的执行从叶节点开始,每层的每一个节点都完成之后才会执行上一层的任务。

with(Threads:-Task):
fun:=proc()
 Continue(passed,x,Task=[`+`,1,2,3],y,Tasks=[`*`,[1,2],[2,3,3]],z);
end proc:
Start(passed,x,Task=[fun],Tasks=[`+`,[2,3,3],[6,6,6]]);

Return

Return(value)能够通过Start返回value,一旦返回,将不再执行新的任务,但不会中断正在执行的任务。

with(Threads:-Task):
randomize():
roll:=rand(10):
nTasks:=10:
TaskExec:=Array(1..nTasks):
fun:=proc()
 local x;
 global TaskExec;
 TaskExec[_passed]:=1;
 x:=roll();
 if x>5 then
 Return(x);
 end if;
 return x;
end proc:
Start(null,Tasks=[fun,seq([i],i=1..10)]);
print(convert(TaskExec,list));

具有多线程实现的函数

Threads包下具有

  • Add
  • Mul
  • Map
  • Seq 这些函数是普通函数的多线程实现,是基于Task模型实现的。和普通函数的唯一区别是,调用时可以指定最大task数。 以Add为例,即为Add[tasksize=s](...)

Threads基础实现

  • Create--用于创建一个线程来计算表达式
  • Self----用于返回线程id
  • Sleep---用于线程睡眠
  • Wait----等待某个线程结束

Create

Create 用于创建一个线程,并返回一个线程id,可以用于传递给 Wait 来等待线程结束。

Create(expr,var,opt1,...)

其中expr是需要在新的线程中计算的表达式

  • 如果 expr 是一个函数调用,则参数的计算将在当前线程完成,具体的调用将在新的线程完成。
  • 否则,整个表达式都将在新的线程中完成。 其中var可以用于接收expr的返回值。
with(Threads):
id:=Create(int(sin(x)^x,x),res);
res;
Wait(id);
res;

Sleep

Sleep(n)

可以使当前线程水面n秒,n也可以是小于1的数。

注意不要使用Sleep来同步线程,使用Mutex或者ConditionVariable

Self

Self()用于返回当前线程的id,主线程的id为0.

Wait

Wait(id1,id2,...)

等待id列表中所有的线程结束

Mutex

Mutex用于对操作进行加锁同步。

  • Create 创建一个新的锁
  • Destroy 释放一个锁的相关资源
  • Lock 加锁
  • Unlock 解锁

Maple2016提供了option lock

with(Threads:-Mutex):
with(Threads:-Task):
m:=Create();
c:=1;
fun:=proc()
 global c,m;
 Lock(m);
 print(c);
 c:=c+1;
 Unlock(m); 
end proc:
Start(null,Tasks=[fun,seq([],i=1..10)]);
Destroy(m);

ConditionVariable

ConditionVariable提供了更加丰富的同步功能,两个典型的应用是:

  • 在某些点交换数据
  • 生产者-消费者模型:生产者提供任务,通知消费者来完成任务。 ConditionVariable和Mutex的主要区别在于, ConditionVariable可以选择使通知一个等待线程,还是通知所有等待线程。
  • Create------创建
  • Destroy-----销毁
  • Wait--------等待
  • Signal------通知一个
  • Broadcast---通知全部

Grid

Grid包在Maple15引入,并在Maple2015有较大更新。

  • 并行实现
    • Map
    • Seq
  • 基本用法
    • Launch
    • Barrier
    • Interrupt
    • MyNode
    • NumNodes
  • 通信
    • Send
    • Receive
  • 服务器
    • Status
    • Setup
    • Server
    • Run[2015]
    • Get[2015]
    • GetLastResult[2015]
    • Set[2015]
    • Wait[2015]
    • WaitForFirst[2015]

基本用法

Launch

Launch(code,args,options)
参数 意义
code 命令字符串或者函数
args 传递的参数
numnodes = posint 指定进程个数
printer = procedure 输出回调函数
checkabort = procedure 提前中断的回调函数
imports = {list,set} 引入变量
exports = {list,set} 导出变量
clear = truefalse 是否清除状态
allexternal = truefalse 是否让node0在外部运行
  • code 可以是包含Maple命令的字符串,也可以是一个函数,但是需要这个函数不依赖于外部变量。
  • args 如果code是一个函数,则将会把args作为参数传递。
  • 所有node上执行的代码都是相同的,可以利用MyNode来分配不同的任务。
  • 整个任务将在node0结束后立即结束,其它节点将被中断。
  • 返回值是node0中的返回值。
  • numnodes 用于指定进程个数,在本地模式下,这个是由kernerlopts(numcpus)决定的,在远程模式下,这是由Grid[Status]的第二个返回参数决定的。
  • imports 用于导入当前变量,可以有3种形式:name=value,assigned names,string representing global names;
  • exports 用于返回node0中的变量,这个列表只能包含global names。
  • printer 用于在具有外部字符串输出时调用,默认为printf,以一个字符串参数进行调用。
  • checkabort 一个无参调用的函数,将被周期性的调用,返回true则中断所有节点,返回false则继续执行。在本地模式下是无效的。
  • clear 仅在本地模式下使用,默认为true表示调用结束后重置各节点的状态。
  • allexternal 仅在本地模式下有效。
  • 在分布式模式下运行,需要预先进行配置。
fun:=proc()
 uses Grid;
 printf("node %d / %d --- %a \n",MyNode(),NumNodes(),[_passed]);
 Barrier();
end proc:
Grid:-Launch(fun,1,2,3);
Grid:-Launch("rand();"):

Barrier

Barrier :

  • 用于中断所有node,直到所有node都执行了 Barrier 命令,可用于同步。
  • 在 hpc 模式下无效。

MyNode

返回当前进程标记,标记为 0..(N-1)。

NumNodes

返回总的进程个数

Interrupt

Interrupt()
Interrupt(node)
  • 用于中断一个进程
  • node为进程id
  • 无参调用可以在主线程中使用,用于停止由Grid:-Run所创建的命令
  • node0 不能被中断。
  • 中断进程造成的死锁能够被检测到,并自动终止相关进程。
  • mpi 模式下无效。

一些例子

x:=1:y:=2:z:=3:w:=4:
fun:=proc()
 uses Grid;
 global x,y,z;
 local w:=1;
 printf("%d --> %a \n",MyNode(),[x,y,z,w]);
 x:=233+MyNode();
 y:=666+MyNode();
 z:=888+MyNode();
 w:=999+MyNode();
 Barrier();
end proc:
Grid:-Launch(fun,imports={'x','y'},exports={'x','z','w'});
[x,y,z,w];
  • x和z对比,说明全局变量必须声明imports才能导入。
  • z说明,全局变量声明了导出,修改就能生效。
  • w说明,说明全局变量才能导出,局部变量不行。
  • 返回结果表明,只有node0的全局变量值才有效。 并且需要注意的是:
  • print/printf是原子的,但是各进程/线程之间的调用顺序是未知的,所以尽可能的在同一句输出,或者运行完成后再输出。
  • 需要考虑 Barrier 的特性和 node0 返回的特性。

进程通信

Send(node,msg)
  • node 指定节点编号
  • msg 可以是任意类型的参数,可以为NULL和seq,但是具有last name evaluation的嵌套表达式不会对子表达式进行求值。
  • Send只有在传输阶段会中断程序,传输完成后立即返回NULL,不管是否Receive。
  • 所有计算都已经完成的死锁可以被自动检测并终止。
Receive()
Receive(node)
  • 在接收到信息前会中断进程,若指定了node则接收到了该node的信息再终止,若不指定,则在接收到第一个参数后终止。
N:=kernelopts(numcpus):
x:=Array(1..N):
fun:=proc()
 uses Grid;
 global x;
 local node,i,id,v;
 node:=MyNode();
 if node<>0 then
 Send(0,node,node^2);
 else
 for i from 1 to NumNodes()-1 do
 id,v:=Receive();
 x[id+1]:=v;
 end do;
 x[1]:=0;
 end if;
 return NULL;
end proc:
Grid:-Launch(fun,imports=["x"],exports=["x"]);
print(convert(x,list));

当一个进程需要接收其它进程的信息时,尽管写成如下形式,经简单测试,不会造成信息丢失,但总觉得不好。 当接收无序输入时,还是推荐不要指定接收的节点编号。

fun:=proc()
 uses Grid;
 local node,i,N;
 node:=MyNode();
 N:=NumNodes();
 if node<>0 then
 Threads:-Sleep(N-node);
 print(node);
 Send(0,evalf(Pi-3+node,100));
 else
 for i from 1 to N-1 do
 print(Receive(i));
 end do;
 end if;
end proc:
Grid:-Launch(fun);

参数传递

参数传递总结

三种imports方式可以分为两种类型:

  • assigned name, global name string : 从global空间提取对应信息,放入Grid命名空间中去。
  • name=value : 从value取值,放入Grid命名空间的 name 中去。

从上述总结可以看出,

  • 第一种类型的变量来源只能是global的,而第二种类型可以是local的。

另外需要注意的是

  • 在传递procedure和module时,要注意_last name evaluation_ 的作用
  • 在传递 procedure / set / list 作为参数时,需要传入补位参数。

About

Maple Parallel Programming Study Note

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

Contributors

AltStyle によって変換されたページ (->オリジナル) /