奇偶排序
odd-even-sort, using MPI
代碼在 https://github.com/thkkk/odd-even-sort
使用 MPI 實現奇偶排序算法, 並且 MPI 進程 只能向其相鄰進程發送消息
nprocs
是進程數。 每個進程擁有獨立的一塊數據 data[0 ~ block_len-1]
,組合起來為整個待排序的數組。
方法
每個階段排序之后不進行check
此前,在每個階段的奇偶排序進行完之后,都會進行一次進程之間的信息傳遞,以判斷排序是否完成,這個過程要進行約\(3*nprocs\)次的send/recv。現在的優化是:總共只進行nprocs輪排序,不再進行check。這樣的話,即使是目前在最小編號進程中的元素,而它值較大,本應排序到最大編號進程中,也可以在nprocs輪中排到正確的位置。
這樣之后,大約有幾十ms的優化。
進程之間互相傳遞數據,然后進行優化后的歸並
在一個排序階段中,相鄰進程塊互相發送自己的全部數據,之后在每個塊內部將兩個塊的數據進行歸並,但是只保留最小/最大的block_len個元素,將其拷貝到自己的data上。這樣可以省掉一半的歸並時間。
這樣之后大約有100+ms的優化。
進程之間發送全部數據之前,先發送端點處的數據
進程之間發送全部數據之前,先發送端點處的數據,判斷左邊進程中的最大元素是否小於等於右邊進程中的最小元素,如果是,那么無需進行后續數據的發送和歸並。
這樣之后大約有幾十ms的優化。
代碼
#include <algorithm>
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <mpi.h>
#include <cmath>
#include "worker.h"
using namespace std;
bool is_edge(int rank, bool odd_or_even, bool last_rank){
if (odd_or_even == 0){
return (rank & 1) == 0 && last_rank;
}
else{
return rank == 0 || ((rank & 1) == 1 && last_rank);
}
}
void merge_left(float *A, int nA, float *B, int nB, float *C){ //make sure C[nA-1] is available
float *p1 = A, *A_end = A + nA, *p2 = B, *B_end = B + nB, *p = C, *C_end = C + nA;
while( p != C_end && p1 != A_end && p2 != B_end)
*(p++) = ((*p1) <= (*p2)) ? *(p1++) : *(p2++);
while( p != C_end )
*(p++) = *(p1++);
}
void merge_right(float *A, int nA, float *B, int nB, float *C){
float *p1 = A + nA , *p2 = B + nB , *p = C + nB;
while( p != C && p1 != A && p2 != B )
*(--p) = (*(p1-1) >= *(p2-1)) ? *(--p1) : *(--p2);
while( p != C )
*(--p) = *(--p2);
}
void Worker::sort() {
//data[0, block_len)
if (out_of_range) return ;
std::sort(data, data + block_len);
//先把當前進程數據排好序
if (nprocs == 1) return ;
bool odd_or_even = 0; // = 0: even; = 1: odd;
float *cp_data = new float [block_len];
float *adj_data = new float [ceiling(n, nprocs)];
int limit = nprocs;
while(limit--){
if(is_edge(rank, odd_or_even, last_rank)){
//邊界情況,沒有與其他進程存在於同一個進程塊內
}
else if((rank & 1) == odd_or_even){ //receive info
size_t adj_block_len = std::min(block_len, n - (rank + 1) * block_len);
MPI_Request request[2];
MPI_Isend(data + block_len - 1, 1, MPI_FLOAT, rank + 1, 0, MPI_COMM_WORLD, &request[0]);
MPI_Irecv(adj_data, 1, MPI_FLOAT, rank + 1, 1, MPI_COMM_WORLD, &request[1]);
MPI_Wait(&request[0], MPI_STATUS_IGNORE);
MPI_Wait(&request[1], MPI_STATUS_IGNORE); //發送端點數據
if(data [block_len - 1] > adj_data[0]) {
//此時兩塊之間存在未排好序的數據,需要排序
MPI_Sendrecv(data, block_len, MPI_FLOAT, rank + 1, 0,
adj_data, adj_block_len, MPI_FLOAT, rank + 1, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
//互相交換數據
// merge
merge_left(data, (int)block_len, adj_data, (int)adj_block_len, cp_data);
//進行歸並排序,取前block_len個數據返回到cp_data中
memcpy(data, cp_data, block_len * sizeof(float)); //拷貝回data
}
}
else if ((rank & 1) == !odd_or_even){ //send info
size_t adj_block_len = ceiling(n, nprocs);
MPI_Request request[2];
MPI_Isend(data, 1, MPI_FLOAT, rank - 1, 1, MPI_COMM_WORLD, &request[1]);
MPI_Irecv(adj_data + adj_block_len - 1, 1, MPI_FLOAT, rank
- 1, 0, MPI_COMM_WORLD, &request[0]);
MPI_Wait(&request[1], MPI_STATUS_IGNORE);
MPI_Wait(&request[0], MPI_STATUS_IGNORE);
//發送端點數據
if (adj_data[adj_block_len - 1] > data[0]){
//此時兩塊之間存在未排好序的數據,需要排序
MPI_Sendrecv(data, block_len, MPI_FLOAT, rank - 1, 1,
adj_data, adj_block_len, MPI_FLOAT, rank - 1, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
//互相交換數據
// merge
merge_right(adj_data, (int)adj_block_len, data, (int)block_len, cp_data);
//進行歸並排序,取前block_len個數據返回到cp_data中
memcpy(data, cp_data, block_len * sizeof(float)); //拷貝回data
}
}
odd_or_even ^= 1;
}
delete[] cp_data;
delete[] adj_data;
}
實驗數據
n | N\(\times\) P | 耗時(ms) | 相對單進程的加速比 |
---|---|---|---|
100000000 | 1$\times$1 | 12728.326000 | 1 |
100000000 | 1$\times$2 | 6754.229000 | 1.884 |
100000000 | 1$\times$4 | 3559.514000 | 3.576 |
100000000 | 1$\times$8 | 2007.818000 | 6.339 |
100000000 | 1$\times$16 | 1340.771000 | 9.493 |
100000000 | 2$\times$16 | 870.302000 | 14.625 |