使用MPI计算素数个数(只计算奇数部分)
在上一版本中实现了基本的功能,但是还有不是很完美。
这一次要再上一版的基础中,不计算偶数,这样在理论上可以提高一倍的效率。
在串行的程序中实现这样的想法是一个很简单的功能,但是现在是一个并行计算的程序,在任务的分配和计算上会有一些小麻烦。
任务的分配:
low_index = id * ((n - 1) / 2) / p;
high_index = (id+1) * ((n-1) / 2) / p;
low_value = low_index * 2 + 3;
high_value = high_index * 2 + 1;
size = (high_value - low_value) / 2 + 1;
采用的方法是先进行分配索引,然后再让每一进程计算自己所需要计算的最小值和最大值。
first的计算:
if (prime * prime > low_value)
first = (prime * prime - low_value)/2;
else {
if (!(low_value % prime)) first = 0;
else if(low_value % prime % 2 ==0)
first = prime - ((low_value % prime)/2); // 此处在求局部first(数组中第一个可以被prime整除的数)的时候非常巧妙
else
first = (prime - (low_value % prime))/2;
关于改造first的计算用了比较长的时间,这里涉及到了一点的小技巧。
- 当四个进程来分配计算100内的素数时:
0:3-25
0:count 9
1:27-49
1:count 6
2:51-73
2:count 6
3:75-99
3:count 4
The total number of prime: 25, total time: 0.000061, total node 4
如上图,在寻找第一行能被5整除的元素时,距离27最近的是25,在上面由黑色笔写。
这个时候通过下面语句可以计算出结果:
first = prime - ((low_value % prime)/2);
但是对于第二行在寻找可以被7整除的元素是,发现计算出来的索引值是错误的。通过分析发现,距离75最近可以被7整除的数数时70(但是70并没有在计算遍历范围内),通过上式计算出来的结果也不是正确的。
这时使用:
first = (prime - (low_value % prime))/2;
可以计算出正确的索引值first。
(为了找到合适的方法计算出first的值,在这里折腾了好长时间。。。)
版本二(跳过偶数)
#include "mpi.h"
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#define MIN(a, b) ((a)<(b)?(a):(b))
int main(int argc, char *argv[]) {
unsigned long int count; /* Local prime count */
double elapsed_time; /* Parallel execution time */
unsigned long int first; /* Index of first multiple */
unsigned long int global_count = 0; /* Global prime count */
unsigned long long int high_value; /* Highest value on this proc */
unsigned long int i;
int id; /* Process ID number */
unsigned long int index; /* Index of current prime */
unsigned long long int low_value; /* Lowest value on this proc */
char *marked; /* Portion of 2,...,'n' */
unsigned long long int n; /* Sieving from 2, ..., 'n' */
int p; /* Number of processes */
unsigned long int proc0_size; /* Size of proc 0's subarray */
unsigned long int prime; /* Current prime */
unsigned long int size; /* Elements in 'marked' */
unsigned long int low_index; // 低位对应的全局索引值
unsigned long int high_index; // 高位对应的全局索引值
MPI_Init(&argc, &argv);
/* Start the timer */
MPI_Comm_rank(MPI_COMM_WORLD, &id); //获取进程id
MPI_Comm_size(MPI_COMM_WORLD, &p); //获取进程数量
MPI_Barrier(MPI_COMM_WORLD); //进行同步
elapsed_time = -MPI_Wtime();
// if (argc != 2) {
// if (!id) printf("Command line: %s <m>\n", argv[0]);
// MPI_Finalize();
// exit(1);
// }
// n = atoll(argv[1]); //获取要计算的数
n = 1000; //获取要计算的数
/* Figure out this process's share of the array, as
well as the integers represented by the first and
last array elements 计算这个进程在数组中的份额,以及第一个和最后一个数组元素表示的整数*/
/*********originalSoution*******/
// low_value = 2 + id * (n - 1) / p;
// high_value = 1 + (id + 1) * (n - 1) / p;
// size = high_value - low_value + 1;
/********solution1**********/
if (n % 2 == 0) // 如果给出的是一个偶数,将给出的数减一变为奇数。
n = n -1;
// low_value = 3 + id * (n - 1) / p;
// high_value = 1 + (id + 1) * (n - 1) / p;
low_index = id * ((n - 1) / 2) / p;
high_index = (id+1) * ((n-1) / 2) / p;
low_value = low_index * 2 + 3;
high_value = high_index * 2 + 1;
printf("%d:%d-%d\n",id,low_value,high_value);
size = (high_value - low_value) / 2 + 1;
/* Bail out if all the primes used for sieving are
not all held by process 0 如果用于筛选的所有质数不都由进程0持有,则退出*/
proc0_size = (n - 1) / p;
if ((2 + proc0_size) < (int) sqrt((double) n)) {
if (!id) printf("Too many processes\n");
MPI_Finalize();
exit(1);
}
/* Allocate this process's share of the array.分配此进程在数组中的份额 */
marked = (char *) malloc(size);
if (marked == NULL) {
printf("Cannot allocate enough memory\n");
MPI_Finalize();
exit(1);
}
for (i = 0; i < size; i++) marked[i] = 0;
if (!id) index = 0; // 不明白为什么要一直判断!id----->只有0号进程才会执行。
prime = 3;
do {
if (prime * prime > low_value)
first = (prime * prime - low_value)/2;
else {
if (!(low_value % prime)) first = 0;
else if(low_value % prime % 2 ==0)
first = prime - ((low_value % prime)/2); // 此处在求局部first(数组中第一个可以被prime整除的数)的时候非常巧妙
else
first = (prime - (low_value % prime))/2;
}
// if (!id)
// printf("---%d",first);
for (i = first; i < size; i += prime)
marked[i] = 1;
if (!id) {
while (marked[++index]);
// prime = index + 2;
prime = index * 2 + 3; // 要重新写出一个prime的计算表达式
}
if (p > 1) MPI_Bcast(&prime, 1, MPI_INT, 0, MPI_COMM_WORLD); //广播,将一个进程中的数据发送到所有进程
} while (prime * prime <= n);
count = 0;
for (i = 0; i < size; i++)
if (!marked[i])
{
count++; // 统计单个进程中素数的个数,看有多少个0
}
if(!id) count++;
printf("%d:count %d\n",id,count);
if (p > 1)
MPI_Reduce(&count, &global_count, 1, MPI_INT, MPI_SUM,
0, MPI_COMM_WORLD); // 规约,集合通信,由进程0来计算全局的count
/* Stop the timer */
elapsed_time += MPI_Wtime();
/* Print the results */
if (!id) {
printf("The total number of prime: %ld, total time: %10.6f, total node %d\n", global_count, elapsed_time, p);
}
MPI_Finalize();
return 0;
}
输出:(四个核心计算10的八次方)
>>>
0:3-24999999
1:25000001-49999999
2:50000001-74999999
3:75000001-99999999
1:count 1435207
0:count 1565926
2:count 1393170
3:count 1367151
The total number of prime: 5761455, total time: 0.881289, total node 4
版本一的计算要花费2.1秒,而版本二只要0.8812秒,效果显著。
再看看计算10的九次方(16个核心):
>>>
0:3-62499999
1:62500001-124999999
2:125000001-187499999
3:187500001-249999999
4:250000001-312499999
5:312500001-374999999
6:375000001-437499999
8:500000001-562499999
9:562500001-624999999
10:625000001-687499999
12:750000001-812499999
13:812500001-874999999
7:437500001-499999999
11:687500001-749999999
14:875000001-937499999
15:937500001-999999999
2:count 3314265
0:count 3701487
3:count 3254911
1:count 3408655
5:count 3179482
4:count 3212954
7:count 3130885
6:count 3153228
8:count 3110499
10:count 3078479
11:count 3064367
9:count 3093969
15:count 3020834
12:count 3052785
14:count 3029831
13:count 3040903
The total number of prime: 50847534, total time: 4.620501, total node 16