尽管array_size不能被进程数完全整除,但如何在MPI中的进程之间大致均匀地共享工作?

时间:2013-03-27 11:53:45

标签: mpi

大家好,我有一个长度为N的数组,我想在'size'处理器之间尽可能地划分它。 N /大小有一个余数,例如1000个数组元素除以7个进程,或14个进程3个进程。

我至少知道MPI中的两种工作共享方式,例如:

for (i=rank; i<N;i+=size){ a[i] = DO_SOME_WORK } 

但是,这并没有将数组划分为连续的块,我想这样做,因为我认为由于IO原因更快。

另一个我知道的是:

int count = N / size;
int start = rank * count;
int stop = start + count;

// now perform the loop
int nloops = 0;

for (int i=start; i<stop; ++i)
{
    a[i] = DO_SOME_WORK;
} 

但是,使用这种方法,对于我的第一个例子,我们得到1000/7 = 142 = count。所以最后一个等级从852开始,到994结束。最后6行被忽略。

最好的解决方案是将这样的东西附加到上一个代码中吗?

int remainder = N%size;
int start = N-remainder; 
if (rank == 0){
     for (i=start;i<N;i++){
         a[i] = DO_SOME_WORK;
     }

这看起来很混乱,如果它是最好的解决方案我很惊讶我没有在其他地方见过它。

感谢您的帮助!

8 个答案:

答案 0 :(得分:6)

如果我有N个任务(例如,数组元素)和size个工作人员(例如,MPI排名),我会按如下方式进行:

int count = N / size;
int remainder = N % size;
int start, stop;

if (rank < remainder) {
    // The first 'remainder' ranks get 'count + 1' tasks each
    start = rank * (count + 1);
    stop = start + count;
} else {
    // The remaining 'size - remainder' ranks get 'count' task each
    start = rank * count + remainder;
    stop = start + (count - 1);
}

for (int i = start; i <= stop; ++i) { a[i] = DO_SOME_WORK(); }

这就是它的工作原理:

/*
  # ranks:                    remainder                     size - remainder
            /------------------------------------\ /-----------------------------\
     rank:      0         1             remainder-1                         size-1
           +---------+---------+-......-+---------+-------+-------+-.....-+-------+
    tasks: | count+1 | count+1 | ...... | count+1 | count | count | ..... | count |
           +---------+---------+-......-+---------+-------+-------+-.....-+-------+
                      ^       ^                            ^     ^
                      |       |                            |     |
   task #:  rank * (count+1)  |        rank * count + remainder  |
                              |                                  |
   task #:  rank * (count+1) + count   rank * count + remainder + count - 1

            \------------------------------------/ 
  # tasks:       remainder * count + remainder
*/

答案 1 :(得分:3)

考虑您的“1000步和7个流程”示例。

  • 简单除法不起作用,因为整数除法(在C中)给你发言权,你留下一些余数:即1000/7是142,并且将有6个doodads挂出

  • 天花板分区有相反的问题:ceil(1000/7)是143,但是最后一个处理器超出阵列,或者最终用比其他处理器做得少。

您要求的方案是将余数均匀分配到处理器上。一些流程应该有142个,其他流程143个。必须有一个更正式的方法,但考虑到这个问题在过去六个月中得到的关注可能不是。

这是我的方法。每个进程都需要执行此算法,并选择自己需要的答案。

#include <mpi.h>
#include <stdio.h>
#include <stdlib.h>

int main (int argc, char ** argv)
{
#define NR_ITEMS 1000
    int i, rank, nprocs;;
    int *bins;

    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    MPI_Comm_size(MPI_COMM_WORLD, &nprocs);
    bins = calloc(nprocs, sizeof(int));

    int nr_alloced = 0;
    for (i=0; i<nprocs; i++) {
        remainder = NR_ITEMS - nr_alloced;
        buckets = (nprocs - i);
        /* if you want the "big" buckets up front, do ceiling division */
        bins[i] = remainder / buckets;
        nr_alloced += bins[i];
    }

    if (rank == 0)
        for (i=0; i<nprocs; i++) printf("%d ", bins[i]);

    MPI_Finalize();
    return 0;
}

答案 2 :(得分:2)

这是一个封闭形式的解决方案。

设N =数组长度,P =处理器数。

j = 0到 P -1,

处理器上的数组的起点 j = floor( N * j / P

处理器上的数组长度 j = floor( N *( j + 1)/ P ) - floor( N * j / P

答案 3 :(得分:1)

我认为最好的解决方案是为自己编写一个小功能,以便跨进程分配。这里有一些伪代码,我确定你能写出C(在你的问题中是C吗?)比我能做得更好。

function split_evenly_enough(num_steps, num_processes)
    return = repmat(0, num_processes)  ! pseudo-Matlab for an array of num_processes 0s
    steps_per_process = ceiling(num_steps/num_processes)
    return = steps_per_process - 1 ! set all elements of the return vector to this number
    return(1:mod(num_steps, num_processes)) = steps_per_process  ! some processes have 1 more step
end

答案 4 :(得分:1)

我知道这已经很久没了,但是一个简单的方法是给每个进程提供(项目数)/(进程数)+(如果process_num&lt; num_items mod num_procs则为1)。在python中,一个具有工作计数的数组:

# Number of items
NI=128
# Number of processes
NP=20

# Items per process
[NI/NP + (1 if P < NI%NP else 0)for P in range(0,NP)]

答案 5 :(得分:1)

改进@Alexander 的回答:利用 min 来浓缩逻辑。

int count = N / size;
int remainder = N % size;
int start = rank * count + min(rank, remainder);
int stop = (rank + 1) * count + min(rank + 1, remainder);

for (int i = start; i < stop; ++i) { a[i] = DO_SOME_WORK(); }

答案 6 :(得分:0)

这个怎么样?

int* distribute(int total, int processes) {
    int* distribution = new int[processes];
    int last = processes - 1;        

    int remaining = total;
    int process = 0;

    while (remaining != 0) {
        ++distribution[process];
        --remaining;

        if (process != last) {
            ++process;
        }
        else {
            process = 0;
        }
    }

    return distribution;
}

这个想法是你将一个元素分配给第一个进程,然后将元素分配给第二个进程,然后将元素分配给第三个进程,依此类推,每当到达最后一个进程时跳回第一个进程。 / p>

即使进程数大于而不是元素数,此方法也能正常工作。它只使用非常简单的操作,因此应该非常快。

答案 7 :(得分:0)

我遇到了类似的问题,这是我使用Python和mpi4py API的非最佳解决方案。一个最佳的解决方案将考虑处理器的布局,这里额外的工作是低级别的。不均衡的工作量只有一项任务不同,所以一般来说这不应该是一件大事。

from mpi4py import MPI
import sys
def get_start_end(comm,N):
    """
    Distribute N consecutive things (rows of a matrix , blocks of a 1D array)
    as evenly as possible over a given communicator.
    Uneven workload (differs by 1 at most) is on the initial ranks.

    Parameters
    ----------
    comm: MPI communicator
    N:  int
    Total number of things to be distributed.

    Returns
    ----------
    rstart: index of first local row
    rend: 1 + index of last row

    Notes
    ----------
    Index is zero based.
    """

    P      = comm.size
    rank   = comm.rank
    rstart = 0
    rend   = N
    if P >= N:
        if rank < N:
            rstart = rank
            rend   = rank + 1
        else:
            rstart = 0
            rend   = 0
    else:
        n = N//P # Integer division PEP-238
        remainder = N%P
        rstart    = n * rank
        rend      = n * (rank+1)
        if remainder:
            if rank >= remainder:
                rstart += remainder
                rend   += remainder
            else:
                rstart += rank
                rend   += rank + 1
    return rstart, rend

if __name__ == '__main__':
    comm = MPI.COMM_WORLD
    n = int(sys.argv[1])
    print(comm.rank,get_start_end(comm,n))