Интерфейс инструментации Fortran DVM/OpenMP-программ
Версия 1.2.3
Владимир Бахтин (от 01.01.2001)
1. Введение. 1
1.1 Инициализация/завершение работы отладчика. 2
1.2 Передача статической информации отладчику/анализатору. 2
1.3 Регистрация контекстных строк. 2
1.4 Схема инструментации Фортран-программы.. 3
1.5 Согласованность типов Фортран - и Си-программ.. 3
1.6 Идентификатор нити (ThreadID) 4
2. Инструментация OpenMP-директив. 5
2.4 Директива PARALLEL. Создание параллельной области. 5
2.5 Конструкции разделения работы.. 7
2.5.1 Параллельный цикл. 7
2.5.2 Директива SECTIONS. 8
2.5.3 Директива SINGLE.. 9
2.5.4 Директива WORKSHARE.. 10
2.6 Составные директивы.. 11
2.7 Директива MASTER и другие синхронизационные директивы. 12
2.7.1 Директива MASTER.. 12
2.7.2 Директива CRITICAL.. 12
2.7.3 Директива BARRIER.. 13
2.7.4 Директива ATOMIC.. 13
2.7.5 Директива FLUSH.. 13
2.7.6 Директива ORDERED.. 14
2.8 Классы переменных. 14
2.8.2 Директива THREADPRIVATE.. 14
3. Инструментация операторов Fortran 77. 15
3.1 Раздел описаний. 15
3.1.1 Описание переменных. 15
3.1.2 Описание COMMON-блоков. 16
3.2 Выполняемые операторы.. 17
3.2.1 Чтение/запись переменных. 17
3.2.2 Последовательные циклы.. 18
3.2.3 Функции. 18
4. Управление инструментацией. 20
4.1 Управление инструментацией опциями инструментатора. 20
4.2 Управление инструментацией из программы пользователя. 21
4.2.1 Управление уровнем подробности: 21
4.2.3 Управление инструментацией с условными операторами: 21
4.3 Инструментация с условными операторами. 21
4.4 Соответствие между уровнями подробности инструментации и инструментируемыми событиями 22
5. Недостатки. 23
6. Литература. 23
1. Введение
Предлагаемый интерфейс предполагается использовать, как для анализа эффективности OpenMP программ, так и для сравнительной отладки и динамического контроля OpenMP программ.
1.1 Инициализация/завершение работы отладчика
void DBG_Init(long *ThreadID)
Функция инициализации отладчика, вызывается из программы пользователя до любых обращений к отладчику, только в одной нити. Должна быть вызвана перед всеми обращениями к отладчику.
Параметр ThreadID – идетификатор нити (см. п.1.6).
void DBG_Finalize(void)
Функция завершения работы отладчика, вызывается перед завершением программы. Эта функция может вызываться из нескольких нитей, отладчик должен это корректно отрабатывать. В случае невызова функции DBG_Finalize перед завершением программы отладчик должен уметь отрабатывать свое завершение.
1.2 Передача статической информации отладчику/анализатору
Передача статической информации о программе (привязка к строкам и т. п.) в отладчик/анализатор осуществляется при помощи строк специального вида (контекстные строки) [EWOMP02-POMP. pdf]. Контекстная строка представляет собой последовательность пар «атрибут=значение» разделенных звездочками «*».
"<length>*type=<type>*name1=<name>*file=<file>*line1=<number>*...**"
Строка заканчивается пустым полем (двойной звездочкой «**»). Длина строки обязательна, все остальные поля не обязательны. Две звездочки на конце обязательны. Под длиной строки понимается количество символов между первой звездочкой и последней (включая обе), не считая каких-либо завершающих нулей или пустых символов в Фортране. Если звездочка встречается внутри какого-либо поля (например, в выражении клаузы NUM_THREADS), то после нее должен быть вставлен символ слеш (‘\’). Это позволяет однозначно определять границы пар, поскольку ни одно описание типа не может начинаться с символа слеш. Самая короткая строка - «2**».
1.3 Регистрация контекстных строк
Все контекстные строки со статической информацией о программе регистрируются при помощи вызова:
void DBG_Get_handle(long *StaticContextHandle, char* ContextString, long StringLength)
где StaticContextHandle - адрес дескриптора, ContextString - соответствующая контекстная строка, StringLength – длина этой строки. Данная функция вызывается только из одной нити.
Для чего нужна такая регистрация.
Отладчик/анализатор может создавать свои внутренние структуры данных. Например, для параллельной области, может создаваться следующая структура:
struct {
char* filename; //имя файла
int begin_line; // строка программы, в которой параллельная область начинается
int end_line; // строка программы, в которой параллельная область заканчивается
list* private; // список приватных переменных
list* firstprivate;
…
} parallel_region;
При помощи дескриптора StaticContextHandle можно вернуть в Fortran-программу адрес этой созданной структуры. Этот адрес будет использоваться во всех последующих вызовах функций инструментации параллельной области (при входе и выходе из параллельной области будет использоваться один и тот же дескриптор).
1.4 Схема инструментации Фортран-программы
Каждый модуль (файл) программы инструментируется отдельно. При инструментации программы проверяется наличие в текущем (или заданном) каталоге файлов dbg_init. h и dbg_vars. h
В файле dbg_vars. h – хранятся дескрипторы для всех контекстных строк программы:
integer*8 dbgsid1
...
integer*8 dbgsidN
Где N – общее количество контекстных строк в программе.
Переменные dbgsid1, … dbgsidN – являются компонентами COMMON-блока:
common /DBG_VARS/ dbgsid1, ..., dbgsidN
и являются общими (SHARED) для всех нитей.
Замечание. Как уже отмечалось ранее, переменные dbgsid1,…, dbgsidN могут использоваться для передачи адресов. Тип этих переменных может меняться в зависимости от платформы (integer или integer*8). Зависит от количества байт, которые отводятся для хранения адреса, и задается при помощи параметра –bind (см. раздел 1.5).
В файле dbg_init. h описана процедура, которая регистрирует все контекстные строки программы.
subroutine DBG_Init_Handles ()
include 'dbg_vars. h'
call DBG_Get_handle(dbgsid1, "Context String1")
...
call DBG_Get_Handles (dbgsidN, " Context StringN")
end subroutine DBG_Init_Handles
При инструментации очередного модуля программы:
- модифицируется процедура DBG_Init_Handles из файла dbg_init. h. В нее добавляется регистрация всех новых контекстных строк модуля; в файл dbg_vars. h добавляется необходимое количество дескрипторов для новых контекстных строк; во все процедуры/функции данного модуля добавляется:
include 'dbg_vars. h'
- в главную программную единицу (PROGRAM)
- добавляется
include 'dbg_init. h'
- после вызова функции инициализации отладчика:
call DBG_Init ()
добавляется вызов:
call DBG_Init_Handles ()
Такой подход позволяет сделать работу отладчика/анализатора более эффективной (фактически еще до начала работы, он имеет всю необходимую информацию о структуре программы).
1.5 Согласованность типов Фортран - и Си-программ
Вызовы библиотек отладки/анализа описаны на языке Си. Соответствие Си - и Фортран-типов данных должно определяться пользователем с помощью опций инструментатора –bindk, аналогично тому, как это делается в системе DVM [fdvmUGr. html]. На базе установленной опции инструментатор выбирает соответствующие Фортран-типы для Си-типов библиотек анализа/отладки. Ниже указана таблица соответствия.
-bindk
где k - это целое число, которое указывает номер таблицы соответствия.
k = 0: |
| ||
Фортран | Си | Размер (в байтах) | |
Integer | long | 4 | |
Real | float | 4 | |
Double precision | Double | 8 | |
Character | char | 1 | |
Logical | long | 4 | |
k = 1: |
| ||
Фортран | Си | Размер (в байтах) | |
Integer | int | 4 | |
Real | float | 4 | |
Double precision | Double | 8 | |
Character | char | 1 | |
Logical | int | 4 | |
Integer*8 | long | 8 | |
1.6 Идентификатор нити (ThreadID)
Во всех функциях инструментации, рассматриваемых в последующих разделах, указывается идентификатор нити, от имени которой идет обращение к отладчику (параметр long *ThreadID).
Средства OpenMP не позволяют определить уникальный идентификатор нити. Вызов omp_get_thread_num() – возвращает номер нити в группе (меняется от 0 до omp_get_num_threads()-1) и не является уникальным. Предполагается, что вся работа по идентификации нитей выполняется отладчиком.
Инструментатор для каждой нити заводит приватную переменную (threadid), которая инициализируется значением -1 в начале выполнения программы. После того, как создана группа нитей (после директивы PARALLEL), инструментатор добавляет вызов, в котором отладчику передается адрес этой переменной:
DBG_ParallelEvent (…, long *ThreadID) // инструментация директивы PARALLEL
// подробно описана в разделе 2.4
В случае если значение переменной ThreadID равно -1, отладчик, используя системные вызовы (например, GetCurrentThreadId() в Windows), определяет уникальный идентификатор текущей нити и присваивает это значение переменной ThreadID. Далее этот идентификатор используется во всех обращениях к отладчику.
Такая реализация позволяет сократить накладные расходы на вызовы системных функций: не используя параметр ThreadID, отладчику пришлось бы каждый раз определять идентификатор нити, от имени которой идет обращение.
Для реализации данного подхода:
- в файл dbg_vars. h, в котором описаны все дескрипторы для контекстных строк, добавляется описание переменной:
integer*8 threadid
common /DBG_THREAD/ threadid
C$OMP THREADPRIVATE (/DBG_THREAD/)
- в главную программную единицу (PROGRAM) добавляется оператор инициализации переменной:
data threadid /-1/
2. Инструментация OpenMP-директив
В главе 2 нумерация разделов совпадает с нумерацией в стандарте OpenMP[spec25.pdf].
2.4 Директива PARALLEL. Создание параллельной области.
!$omp parallel [clause[[,] clause]...]
structured-block
!$omp end parallel
Где клауза одна из:
if(scalar-logical-expression)
private(list)
firstprivate(list)
default(private | shared | none)
shared(list)
copyin(list)
reduction({operator|intrinsic_procedure_name}:list)
num_threads(scalar-integer-expression)
Директива PARALLEL инструментируется при помощи функции:
void DBG_BeforeParallel (long *StaticContextHandle, long *ThreadID, int *NumThreadsResults, int *IfExprResult),
которая вызывается непосредственно перед началом параллельной области (перед директивой parallel).
Статическая информация о директиве PARALLEL передается отладчику при помощи контекстной строки, оформленной в соответствии с п.1.2. Данная строка должна быть зарегистрирована при помощи вызова DBG_Get_handle. Полученный в результате регистрации дескриптор, передается как параметр StaticContextHandle.
Формат контекстной строки следующий:
Тип строки:
type=parallel
Передается имя файла:
file = <имя файла>
Номер строки файла, в которой начинается параллельная область:
line1=<номер строки>
Номер строки окончания параллельной области:
line2=<номер строки>
Для спецификации PRIVATE:
private=<список имен переменных/common-блоков через запятую>
Для спецификации SHARED:
shared=<список имен переменных/common-блоков через запятую>
Для спецификации FIRSTPRIVATE:
firstprivate=<список имен переменных/common-блоков через запятую>
Для спецификации COPYIN:
copyin=<список имен переменных/common-блоков через запятую>
Для спецификации REDUCTION:
reduction=<список имен редукционных переменных через запятую>
redop=<тип редукционного оператора>,
где <тип редукционного оператора> - может принимать следующие значения:
PLUS
MINUS
PRODUCT
AND
OR
EQV
NEQV
MAX
MIN
IAND
IOR
IEOR
Для спецификации DEFAULT:
default=<тип>,
где <тип> - может принимать следующие значения:
PRIVATE,
SHARED,
NONE
Для спецификации IF:
if=<текст выражения>
P. S. все “*” в выражении заменяются на “\*”
Для спецификации NUM_THREADS
num_threads=<текст выражения>
P. S. все “*” в выражении заменяются на “\*”
Помимо текстового представления значения выражений, передаются:
IfExprResult – значение, полученное в результате вычисления клаузы if или (-1), если этой клаузы в текущей директиве parallel нет.
NumThreadsResult – значение, полученное в результате вычисления клаузы NUM_THREADS или (-1), если этой клаузы в текущей директиве parallel нет.
void DBG_ParallelEvent (long *StaticContextHandle, long *ThreadID)
Функция сообщает отладчику о начале параллельной области. Вызывается сразу после директивы PARALLEL. Предполагается, что данная функция инициализирует значение переменной ThreadID– уникальный идентификатор нити.
Директива END PARALLEL инструментируется при помощи:
void DBG_AfterParallel (long *StaticContextHandle, long *ThreadID)
Функция вызывается сразу после завершения параллельной области.
2.5 Конструкции разделения работы
2.5.1 Параллельный цикл
!$omp do [clause[[,] clause] ... ]
do-loop
[!$omp end do [nowait] ]
Где клауза одна из:
private(list)
firstprivate(list)
lastprivate(list)
reduction({operator|intrinsic_procedure_name}:list)
ordered
schedule(kind[, chunk_size])
void DBG_BeforeOMPLoop(long *StaticContextHandle, long *ThreadID, long *Init, long *Last, long *Step, int *ChunkSize)
Функция сообщает отладчику о начале OpenMP цикла, вызывается перед входом в цикл.
Init, Last, Step – адреса номеров первой и последней итераций и шага цикла соответственно.
ChunkSize - значение, полученное в результате вычисления выражения chunk_size, указанного в клаузе SCHEDULE или (-1), если chunk_size не указан.
Остальная информация о параллельном цикле передается при помощи контекстной строки (StaticContextHandle).
Формат контекстной строки следующий:
Тип строки:
type=omploop
Передается имя файла:
file = <имя файла>
Номер строки, в которой начинается OpenMP цикл:
line1=<номер строки>
Номер строки, в которой OpenMP цикл заканчивается:
line2=<номер строки>
В случае наличия спецификации ORDERED:
ordered=1
В случае наличия спецификации NOWAIT:
nowait=1
Для спецификации PRIVATE:
private=<список имен переменных/common-блоков через запятую>
Для спецификации FIRSTPRIVATE:
firstprivate=<список имен переменных/common-блоков через запятую>
Для спецификации LASTPRIVATE:
lastprivate=<список имен переменных/common-блоков через запятую>
Для спецификации REDUCTION:
reduction=<список имен редукционных переменных через запятую>
redop=<тип редукционного оператора>
Тип редукционного оператора (поле redop) подробно описано в разделе 2.4.
Для спецификации SCHEDULE:
schedule=<тип>
где <тип> - одна из следующих строк:
STATIC,
DYNAMIC,
GUIDED,
RUNTIME
и передается текстовое представление для выражения chunk_size:
chunk_size=<текст выражения>
P. S. все “*” в выражении заменяются на “\*”
void DBG_OMPIter(long *StaticContextHandle, long *ThreadID, long *Index)
Функция сообщает отладчику о начале очередной итерации OpenMP цикла. Функция вызывается в самом начале выполнения каждой итерации.
Index – адрес номера текущей итерации.
void DBG_AfterOMPLoop (long *StaticContextHandle, long *ThreadID)
Функция сообщает отладчику о конце OpenMP цикла. Функция вызывается при выходе из OpenMP цикла.
При инструментации одного и того же цикла во всех вызовах используется один и тот же дескриптор StaticContextHandle.
2.5.2 Директива SECTIONS
!$omp sections [clause[[,] clause] ...]
[!$omp section]
structured-block
[!$omp section
structured-block ]
...
!$omp end sections [nowait]
Где клауза одна из:
private(list)
firstprivate(list)
lastprivate(list)
reduction({operator|intrinsic_procedure_name}:list)
void DBG_BeforeSections (long *StaticContextHandle, long *ThreadID)
Вызов функции добавляется непосредственно перед директивой SECTIONS.
Дескриптор StaticContextHandle указывает на контекстную строку следующего формата:
Тип строки:
type=sections
Передается имя файла:
file = <имя файла>
Номер строки, в которой стоит директива SECTIONS:
line1=<номер строки>
Номер строки, в которой стоит директива END SECTIONS:
line2=<номер строки>
В случае наличия спецификации NOWAIT:
nowait=1
Для спецификации PRIVATE:
private=<список имен переменных/common-блоков через запятую>
Для спецификации FIRSTPRIVATE:
firstprivate=<список имен переменных/common-блоков через запятую>
Для спецификации LASTPRIVATE:
lastprivate=<список имен переменных/common-блоков через запятую>
Для спецификации REDUCTION:
reduction=<список имен редукционных переменных через запятую>
redop=<тип редукционного оператора>
Тип редукционного оператора (поле redop) подробно описано в разделе 2.4.
void DBG_AfterSections(long *StaticContextHandle, long *ThreadID)
Функция вызывается сразу после директивы END SECTIONS
Для инструментации каждой отдельной секции используется функция:
void DBG_SectionEvent(long *StaticContextHandle1, long *ThreadID),
которая вызывается в самом начале выполнения каждой секции - после директивы SECTION или директивы SECTIONS (если первая директива SECTION не указана).
Дескриптор StaticContextHandle1 должен содержать начальный и конечный номер строки этой секции в исходном коде:
Тип строки:
type=sect_ev
Передается имя файла:
file = <имя файла>
Номер строки, в которой начинается секция:
line1=<номер строки>
Номер строки, в которой в которой секция заканчивается:
line2=<номер строки>
2.5.3 Директива SINGLE
!$omp single [clause[[,] clause] ...]
structured-block
!$omp end single [end_clause[[,] end_clause] ...]
Где clause:
private(list)
firstprivate(list)
а end_clause:
copyprivate(list)
nowait
Инструментируется следующим образом:
void DBG_BeforeSingle (long *StaticContextHandle, long *ThreadID)
Функция вызывается непосредственно перед директивой SINGLE.
Дескриптор StaticContextHandle указывает на контекстную строку следующего формата:
Тип строки:
type=single
Передается имя файла:
file = <имя файла>
Номер строки, в которой стоит директива SINGLE:
line1=<номер строки>
Номер строки, в которой стоит директива END SINGLE:
line2=<номер строки>
В случае наличия спецификации NOWAIT:
nowait=1
Для спецификации PRIVATE:
private=<список имен переменных/common-блоков через запятую>
Для спецификации FIRSTPRIVATE:
firstprivate=<список имен переменных/common-блоков через запятую>
Для спецификации COPYPRIVATE:
copyprivate=<список имен переменных/common-блоков через запятую>
void DBG_SingleEvent(long *StaticContextHandle, long *ThreadID),
Функция вызывается сразу после директивы SINGLE. Может использоваться отладчиком, для определения нити, которая выполняет блок кода секции SINGLE.
void DBG_AfterSingle (long *StaticContextHandle, long *ThreadID)
Функция вызывается сразу после завершения конструкции (после директивы END SINGLE).
2.5.4 Директива WORKSHARE
!$omp workshare
structured-block
!$omp end workshare [nowait]
void DBG_BeforeWorkshare (long *StaticContextHandle, long *ThreadID)
Функция вызывается непосредственно перед директивой WORKSHARE.
Дескриптор StaticContextHandle указывает на контекстную строку следующего формата:
Тип строки:
type=workshare
Передается имя файла:
file = <имя файла>
Номер строки, в которой стоит директива WORKSHARE:
line1=<номер строки>
Номер строки, в которой стоит директива END WORKSHARE:
line2=<номер строки>
В случае наличия спецификации NOWAIT:
nowait=1
void DBG_AfterWorkshare(long *StaticContextHandle, long *ThreadID)
Функция вызывается сразу после директивы WORKSHARE.
Замечание 1. Инструментация операторов внутри блока WORKSHARE не производится, поскольку стандарт OpenMP не разрешает использовать внутри блока вызовы функции.
Замечание 2. Блок WORKSHARE, как правило, используется для операторов, в которых осуществляется работа с секциями массивов, и поэтому в программах написанных на Fortran77 не используется.
2.6 Составные директивы
Введение новых функций интерфейса для составных директив PARALLEL/разделения работы приведет к не очень нужному усложнению как средств анализа и отладки, так и инструментации. Поэтому в данном пункте рассмотрены правила преобразования составных директив во вложенные. Единственной сложностью здесь является соотнесение клауз – какие клаузы совмещенной директивы привязать к директиве PARALLEL, а какие – к директиве разделения работы. Материал взят из [lacsi01.pdf].
a) Совмещение PARALLEL с DO
В этом случае SCHEDULE, ORDERED и LASTPRIVATE клаузы исходной директивы должны относиться к циклу DO или FOR, а все остальные клаузы – к директиве PARALLEL.
b) Совмещение PARALLEL с SECTIONS
В этом случае клауза LASTPRIVATE должна относиться к директиве SECTIONS, а остальные клаузы – к директиве PARALLEL.
c) Совмещение PARALLEL с WORKSHARE
В этом случае все клаузы совмещенной директивы должны относиться к директиве PARALLEL.
2.7 Директива MASTER и другие синхронизационные директивы.
2.7.1 Директива MASTER
!$omp master
structured-block
!$omp end master
void DBG_MasterBegin(long *StaticContextHandle, long *ThreadID)
Функция вызывается сразу после директивы MASTER.
Дескриптор StaticContextHandle указывает на контекстную строку следующего формата:
Тип строки:
type=master
Передается имя файла:
file = <имя файла>
Номер строки, в которой стоит директива MASTER:
line1=<номер строки>
Номер строки, в которой стоит директива END MASTER:
line2=<номер строки>
void DBG_MasterEnd(long *StaticContextHandle, long *ThreadID)
Функция вызывается перед директивой END_MASTER.
2.7.2 Директива CRITICAL
!$omp critical [(name)]
structured-block
!$omp end critical [(name)]
void DBG_BeforeCritical (long *StaticContextHandle, long *ThreadID)
Функция вызывается непосредственно перед директивой CRITICAL.
Дескриптор StaticContextHandle указывает на контекстную строку следующего формата:
Тип строки:
type=critical
Передается имя файла:
file = <имя файла>
Передается имя, указанное в директиве CRITICAL:
name1 = <имя>
Номер строки, в которой стоит директива CRITICAL:
line1=<номер строки>
Номер строки, в которой стоит директива END CRITICAL:
line2=<номер строки>
void DBG_CriticalEvent(long *StaticContextHandle, long *ThreadID)
Функция вызывается сразу после директивы CRITICAL.
void DBG_AfterCritical(long *StaticContextHandle, long *ThreadID)
Функция вызывается сразу после директивы END_CRITICAL.
2.7.3 Директива BARRIER
!$omp barrier
void DBG_BeforeBarrier(long *StaticContextHandle, long *ThreadID)
Функция вызывается до директивы BARRIER.
Дескриптор StaticContextHandle указывает на контекстную строку следующего формата:
Тип строки:
type=barrier
Передается имя файла:
file = <имя файла>
Номер строки, в которой стоит директива BARRIER:
line1=<номер строки>
void DBG_AfterBarrier(long *StaticContextHandle, long *ThreadID)
Функция вызывается сразу после директивы BARRIER.
2.7.4 Директива ATOMIC
!$omp atomic
statement
Все директивы ATOMIC преобразуются инструментатором в:
!$omp critical (dbg_atomic)
statement
!$omp end critical (dbg_atomic)
Далее эта критическая секция инструментируется как это описано в п 2.7.2
2.7.5 Директива FLUSH
!$omp flush [(list)]
void DBG_FlushEvent(long *StaticContextHandle, long *ThreadID)
Функция вызывается сразу после директивы FLUSH. Дескриптор StaticContextHandle указывает на контекстную строку следующего формата:
Тип строки:
type=flush
Передается имя файла:
file = <имя файла>
Номер строки, в которой стоит директива FLUSH:
line1=<номер строки>
Дескриптор StaticContextHandle также содержит информацию о переменных - параметрах директивы Flush:
name1=<список имен переменных/common-блоков через запятую>
2.7.6 Директива ORDERED
!$omp ordered
structured-block
!$omp end ordered
void DBG_BeforeOrdered (long *StaticContextHandle, long *ThreadID)
Функция вызывается перед директивой ORDERED. Дескриптор StaticContextHandle указывает на контекстную строку следующего формата:
|
Из за большого объема этот материал размещен на нескольких страницах:
1 2 |


