【问题标题】:name of algorithm related to load balancing / re-distribution与负载平衡/重新分配相关的算法名称
【发布时间】:2011-12-07 07:45:40
【问题描述】:

给定一个数组 [x1, x2, x3, ..., xk ],其中 xi 是盒子 i 中的项目数, 我怎样才能重新分配这些项目,以便没有一个盒子包含超过 N 个项目。 N 接近 sum(xi)/k - 也就是说,N 接近于每个具有相同数量项目的框。中间箱子不应该用来搬运物品——如果 x1 有盈余,而 x2 和 x3 有赤字,x1 应该将一些物品发送给 x2 和 x3,但不要将其所有物品发送给 x2,然后让 x2 解决盈余.

实际的问题是:每个计算节点都有一组样本,在重采样步骤之后,一些计算机节点可能有盈余,而另一些则有赤字,所以我想在最小化通信的同时重新分配样本。

我想这类问题是有名字的。

【问题讨论】:

  • 您的问题未详细说明。例如,十几个远距离节点能否以 1 的成本同时与其近邻通信?如果有 100 个节点向 X 发送消息,它们可以一次全部发送,成本为 1,还是必须序列化,成本为 100?不同的算法将适用于不同的models of computation。特别要说明network topology 和/或分布式内存模型。
  • 确实说的不够详细,但是下面的几个答案已经提供了很大的帮助。

标签: algorithm distributed-computing sampling


【解决方案1】:

这个问题可以建模为minimum-cost flow的一个实例。

令 G 是一个有向图,顶点为 s, t, a1, ..., ak, b1, ..., b k 和弧 s→ai 容量 xi 和成本 0,弧 ai→bj 容量无限且成本为 0 如果 i = j 和成本 1 如果 i ≠ j 且弧 bj→t 容量为 N 且成本为 0。我们想要最小成本将 ∑i xi 个单位从 s 移动到 t 的流。这个想法是,如果 y 个单元流动 ai→bj,那么 y 个项目从框 i 移动到框 j(如果 i ≠ j;如果 i = j,然后不会发生移动)。

使用最小成本流来解决这个简单的问题当然是用大锤来破解难题,但是可以将几种变体建模为网络流问题。

  • 如果一对节点i、j不直接相连,去掉ai→bj弧。

  • 我们可以通过仅在“a”侧或“b”侧为节点提供顶点来启动和关闭节点。

  • 我们可以通过调整统一成本来模拟通信速度的差异。

  • 我们可以通过限制弧的容量来限制两个节点交换的项目数。

  • 我们可以引入更多内部顶点并更改连接的完整二分拓扑,以模拟网络争用的影响。

【讨论】:

    【解决方案2】:

    我不认为您的问题(如上所述)复杂到足以吸引独立研究。如果机器计数(称为C)为数千,而您的样本计数甚至数万亿,那么将样本counts发送到协调主节点是微不足道的。

    然后,主节点可以简单地 (O(C)) 计算 N,识别违反此界限的节点以及数量。请注意,超出界限的总和恰好是所需的最小通信量。通过在计算N时插入一个松弛参数(即通过接受不平衡),您可以控制这个数量。

    使用排序,可以在O(C log C) 中按样本数对节点进行排序。从两端向中间移动两个光标。在每个步骤中,安排从大节点到小节点的传输,大小为大节点中剩余的多余部分或小节点中剩余的松弛部分的最小值。推进最后一句中具有活动约束的节点的指针,并重复直到新的大节点没有多余。 (我相信这是@Noxville 的贪婪算法。)

    假设N 大于每个节点的平均样本数,很容易看出这是理想的w.r.t。最少的沟通。

    如果您的机器网络有任何约束,例如缺少链接或最大流量或跨边的可变成本,那么您需要分解图流的东西。但是你没有提到这样的事情,而且同一个数据中心内的计算机集群通常没有。

    【讨论】:

      【解决方案3】:

      听起来你想使用一致的哈希

      http://en.wikipedia.org/wiki/Consistent_hashing

      基本上,使用良好的随机散列函数可以让您为样本获得唯一的 ID,从而实现均匀分布。然后很容易将样本一致地分布在一组节点上。

      http://www.lexemetech.com/2007/11/consistent-hashing.html http://www.tomkleinpeter.com/2008/03/17/programmers-toolbox-part-3-consistent-hashing/

      了解更多信息。

      【讨论】:

      • 感谢您的回答。我不打算添加或删除计算节点,我还应该使用它吗?我不完全明白这如何解决我提出的问题。
      • 一致的哈希只是分布式哈希表的一种特殊形式。从技术上讲,如果您从不添加或删除节点,您可以做一些稍微简单的事情。人们通常只是简单地对数据的 id 进行哈希处理,然后通过节点数来确定数据属于哪个节点。
      • 我想尽量减少交流。如果一个节点有多余的项目并且一个节点有足够的空缺,我想将所有多余的项目从一个节点移动到另一个节点。我不需要知道哪个节点包含某个项目,只要某个节点确实
      • 这样的散列解决方案保证了在所有节点上的均匀分布。假设您使用的是分布良好的哈希函数(md5 可以正常工作)并且不仅仅是几个项目。
      • 我认为您没有考虑到我想最小化从一个节点到另一个节点的通信。如果节点 A 有 50 个超出容量的项目,节点 B、C 和 D 有 100 个空闲槽,我不想在 B、C 和 D 之间平均分配。我想从 B、C 或 D 中选择一个并移动所有 50 个到那个节点。
      【解决方案4】:

      我认为应该这样澄清问题:

      对于M 盈余节点和K 赤字节点,我们应该将节点之间的样本重新分配到具有 0 个盈余节点和(可能)一些赤字节点的状态。样品应成包交换,并且应尽量减少这些包装的数量。

      或者,在数学上,我们有M*K 矩阵,它的每个单元格表示从节点M 传递到节点K 的样本数,给定每行中的元素总和并给定元素总和的最大值在每一列。目标是尽量减少非零单元的数量。

      这是"Constraint satisfaction problems"的一种。它是NP完全的。我发现了两个与这个问题类似的经典问题:“Set packing”和“Generalized exact cover”。

      为了将问题减少到"Set packing",我们需要(临时)添加几个带有N+1 样本的剩余节点,以便在重新分配后没有剩余节点。然后对于每个节点,我们需要为剩余元素和“不足”元素生成所有可能的分区。然后对盈余和赤字分区的Cartesian product 应用其“优化”版本中的“集打包”,它会找到最小的子集数。

      为了将问题减少到"Generalized exact cover",对于每个节点,我们需要为剩余元素和“赤字”元素生成所有可能的分区。然后我们应该添加MM+1,...优化节点以最小化封面中的子集数量。然后对盈余和赤字分区的笛卡尔积和优化节点应用“广义精确覆盖”。对于较少数量的优化节点,此算法将失败,对于较大数量的优化节点,它将找到最小子集数。

      “广义精确覆盖”可以通过"Knuth's Algorithm X"解决。我不知道“设置打包”的任何算法。

      所有这些解决方案都给出了准确的答案,但它们具有巨大的计算复杂性,不太可能有人在真正的调度程序中使用它们。实用的方法是使用一些启发式和贪心算法。只需对剩余节点和不足节点进行排序并应用“最佳拟合”策略即可。

      【讨论】:

        【解决方案5】:

        我通常只看到这称为数据重新分配,其理解是,如果您要重新分配它,您希望分配在某些指标下是最佳的,例如任务之间的均匀性。

        当您尝试进行计算负载平衡时,这确实会出现在科学/技术计算中。即使您在多个维度上进行计算,如果您通过空间填充曲线重新分配分配给处理器的空间数据,就会出现这个确切的问题,并且您通常确实希望数据被平均分配。

        程序非常简单;您首先获取 xi 的专属prefix sum,以便知道您的“左侧”有多少项目。例如,对于上面 Noxville 的示例,如果您有数据

        [9, 6,  1,  6,  2] 
        

        前缀总和是

        [0, 9, 15, 16, 22]
        

        您会发现(从最后一个处理器的总和加上它的数量)总共有 24 个项目。

        然后你会计算出你理想的分区有多大——比如 ceil(totitems / nprocs)。您可以随意执行此操作,只要每个处理器都同意所有分区大小。

        现在,您有几种方法可以继续。如果数据项在某种意义上很大,并且您不能在内存中拥有它们的两个副本,那么您可以开始将数据转移到最近的邻居。你知道你左边的项目数量和那个方向的“过剩”或“赤字”;而且你也知道你有多少(并且在你完成了你的部分来解决过剩或赤字之后将会有)。因此,您开始向您的左右邻居发送数据,并从您的左右邻居接收数据,直到左侧的处理器共同拥有正确数量的项目并且您也这样做。

        但是,如果您有能力拥有两个数据副本,那么您可以采用另一种方法来最大限度地减少发送的消息数量。您可以将左侧的单元格数视为本地数据在“全局”数组中的起始索引。由于您知道每个处理器最终将处理多少个项目,因此您可以直接确定这些项目最终将在哪个进程中,并可以直接发送它们。 (例如,在上面的示例中,处理器 0 - 具有项目 0..8 - 知道如果除最后一个处理器之外的每个处理器都将结束 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
        

        【讨论】:

          【解决方案6】:

          基本上你想从

          [9, 6, 1, 6, 2] 
          

          [5, 5, 4, 5, 5]
          

          我认为最好的方法是计算 floor(sum(array)/len(array)),然后评估到达该位置所需的差异。在这种情况下, floor( (9+6+1+6+2) / 5) = 4,因此我们正在查看 [-5, -2, +3, -2, +2] 的初始微分。然后,您可以贪婪地交换符号不同的相邻对中的值(例如从 array[2] -> arr[1] 传输 2 和从 array[4] -> array[3] 传输 2)。然后你就剩下 [-5,0,1,0,0] 了,你可以从这里贪婪地分配剩余的位。

          【讨论】:

          • 我真的在寻找一个名字。您提供的算法很直观,但我想要最小化或接近最小化交换的东西。
          【解决方案7】:

          我认为这个重新分配问题与计算中的负载平衡有点不同。

          负载平衡算法术语通常表示策略/启发式的集合,以确保未来负载(而不是当前负载)的相对均匀分布。

          在这种情况下,负载均衡器不会重新分配来自现有服务器/系统的负载,但是任何新的请求都会尝试使用一些策略(即最小负载、循环等)进行分配,这将保持从长远来看,服务器的负载相对均匀。

          http://en.wikipedia.org/wiki/Load_balancing_(computing)

          这个问题中的负载再分配,也许可以通过迭代地将项目从最大负载框移动到最小负载框来实现。

          【讨论】:

          • 我也很犹豫是否称它为负载平衡,但我确实认为这个概念是相关的,或者至少这是我现在脑海中唯一的查询词。您区分了“未来”负载与“当前”负载。重新采样后,一台服务器的粒子数可能是另一台服务器的两倍。对这些粒子进行的计算可以被认为是“未来”负载,您可以通过分布 当前 样本集来平衡它。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2017-04-20
          • 2022-01-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-06-12
          • 2018-02-17
          相关资源
          最近更新 更多