BSP模型简介

使用的BSP模型是基于MPI编译的BSPonMPI

项目连接: wijnand-suijlen/bsponmpi: BSPlib implementation on top MPI

项目里的版本较低,最新版在官网里

官网: BSPonMPI

文档:refman.pdf

编译和使用

这里下载的是BSPonMPI2.0,编译和运行与MPI的命令类似

编译 注意-lbsponmpi参数

1
mpicc ./hello.c -o hello -lbsponmpi

运行

1
mpirun -np 4 example

使用时引入头文件

1
#include<bsp.h>

BSPonMPI使用的是c99,标准,不支持c++,如restrict关键字等使用导致c++编译器无法编译引入bsp.h的程序

编译成功后把libbsponmpi.so.0动态链接库的位置添加到环境变量中。

1
export LD_LIBRARY_PATH=/path/to/library:$LD_LIBRARY_PATH

BSP使用

bsp.h存在如下函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/** @name Initialisation */
void bsp_init (void (*smpd_part) (void), int, char *[]);
void bsp_begin (int maxprocs);
void bsp_end ();

/** @name Halt */
void bsp_abort (const char *format, ...);

/** @name Enquiry */
int bsp_nprocs ();
int bsp_pid ();
double bsp_time ();

/** @name Superstep */
void bsp_sync ();

/** @name DRMA */
void bsp_push_reg (const void *, int);
void bsp_pop_reg (const void *);
void bsp_put (int, const void *, void *, int, int);
void bsp_get (int, const void *, int, void *, int);

/** @name BSMP */
void bsp_send (int, const void *, const void *, int);
void bsp_qsize (int * restrict , int * restrict );
void bsp_get_tag (int * restrict , void * restrict);
void bsp_move (void *, int);
void bsp_set_tagsize (int *);

初始化函数

bsp_init

1
void bsp_init (void(∗)(void) spmd_part, int argc, char ∗ argv[])

类似于MPI::Init()在程序开始时调用该函数

第一个参数是用来执行并行部分函数的地址

1
2
3
4
5
6
7
bsp_init(&spmd_part, argc, argv);

printf("First perform some sequential code\n");

spmd_part();

printf("Finally perform some sequential code\n");

bsp_begin

1
void bsp_begin (int maxprocs)

并行部分的开始,参数是用于分配进程的数量。

这里进程数可自己设置-np参数设置的进程数不一定全部会用上。但不会超过这个数

bsp_end

1
void bsp_end ()

在并行化部分执行完后调用该函数,该函数之后的代码仅由0号进程执行

终止函数

bsp_abort

1
void bsp_abort (const char ∗ format, ...)

终止函数,终止程序并打印信息,参数类似于printf

注:spmd_part()中不在bsp_begin中的部分会在调用bsp_init时多执行一遍,而不是主动调用spmd_part时去执行。主动去调用spmd_part()时也只会从bsp_begin开始去执行

查询函数

bsp_nprocs

1
int bsp_nprocs ()

返回可用/已分配的进程数

bsp_init()执行过,但bsp_begin()没有执行官,返回可用的进程数

两个函数都执行过,返回已分配进程数

如果都没执行过,返回错误代码-1

bsp_pid

1
int bsp_pid ()

返回当前进程号。

类似于MPI::COMM_WORLD.Get_rank()

bsp_time()

1
double bsp_time ()

返回从bsp_begin()开始所经过的时间

supersteps函数

bsp_sync()

1
void bsp_sync ()

划分supersteps

bsp_sync()前后为两步supersteps

内存管理函数

bsp_push_reg

1
void bsp_push_reg (const void ∗ ident, int size)

使指定大小的内存空间可用,内存空间可以用于下一步的supersteps中内存的操作

不加这个,无法对目标内存进行bsp_putbsp_get操作

参数

  • ident 指向一块内存的指针
  • size 内存大小

bsp_pop_reg

1
void bsp_pop_reg (const void ∗ ident)

注销内存位置

bsp_pop_reg用完后注销掉

参数

  • ident 指向目标内存的指针

bsp_put

1
void bsp_put (int pid, const void ∗ src, void ∗ dst, int offset, int nbytes)

在下一个supersteps中,给目标进程的目标位置特定的数据,其他进程数据不变

参数

  • pid 目标进程号
  • src 指向源位置的指针
  • dst 指向目标位置的指针
  • offset dst上的偏移
  • nbytes 发送的字节数

bsp_get

1
void bsp_get (int pid, const void ∗ src, int offset, void ∗ dst, int nbytes)

在下一个supersteps中从其他进程获取数据

参数

  • pid 源进程号
  • src 指向源的指针
  • offset src上的偏移
  • dst 目标位置
  • nbytes

结合bsp_sync

bsp_getbsp_put两个函数仅仅作为缓冲,不会立刻改变内存,只有调用了bsp_sync后内存才会刷新

简单的进程间通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <bsp.h>
#include <stdio.h>

void spmd_part()
{
printf("hello,%d\n",bsp_nprocs());
int a=1, b=2, c=3;
bsp_begin(2);
printf("HELLO,%d\n",bsp_nprocs());
bsp_push_reg(&a, sizeof(int));
bsp_sync();

if (bsp_pid() == 0)
{
bsp_put(1, &b, &a, 0, sizeof(int));
bsp_get(1, &a, 0, &c, sizeof(int));
}
printf("pid=%d, a=%d, b=%d, c=%d\n",bsp_pid(),a,b,c);
bsp_sync();
printf("pid=%d, a=%d, b=%d, c=%d\n",bsp_pid(),a,b,c);
bsp_pop_reg(&a);
bsp_sync();

bsp_end();
}

int main(int argc, char *argv[])
{
bsp_init(&spmd_part, argc, argv);

printf("First perform some sequential code\n");

spmd_part();

printf("Finally perform some sequential code\n");
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ mpirun -np 5 ./hello
First perform some sequential code
hello,5
hello,5
hello,5
hello,5
hello,5
HELLO,2
pid=1, a=1, b=2, c=3
pid=1, a=2, b=2, c=3
HELLO,2
pid=0, a=1, b=2, c=3
pid=0, a=1, b=2, c=1
Finally perform some sequential code

BSMP函数

bsp_send

1
void bsp_send (int pid, const void ∗ tag, const void ∗ payload, int payload_nbytes)

给一个进程发送信息,可以发送tag和payload,tag默认发送的大小为0,需要在bsp_set_tagsize()单独设置

经过实验,bsp_send也要加bsp_sync才能保障消息进入消息队列

参数

  • pid 目标进程号
  • tag 指向tag的指针
  • payload 指向payload的指针
  • payload_nbytes 发送信息的大小

bsp_qsize

1
void bsp_qsize (intrestrict nmessages, intrestrict accum_nbytes)

给出队列中消息的数量和payload的大小总和。

结果直接赋值到两个指针中去

参数

  • nmessages int类型的指针,指向的值会被设置成队列中消息的数量
  • accum_nbytes int类型指针,指针的值会被设置成payload大小的和

bsp_get_tag

1
void bsp_get_tag (intrestrict status, voidrestrict tag)

给出消息队列中tag和payload的大小

status返回payload,tag返回tag,具体同上

bsp_move

1
void bsp_move (void ∗ payload, int reception_nbytes)

从消息队列中接收数据

参数

  • payload 指向一块足够大的内存空间
  • reception_nbytes 最大接收数量

bsp_set_tagsize

1
void bsp_set_tagsize (int ∗ tag_nbytes)

为下一个superstep的tag设置大小

参数

  • tag_nbytes 是一个int类型的指针,指向的是tag的大小

bsp_send和bsp_move实现进程间通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <bsp.h>
#include <stdio.h>

void spmd_part()
{
char text[10]={"txet emos"};
bsp_begin(4);

if (bsp_pid() == 0)
{
bsp_send(1, NULL, "some text", 10);//这里向1进程发送消息
}
bsp_sync();//经过实验,bsp_send也要加这个

if(bsp_pid()==1) bsp_move(text,10);//这里如果在其他进程尝试接收消息,则会报错

printf("pid=%d, text=%s\n",bsp_pid(),text);
bsp_end();
// printf("222\n");
}

int main(int argc, char *argv[])
{
bsp_init(&spmd_part, argc, argv);

spmd_part();
return 0;
}
1
2
3
4
5
$ mpirun -np 5 ./hello
pid=0, text=txet emos
pid=1, text=some text
pid=2, text=txet emos
pid=3, text=txet emos

高性能函数

1
2
void bsp_hpput (int pid, const void ∗src, void ∗dst, int offset, int nbytes)
void bsp_hpget (int pid, const void ∗ src, int offset, void ∗ dst, int nbytes)

这两个函数和bsp_putbsp_get一模一样

1
2
#define bsp_hpput bsp_put
#define bsp_hpget bsp_get

定义

1
int bsp_hpmove (void ∗∗ tag_ptr, void ∗∗ payload_ptr)

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <bsp.h>
#include <stdio.h>

void spmd_part()
{
int tag=2;
bsp_begin(4);

if (bsp_pid() == 0)
{
bsp_send(1, &tag, "some text", 10);//这里向1进程发送消息
}
bsp_sync();//经过实验,bsp_send也要加这个
void* pointer=NULL;
void* tag_pointer=(void*)&tag;
int size=bsp_hpmove(&tag_pointer,&pointer);

printf("pid=%d, text=%s, size=%d\n",bsp_pid(),pointer,size);
bsp_end();
}

int main(int argc, char *argv[])
{
bsp_init(&spmd_part, argc, argv);

spmd_part();
return 0;
}

bsp_hpmove直接修改指针,省去了内存复制

  • 相比于bsp_move只需要根据tag判断消息,不需要用if判断进程