Skip to main content

New Blog

代码人生:编织技术与生活的博客之旅

全部 (122)前端 (15)学习笔记 (14)CSS (10)工具 (7)总结 (5)AI抠图 (4)AI编辑图片 (4)git (4)下载 (4)商业项目 (3)实用小工具 (3)算法 (3)软件 (3)AI (2)GCP (2)gradle (2)jacoco (2)JavaScript (2)NodeJs (2)Python (2)SOP (2)Spring (2)Taro (2)创业 (2)在线网站 (2)在线网站,客户端 (2)定位 (2)并发 (2)日记 (2)自媒体 (2)配音 (2)面试 (2)AI助教 (1)AI检测助手,原创检测,图像AI检测,视频AI检测 (1)border图形 (1)B站视频下载, Bilibili视频下载,bilibili弹幕下载,B站动画片下载,B站电影下载,B站批量下载,B站音频下载 (1)CSS精灵图 (1)cursor (1)Excel免费提取图片 (1)Excel图片批量提取 (1)Excel提取图片 (1)Flex (1)iac (1)idea (1)iterm (1)langGraph (1)Linux (1)MAC (1)ncm格式如何转换为mp3 (1)ncm转mp3 (1)ncm转mp3格式转换器 (1)ncm转换 (1)PostgreSQL (1)pulumi (1)pycharm (1)questions (1)RAG使用交付 (1)tiktok视频下载 (1)U23国足体育赛事4K (1)vscode (1)webpack打包 (1)xd1997.com (1)体育赛事转播回看 (1)修行 (1)免费体育赛事直播 (1)免费无版权 (1)免费无版权可商用字体 (1)内家拳 (1)动力 (1)包管理工具 (1)呼吸 (1)商机助手 (1)图片处理工具,批量去水印,图片去水印,图片去物体,图片去背景, 图片加水印 (1)图片批量下载,百度图片下载,谷歌图片下载,必应图片下载,搜索引擎图片下载 (1)地球 (1)大模型 (1)字体 (1)字体网站 (1)宇宙 (1)小红书图文下载,小红书视频下载,小红书笔记下载,小红书封面下载,小红书无水印下载 (1)小红书图文笔记下载 (1)小红书封面生成 (1)小红书无水印下载 (1)小红书视频下载 (1)工作 (1)开悟 (1)微信对话截图生成,手机短信截图生成,收款账单截图生成,支付宝商品付款截图生成,支付宝截图生成 (1)抖音下载,抖音去水印,douyin下载,抖音批量下载 (1)抖音提取音频 (1)抖音视频下载 (1)抖音视频批量下载 (1)搜索 (1)搬家 (1)操作系统 (1)效率神器, PDF转图片, 图片转PDF (1)效率神器, 剪切板历史 (1)效率神器, 图标提取器,Icon提取器 (1)数据 (1)文件传输,文本传输,屏幕传输,视频实时传输 (1)文字转图片 (1)文字转语音声音网站 (1)新电脑 (1)智能助手使用交付 (1)浮动布局 (1)清单 (1)短视频下载 (1)短视频无水印下载 (1)视频分割,视频删减,视频剪辑 (1)视频转文字 (1)视频转文字,音频转文字助手 (1)网易云ncm转mp3 (1)自媒体必备 (1)自我管理 (1)英语学习技巧 (1)记录 (1)购物 (1)配音网站 (1)量化投资 (1)锻炼 (1)阅读 (1)雷军 (1)音效素材 (1)音频转文字,视频转音频,视频加水印,视频翻译 (1)

初识MPI

MPI介绍

  • MPI,全称Message Passing Interface(消息传递接口),是业界定义的一种消息传递标准,用于编写并行计算的程序,广泛应用于高性能计算领域。

环境准备

  • 安装过程(ubuntu 18.04)
  • sudo apt-get update
  • sudo apt-get install -y build-essential
  • gcc -v

MPICH

  • MPICH是MPI标准的一种重要的实现,可以免费从网上下载。MPICH的开发与MPI规范的制订是同步进行的,因此MPICH最能反映MPI的变化和发展。
  • sudo apt-get install -y mpich
  • mpicc -v

第一个MPI程序

  • sudo apt-get install -y vim
  • vim mpi.c

#include <mpi.h>

#include <stdio.h>

int main(int argc, char **argv)

{

MPI_Init(&argc, &argv);

printf("Hello World!\n");

MPI_Finalize();

return 0;

}

  • mpicc mpi.c -o mpi.o
  • mpirun -np 8 ./mpi.o
  • MPI程序和普通的C程序的区别在于有一个开始的函数和结束的函数来标识MPI部分,再在这个部分进行你想要进行的操作
  • int MPI_Init(int *argc, char **argv)
  • 通过MPI_Init函数进入MPI环境并完成所有的初始化工作,标志并行代码的开始。
  • int MPI_Finalize(void)
  • 通过MPI_Finalize函数从MPI环境中退出,标志并行代码的结束,如果不是MPI程序最后一条可执行语句,则运行结果不可知。

MPI学习

获取进程数量

  • 在MPI编程中,我们常常需要获取指定通信域的进程个数,以确定程序的规模。
  • 一组可以相互发送消息的进程集合叫做通信子,通常由MPI_Init()在用户启动程序时,定义由用户启动的所有进程所组成的通信子,缺省值为 MPI_COMM_WORLD。这个参数是MPI通信操作函数中必不可少的参数,用于限定参加通信的进程的范围。
  • int MPI_Comm_size(MPI_Comm comm, int *rank)
  • 获取指定通信域的进程个数。
  • 第一个参数是通信子,第二个参数返回进程的个数。

#include <stdio.h>

#include <mpi.h>

int main(int argc, char **argv)

{

int numprocs;

MPI_Init(&argc, &argv);

//your code here

MPI_Comm_size(MPI_COMM_WORLD, &numprocs);

//end of your code

printf("Hello World! The number of processes is %d\n",numprocs);

MPI_Finalize();

return 0;

}

获取进程id

  • 常常需要输出当前进程的id,以此来判断具体哪个进程完成了对应的任务。
  • int MPI_Comm_rank(MPI_Comm comm, int *rank)
  • 获得当前进程在指定通信域中的编号,将自身与其他程序区分。
  • 第一个参数是通信子,第二个参数返回进程的编号。

#include <stdio.h>

#include <mpi.h>

int main(int argc, char **argv)

{

int myid, numprocs;

MPI_Init(&argc, &argv);

MPI_Comm_size(MPI_COMM_WORLD, &numprocs);

//your code here

MPI_Comm_rank(MPI_COMM_WORLD, &myid);

//end of your code

printf("Hello World!I'm rank %d of %d\n", myid, numprocs);

MPI_Finalize();

return 0;

}

获取处理器名

  • 在实际处理中我们可能需要将进程迁移至不同的处理器,而MPI提供了获取处理器名的函数以简单地允许这种行为。
  • 注意在MPI中不需要定义这种迁移。
  • int MPI_Get_processor_name ( char *name, int *resultlen)
  • char *name : 实际节点的唯一说明字;
  • int *resultlen:在name中返回结果的长度;
  • 返回调用时调用所在的处理器名。

#include <stdio.h>

#include <mpi.h>

int main(int argc, char **argv)

{

int len;

char name[MPI_MAX_PROCESSOR_NAME];

MPI_Init(&argc, &argv);

//your code here

MPI_Get_processor_name (name, &len);

//end of your code

printf("Hello, world. I am %s.\n", name);

MPI_Finalize();

return 0;

}

运行时间

  • 在MPI编程我们可以使用MPI_Wtime函数在并行代码中计算运行时间,用MPI_Wtick来查看精度。
  • double MPI_Wtime(void)
  • MPI_WTIME返回一个用浮点数表示的秒数, 它表示从过去某一时刻到调用时刻所经历的时间。
  • double MPI_Wtick(void)
  • MPI_WTICK返回MPI_WTIME的精度,单位是秒,可以认为是一个时钟滴答所占用的时间。

#include<stdio.h>

#include<mpi.h>

int main(int argc, char **argv)

{

int myid, numprocs;

double start, finish;

MPI_Init(&argc, &argv);

MPI_Comm_rank(MPI_COMM_WORLD, &myid);

MPI_Comm_size(MPI_COMM_WORLD, &numprocs);

//your code here

start = MPI_Wtime();

printf("The precision is: %f\n", MPI_Wtick());

finish = MPI_Wtime();

//your code here

printf("Hello World!I'm rank %d of %d, running %f seconds.\n", myid, numprocs, finish-start);

MPI_Finalize();

return 0;

}

同步

  • 在实际工作中,我们常常会因为许多原因需要进行同步操作。例如,希望保证所有进程中并行代码在某个地方同时开始运行,或者在某个函数调用结束之前不能返回。
  • int MPI_Barrier(MPI_Comm comm)
  • 阻止调用直到communicator中所有进程已经完成调用,
  • 就是说,任意一次进程的调用只能在所有communicator中的成员已经开始调用之后进行。

#include<stdio.h>

#include<mpi.h>

int main(int argc, char **argv)

{

int myid, numprocs;

double start, finish;

MPI_Init(&argc, &argv);

MPI_Comm_rank(MPI_COMM_WORLD, &myid);

MPI_Comm_size(MPI_COMM_WORLD, &numprocs);

//your code here

MPI_Barrier(MPI_COMM_WORLD);

//end of your code

start = MPI_Wtime();

printf("The precision is: %f\n", MPI_Wtick());

finish = MPI_Wtime();

printf("Hello World!I'm rank %d of %d, running %f seconds.\n", myid, numprocs, finish-start);

MPI_Finalize();

return 0;

}

消息传递

  • 在并行编程中,消息传递占了很大的比重。良好的消息传递是正常完成进程/节点之间操作的基本条件。在这里先介绍的最基本发送/接收函数。
  • 最基本的发送/接收函数都是以缓冲区作为端点,通过参数配置来完成指定操作。
  • `int MPI_Send(void* msg_buf_p, int msg_size, MPI_Datatype msg_type, int dest, int tag, MPI_Comm communicator)

`

  • 发送缓冲区中的信息到目标进程。

void* msg_buf_p : 发送缓冲区的起始地址;

int buf_size : 缓冲区大小;

MPI_Datatype msg_type : 发送信息的数据类型;

int dest :目标进程的id值;

int tag : 消息标签;

MPI_Comm communicator : 通信子;

  • `int MPI_Recv(void* msg_buf_p, int buf_size, MPI_Datatype msg_type, int source, int tag, MPI_Comm communicator, MPI_Status *status_p)

`

  • 发送缓冲区中的信息到目标进程。

void* msg_buf_p : 缓冲区的起始地址;

int buf_size : 缓冲区大小;

MPI_Datatype msg_type : 发送信息的数据类型;

int dest :目标进程的id值;

int tag : 消息标签;

MPI_Comm communicator : 通信子;

MPI_Status *status_p : status_p对象,包含实际接收到的消息的有关信息

  • 实验说明:
  • 在这里我们把id为0的进程当作根进程,然后在除此之外的进程中使用函数MPI_Send发送一句"hello world!"到根进程中,然后在根进程中把这些信息打印出来。

#include <stdio.h>

#include <mpi.h>

int main(int argc, char **argv)

{

int myid, numprocs, source;

MPI_Status status;

char message[100];

MPI_Init(&argc, &argv);

MPI_Comm_rank(MPI_COMM_WORLD, &myid);

MPI_Comm_size(MPI_COMM_WORLD, &numprocs);

if(myid != 0) {

strcpy(message, "hello world!");

//your code here

MPI_Send(message, strlen(message)+1, MPI_CHAR, 0, 99, MPI_COMM_WORLD);

//end of your code

}

else { //myid == 0

for(source=1; source<numprocs; source++) {

//your code here

MPI_Recv(message, 100, MPI_CHAR, source, 99, MPI_COMM_WORLD, &status);

//end of your code

printf("%s\n", message);

}

}

MPI_Finalize();

return 0;

}

地址偏移量

  • 在通信操作中,我们常常需要对地址进行传递或操作,例如传送/接收缓冲区。
  • 而一个位置在内存中的地址可以通过MPI_ADDRESS函数获得。
  • int MPI_Address(void* location, MPI_Aint *address)
  • void* location : 调用者的内存位置;
  • MPI_Aint *address:位置的对应地址;
  • 实验说明:
  • 给出三个临时变量a, b, c, 分别求出a与b、a与c之间的地址偏移量。
  • 输出结果:
  • 由于这里采用的变量类型为int,所以如果变量地址是连续的话应该是:

The distance between a and b is 4

The distance between a and c is 8


#include<stdio.h>

#include<mpi.h>

int main(int argc, char **argv)

{

int myid, numprocs;

MPI_Aint address1, address2, address3;

int a, b, c, dist1, dist2;

a = 1;

b = 2;

c = 3;

MPI_Init(&argc, &argv);

MPI_Comm_rank(MPI_COMM_WORLD, &myid);

MPI_Comm_size(MPI_COMM_WORLD, &numprocs);

// your code here

MPI_Address(&a, &address1);

MPI_Address(&b, &address2);

MPI_Address(&c, &address3);

// end of your code

dist1 = address2 - address1 ;

dist2 = address3 - address1 ;

if(myid == 0) {

printf("The distance between a and b is %d\n", dist1);

printf("The distance between a and c is %d\n", dist2);

}

MPI_Finalize();

return 0;

}

数据的打包(pack)

  • 有时候我们希望将不连续的数据或是不相同的数据类型的数据一起发送到其他进程,而不是效率很低地逐个发送。
  • 一个解决这个问题的方案是将数据封装成包,再将数据包放到一个连续的缓冲区,发送到接收缓冲区后再提取出来尽心解包。
  • 值得注意的是,打包/解包函数有时候还会用来代替系统缓存策略。此外,对于在MPI顶层进一步开发附加的通信库会起到辅助的作用。

int MPI_Pack(void* inbuf, int incount, MPI_datatype datatype, void *outbuf, int outcount, int *position, MPI_Comm comm)

void* inbuf : 输入缓冲区地址;

int incount :输入数据项数目;

MPI_datatype datatype :数据项的类型;

void *outbuf :输出缓冲区地址;

int outcount :输出缓冲区大小;

int *position :缓冲区当前位置;

MPI_Comm comm :通信子;

  • 实验说明:
  • 在源进程中打包发送一个数据包到进程1,进程1解包并打印出数据。
  • 输出结果:

The number is 1 and 2


#include <stdio.h>

#include <mpi.h>

int main(int argc, char **argv)

{

int myid, numprocs, source;

MPI_Status status;

int i, j, position;

int k[2];

int buf[1000];

MPI_Init(&argc, &argv);

MPI_Comm_rank(MPI_COMM_WORLD, &myid);

MPI_Comm_size(MPI_COMM_WORLD, &numprocs);

i = 1;

j = 2;

if(myid == 0) {

position = 0 ;

// your code here

MPI_Pack(&i, 1, MPI_INT, buf, 1000, &position, MPI_COMM_WORLD);

MPI_Pack(&j, 1, MPI_INT, buf, 1000, &position, MPI_COMM_WORLD);

// end of your code

MPI_Send(buf, position, MPI_PACKED, 1, 99, MPI_COMM_WORLD);

}

else if (myid == 1){

MPI_Recv(k, 2, MPI_INT, 0, 99, MPI_COMM_WORLD, &status);

position = 0 ;

MPI_Unpack(k, 2, &position, &i, 1, MPI_INT, MPI_COMM_WORLD);

MPI_Unpack(k, 2, &position, &j, 1, MPI_INT, MPI_COMM_WORLD);

printf("The number is %d and %d", i, j);

}

MPI_Finalize();

return 0;

}

数据的解包(unpack)

  • 解包是对应于打包的MPI操作。
  • 需要特别注意的是:
  • MPI_RECV和MPI_UNPACK的区别:
  • 在MPI_RECV中, count参数指明的是可以接收的最大项数. 实际接收的项数是由接收的消息的长度来决定的.
  • 在MPI_UNPACK中, count参数指明实际打包的项数; 相应消息的"size"是position的增加值.
  • 这种改动的原因是"输入消息的大小" 直到用户决定如何解包之前是不能预先确定的;
  • 从解包的项数来确定"消息大小"也是很困难的。

int MPI_Unpack(void* inbuf, int insize, int *position, void *outbuf, int outcount, MPI_Datatype datatype, MPI_Comm comm)

void* inbuf : 输入缓冲区地址;

int insize :输入数据项数目;

MPI_datatype datatype :数据项的类型;

void *outbuf :输出缓冲区地址;

int outcount :输出缓冲区大小;

int *position :缓冲区当前位置;

MPI_Comm comm :通信子;

规约(reduce)

  • 在现实生活中,我们常常需要对于数据做同一种操作,并将结果返回到指定的进程中,这个过程称为集合通信。例如,将数据分散到各个进程中,先在各个进程内进行求和,再在全局完成求和-平均这个操作,这个过程是一个规约的过程。
  • 一般来说,集合通信包括通信、同步和计算三个功能。不过,目前我们暂时不需要关注整个过程,而是先使用一个规约函数去体验一下集合通信。
  • int MPI_Reduce(void * input_data_p, void * output_data_p, int count, MPI_Datatype datatype, MPI_Op operator, int dest_process, MPI_Comm comm)
  • 规约函数,所有进程将待处理数据通过输入的操作子operator计算为最终结果并将它存入目标进程中。

void * input_data_p : 每个进程的待处理数据存放在input_data_p中;

void * output_data_p : 存放最终结果的目标进程的地址;

int count : 缓冲区中的数据个数;

MPI_Datatype datatype : 数据项的类型;

MPI_Op operator : 操作子,例如加减;

int dest_process : 目标进程的编号;

  • 实验说明:
  • 使用函数MPI_Reduce来完成加法规约到根进程的操作,并在根进程打印出总和和平均值。
  • 输出结果:
  • 由于这里是测试用例,所以每个进程的数值都是取3.0。所以,输出结果应该是总和等于进程数乘以3,平均值应该是3。

#include <stdio.h>

#include <mpi.h>

int main(int argc, char **argv)

{

int myid, numprocs;

double local_num = 3.0;

MPI_Init(&argc, &argv);

MPI_Comm_rank(MPI_COMM_WORLD, &myid);

MPI_Comm_size(MPI_COMM_WORLD, &numprocs);

double global_num;

//your code here

MPI_Reduce(&local_num, &global_num, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);

//end of your code

if(myid == 0) {

printf("Total sum = %f, avg = %f\n", global_num, global_num / numprocs);

}

MPI_Finalize();

return 0;

}

广播(broadcast)

  • 在一个集合通信中,如果属于一个进程的数据被发送到通信子中的所有进程,这样的集合通信叫做广播。
  • int MPI_Bcast(void* buffer, int count, MPI_Datatype datatype, int source, MPI_Comm comm)
  • 广播函数,从一个id值为source的进程将一条消息广播发送到通信子内的所有进程,包括它本身在内。

void*  buffer   缓冲区的起始地址;

int   count    缓冲区中的数据个数;

MPI_Datatype datatype   缓冲区中的数据类型;

int   source    发送信息的进程id;

MPI_Comm comm    通信子;

  • 实验说明:
  • 使用函数MPI_Bcast在根进程中发送一个数组到其他进程,并在其他进程中打印出来。
  • 输出结果:
  • 输出应该是这样的格式:

In process 1, arr[0]=1 arr[1]=2 arr[2]=3 arr[3]=4 arr[4]=5

In process 3, arr[0]=1 arr[1]=2 arr[2]=3 arr[3]=4 arr[4]=5

...

In process n, arr[0]=1 arr[1]=2 arr[2]=3 arr[3]=4 arr[4]=5


#include<stdio.h>

#include<mpi.h>

int main(int argc, char **argv)

{

int myid, numprocs;

int source = 0;

int array[5]={1,2,3,4,5};

int i;

MPI_Init(&argc, &argv);

MPI_Comm_rank(MPI_COMM_WORLD, &myid);

MPI_Comm_size(MPI_COMM_WORLD, &numprocs);

if(myid == source) {

for(i = 1; i <= 5; i++)

array[i] = i;

}

//your code here

MPI_Bcast(array, 5, MPI_INT, source, MPI_COMM_WORLD);

//end of your code

if(myid != source) {

printf("In process %d, ", myid);

for(i = 0; i < 5; i++)

printf("arr[%d]=%d\t", i, array[i]);

printf("\n");

}

MPI_Finalize();

return 0;

}

收集(gather)

  • 同样,有时候我们希望在一个进程中从所有进程获取信息,例如将所有进程中的一个数组都收集到根进程中作进一步的处理,这样的集合通信我们叫做收集。
  • `int MPI_Gather(void* sendbuf, int sendcount, MPI_Datatype sendtype,

void* recvbuf, int recvcount, MPI_Datatype recvtype,

int root, MPI_Comm comm)`

  • 收集函数,根进程(目标进程)从所有进程(包括它自己)收集发送缓冲区的数据,根进程根据发送这些数据的进程id将它们依次存放到自已的缓冲区中.

void* sendbuf 发送缓冲区的起始地址

int sendcount 发送缓冲区的数据个数

MPI_Datatype sendtype 发送缓冲区的数据类型

void* recvbuf 接收缓冲区的起始地址

int recvcount 待接收的元素个数

MPI_Datatype recvtype 接收的数据类型

int root 接收进程id

MPI_Comm comm 通信子

  • 实验说明:
  • 使用函数MPI_Gather在根进程中从所有进程接收一个数组,并在根进程中打印出来。
  • 输出结果:输出应该是这样的格式:

Now is process 1's data: arr[0]=1 arr[1]=2 arr[2]=3 arr[3]=4 arr[4]=5

Now is process 4's data: arr[0]=1 arr[1]=2 arr[2]=3 arr[3]=4 arr[4]=5

Now is process 2's data: arr[0]=1 arr[1]=2 arr[2]=3 arr[3]=4 arr[4]=5

...

Now is process n's data: arr[0]=1 arr[1]=2 arr[2]=3 arr[3]=4 arr[4]=5

...


#include<stdio.h>

#include<mpi.h>

int main(int argc, char **argv)

{

int myid, numprocs;

int dest = 0;

int array[5]={1,2,3,4,5};

int *rbuf;

int i,j;

MPI_Init(&argc, &argv);

MPI_Comm_rank(MPI_COMM_WORLD, &myid);

MPI_Comm_size(MPI_COMM_WORLD, &numprocs);

if(myid == dest) {

rbuf=(int *)malloc(numprocs*5*sizeof(int));

}

//your code here

MPI_Gather(array, 5, MPI_INT, rbuf, 5, MPI_INT, dest, MPI_COMM_WORLD);

//end of your code

if(myid == dest) {

for(i=dest+1;i<numprocs;i++) {

printf("Now is process %d's data: ", i);

for(j=0;j<5;j++) {

printf("array[%d]=%d\t", j, rbuf[i*5+j]);

}

printf("\n");

}

}

MPI_Finalize();

return 0;

}

14. 散发(scatter)

  • 在前面我们学习了收集(gather)操作,那么与之相对应也有一个相反的集合通信操作,即根进程向所有进程发送缓冲区的数据,称为散发。
  • 需要特别说明的是,散发操作和广播操作的区别在于发送到各个进程的信息可以是不同的
  • `int MPI_Scatter(void* sendbuf, int sendcount, MPI_Datatype sendtype,

void* recvbuf, int recvcount, MPI_Datatype recvtype,

int root, MPI_Comm comm)`

  • MPI_SCATTER是MPI_GATHER的逆操作,另外一种解释是根进程通过MPI_Send发送一条消息,这条消息被分成n等份,第i份发送给组中的第i个处理器, 然后每个处理器如上所述接收相应的消息。

void* sendbuf 发送缓冲区的起始地址

int sendcount 发送的数据个数

MPI_Datatype sendtype 发送缓冲区中的数据类型

void* recvbuf 接收缓冲区的起始地址

int recvcount 待接收的元素个数

MPI_Datatype recvtype 接收的数据类型

int root 发送进程id

MPI_Comm comm 通信子

  • 实验说明:
  • 使用函数MPI_Scatter在根进程中向所有进程发送对应数组,并在对应进程中打印出来。
  • 输出结果:输出应该是这样的格式:

Now is process 1: arr[0]=5 arr[1]=6 arr[2]=7 arr[3]=8 arr[4]=9

Now is process 4: arr[0]=20 arr[1]=21 arr[2]=22 arr[3]=23 arr[4]=24

Now is process 2: arr[0]=10 arr[1]=11 arr[2]=12 arr[3]=13 arr[4]=14

...

Now is process n: arr[0]=5n arr[1]=5n+1 arr[2]=5n+2 arr[3]=5n+3 arr[4]=5*n+4

...


#include<stdio.h>

#include<mpi.h>

int main(int argc, char **argv)

{

int myid, numprocs;

int source = 0;

int *sbuf;

int rbuf[5];

int i;

MPI_Init(&argc, &argv);

MPI_Comm_rank(MPI_COMM_WORLD, &myid);

MPI_Comm_size(MPI_COMM_WORLD, &numprocs);

if(myid == source) {

sbuf=(int *)malloc(numprocs*5*sizeof(int));

for(i=0;i<numprocs*5;i++) {

sbuf[i]=i;

}

}

// your code here

MPI_Scatter(sbuf, 5, MPI_INT, rbuf, 5, MPI_INT, source, MPI_COMM_WORLD);

// end of your code

printf("Now is process %d: ", myid);

for(i=0;i<5;i++) {

printf("array[%d]=%d\t", i, rbuf[i]);

}

printf("\n");

MPI_Finalize();

return 0;

}

15. 组的管理-创建(1)

并发面试14 min read

1. 初级

1.1 初级学习者的定义

初级阶段规划针对的主要人群:

  • 绝大部分刚入学的大学生(高考非高分)
  • 低分飞过四六级的大学生
  • 基础一般但也没太差的人
  • 过去英文还不错,但是放弃太久想捡回来的人
  • 有一定基础,想快速提高使用技能尤其是听说能力的职场人士
  • 勉强能听懂VOA慢速英语,长苏英语完全抓瞎,也不知道怎么学英语的人

初级可能是整个课程里性价比最高的章节:

  • 几乎每天都能觉察到提高
  • 每天学到的东西第二题立刻就能涌出来
  • 尤其适用于太忙没有时间的职场人士

好处:

  • 听懂同事说话
  • 各方面交流头功面试都没问题
  • 几乎免费,而且非常有效果
  • 搞定职场大部分英文口语问题

坚持下去!!!

1.2 初级学习者目标和效果评估

初级希望大家能够达到的学习目标

  • 基本的日常和商业口语用法词汇量(大约达到3000左右)
  • 系统的学完发音,并掌握基本的连读只是和自然语流规律
  • 具备基本的开口交流能力

选择初级的前提:具备了基本的高中英语基础!

1.3 初级学习者学习规划和方法

初级分为三大阶段,一共学完大约需要1到3个月(视水平基础)

  • 正音(1-3周)
  • 日常口语和语流训练(1到2个月)
  • 商业材料听力,商业领域用法,和词汇量积累(1到2个月)

1.3.1 正音

1.3.1.1 材料1《赖世雄 美语音标》

选用教材:《赖世雄 美语音标》不到20元

这本书比较与众不同,全书的精华就在于播讲录音

这本书配有他的全文讲解,详细讲解了所有发音的诀窍,并且用自己的发音反复做例证,带领大家认证学好

这种个人学习的诀窍和体验,对于大部分想提高发音的同学来说,可以说是无价之宝

比其它粗制滥造拼凑出来的发音书好太多了

为什么一定要从音标学起?

因为练听力的最佳方法不是直接就挑选适合自己的材料开始挺,而是先学发音,然后再听

推荐学习方法

第一步:

看书听CD不要暂停,跟着录音大声模仿,从头到尾系统把书学个2-3遍

中间遇到自己觉得比较难的章节和句子,在书里面做笔记

第一步建议在一周内搞定

第二步:

找出重难点的章节,重点出击学习,反复听讲解和模仿,尽可能突破

如果实在突破不了,可以暂时先放着,不影响,因为一开始听力能力较低,肯定不能模仿好所有发音的。

这一步也尽量在一两周内搞定

少即是多,慢即是快

建议至少要在2-3周把书过几遍

然后立刻进入初级阶段的第二个环节大量的实战输入,这样才能达到学习效果的巩固和最大化

注意要点1

本书中的所有音标,都是用KK美式音标标注的

  • 和大家从小学习的国际音标标注法略有不同,因此会有人反映学习起来有些困难
  • 完全忘记自己学过音标,从头开始学,当做一门新的学问来学习

注意要点2

有关KK音标的实用问题

  • 目前只要是稍微正式的词典,都一定会标注英美两种发音
  • 一般都是英音在前,美音在后,无论是牛津朗文还是剑桥,都是如此

注意要点3

发音不可能通过一本书就学好

  • 仅仅通过一本书的学习量,很难把你持续了整整20年的糟糕发音习惯全部都改过来
  • 一本书的含量毕竟有限,不可能把所有发音现象规律都囊括进去,更不能提供足够多的练习让你把发音规律彻底活化
  • 这些练习,我们将在第三阶段提供

注意要点4

学音标就一定要用!绝对不要自我欺骗

  • 有很多人已学习就会患上金鱼症,记忆时间超级短,学的时候跟着赖老师读的很欢唱,很标准,把书抛开,立刻打回原形,而且不是说不知道自己说错了,而是明明知道错了,却懒得改
  • 每天学一点新的规则和舌位,就必须立刻用起来!不管什么场合,谈话,朗读,背单词,都要有意识的去多使用,哪怕再难受,直到彻底成为习惯
1.3.1.2 材料2

Coach Shane's Daily Easy English Expression

  • 是由美国的英语老师coach shane制作的
  • Coach Shane并没有什么显赫的背景,身份和头衔,但他是我在数十年间,遇到过最厉害的老师,教学功力非常深厚,很难找到这么一位如此擅长语音,听力和口语教学的老师
  • 这套材料,一共有1000多个视频,每个视频都很短,也就只有几分钟而已

为什么一定要从音标学起?

  • 想要提高耳朵和大脑对听力的反应速度,训练的关键点在于摸清英文信息语流的规律
  • 把这些规律总结出来,让大脑进行学习
  • 一旦大脑熟悉摸清各类规律,就能大幅提高理解力和理解速度
  • 而英文语流的规律中,分拆到最简化最细小的元素,就是单个的音标了

单单是把音标学好,就能大幅优化你的大脑处理器,学完之后你会发现自己对于英文听力更加敏感,听力能力也突然提高了一大截,没有比这性价比更高的事儿了

英语学习技巧6 min read