实现一种或多种并行排序算法——基于MPI+OpenMP的并行程序设计
目录
1 题目描述
实现一种或多种并行排序算法。
(1)使用MPI、OpenMP、MPI+OpenMP编写上述并行程序。
(2)使用VTune等工具对程序进行瓶颈分析和优化。
(3)提交程序源代码、变量和语句的详细说明。
(4)在实验报告中通过图表说明CPU串行程序和三种并行程序在各种规模的运行时间。
(5)(选做)在实验报告中通过图表说明三种并行程序使用不同的数据分配方法在各种规模的运行时间。
2 设计思路
CPU串行程序:定义快速排序quicksort
函数,在主函数中生成大小为SIZE * ARRAY_SIZE
的逆序数组,调用quicksort
函数进行排序。利用chrono
库中的chrono::system_clock::now()
函数进行计时,auto duration = chrono::duration_cast<chrono::microseconds>(end - start)
(单位为微秒)即为快速排序串行程序所需时间。
MPI程序:定义快速排序quicksort
函数,生成大小为SIZE * ARRAY_SIZE
的二维逆序数组,MPI_Comm_rank(MPI_COMM_WORLD,&comm_id)
获取进程ID,传入主函数InitF(comm_id)
。MPI_Comm_size(MPI_COMM_WORLD,&numtasks)
获取进程数量numtasks
。通过通信函数MPI_Scatter
将每个子矩阵发送到每个子进程,然后通过quicksort
函数对每个子进程进行快速排序,将每个子进程通过通信函数MPI_Gather
收回到0号进程中,最后再对收回了所有数的0号进程中的数组进行最后一次排序,就能得到排完序的数组。利用MPI_Wtime()
函数进行计时,double(end_time - start_time)
即为快速排序串行程序所需时间。
OpenMP程序:定义快速排序quicksort
函数。在quicksort
里面用#pragma omp parallel sections
选择并行区域,将数组均分通过#pragma omp section
将两部分通过两个线程进行快速排序。在主函数内生成大小为SIZE * ARRAY_SIZE
的二维逆序数组,也将数组均分通过#pragma omp section
将两部分通过两个线程调用quicksort
函数。利用chrono
库中的chrono::system_clock::now()
函数进行计时,auto duration = chrono::duration_cast<chrono::microseconds>(end - start)
(单位为微秒)即为OpenMP并行快速排序程序所需时间。
OpenMP+MPI混合编程:在MPI程序的基础上,将MPI程序中的quicksort
函数改为OpenMP程序中使用了并行块的快速排序quicksort
函数,然后运行排序即可。利用MPI_Wtime()
函数进行计时,double(end_time - start_time)
即为快速排序串行程序所需时间。
实验环境
操作系统:Windows10
开发环境:Visual Studio 2019 + MPI + OpenMP + VTune
3 源码
3.1 串行程序
#include "bits/stdc++.h"
#define SIZE 4
#define ARRAY_SIZE 3000
using namespace std;
//串行数组
int arrC[SIZE * ARRAY_SIZE];
//输出数组
void PrintArr(int *array, int len) {
for (int i = 0; i < len; i++) {
cout << array[i] << " ";
}
cout << endl;
}
//交换函数
void swap(int &a, int &b) {
int tp;
tp = a;
a = b;
b = tp;
}
//快速排序
void quicksort(int *arr, int l, int r) {
int i, m;
if (l >= r) return;
m = l;
for (i = l + 1; i <= r; i++)
if (arr[i] < arr[l]) {
swap(arr[++m], arr[i]);
}
swap(arr[l], arr[m]);
quicksort(arr, l, m - 1);
quicksort(arr, m + 1, r);
}
//串行主程序
void InitC() {
//用于给数组赋值
int FG = 50000;
//生成数组
for (int i = 0; i < SIZE * ARRAY_SIZE; i++) {
arrC[i] = FG--;
}
//开始计时
auto start = chrono::system_clock::now();
//快速排序
quicksort(arrC, 0, SIZE * ARRAY_SIZE - 1);
//结束计时
auto end = chrono::system_clock::now();
//计算耗时
auto duration = chrono::duration_cast<chrono::microseconds>(end - start);
// //输出数组arrC
// PrintArr(arrC, SIZE * ARRAY_SIZE);
//输出时间
cout << "Serial running time = " << (double) (duration.count()) / 1000000 << " s" << endl;
}
int main() {
//串行排序
InitC();
return 0;
}
3.2 MPI程序
#include "mpi.h"
#include "omp.h"
//#include <iostream>
//#include <cstdlib>
//#include <ctime>
//#include <iomanip>
//#include "chrono"
#include "bits/stdc++.h"
using namespace std;
//进程数
#define SIZE 4
//每个进程分配的个数
#define ARRAY_SIZE 3000
//初始数组
int arr[SIZE][ARRAY_SIZE];
//合并数组
int G_S_arr[SIZE][ARRAY_SIZE];
//时间标记
double start_time, end_time;
//串行数组
int arrC[SIZE * ARRAY_SIZE];
//输出数组
void PrintArr(int *array, int len) {
for (int i = 0; i < len; i++) {
cout << array[i] << " ";
}
cout << endl;
}
//交换函数
void swap(int &a, int &b) {
int tp;
tp = a;
a = b;
b = tp;
}
//快速排序
void quicksort(int *arr, int l, int r) {
int i, m;
if (l >= r) return;
m = l;
for (i = l + 1; i <= r; i++)
if (arr[i] < arr[l]) {
swap(arr[++m], arr[i]);
}
swap(arr[l], arr[m]);
quicksort(arr, l, m - 1);
quicksort(arr, m + 1, r);
}
//并行主程序
void InitF(int comm_id) {
//通信域comm中所包含的进程数
int numtasks;
//四分之一个初始数组数组的数
int G_4_arr[ARRAY_SIZE];
//用于给数组赋值
int FG = 50000;
//排完序的完整数组
static int arrD[SIZE * ARRAY_SIZE];
//生成数组
for (int i = 0; i < SIZE; i++) {
for (int j = 0; j < ARRAY_SIZE; j++) {
arr[i][j] = FG--;
}
}
//返回调用处理器上经过的挂钟时间,以秒为单位(双精度)。
start_time = MPI_Wtime();
//返回指定通信器中MPI进程的总数numtasks
MPI_Comm_size(MPI_COMM_WORLD, &numtasks);
/** MPI_Comm_size(comm,&size)
* 返回指定通信器中MPI进程的总数,例如MPI_COMM_WORLD。
* 如果通信器是MPI_COMM_WORLD,则它表示可用于您的应用程序的MPI任务数。
* IN comm 通行域(句柄)
* OUT size 通信域comm中所包含的进程数
*/
if (numtasks == SIZE) {
//并行排序,将数组平均分成4块后分给每个子进程排序
MPI_Scatter(arr, ARRAY_SIZE, MPI_INT, G_4_arr, ARRAY_SIZE, MPI_INT, 0, MPI_COMM_WORLD);
/** MPI_Scatter(&sendbuf,sendcnt,sendtype,&recvbuf,recvcnt,recvtype,root,comm)
* IN sendbuf 发送消息缓冲区的起始地址(可变,仅对于根进程)
* IN sendcount 发送到各个进程的数据个数(整型,仅对于根进程)
* IN sendtype 发送消息缓冲区中的数据类型(句柄,仅对于根进程)
*OUT recvbuf 接收消息缓冲区的起始地址(可变)
* IN recvcount 待接收的元素个数(整型)
* IN recvtype 接收元素的数据类型(句柄)
* IN root 发送进程的序列号(整型)
* IN comm 通信子(句柄)
* 通过根进程向同一个通信域中的所有进程发送数据,
* 将数据发送缓冲区的数据分割成长度相等的段,然后分段发送数据给每个进程,
* 如果每段包含N个数据,则向进程i发送的段为[send[iN],int[iN+N])
*/
//每个进程里的排序
quicksort(G_4_arr, 0, ARRAY_SIZE - 1);
//将子进程排完序的数组合并成一个
MPI_Gather(G_4_arr, ARRAY_SIZE, MPI_INT, G_S_arr, ARRAY_SIZE, MPI_INT, 0, MPI_COMM_WORLD);
/** MPI_Gather(&sendbuf,sendcnt,sendtype,&recvbuf, recvcount,recvtype,root,comm)
* IN sendbuf 发送消息缓冲区的起始地址(可变)
* IN sendcount 发送消息缓冲区中的数据个数(整型)
* IN sendtype 发送消息缓冲区中的数据类型(句柄)
* OUT recvbuf 接收消息缓冲区的起始地址(可变,仅对于根进程)
* IN recvcount 待接收的元素个数(整型,仅对于根进程)
* IN recvtype 接收元素的数据类型(句柄,仅对于根进程)
* IN root 接收进程的序列号(整型)
* IN comm 通信子(句柄)
* 数据移动操作。收集从组中每个任务到单个目标任务的不同消息。
* 此例程是MPI_Scatter的反向操作。
* 每个进程(包括根进程)将其发送缓冲区中的内容发送到根进程,
* 根进程根据发送这些数据的进程的序列号将它们依次存放到自已的消息缓冲区中.
* 其结果就象一个组中的n个进程(包括根进程在内)都执行了一个调用
*/
//数组用于记录存放选择次数
int num_times[SIZE] = {0};
//用于存放合并数组时最大的四个数的数组
int max_num_4arr[SIZE];
//按升序顺序合并为一个数组
for (int i = ARRAY_SIZE * SIZE - 1; i >= 0; i--) {
//找出四个数组里面分别最大的数
for (int j = 0; j < SIZE; j++) {
if (num_times[j] >= ARRAY_SIZE) {
max_num_4arr[j] = 0;
} else {
max_num_4arr[j] = G_S_arr[j][ARRAY_SIZE - num_times[j] - 1];
}
}
//找出四个数里面最大的数
int max_num = max_num_4arr[0];
for (int k = 1; k < SIZE; k++) {
if (max_num_4arr[k] > max_num) {
max_num = max_num_4arr[k];
}
}
//标记数最大的数组
for (int n = 0; n < SIZE; n++) {
if (max_num == max_num_4arr[n]) {
num_times[n] = num_times[n] + 1;
break;
}
}
//存入数组arrD
arrD[i] = max_num;
}
//结束计时
end_time = MPI_Wtime();
// //输出数组
// if (!comm_id) {
// PrintArr(arrD, SIZE * ARRAY_SIZE);
// }
}
//输出运行时间
if (!comm_id) {
cout << "Parallel running time = " << double(end_time - start_time) << " s" << endl;
}
}
//串行主程序
void InitC() {
//用于给数组赋值
int FG = 50000;
//生成数组
for (int i = 0; i < SIZE * ARRAY_SIZE; i++) {
arrC[i] = FG--;
}
//开始计时
auto start = chrono::system_clock::now();
//快速排序
quicksort(arrC, 0, SIZE * ARRAY_SIZE - 1);
//结束计时
auto end = chrono::system_clock::now();
//计算耗时
auto duration = chrono::duration_cast<chrono::microseconds>(end - start);
// //输出数组arrC
// PrintArr(arrC, SIZE * ARRAY_SIZE);
//输出时间
cout << "Serial running time = " << (double) (duration.count()) / 1000000 << " s" << endl;
}
int main(int argc, char *argv[]) {
//进程在comm中的标识号id
int comm_id;
//MPI初始化
MPI_Init(&argc, &argv);
/** MPI_Init(&argc,&argv)
* 是MPI程序的第一个调用,初始化MPI执行环境。
* 必须在每个MPI程序中调用此函数,必须在任何其他MPI函数之前调用此函数,并且在MPI程序中只能调用一次。
* 对于C程序,MPI_Init可以用于将命令行参数传递给所有进程,这不是标准要求的,取决于实际。
*/
//获取MPI进程编号
MPI_Comm_rank(MPI_COMM_WORLD, &comm_id);
/** MPI_Comm_rank(comm,&rank)
* 返回指定通信器中调用MPI进程的编号
* IN comm 该进程所在的通信域(句柄)一般为MPI_COMM_WORLD
* OUT rank 所调用进程在comm中的标识号id
*/
//并行主函数
InitF(comm_id);
//终止MPI
MPI_Finalize();
/** MPI_Finalize();
* 是MPI程序的最后一个调用,终止MPI执行环境。
* 此函数应该是每个MPI程序中最后一个调用的MPI例程-此后不得再调用其他MPI例程。
*/
//串行主函数
// InitC();
return 0;
}
3.3 OpenMP程序
#include "omp.h"
//#include <iostream>
//#include <cstdlib>
//#include <ctime>
//#include <iomanip>
//#include "chrono"
#include "bits/stdc++.h"
#define SIZE 2
using namespace std;
const int Num = 12000;
//输出数组
void PrintArr(int *array, int len) {
for (int i = 0; i < len; i++) {
cout << array[i] << " ";
}
cout << endl;
}
//交换函数
void swap(int &a, int &b) {
int tp;
tp = a;
a = b;
b = tp;
}
//快速排序函数
void quicksort(int *arr, int l, int r) {
int i, m;
if (l >= r) return;
m = l;
for (i = l + 1; i <= r; i++)
if (arr[i] < arr[l]) {
swap(arr[++m], arr[i]);
}
swap(arr[l], arr[m]);
#pragma omp parallel sections//两个并行块
{
#pragma omp section
quicksort(arr, l, m - 1);
#pragma omp section
quicksort(arr, m + 1, r);
};
}
//并行主程序
void InitB(int *arrB, int len) {
//设置线程数
omp_set_num_threads(SIZE);
//计时标记
//将arrB分为两个小的数组,并行的对他们调用快速排序算法
int half_len = len / 2;
//创建两个长度为arrB一半的数组
int arrR[half_len], arrL[half_len];
//开始计时
auto start = chrono::system_clock::now();
//#pragma omp parallel default(none) shared(arrB, arrR, arrL, half_len)
//并行分配,parallel是构造并行块的一个指令
{
#pragma omp parallel for
for (int i = 0; i < half_len; i++) {
arrR[i] = arrB[i];//将B[]的前5000个数放入D[]
arrL[i] = arrB[i + half_len];//将B[]的后5000个数放入E[]
}
}
//并行排序,变量共享,
#pragma omp parallel default(none) shared(arrR, arrL, half_len) //快速排序的并行region
{
//sections语句将代码分成不同的分块,通过section指令来指定分块。每一个分块都会并行执行
#pragma omp parallel sections
{
//section分块,各分块并行执行
#pragma omp section
quicksort(arrR, 0, half_len - 1);//排序
#pragma omp section
quicksort(arrL, 0, half_len - 1);//排序
}
}
//二维数组,为了合并用
int arrDone[SIZE][half_len];
//将两个数组合并为一个的二维数组
{
int h = 0;
for (int ll = 0; ll < half_len; ll++) {
arrDone[h][ll] = arrR[ll];
}
h++;
for (int ll = 0; ll < half_len; ll++) {
arrDone[h][ll] = arrL[ll];
}
}
//数组存放选择次数
int num_times[SIZE] = {0};
//数组存放各个数组中最大的数
int max_num_2arr[SIZE];
// int arrSorted[half_len * SIZE];//排完序的数组
//按升序顺序合并为一个数组
for (int i = half_len * SIZE - 1; i >= 0; i--) {
//找出2个数组里面分别最大的
for (int j = 0; j < SIZE; j++) {
if (num_times[j] >= half_len) {
max_num_2arr[j] = 0;
} else {
max_num_2arr[j] = arrDone[j][half_len - num_times[j] - 1];
}
}
//初始化最大值
int max_num = max_num_2arr[0];
//找出2个数里面最大的
for (int k = 1; k < SIZE; k++) {
if (max_num_2arr[k] > max_num) {
max_num = max_num_2arr[k];
}
}
//标记数最大的数组
for (int n = 0; n < SIZE; n++) {
if (max_num == max_num_2arr[n]) {
num_times[n] = num_times[n] + 1;
break;
}
}
//存入arrB
arrB[i] = max_num;
}
//结束计时
auto end = chrono::system_clock::now();
// chrono::duration<double> diff = end - start;
// PrintArr(arrB, len); //打印数组arrB
//输出运行时间
auto duration = chrono::duration_cast<chrono::microseconds>(end - start);
cout << "Parallel running time = " << (double) (duration.count() ) / 1000000 << " s" << endl;
}
void InitC(int *arrC, int len) {
//时间标记
//开始计时
auto start = chrono::system_clock::now();
//快速排序
quicksort(arrC, 0, len - 1);
//结束计时
auto end = chrono::system_clock::now();
//输出运行时间
auto duration = chrono::duration_cast<chrono::microseconds>(end - start);
cout << "Serial running time = " << (double) (duration.count() ) / 1000000 << " s" << endl;
}
int main(int argc, char *argv[]) {
int len = Num;
int arrB[len], arrC[len];
//用于给数组赋值
int FG = 50000;
//初始化数组
for (int i = 0; i < Num; i++) {
arrB[i] = FG--;
arrC[i] = FG--;
}
//并行开始
InitB(arrB, len);
//串行开始
// InitC(arrC, len);
return 0;
}
3.4 MPI+OpenMP程序
#include "mpi.h"
#include "omp.h"
//#include <iostream>
//#include <cstdlib>
//#include <ctime>
//#include <iomanip>
//#include "chrono"
#include "bits/stdc++.h"
using namespace std;
//进程数
#define SIZE 4
//每个进程分配的个数
#define ARRAY_SIZE 3000
//初始数组
int arr[SIZE][ARRAY_SIZE];
//合并数组
int G_S_arr[SIZE][ARRAY_SIZE];
//时间标记
double start_time, end_time;
//串行数组
int arrC[SIZE * ARRAY_SIZE];
//输出数组
void PrintArr(int *array, int len) {
for (int i = 0; i < len; i++) {
cout << array[i] << " ";
}
cout << endl;
}
//交换函数
void swap(int &a, int &b) {
int tp;
tp = a;
a = b;
b = tp;
}
//快速排序
void quicksort(int *arr, int l, int r) {
int i, m;
if (l >= r) return;
m = l;
for (i = l + 1; i <= r; i++)
if (arr[i] < arr[l]) {
swap(arr[++m], arr[i]);
}
swap(arr[l], arr[m]);
#pragma omp parallel sections//两个并行块
{
#pragma omp section
quicksort(arr, l, m - 1);
#pragma omp section
quicksort(arr, m + 1, r);
};
}
//并行主程序
void InitF(int comm_id) {
//通信域comm中所包含的进程数
int numtasks;
//四分之一个初始数组数组的数
int G_4_arr[ARRAY_SIZE];
//用于给数组赋值
int FG = 100000;
//排完序的完整数组
static int arrD[SIZE * ARRAY_SIZE];
//生成数组
for (int i = 0; i < SIZE; i++) {
for (int j = 0; j < ARRAY_SIZE; j++) {
arr[i][j] = FG--;
}
}
//返回调用处理器上经过的挂钟时间,以秒为单位(双精度)。
start_time = MPI_Wtime();
//返回指定通信器中MPI进程的总数numtasks
MPI_Comm_size(MPI_COMM_WORLD, &numtasks);
/** MPI_Comm_size(comm,&size)
* 返回指定通信器中MPI进程的总数,例如MPI_COMM_WORLD。
* 如果通信器是MPI_COMM_WORLD,则它表示可用于您的应用程序的MPI任务数。
* IN comm 通行域(句柄)
* OUT size 通信域comm中所包含的进程数
*/
//并行排序,将数组平均分成4块后分给每个子进程排序
MPI_Scatter(arr, ARRAY_SIZE, MPI_INT, G_4_arr, ARRAY_SIZE, MPI_INT, 0, MPI_COMM_WORLD);
/** MPI_Scatter(&sendbuf,sendcnt,sendtype,&recvbuf,recvcnt,recvtype,root,comm)
* IN sendbuf 发送消息缓冲区的起始地址(可变,仅对于根进程)
* IN sendcount 发送到各个进程的数据个数(整型,仅对于根进程)
* IN sendtype 发送消息缓冲区中的数据类型(句柄,仅对于根进程)
*OUT recvbuf 接收消息缓冲区的起始地址(可变)
* IN recvcount 待接收的元素个数(整型)
* IN recvtype 接收元素的数据类型(句柄)
* IN root 发送进程的序列号(整型)
* IN comm 通信子(句柄)
* 通过根进程向同一个通信域中的所有进程发送数据,
* 将数据发送缓冲区的数据分割成长度相等的段,然后分段发送数据给每个进程,
* 如果每段包含N个数据,则向进程i发送的段为[send[iN],int[iN+N])
*/
//每个进程里的排序
quicksort(G_4_arr, 0, ARRAY_SIZE - 1);
//将子进程排完序的数组合并成一个
MPI_Gather(G_4_arr, ARRAY_SIZE, MPI_INT, G_S_arr, ARRAY_SIZE, MPI_INT, 0, MPI_COMM_WORLD);
/** MPI_Gather(&sendbuf,sendcnt,sendtype,&recvbuf, recvcount,recvtype,root,comm)
* IN sendbuf 发送消息缓冲区的起始地址(可变)
* IN sendcount 发送消息缓冲区中的数据个数(整型)
* IN sendtype 发送消息缓冲区中的数据类型(句柄)
* OUT recvbuf 接收消息缓冲区的起始地址(可变,仅对于根进程)
* IN recvcount 待接收的元素个数(整型,仅对于根进程)
* IN recvtype 接收元素的数据类型(句柄,仅对于根进程)
* IN root 接收进程的序列号(整型)
* IN comm 通信子(句柄)
* 数据移动操作。收集从组中每个任务到单个目标任务的不同消息。
* 此例程是MPI_Scatter的反向操作。
* 每个进程(包括根进程)将其发送缓冲区中的内容发送到根进程,
* 根进程根据发送这些数据的进程的序列号将它们依次存放到自已的消息缓冲区中.
* 其结果就象一个组中的n个进程(包括根进程在内)都执行了一个调用
*/
//数组用于记录存放选择次数
int num_times[SIZE] = {0};
//用于存放合并数组时最大的四个数的数组
int max_num_4arr[SIZE];
//按升序顺序合并为一个数组
for (int i = ARRAY_SIZE * SIZE - 1; i >= 0; i--) {
//找出四个数组里面分别最大的数
for (int j = 0; j < SIZE; j++) {
if (num_times[j] >= ARRAY_SIZE) {
max_num_4arr[j] = 0;
} else {
max_num_4arr[j] = G_S_arr[j][ARRAY_SIZE - num_times[j] - 1];
}
}
//找出四个数里面最大的数
int max_num = max_num_4arr[0];
for (int k = 1; k < SIZE; k++) {
if (max_num_4arr[k] > max_num) {
max_num = max_num_4arr[k];
}
}
//标记数最大的数组
for (int n = 0; n < SIZE; n++) {
if (max_num == max_num_4arr[n]) {
num_times[n] = num_times[n] + 1;
break;
}
}
//存入数组arrD
arrD[i] = max_num;
}
//结束计时
end_time = MPI_Wtime();
//输出数组
if (!comm_id) {
PrintArr(arrD, SIZE * ARRAY_SIZE);
}
//输出运行时间
if (!comm_id) {
double time = end_time - start_time;
cout << "Parallel running time = " << time << " s" << endl;
//cout << time << endl;
}
}
int main(int argc, char *argv[]) {
omp_set_num_threads(SIZE);
//进程在comm中的标识号id
int comm_id;
//MPI初始化
MPI_Init(&argc, &argv);
/** MPI_Init(&argc,&argv)
* 是MPI程序的第一个调用,初始化MPI执行环境。
* 必须在每个MPI程序中调用此函数,必须在任何其他MPI函数之前调用此函数,并且在MPI程序中只能调用一次。
* 对于C程序,MPI_Init可以用于将命令行参数传递给所有进程,这不是标准要求的,取决于实际。
*/
//获取MPI进程编号
MPI_Comm_rank(MPI_COMM_WORLD, &comm_id);
/** MPI_Comm_rank(comm,&rank)
* 返回指定通信器中调用MPI进程的编号
* IN comm 该进程所在的通信域(句柄)一般为MPI_COMM_WORLD
* OUT rank 所调用进程在comm中的标识号id
*/
//并行主函数
InitF(comm_id);
//终止MPI
MPI_Finalize();
/** MPI_Finalize();
* 是MPI程序的最后一个调用,终止MPI执行环境。
* 此函数应该是每个MPI程序中最后一个调用的MPI例程-此后不得再调用其他MPI例程。
*/
return 0;
}
3.5 性能对比与分析
从图1 CPU串行程序和三种并行程序在各种规模的运行时间可以看出CPU串行程序运行消耗的时间极大,且增长速度极快。在三种并行程序中,MPI并行程序运行消耗的时间最少,MPI+OpenMP程序运行消耗的时间次之,OpenMP程序运行消耗的时间最多,但都相差不大。当数据规模N=96000时,CPU串行程序运行消耗的时间大约是并行程序的4.79倍~10.87倍。
图1 CPU串行程序和三种并行程序在各种规模的运行时间
利用VTune对程序的瓶颈分析:略