название алгоритма, связанного с балансировкой / перераспределением нагрузки - PullRequest
7 голосов
/ 07 декабря 2011

Учитывая массив [x1, x2, x3, ..., xk], где xi - количество элементов в блоке i, как я могу перераспределить элементы, чтобы ни один блок не содержал больше, чем N элементов.N близко к сумме (xi) / k - то есть N близко к каждой коробке, имеющей одинаковое количество элементов.Промежуточные блоки не должны использоваться для переноса предметов - если у х1 есть излишки, а у х2 и х3 есть дефициты, х1 должен отправить некоторые предметы в х2 и в х3, но не отправлять все свои предметы в х2, а затем позволить х2 разрешить излишки.

Фактическая проблема заключается в том, что каждый вычислительный узел имеет набор выборок, и после шага повторной выборки некоторые компьютерные узлы могут иметь избыток, а другие - дефицит, поэтому я хотел бы перераспределить выборкипри этом сводя к минимуму общение.

Я полагаю, что у такого рода проблемы есть имя.

Ответы [ 7 ]

3 голосов
/ 11 декабря 2011

Я не верю, что ваша проблема (как указано) достаточно сложна, чтобы привлечь независимое изучение. Если количество машин (назовите его C) исчисляется тысячами, а количество выборок составляет даже триллионы, то отправка выборки count на координирующий главный узел - тривиально.

Затем мастер может тривиально (O(C)) вычислить N, определить узлы, нарушающие эту границу, и на сколько. Обратите внимание, что сумма превышений за пределом является как раз минимальным количеством требуемой связи. Вставляя параметр слабости при расчете N (т.е. принимая дисбаланс), вы можете контролировать эту величину.

Используя сортировку, упорядочение узлов по количеству отсчетов может быть выполнено в O(C log C). Пройдите два курсора, один с обоих концов, к середине. На каждом шаге запланируйте передачу от большого узла к маленькому узлу, размер которого должен быть минимальным из оставшегося избытка в большом, или оставшийся слабый в малом. Продвиньте указатель любого узла, который имел активное ограничение в последнем предложении, и повторяйте, пока новый большой не будет иметь избытка. (Я полагаю, что это жадный алгоритм, к которому @Noxville прибегал.)

Предполагая, что N больше, чем среднее количество выборок на узел, легко понять, что это идеально для w.r.t. минимальная связь.

Если ваша сеть машин имеет какие-либо ограничения , либо в виде отсутствующих связей, либо в виде максимального потока или переменных затрат через ребро, то вам нужно разбить материал потока графика. Однако вы не упомянули ничего подобного, и компьютерные кластеры в одном и том же центре обработки данных обычно не имеют ни одного.

3 голосов
/ 11 декабря 2011

Эта проблема может быть смоделирована как пример потока минимальных затрат .

Пусть G - ориентированный граф с вершинами s, t, a 1 ,…, A k , b 1 ,…, b k и дуг s → a i емкости x i и стоимость 0, дуги a i → b j бесконечной емкости и стоимость 0, если i = j, и стоимость 1, если i ≠ j, и дуги b j → t емкости N и стоимости 0. Нам нужен поток с минимальными затратами, который перемещает ∑ i x i единиц от s до t.Идея состоит в том, что если у единиц потока a i → b j , то y элементов перемещаются из блока i в блок j (если i ≠ j; если i = j, то нетпроисходит движение).

Использование потока с минимальными затратами для решения этой простой проблемы - это, конечно, использование кувалды для взлома гайки, но несколько вариантов могут быть смоделированы как проблемы с сетевым потоком.*

Если пара узлов i, j не подключена напрямую, удалите a i → b j arc.

Мы можем запускать и выключать узлы, задавая им вершины только на стороне «a» или «b».

Мы можем смоделировать различия в скоростях связи, регулируя затраты сравномерно.

Мы можем ограничить количество элементов, которыми обмениваются два узла, ограничивая емкость их дуги.

Мы можем ввести больше внутренних вершини измените полную двунаправленную топологию соединений, чтобы смоделировать эффекты конкуренции за сеть.

1 голос
/ 17 декабря 2011

Я обычно вижу это как перераспределение данных, с пониманием, что если вы перераспределяете его, вы хотите, чтобы распределение было оптимальным по некоторой метрике, такой как равномерность между задачами.

Это действительно происходит вНаучно-технические вычисления, когда вы пытаетесь сбалансировать вычислительную нагрузку.Даже если вы выполняете вычисления в нескольких измерениях, если вы перераспределяете пространственные данные, которые вы назначаете процессорам, по кривой заполнения пространства, возникает именно эта проблема, и там вы часто хотите, чтобы данные были равномерно разделены.

Процедура довольно проста;вы начинаете с того, что берете эксклюзивную префиксную сумму из x i , чтобы вы знали, сколько предметов находится «слева» от вас.Например, для приведенного выше примера Ноксвилла, если бы у вас были данные

[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
1 голос
/ 11 декабря 2011

Я думаю, что вопрос должен быть разъяснен таким образом:

С M избыточными узлами и K дефицитными узлами мы должны перераспределить выборки между узлами в состояние с 0 избыточными узлами и (возможно) некоторыми дефицитными узлами. Образцы должны быть обменены в пачках, а количество этих пачек должно быть минимальным.

Или, математически, у нас есть матрица M*K, каждая ячейка которой представляет количество выборок, которые нужно передать от узла M к узлу K, с заданной суммой элементов в каждой строке и с максимальной суммой элементов в каждом столбце. Цель состоит в том, чтобы минимизировать количество ненулевых ячеек.

Это своего рода "Проблемы удовлетворения ограничений" . Это NP-полный. Я обнаружил две классические проблемы, похожие на этот вопрос: «Набор упаковки» и «Обобщенная точная крышка».

Чтобы уменьшить проблему до «Установить упаковку» , нам нужно (временно) добавить несколько избыточных узлов с N+1 выборками каждый, чтобы после перераспределения не осталось дефицитных узлов. Затем для каждого узла нам нужно сгенерировать все возможные разбиения для избыточных элементов и для «дефицитных» элементов. Затем к декартову произведению избыточных и дефицитных разделов применяется «Набор упаковки» в его версии «оптимизации», который находит минимальное количество подмножеств.

Чтобы уменьшить проблему до «Обобщенное точное покрытие» , для каждого узла нам нужно сгенерировать все возможные разбиения для избыточных элементов и для «дефицитных» элементов. Затем мы должны добавить M, M+1, ... узлы оптимизации, чтобы минимизировать количество подмножеств в обложке. Затем к декартову произведению избыточных и дефицитных разбиений и узлов оптимизации применяют «Обобщенное точное покрытие». Для меньшего числа узлов оптимизации этот алгоритм завершится ошибкой, для некоторого большего числа он найдет минимальное количество подмножеств.

«Обобщенное точное покрытие» может быть решено с помощью «Алгоритм Кнута X» . Я не знаю ни одного алгоритма для "набора упаковки".

Все эти решения дают точный ответ, но они имеют огромную вычислительную сложность, очень маловероятно, что кто-то использует их в реальных планировщиках. Практический подход заключается в использовании некоторых эвристических и жадных алгоритмов. Просто отсортируйте лишние узлы и дефицитные узлы и примените стратегию «наилучшего соответствия».

1 голос
/ 07 декабря 2011

Звучит так, как будто вы хотите использовать согласованное хеширование

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

В основном, использование хорошей случайной хеш-функции позволит вам получить уникальные идентификаторы для ваших выборок, которые дают равномерное распределение.Затем легко распределить выборки по набору узлов последовательно.

См.

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

для получения дополнительной информации.

0 голосов
/ 07 декабря 2011

Я думаю, что эта проблема перераспределения немного отличается от распределения нагрузки в вычислениях.

Термин «алгоритм балансировки нагрузки» обычно означает набор политик / эвристик для обеспечения относительно равномерного распределения нагрузки БУДУЩЕГО (а не текущей).

В этом случае балансировщик нагрузки не будет перераспределять нагрузку с существующих серверов / систем, но при каждом новом запросе он будет пытаться назначить с использованием некоторой политики (например, наименее загруженный, циклический и т. Д.), Которая будет сохранять серверы относительно равномерно загружены в долгосрочной перспективе.

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

Перераспределение нагрузки в этом вопросе, возможно, может быть осуществлено путем итеративного перемещения элементов из максимально загруженного в минимально загруженное поле.

0 голосов
/ 07 декабря 2011

В основном вы хотите перейти от

[9, 6, 1, 6, 2] 

до

[5, 5, 4, 5, 5]

Я думаю, что лучший способ сделать это - вычислить пол (сумма (массив) / длина (массив)), а затем оценить дифференциал, необходимый для достижения этой позиции. В этом случае floor ((9 + 6 + 1 + 6 + 2) / 5) = 4, поэтому мы смотрим на начальный дифференциал [-5, -2, +3, -2, +2]. Затем вы можете жадно поменять значения в соседних парах, где знаки отличаются (например, передача 2 из массива [2] -> arr [1] и 2 из массива [4] -> массив [3]). Затем вы получаете [-5,0,1,0,0], и отсюда вы можете жадно назначить оставшиеся биты.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...