Як обчислювальна вартість операції mpi_allgather порівнюється з операцією збирати / розкидати?


11

Я працюю над проблемою, яку можна паралелізувати, використовуючи одну операцію mpi_allgather або одну операцію mpi_scatter та одну mpi_gather. Ці операції викликаються протягом певного циклу, тому їх можна викликати багато разів.

У реалізації за схемою MPI_allgather я збираю розподілений вектор на всі процеси для вирішення дублікатів матриці. В іншому здійсненні я збираю розподілений вектор на один процесор (кореневий вузол), розв'язую лінійну систему на цьому процесорі, а потім розкидаю вектор розчину назад на всі процеси.

Мені цікаво дізнатися, чи вартість операції зі збирання суттєво більша, ніж комбіновані операції розсіювання та збирання. Чи відіграє довжина повідомлення важливу роль у його складності? Чи змінюється це залежно від реалізації mpi?

Редагувати:


Опишіть, будь ласка, структуру спілкування та розміри, що стосуються. MPI_ScatterПотім MPI_Gatherне забезпечує такий же зв'язку семантичний , як MPI_Allgather. Можливо, є надмірність, коли ви виражаєте операцію в будь-який спосіб?
Джед Браун

Пол, Джед прав, ти мав на увазі, що MPI_Gatherйде за ним MPI_Bcast?
Арон Ахмадія

@JedBrown: Я додав трохи більше інформації.
Пол

@AronAhmadia: Я не думаю, що я повинен використовувати MPI_Bcast, тому що я надсилаю частину вектора до кожного процесу, а не весь вектор. Моє обгрунтування полягає в тому, що коротше повідомлення буде швидше надіслати, ніж велике повідомлення взагалі. Це має сенс?
Пол

Чи матриця вже розподілена надлишково? Це вже фактор? Чи ділять кілька процесів однакові кеші та шину пам'яті? (Це вплине на швидкість вирішення зайвих систем.) Наскільки великі / дорогі системи? Навіщо вирішувати серійно?
Джед Браун

Відповіді:


9

По-перше, точна відповідь залежить від: (1) використання, тобто аргументів введення функції, (2) якості та деталей реалізації MPI та (3) обладнання, яке ви використовуєте. Часто (2) і (3) пов'язані між собою, наприклад, коли постачальник обладнання оптимізує MPI для своєї мережі.

Загалом, злиття колективів MPI краще для менших повідомлень, оскільки витрати на запуск можуть бути нетривіальними, а синхронізація, пов’язана з блокуванням колективів, повинна бути зведена до мінімуму, якщо є різниця в часі обчислення між викликами. Для більшої кількості повідомлень метою має бути мінімізація кількості даних, що надсилаються.

Наприклад, теоретично MPI_Reduce_scatter_blockмає бути краще, ніж MPI_Reduceслідувати MPI_Scatter, хоча перший часто реалізується з точки зору останнього, таким, що не має реальної переваги. Існує кореляція між якістю впровадження та частотою використання у більшості впроваджень MPI, і виробники, очевидно, оптимізують ті функції, для яких це вимагається машинним контрактом.

З іншого боку, якщо один знаходиться на Blue Gene, роблячи при MPI_Reduce_scatter_blockдопомоги MPI_Allreduce, яка робить більше спілкування , ніж MPI_Reduceта в MPI_Scatterпоєднанні, насправді зовсім небагато швидше. Це те, що я нещодавно виявив, і є цікавим порушенням принципу самозгодженості продуктивності в MPI (цей принцип більш докладно описаний в "Постійних принципах щодо ефективності MPI" ).

У конкретному випадку розкидання + зібрати проти всіх, врахуйте, що в першому всі дані повинні надходити в один процес і з нього, що робить його вузьким місцем, тоді як в цілому збір даних може надходити в та з усіх рангів відразу , тому що всі ранги мають деякі дані для надсилання до всіх інших рангів. Однак надсилання даних з усіх вузлів одночасно не обов'язково є доброю ідеєю в деяких мережах.

Нарешті, найкращий спосіб відповісти на це питання - це зробити наступне у своєму коді та відповісти на питання експериментом.

#ifdef TWO_MPI_CALLS_ARE_BETTER_THAN_ONE
  MPI_Scatter(..)
  MPI_Gather(..)
#else
  MPI_Allgather(..)
#endif

Ще кращим варіантом є те, щоб ваш код вимірювався експериментально під час перших двох ітерацій, а потім використовувати те, що швидше для інших ітерацій:

const int use_allgather = 1;
const int use_scatter_then_gather = 2;

int algorithm = 0;
double t0 = 0.0, t1 = 0.0, dt1 = 0.0, dt2 = 0.0;

while (..)
{
    if ( (iteration==0 && algorithm==0) || algorithm==use_scatter_then_gather )
    {
        t0 = MPI_Wtime();
        MPI_Scatter(..);
        MPI_Gather(..);
        t1 = MPI_Wtime();
        dt1 = t1-t0;
    } 
    else if ( (iteration==1 && algorithm==0) || algorithm==use_allgather)
    {
        t0 = MPI_Wtime();
        MPI_Allgather(..);
        t1 = MPI_Wtime();
        dt2 = t1-t0;
    }

    if (iteration==1)
    {
       dt2<dt1 ? algorithm=use_allgather : algorithm=use_scatter_then_gather;
    }
}

Це не погана ідея ... час їх обох і визначити, хто з них швидший.
Павло

Більшість сучасних апаратних середовищ HPC оптимізують багато MPI-дзвінків. Іноді це призводить до неймовірних прискорень, в інший час, надзвичайно непрозорі форми поведінки. Будь обережний!
meawoppl

@Jeff: Я щойно зрозумів, що залишив одну важливу деталь ... Я працюю з кластером в Техаському розширеному обчислювальному центрі, де вони використовують топологічну мережу жирових дерев. Чи вплине це на різницю у ефективності між підходами "все збирати та збирати"?
Пол

Топологія @Paul тут не є домінуючим фактором, але жирове дерево має значну пропускну здатність бісекції, що повинно зробити дешевше все. Однак збирати завжди слід дешевше, ніж все збирати. Однак для більш великих повідомлень це може бути менше, ніж коефіцієнт 2.
Джефф

5

Абсолютно прав Джефф щодо єдиного способу бути впевненим - це виміряти - зрештою, ми вчені, і це емпіричне питання - і дає чудові поради щодо здійснення таких вимірювань. Дозвольте зараз запропонувати протилежний (або, може, доповнюючий) погляд.

Існує різниця між написанням коду, який широко використовується, і налаштуванням його на певну мету. Взагалі ми робимо перше - будуємо наш код так, щоб: а) ми могли використовувати його на найрізноманітніших платформах; і b) код можна підтримувати та розширювати протягом наступних років. Але іноді ми робимо інше - у нас на рік варто виділити якусь велику машину, і ми нарощуємо деякий необхідний набір великих симуляцій, і нам потрібна певна основна характеристика, щоб отримати те, що нам потрібно зробити під час час наданого розподілу.

Коли ми пишемо код, зробити його широко корисним і ремонтом набагато важливіше, ніж гоління на кілька відсотків часу виконання на певній машині. У цьому випадку правильним є майже завжди використовувати рутину, яка найкраще описує те, що ви хочете зробити - це, як правило, найбільш конкретний дзвінок, який ви можете здійснити, і робить те, що ви хочете. Наприклад, якщо прямий allgather або allgatherv робить те, що ви хочете, ви повинні використовувати це, а не викочувати свої власні дії з розсіювання / збирання. Причини полягають у тому, що:

  • Код тепер більш чітко відображає те, що ви намагаєтеся зробити, зробивши його більш зрозумілим для наступної людини, яка наступного року прийде до вашого коду, не маючи поняття, що з цим кодом потрібно робити (цією людиною цілком можете бути ви);
  • Оптимізації доступні на рівні MPI для цього більш конкретного випадку, який не є більш загальним випадком, тому ваша бібліотека MPI може вам допомогти; і
  • Спроба розгорнути власну, швидше за все, негативна реакція; навіть якщо вона працює краще на машині X із впровадженням MPI Y.ZZ, вона може бути набагато гіршою при переході на іншу машину або вдосконаленні реалізації MPI.

У цьому досить поширеному випадку, якщо ви дізнаєтесь, що якийсь колектив MPI працює на вашій машині необгрунтовано повільно, найкраще робити - подати звіт про помилку у постачальника mpi; ви не хочете ускладнювати власне програмне забезпечення, намагаючись обійти код програми, що слід правильно фіксувати на рівні бібліотеки MPI.

Однак . Якщо ви перебуваєте в режимі "настройки" - у вас є робочий код, вам доведеться за короткий проміжок часу збільшити масштаб до дуже великих масштабів (наприклад, виділення на рік), і ви профайлювали свій код і з’ясував, що саме ця частина вашого коду є вузьким місцем, то є сенс почати виконувати ці дуже конкретні настройки. Сподіваємось, вони не стануть частинами вашого коду в довгостроковому періоді - в ідеалі ці зміни залишаться в певній галузі вашого сховища, але можливо вам доведеться їх виконувати. У цьому випадку кодування двох різних підходів, що відрізняються препроцесорними директивами, або "автоматичний підхід" для конкретної моделі зв'язку - може мати багато сенсу.

Тож я не погоджуюся з Джеффом, я просто хочу додати деякий контекст про те, коли вам належить перейматися такими питаннями щодо відносної продуктивності, щоб змінити ваш код для вирішення цього питання.


Я думаю, що мене більше цікавить портативність, ніж оптимізація, але мені завжди цікаво знати, чи є ще одна реалізація, яка однаково портативна, але швидша :)
Пол
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.