私は通常、これをデータ再配布と呼びます。再配布する場合は、タスク間の均一性など、あるメトリックの下で分散を最適化する必要があることを理解しています。
これは、計算負荷分散を行おうとしているときに、科学/技術コンピューティングで発生します。複数の次元で計算を行っている場合でも、空間充填曲線によってプロセッサに割り当てた空間データを再配布する場合、この正確な問題が発生し、データを均等に分割したいことがよくあります。
手順は非常に簡単です。まず、x iの排他的なプレフィックスの合計を取得して、「左側」にあるアイテムの数を把握します。たとえば、上記のNoxvilleの例では、データがある場合
[9, 6, 1, 6, 2]
プレフィックスの合計は次のようになります
[0, 9, 15, 16, 22]
そして、(最後のプロセッサの合計とそのプロセッサの数から)合計で24個のアイテムがあることがわかります。
次に、理想的なパーティションの大きさを計算します。たとえば、ceil(totitems / nprocs)です。すべてのプロセッサがすべてのパーティションサイズに同意する限り、これは好きなように行うことができます。
これで、続行する方法がいくつかあります。データ項目がある意味で大きく、それらの2つのコピーをメモリに保持できない場合は、データを最も近い隣人だけにシフトし始めることができます。あなたはあなたの左側のアイテムの数とその方向の「過剰」または「不足」を知っています。そして、あなたはあなたが持っている数も知っています(そしてあなたが過剰または不足を修正するためにあなたの役割を果たした後に持っているでしょう)。したがって、左右のネイバーにデータを送信し始め、左側のプロセッサが集合的に適切な量のアイテムを取得するまで、左右のネイバーからデータを受信します。
ただし、データのコピーを2つ持つ余裕がある場合は、送信されるメッセージの数を最小限に抑える別のアプローチを取ることができます。左側のセルの数は、「グローバル」配列へのローカルデータの開始インデックスと考えることができます。各プロセッサが最終的にいくつのアイテムになるかがわかっているので、それらのアイテムが最終的にどのプロセスになるかを直接把握し、それらを直接送信できます。(たとえば、上記の例では、項目0..8を持つプロセッサ0は、最後のプロセッサ以外の各プロセッサが5つのデータ項目で終わる場合、値5〜8をプロセッサ1に送信できることを認識しています。 )それらが送信されると、期待する量のデータが得られるまで受信するだけです。これで完了です。
以下は、CおよびMPIでこれを行う簡単な例ですが、基本的なアプローチはほとんどどこでも機能するはずです。MPIのプレフィックススキャン操作は包括的合計を生成するため、排他的合計を取得するには、独自の値の数を減算する必要があります。
#include <stdio.h>
#include <stdlib.h>
#include <mpi.h>
#include <time.h>
void initdata(const int rank, const int maxvals, char **data, int *nvals) {
time_t t;
unsigned seed;
t = time(NULL);
seed = (unsigned)(t * (rank + 1));
srand(seed);
*nvals = (rand() % (maxvals-1)) + 1;
*data = malloc((*nvals+1) * sizeof(char));
for (int i=0; i<*nvals; i++) {
(*data)[i] = 'A' + (rank % 26);
}
(*data)[*nvals] = '\0';
}
int assignrank(const int globalid, const int totvals, const int size) {
int nvalsperrank = (totvals + size - 1)/size;
return (globalid/nvalsperrank);
}
void redistribute(char **data, const int totvals, const int curvals, const int globalstart,
const int rank, const int size, int *newnvals) {
const int stag = 1;
int nvalsperrank = (totvals + size - 1)/size;
*newnvals = nvalsperrank;
if (rank == size-1) *newnvals = totvals - (size-1)*nvalsperrank;
char *newdata = malloc((*newnvals+1) * sizeof(char));
newdata[(*newnvals)] = '\0';
MPI_Request requests[curvals];
int nmsgs=0;
/* figure out whose data we have, redistribute it */
int start=0;
int newrank = assignrank(globalstart, totvals, size);
for (int val=1; val<curvals; val++) {
int nextrank = assignrank(globalstart+val, totvals, size);
if (nextrank != newrank) {
MPI_Isend(&((*data)[start]), (val-1)-start+1, MPI_CHAR, newrank, stag, MPI_COMM_WORLD, &(requests[nmsgs]));
nmsgs++;
start = val;
newrank = nextrank;
}
}
MPI_Isend(&((*data)[start]), curvals-start, MPI_CHAR, newrank, stag, MPI_COMM_WORLD, &(requests[nmsgs]));
nmsgs++;
/* now receive all of our data */
int newvalssofar= 0;
int count;
MPI_Status status;
while (newvalssofar != *newnvals) {
MPI_Recv(&(newdata[newvalssofar]), *newnvals - newvalssofar, MPI_CHAR, MPI_ANY_SOURCE, stag, MPI_COMM_WORLD, &status);
MPI_Get_count(&status, MPI_CHAR, &count);
newvalssofar += count;
}
/* wait until all of our sends have been received */
MPI_Status statuses[curvals];
MPI_Waitall(nmsgs, requests, statuses);
/* now we can get rid of data and relace it with newdata */
free(*data);
*data = newdata;
}
int main(int argc, char **argv) {
const int maxvals=30;
int size, rank;
char *data;
int mycurnvals, mylvals, myfinalnvals;
int totvals;
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &size);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
initdata(rank, maxvals, &data, &mycurnvals);
MPI_Scan( &mycurnvals, &mylvals, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD );
if (rank == size-1) totvals = mylvals;
mylvals -= mycurnvals;
MPI_Bcast( &totvals, 1, MPI_INT, size-1, MPI_COMM_WORLD );
printf("%3d : %s %d\n", rank, data, mylvals);
redistribute(&data, totvals, mycurnvals, mylvals, rank, size, &myfinalnvals);
printf("%3d after: %s\n", rank, data);
free(data);
MPI_Finalize();
return 0;
}
これを実行すると、期待される動作が得られます。(ceil(totvals / nprocesses)を使用して)「望ましい」パーティショニングを決定した方法では、通常、最終的なプロセッサの負荷が低くなることに注意してください。また、再配布で順序が保持されるようにする試みは行っていません(ただし、順序が重要な場合は簡単に実行できます)。
$ mpirun -np 13 ./distribute
0 : AAAAAAAAAAA 0
1 : BBBBBBBBBBBB 11
2 : CCCCCCCCCCCCCCCCCCCCCCCCCC 23
3 : DDDDDDD 49
4 : EEEEEEEEE 56
5 : FFFFFFFFFFFFFFFFFF 65
6 : G 83
7 : HHHHHHH 84
8 : IIIIIIIIIIIIIIIIIIIII 91
9 : JJJJJJJJJJJJJJJJJJ 112
10 : KKKKKKKKKKKKKKKKKKKK 130
11 : LLLLLLLLLLLLLLLLLLLLLLLLLLLL 150
12 : MMMMMMMMMMMMMMMMMM 178
0 after: AAAAAAAAAAABBBBB
1 after: BBBBBBBCCCCCCCCC
2 after: CCCCCCCCCCCCCCCC
3 after: DDDDDDDCEEEEEEEE
4 after: EFFFFFFFFFFFFFFF
5 after: FFFHHHHHHHIIIIIG
6 after: IIIIIIIIIIIIIIII
7 after: JJJJJJJJJJJJJJJJ
8 after: JJKKKKKKKKKKKKKK
9 after: LLLLLLLLLLKKKKKK
10 after: LLLLLLLLLLLLLLLL
11 after: LLMMMMMMMMMMMMMM
12 after: MMMM