TAG = 0x(aa)(id)(aa/bb).
Тэг является четырехбайтным целым числом. Первый байт у «нашего» тэга – это 0xaa. Это позволяет отличить его от обычных посылок/приемов сообщений. Последний байт может быть 0xaa – символизирует начало интервала, 0xbb – конец интервала. Внутри специальный идентификатор интервала (2 байта), его можно использовать, например, для того, чтобы отдельно выделить итерации цикла.
Такой способ выделения был выбран потому, что:
· он всегда попадает в трассировку (некоторые специальные функции вроде MPI_Pcontrol() в текущей версии трассировщика не попадают).
· занимает относительно немного времени (порядка 100 тиков процессора).
· прост в использовании и не требует дополнительных средств, помимо стандартных MPI-функций.
Таким образом, программист может добавить в свой код границы интересующих его областей программы (в нашей терминологии интервалы).
Далее по этим тэгам среди всех событий будут найдены те, которые являются границами интервалов и будут определены их идентификаторы.
Для этого вводится специальный класс:
class Margin
{
public:
Margin(bool ,unsigned long ,int ,unsigned int ,int);
friend bool operator <( const Margin& s1, const Margin& s2)
bool enter_leave;
unsigned long time;
int identity;
unsigned int proc;
unsigned int scl;
};
Ифункция:
vector<Margin>* createMargins(void);
которая и вычисляет=> определяет необходимые границы вместе со всеми параметрами.
После определения границ, создается структура дерево, в которой хранятся все данные обо всех интервалах.
Кратко об используемых структурах данных.
Создан специальный класс tree:
classtree
{
public:
static int Intervallevel; // current interval level
static int IntervalID; // current interval ID
long index;
int level; // Interval level
int EXE_count;
int source_line;
string source_file;
int ID;
//Characteristics for every interval
unsigned long Exec_time;
unsigned long Productive_time;
double Efficiency;
unsigned long CPU_time;
unsigned long MPI_time;
unsigned long Lost_time;
unsigned long Comm_time;
unsigned long SendRecv_time;
unsigned long CollectiveAll_time;
unsigned long Idle_time;
unsigned long AllToAll_time;
unsigned long Time_variation;
unsigned long Potent_sync;
unsigned long T_start;
vector < pair<unsigned long,unsigned int> >* cmp_pairs;
//for intelval's tree
tree* parent_interval;
int count;
vector<tree *> nested_intervals;
vector<Processors> Procs;
};
Этот класс содержит информацию обо всех характеристиках данного интервала, описанных в 5.2. Кроме того, в нем есть информация о родительском узле дерева, а также обо всех «листьях-потомках».
В этом классе в качестве вспомогательного используется класс Processors.
class Processors
{
public:
unsigned long enter_time;
unsigned long leave_time;
unsigned int number;
unsigned long MPI_time;
unsigned long SendRecv_time;
unsigned long CollectiveAll_time;
unsigned long Idle_time;
unsigned long AllToAll_time;
unsigned long CPU_time;
unsigned long Comm_time;
unsigned long Time_variation;
unsigned long Potent_sync;
unsigned long T_start;
};
В этом классе содержатся элементарные составляющие всех компонентов, собранные на каждом интервале каждого процессора.
Далее, после определения границ интервалов, происходит создание дерева интервалов. В этом дереве и будет храниться информация обо всех интервалах.
Класс treeвключает методы, которые и собирают информацию из структур, собранных на трассе.
Первая группа характеристик собирается в функции
Leave(int line, char* file, long index,unsigned int proc,unsigned long time).
· MPI_time Используем – getMPITimebyProc();
· SendRecv_time - getSendRecvCommunicationTimebyProc();
· CollectiveAll_time – getCollectiveAllByProc();
· AllToAll_time - getAllToAllByProc();
· Potent_sync - getPotentSyncByProc();
· Time_variation - getTimeVariationByProc();
· T_start - getNonBlockedTimebyProc();
Вычисление характеристик.
getMPITimebyProc() – Происходит суммирование интервалов времени, занятых под MPI-функции (интервалы получаются как разность между временем выхода и входа в MPI-функцию).
getSendRecvCommunicationTimebyProc( )- Происходит суммирование интервалов времени, вычисляемых как разность времени выхода из функции приема сообщения и времени входа в функцию посылки сообщения.
getPotentSyncByProc() – Вычисляется по-разному для операций одиночных посылок/приемов сообщений и коллективных операций. Сюда входят все случаи, когда Recv был выдан раньше Send’а. Эти «задержки» как раз и суммируются. Для коллективных же операций суммируется время «задержки» старта операции на некоторых процессорах.
getTimeVariationByProc() – Вычисляется время, рассинхронизации окончания коллективной операции.
getNonBlockedTimebyProc() – Вычисляется аналогично getMPITimebyProc(), только суммируются времена работы только не блокирующих операций.
Все эти характеристики собираются на каждом процессоре для данного интервала. Прототипвсехфункцийодинаков:
getFunction(unsigned long enter_time, unsigned long leave_time, unsigned int proc).
Собранные «элементарные» характеристики, затем собираются в более общие по всему интервалу.
Первая используемая для этого функция – это функция Integrate().
В этой функции собираются следующие характеристики:
· CPU_time
· MPI_time
· SendRecv_time
· CollectiveAll_time
· AllToAll_time
· Comm_time(Общее время коммуникаций)
· Idle_time(время бездействия)
· Potent_sync
· Time_variation
· T_start
Все они уже являются характеристиками всего интервала.
Далее происходит вычисление уже не общих, а сравнительных характеристик. Зная все эти компоненты на каждом процессоре для интервала, мы находим процессоры с максимальным, минимальным значением по времени, а также среднее значения всех характеристик.
После функции Integrate() вычисляется полезное время calculateProductive(), потом время запуска - calculateExecution(),
эффективность распараллеливания - efficiency(), и, наконец, потерянное время – calculateLost().
На этом сбор и анализ информации оканчиваются. Следующий этап, это генерация соответствующих текстовых выдач. Эти выдачи представляют собой текстовый файл и имеют следующий вид (Пример).
Пример. Текстовый файл с итоговыми характеристиками.
Interval (LineNumber = 153 SourceFile = exch.c) Level=0 EXE_Count=1
---Main Characteristics---
Parallelization Efficiency 0.978833
Execution Time 2.079975
Processors 4
Total Time 8.319900
Productive Time 8.143794 (CPU MPI)
MPI время на одном процессоре считается полезным, а на остальных - потерянным
Lost Time 0.176106
---MPI Time 0.173490
---Idle Time 0.002616
*****SendRecv Time 0.013295
*****CollectiveAll Time 0.063268
*****AllToAll Time 0.000000
Potential Sync. 0.068763
Time Variation 0.001790
Time of Start 0.000000
---Comparative Characteristics---
Tmin Nproc Tmax Nproc Tmid
Lost Time 0.033087 3 0.060057 0 0.044026
Idle Time 0.000000 1 0.000898 0 0.000654
Comm. Time 0.006597 3 0.034854 0 0.019140
MPI Time 0.032259 3 0.059159 0 0.043372
Potential Sync. 0.001800 0 0.029369 3 0.017190
Time variation 0.000161 1 0.000607 3 0.000447
Time of Start 0.000000 0 0.000000 0 0.000000
Для каждого интервала выдается следующая информация:
· имя файла с исходным текстом MPI-программы и номер первого оператора интервала в нем (SourceFile, LineNumber);
· номер уровня вложенности (Level);
· количество входов (и выходов) в интервал (EXE_Count);
· основные характеристики выполнения и их компоненты (Main characteristics);
· минимальные, максимальные и средние значения характеристик выполнения программы на каждом процессоре (Comparative characteristics);
При выдаче характеристик их компоненты располагаются в той же строке (справа в скобках), либо в следующей строке (справа от символов “*” или “-“).
Информация о минимальных, максимальных и средних значениях характеристик оформлена в таблицу. Также есть информация обо всех характеристиках на каждом процессоре.
Следующим этапом после того, как все необходимые характеристики собраны, является этап визуализации.
Этот этап необходим, так как хотя текстовый файл содержит всю необходимую информацию, при большом числе интервалов пользоваться им не очень удобно. Кажется целесообразным, что, так как интервалы “отображались” логически в виде дерева, то и визуализировать их нужно в виде дерева. Было выбрана форма отображения, аналогичная древовидной организации файловой структуры данных на дисках. Соответственно, каждый интервал доступен из своего родителя, интервалы нижних уровней отображаются правее. Также при нажатии на интервал, в текстовое поле
выводится информация обо всех характеристиках именно этого интервала.
Это значительно облегчает поиск необходимой для анализа области.
Рис.6. Окно программы анализа.
Также полезным для представления общей картины запуска является упорядоченный по времени список событий. При этом используется так называемый (TimeLine), все события отображаются на линии определенным цветом в соответствии со временем, когда они произошли. Это позволяет отслеживать не просто нужную область, а точно интересующее событие.
Используя механизм Tooltip’ов, пользователь получает возможность узнать тип события (пользовательский (UserCode) или MPI) и название функции (для MPI - функций).
Рис.7. TimeLine
Узнать это можно по цвету линии с событием. Список цветов описан в Приложении 2.
В данной работе исследовались возможности анализа эффективности MPI-программ. Было разработано собственное программное средство, использующее подходы, применяемые в DVM-системе.
Приблизительный объем программы на С++ в строках кода = 6500 строк.
Программа оттестирована на тестах, поставляемых с MPI – реализациями, а также с тестами NAS (NPB2.3), с добавлением описанных выше директив для границ интервала.
В процессе дипломной работы были:
- Проанализированы современные средства анализа параллельных программ.
- Изучены алгоритмы анализа и сбора характеристик.