CONVEX SPP: Руководство по порграммированию

Б. И. Илюшкин



Введение

Данное руководство составлено по следующим материалам :

Руководство [1] описывает эффективные методы программирования с разделяемой памятью (SM) для компьютеров SPP серий (Exemplar). Соответствует Convex Fortran Ver. 9.5, Convex C Ver. 6.5. Первые 4 главы охватывают основные концепции, включая оптимизацию и программирование с минимальными усилиями. Последующие главы рассчитаны на более опытных программистов.

Руководство [2] по сравнению с 10-ым изданием для Convex C серий содержит следующие добавления:

В руководстве [3] по сравнению с 10-ым изданием опции, директивы компилятора, соглашения о вызовах подпрограмм выделены отдельно для SPP и С серий.

1 Архитектура SPP

Отличительная особенность компьютеров серии CONVEX --- легкость их использования для программиста. Компьютеры серии SPP (Exemplar) реализуют данный принцип для систем Массивной Параллельной Обработки (MPP - massive parallel processing), используя архитектуру Масштабируемой Параллельной Обработки (SPP --- scalable parallel processing). Модель серии SPP может содержать от 2 до 64 процессоров HP PA-RISC 7100.

Процессоры объединяются в функциональные блоки, каждый из которых содержит 2 процессора, 128--512 Мбайт памяти и управляющие устройства. Функциональные блоки внутри гиперузла связаны между собой, с памятью и с устройствами ввода-вывода через 5 -ти портовый неблокируемый кроссбар (все порты могут обмениваться информацией одновременно). Все процессоры объединяются в 1--8 гиперузлов (hypernodes), каждый из которых содержит 2--8 процессоров. Каждый процессор имеет 1 Мбайт кэш команд и 1 Мбайт кэш данных.

Все гиперузлы связаны между собой, с памятью и устройствами ввода-вывода через систему когерентной тороидальной связи (CTI - Coherent Torroidal Interconnect). CTI включает неблокируемый кроссбар на каждом гиперузле для связи внутри гиперузла и высокоскоростное кольцо (ring), которое связывает гиперузлы между собой. CTI обеспечивает широкополосный, с низкой задержкой, доступ к данным других узлов.

Согласованность данных обеспечивается при помощи системы глобально разделяемой распределенной виртуальной памяти (Globally Shared Distributed Virtual Memory). В такой модели физическая память разделяется между всеми гиперузлами и каждому процессору доступно полное виртуальное адресное пространство.

Администратор системы может разделить процессоры на один или несколько подкомплексов (subcomplexes). Самый большой подкомплекс может включать всю систему, самый маленький --- два процессора. Процесс загружается в систему для выполнения на процессорах некоторого подкомплекса. Общая физическая память одного гиперузла может достигать 2 Гбайт, в максимальной конфигурации системы (8 гиперузлов) 16 Гбайт. Каждый процесс может иметь доступ к 4 Гбайтам виртуального адресного пространства. В случае необходимости программы могут быть написаны таким образом, чтобы иметь доступ к более чем 4 Гбайтам памяти.

2 Архитектурные различия CONVEX C и CONVEX SPP серий

Компьютеры CONVEX C серий содержат ограниченное число (1--8) процессоров, связанных высокоскоростным кроссбаром с общей разделяемой памятью. Для соединения небольшого числа процессоров кроссбар являются эффективным и быстродействующим средством, позволяющим всем процессорам осуществлять доступ к памяти с равновысокой скоростью. Каждый процессор содержит векторные регистры, ускоряющие операции с массивами. Увеличение производительности компьютеров С серий связано также с возможностью параллелизации на уровне циклов, задач или блоков.

Компьютеры CONVEX SPP серий повышают производительность за счет большей скорости скалярных операций, конвейеризации, использования высокоскоростных кэшей команд и данных, широкого набора регистров. Для мультиузловых комплексов может быть реализована двумерная модель параллелизма: нижний уровень --- параллелизация внутри гиперузла, верхний уровень --- внутри подкомплекса. Поскольку число процессоров для SPP серий может быть достаточно большим, доступ к памяти только через шину кроссбар неэффективен. Поэтому используется система связи CTI.

3 Классы памяти

Память для SPP --- серий разделяется на несколько классов.

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

Память, ``приватная для нити" --- присваивание уникальной виртуальной адресной последовательности нити процесса внутри узла. Каждая нить процесса имеет доступ к собственному 4 Гбайтному адресному пространству. Из них примерно 3.7 Гбайта доступно для программ, данных и стека, остальное используется OC SPP-UX. Размер стека можно настраивать. Процессы не могут получить доступ к виртуальному адресному пространству других процессов. Это виртуальное адресное пространство отображается в физическую память подкомплекса, на котором процесс запускается.

Память, ``приватная для узла" --- отображение виртуальных адресов на физические для всех нитей процесса внутри узла.

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

Различие между ``близко" и ``далеко" разделяемой памятью следующее. Пусть имеется массив двойной точности длиной 65536 элементов, который описан таким образом, чтобы быть размещенным в far_shared памяти на подкомплексе из 4-х гиперузлов. Предположим, что массив попадет на границу 4-байтной страницы в 1-ом узле. Массив будет занимать 8x65536/4096=128 страниц. Его 1-ая страница будет в 1-ом узле, 2-ая во 2-ом, ... , 5-ая в 1-ом узле, ... , 128-ая в 4-ом узле. Если данный массив будет описан в классе near_shared памяти, все его виртуальные страницы будут отображены в один узел.

4 Модели программирования

Компилятор для SPP серии поддерживает следующие модели программирования.

Модель с разделяемой памятью (SM - Shared memory)

Наиболее традиционная и распространенная модель программирования, используемая на большинстве комплексов. Использование SM модели не требует дополнительного кодирования ( как в МР модели). SM программы --- это мультинитевый процесс. Для дополнительной оптимизации кода возможно использование директив и прагм компилятора.

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

Пример

       real a(100),b(100),c(100)
       common /stuff/ big(10000),little(10)
      c$dir  thread_private a,b,c
      c$dir  near_shared /stuff/
Если используются только разделяемые переменные, то процесс имеет 4 Гбайта общего виртуального пространства. Распределение приватных данных может быть использовано для увеличения общего адресного пространства. Например, если каждая нить разделяет 2 Гбайта данных, то процесс имеет: 2 Гбайта (разделяемой памяти) + 2*[число нитей] Гбайт (приватной памяти). Связь приватных данных между нитями процесса можно осуществить путем их копирования в разделяемые переменные.

Если SM-программа компилируется с использованием опции -О3, программный код автоматически генерируется для параллельного выполнения. Программа загружается на множество процессоров как единый процесс с одной нитью на процессор. Нить 0 начинает выполняться, в то время как все другие нити остаются пассивными. Каждая ожидает, когда будет активизирована нитью 0. Основываясь на анализе графа ссылок на элементы массивы, компилятор пытается сгенерировать вычисления для нитей таким образом, чтобы они гарантировали ``близость" данных, т.е. чтобы повторные ссылки к тем же самым данным использовали эти данные из кэшей процессоров или связанного с ним процессорного кэша.

2. Модель с передачей сообщений (МР --- Message passing)

Обычные МР-программы являются множеством однонитевых процессов, выполняющихся параллельно в собственном (private) 4-Гбайтном адресном пространстве и координирующих свое выполнение при помощи явного обмена данными (передачи сообщений). Поддержка данной модели позволяет переносить на SPP серии программы, написанные для систем с распределенной памятью (distributed memory), которые требуют использования больших объемов памяти, чем SM-программы. Более подробная информация имеется в [4]. MP-программа компилируется с использованием всех опций, за исключением -О3. Она загружается как отдельный однонитевый процесс на каждый процессор подкомплекса. Все процессы МР-программы начинают выполняться на всех процессорах одновременно. Координация между ними обеспечивается операторами передачи сообщений, которые пользователь включает в текст программы.

Поскольку разделения памяти между процессами нет, мало резона описывать или распределять переменные с использованием специфических классов памяти.

Гибридная модель

Обеспечивает дополнительные возможности выполнения SM программ с использованием механизма передачи сообщений для координации отдельных процессов.

5 Уровни оптимизации

Оптимизация для SPP-серии включает пять уровней (аналогично С-серии)

-no --- уровень отдельных операторов

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

При выполнении данной опции создается объектный код, полностью использующий скалярные особенности PA-RISC архитектуры процессора.

-O0 --- уровень базовых блоков

Осуществляется машинно-независимая скалярная оптимизация на уровне базовых блоков.

Базовый блок --- сегмент кода, имеющий одну точку входа и одну выхода.

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

Компилятор также осуществляет оптимизацию уровня -no.

-O1 --- уровень подпрограмм

Осуществляется глобальная скалярная оптимизация базовых блоков внутри отдельных подпрограмм и глобальное распределение регистров (global register allocation --- GRA).

Глобальное распределение регистров --- хранение совместно используемых скалярных переменных в регистрах (а не в основной памяти).

Производится замена используемых переменных константами, исключение лишних присваиваний, вынос из тела цикла независимых выражений (code motion), замена арифметических операций более быстрыми неарифметическими (strength reduction). Компилятор осуществляет также оптимизацию уровней -O0 , -no ,то есть оптимизацию на уровне базовых блоков и машинных команд.

Пример

  с  использование глобального распределения регистров
       do i=1,n
        a(i)=x
          .
          .
       enddo
x вычисляется на каждой итерации в цикле. Используя механизм GRA, компилятор генерирует код, эквивалентный следующему псевдокоду:
       reg=x          !  reg  обозначает регистр
       do i=1,n
        a(i)=reg      !  компилятор исключает запись и хранние
          .           !  х  в основной памяти
          .
       enddo
       x=reg
Компилятор автоматически определяет, какие скалярные переменные наиболее подходят для GRA и соответственно распределяет регистры.

Применение GRA может иногда приводить к неправильным результатам.

1. При нарушении стандартных соглашений о передаче параметров подпрограммой.

Используя в качестве фактического параметра i константу, GRA размещает ее в регистре, не зная что это константа, и при переписи обратно из регистра возникает ошибка исполнения (runtime error).

Пример

       call ass(0,10,a)
               .
               .
       subroutine ass(iv,i,a)
       integer a(100,100)
         reg=i                   ! i - помещается в регистр
       do j=1,10
        if(iv.eq.1) then
               .
               .
         i= ...
               .
               .
        else
               .                !  нет присваивания  i
               .
        endif
       enddo
         i=reg                   !  содержимое регистра помещается
                              !  обратно в  i  - невозможно !!
                              !  если  i  константа
       return
       end
Избежать данной ситуации возможно путем использования опции -nga, запрещающей размещение в регистре переменных, передающихся по ссылке.

2. При использовании разделяемых (shared) переменных в мультинитевом процессе.

Проблема возникает, если параллельные нити процесса обновляют разделяемую переменную, размещенную в регистре. В этом случае каждая параллельная нить записывает значение разделяемой переменной в регистр. Такие параллельные присваивания мультинитевого процесса имеют смысл только если они осуществляются внутри критических или упорядоченных секций программного кода. В этом случае GRA не размещает переменные в регистре. Для исключения данной возможности необходимо применять опцию -ngs, запрещающую размещение в регистре разделяемых переменных мультинитевого процесса. GRA выполняется по умолчанию на уровне -O1 и выше.

-O2 --- локализация данных

Локализация предполагает сохранение часто используемых данных в кэше данных процессора, исключая их размещение в основной памяти и более дорогостоящем сетевом кэше (CTI cashe). Основными объектами локализации являются циклы, большинство из которых пригодно для преобразований, автоматически осуществляемых компилятором по умолчанию на уровне -O2. Преобразования осуществляются последовательно в порядке, способствующем более зффективной оптимизации кода, однако часть их может быть отменена опциями или локализована директивами и прагмами компилятора.

Данный уровень оптимизации устанавливается по умолчанию на SPP серии.

6 Основные преобразования уровня -O2

Разбиение цикла (strip mining)

Разбиение простого цикла на два вложенных.
       do i=1,10000             !   исходный код
         a(i)=a(i)*b(i)
       enddo


       do iout=1,10000,1000     !   трансформированный код
        do istrip=iout,iout+999
         a(istrip)=a(istrip)+b(istrip)
        enddo
       enddo

Распределение цикла (loop distribution)

Замена вложенных циклов простыми таким образом что все вычисления осуществляются только в теле самого внутреннего цикла.
       do i=1,n                 !    исходный код
        b(i,1)=0
        do j=1,m
         a(i)=a(i)+b(i,j)*c(i,j)
        enddo
         d(i)=e(i)+a(i)
       enddo


       do i=1,n                 !    трансформированный код
        b(i,1)=0
       enddo
       do i=1,n
        do j=1,m
         a(i)=a(i)+b(i,j)*c(i,j)
        enddo
       enddo
       do i=1,n
         d(i)=e(i)+a(i)
       enddo
Распределение цикла может повысить эффективность программного кода за счет сокращения числа обращений к памяти во время итераций и уменьшения перезаписей кэша (cashe thrashing).

Перестановка цикла (loop interchange)

Перестановка местами вложенных циклов таким образом, чтобы внешний цикл был удобен для дальнейшей параллелизации, а внутренний способствовал оптимальному доступу к памяти.
       do i=1,n                 !    исходный код
        do j=1,m
         a(i,j)=b(i,j)+c(i,j)
        enddo
       enddo


       do j=1,m                 !    трансформированный код
        do i=1,n
         a(i,j)=b(i,j)+c(i,j)
        enddo
       enddo
В исходном коде доступ к массивам a, b, c осуществляется по строкам, что неэффективно для Fortran. Трансформированный код осуществляет доступ по столбцам.

Расщепление цикла (loop blocking)

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

Слияние цикла (loop fusion)

Объединение двух или более циклов с одинаковыми границами в один. Замена элемента массива временной скалярной переменной (assignment substitution). Исключение общих выражений (common subexpression elimination). Сокращает общее время доступа к основной памяти и регистрам.

       do i=1,n                 !    исходный код
        a(i)=b(i)+c(i)
        enddo
       do j=1,n
        if(a(j).LT.0) a(j)=b(j)*b(j)
       enddo


       do i=1,n                 !    после объединения циклов
        a(i)=b(i)+c(i)
        if(a(i).LT.0) a(i)=b(i)*b(i)
       enddo


       do i=1,n                 !    после замены элемента массива
        temp1=b(i)              !    скалярной переменной
        temp2=temp1+c(i)
        if(temp2.LT.0) temp2=temp1*temp1
        a(i)=temp2
       enddo

Раскрутка цикла (loop unrolling)

Непосредственная запись итераций цикла. Раскрутка может быть полной (полная замена тела цикла отдельными итерациями) или частичной. Полная раскрутка имеет смысл для небольших циклов. При частичной раскрутке общее число итераций цикла сокращается, но на каждой итерации увеличивается количество вычислений.

Раскрутка уменьшает потери времени, связанные с инициализацией и приращением переменной цикла. По умолчанию полная раскрутка осуществляется при числе итераций цикла меньше пяти, в остальных случаях частичная (на уровне -О2 и выше). При числе итераций больше 5 раскрутка осуществляется для самых внутренних циклов. Раскрутка цикла сокращает время доступа к основной памяти и увеличивает количество используемых регистров. Осуществляется по умолчанию и может быть отменена опцией -nur.

       do i=1,100               !    исходный код
        a(i)=b(i)+c(i)
       enddo


       do i=1,100,4             !    после частичной раскрутки
        a(i)=b(i)+c(i)
        a(i+1)=b(i+1)+c(i+1)
        a(i+2)=b(i+2)+c(i+2)
        a(i+3)=b(i+3)+c(i+3)
       enddo

Раскрутка и объединение цикла (loop unroll and jamming)

Частичная раскрутка циклов с последующим объединением. Позволяет увеличить количество используемых регистров и сократить число обращений к памяти. Осуществляется по умолчанию и может быть отменена использованием опции -nuj.

Исключение лишних проверок (redundant-test elimination)

Компилятор осуществляет поиск внутри цикла условных операторов, которые можно исключить.
       do i=1,n                 !    исходный код
        if(i.GT.0) then
         do j=1,i
          a(i,j)=0
         enddo
        endif
       enddo


       do i=1,n                 !    после исключения лишних
         do j=1,i               !    проверок
          a(i,j)=0
         enddo
       enddo

Вынос граничных итераций за тело цикла (loop boundary - value peeling)

Вынос за тело цикла первой и последней итераций, использующих условный оператор if для присваивания значений элементам массива.
       do i=1,100               !    исходный код
        if (i.EQ.1) then
         a(i)=b(i)
        else if (i.EQ.100) then
         a(i)=c(i)
        else
         a(i)=-a(i)
        endif
       enddo


       a(1)=b(1)                !    после выноса граничных итераций
       do i=2,99
        a(i)=-a(i)
       enddo
       a(100)=c(100)
Использование выноса граничных итераций увеличивает длину программного кода и время компиляции. По умолчанию выполнение peeling ограничено заданным пределом длины кода. Этот предел можно расширить, используя опцию -peel или отменить совсем при помощи -peelall.

Peeling осуществляется по умолчанию и может быть отменен опцией -nopeel или директивой NO_PEEL для отдельных циклов.

Вынос условного оператора за тело цикла (test promotion)

Осуществляется путем повторения тела цикла в каждой ветви условного выражения.
       do i=1,n                 !    исходный код
        if (foo.EQ.bar) then
         a(i)=b(i)
        else
         a(i)=0
        endif
       enddo


        if (foo.EQ.bar) then    !    после выноса условного оператора
         do i=1,n
          a(i)=b(i)
         enddo
         else
          do i=1,n
           a(i)=0
                 enddo
        endif
Вынос условного оператора за тело цикла ускоряет его выполнение, однако может сильно увеличить размер кода программы. По умолчанию увеличение размера кода компилятором ограничено заданным пределом. Данный предел можно увеличить опцией -ptst или отменить полностью опцией -ptstall. Test promotion осуществляется по умолчанию и может быть отменен опцией -noptst.

Замена элементов массива скаляром (scalar replacement)

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

Увеличивает эффективность цикла эа счет размещения элементов массива в регистрах (а не в основной памяти или кэше).

             do i=1,n                 !    исходный код
        do j=1,m
         a(i)=a(i)+b(j)
        enddo
       enddo


       do i=1,n                 !    преобразованный код
        REG=a(i)                !    a(i)  записывается в регистр
        do j=1,m
         REG=REG+b(j)
        enddo
        a(i)=REG                !    содержимоое регистра
       enddo                    !    записывается в  a(i)
Scalar replacement осуществляется по умолчанию и может быть отменена опцией -nsr.

Автоматической локализации данных на уровне -О2 препятствуют:

7 Оптимизация -O3 --- параллелизация

На SPP серии поддерживается двумерная модель параллелизации. Для подкомплекса распараллеливание осуществляется по гиперузлам (node-параллелизм). Для гиперузла распараллеливание осуществляется по нитям процесса (thread-параллелизм). Node-параллелизм осуществляется только при помощи директив и прагм компилятора. Распараллеливание для гиперузла представляет собой одномерный параллелизм.

Параллелизация может быть реализована на уровне циклов, задач и блоков.

Задача (task) --- сегмент кода программы, который может выполняться параллельно с другими задачами. Каждая задача запускается на отдельной (выделенной) нити процесса.

Параллельный блок (parallel region) --- сегмент кода программы, выполняющийся на нескольких процессорах. Параллельный блок запускается на нескольких нитях процесса.

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

На уровне задач параллелизация осуществляется при помощи следующих директив :

      begin_tasks [(атрибуты)] - начало параллельного выполнения задач

      next_task                - конец задачи и начало следующей

      end_tasks                - конец параллельного выполнения задач
Список атрибутов может включать: nodes, threads, ordered и др.

Параллелизация на уровне задач относится к функциональному параллелизму. Функциональный параллелизм появляется в алгоритме, когда выполняются различные потоки команд над различными данными. Этот тип параллелизма наиболее часто связывается с MIMD-машинами с распределенной (distributed) памятью, но может эффективно использоваться и для модели SM.

При использовании директив begin_tasks ...end_tasks необходимо соблюдать осторожность с целью предотвращения присваивания разделяемым переменным значений одной задачей, которые будут использоваться затем другой задачей. Если необходимо, следует использовать средства синхронизации и при этом учитывать накладные расходы на ее осуществление.

Пример

      c$dir  begin_tasks         !  используется  thread - параллелизм
       do i=1,n-1              !  по умолчанию
        a(i)=a(i+1)+b(i)
       enddo
      c$dir  next_task
       call tsub(x,y)
      c$dir  next_task
       c(1:1000:2)=d(1:500)
      c$dir  end_tasks

Пример

      c$dir begin_tasks (nodes)      !  используется  двумерная модель
      c$dir loop_parallel (threads)  !  параллелизма
       do i=1,n
        if(b(i).NE.0) then
         a(i)=b(i)*c(i)
        else
         a(i)=c(i)*d(i)
        endif
       enddo
      c$dir  next_task
      c$dir  begin_tasks (threads)
       call t1sub()
      c$dir  next_task
       call t2sub()
      c$dir  next_task
       call t3sub()
      c$dir  end_tasks               !  (threads)
      c$dir  next_task
       x(1:1000)=y(1:1000)
      c$dir  end_tasks               !  (nodes)
На уровне параллельных блоков параллелизация осуществляется при помощи следующих директив :
      parallel [(атрибуты)]    - начало параллельного блока

      end_parallel             - конец  параллельного блока
Список атрибутов может включать : nodes, threads (по умолчанию).

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

Пример

       program  paraxpl
          .
          .
       do i=1,1024
        a(i)=b(i)+c(i)
          .
          .
       enddo
Допустим, что внутри цикла нет участков, препятствующих параллелизации. Данная программа может быть распараллелена на 8 процессоров по 128 итераций на каждый. Первый процессор выполняет цикл для i=1, ..., 128; второй --- для i=129, ..., 256 и так далее. По умолчанию компилятор генерирует код для запуска на имеющемся количестве процессоров, однако динамическая оптимизация (dynamic selection) обеспечивает генерацию параллельного кода только в случае выигрыша в производительности по сравнению с последовательным кодом. Динамическая оптимизация включает анализ времен активизации параллельных нитей, объявления приватных переменных, используемых в цикле, объединения параллельных нитей после завершения их работы. На каждом процессоре запускается отдельная нить процесса, которая идентифицируется операционной системой SPP-UX уникальным ID. Все нити, кроме 0-ой, являются неактивными (idle) . Нить 0 выполняет все последовательные участки кода программы. Остальные нити одновременно активизируются 0-ой нитью при прохождении параллельного участка кода. После прохождения параллельного участка они снова становятся пассивными.

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

Пример

Пусть даны 128 итераций и 16 нитей. Нить 0 будет выполнять 1, 17, ..., 113 итерации, нить 1 будет выполнять 2, 18, ..., 114 итерации и так далее. Использование явной синхронизации необходимо для гарантии строго детерминированного доступа к разделяемым данным, которым производится присваивание.

При использовании директивы prefer_parallel (nodes), компилятор осуществляет анализ возможной параллелизации циклов. Так, например, автоматически параллелизуются двойные циклы во вложенных циклах: самый внешний цикл после перестановки будет распараллелен в рамках модели node-параллелизма, самый внутренний --- в рамках модели thread-параллелизма и будет запускаться на процессорах каждого гиперузла. Автоматическая параллелизация циклов не осуществляется при использовании следующих директив:

       loop_parallel (nodes);
       parallel (nodes);
       begin_tasks (nodes), next_task, end_tasks.
Необходимо явно указать threads-parallel циклы, задачи или блоки, содержащиеся внутри node-parallel участков кода.

Выбор уровня параллелизма влияет только на размещение активизированных нитей в физической памяти и не влияет на их допустимое количество. Если задан node-параллелизм, на каждом гиперузле активизируется одна нить процесса. Совокупность нитей составляет множество активизированных (spawn) нитей, пронумерованных от 0 до [число гиперузлов-1]. Если затем thread-параллелизм осуществляется внутри node-параллелизма, то активизируется новая совокупность нитей, пронумерованных от 0 до [количество нитей гиперузла-1]. Это означает, что активизированные нити дублируются на каждом гиперузле, однако внутри него они уникальны.

Пример

        program  2dxpl
          .
          .
      c$dir  prefer parallel (nodes)
       do j=1,1024
      c$dir  prefer parallel (threads)
        do i=1,1024
         a(i,j)=b(i,j)+c(i,j)
          .
          .
        enddo
       enddo
В данном примере компилятор распараллеливает j-цикл по гиперузлам и i-цикл по нитям (внутри гиперузла). Допустим, что программа запускается на субкомплексе, состоящем из 2-х гиперузлов, каждый из которых содержит по 4 процессора. Тогда цикл по j распараллеливается на 2 нити, по одной на каждом гиперузле. Нить 0 запускается на последовательном участке кода и является первоначально активной, нить 1 активизируется при выполнении на параллельном участке кода :

       нить  0 (j=1,512)
       нить  1 (j=513,1024)
Цикл по i распараллеливается на каждом гиперузле. На 1-ом гиперузле нить 0 остается активной, нити 1-3 активизируются на параллельном участке :
       нить  0 (i=1,256)
       нить  1 (i=257,512)
       нить  2 (i=513,768)
       нить  3 (i=769,1024)
На 2-ом гиперузле нить 1 активизируется как нить 0, нити 1--3 активизируются на параллельном участке кода (внутри цикла по i ). После завершения цикла по i нить 0 на 2-ом гиперузле опять становится нитью 1 (внутри цикла по j ).

Элементные операции присваивания с массивами (FORTRAN 90) преобразуются компилятором в циклы, которые автоматически распараллеливаются.

Пример

       x(1:M:2,1:N)=y(2:M+1:2,2:N+1)
Данная строка преобразуется следующим образом :
       do i=1,N
        do j=1,M,2
         x(j,i)=y(j+1,i+1)
        enddo
       enddo
Маскированные операции присваивания массивов с помощью оператора WHERE (FORTRAN 90) также преобразуются компилятором в циклы, которые автоматически распараллеливаются.

Пример

       real data(1000),limit
       logical normal(1000)
          .
          .
       where(data.LE.limit) normal=.TRUE.
          .
Данная строка преобразуется следующим образом:
       do i=1,1000
       if(data(i).LE.limit) normal(i)=.TRUE.
       enddo
Простые циклы могут быть распараллелены без предварительных преобразований (опция -О2), однако часто такие преобразования повышают эффективность параллелизации. Например, перестановка циклов способствует более производительному использованию процессорного кэша самым внутренним циклом и параллелизации самого внешнего цикла; расщепление цикла оптимизирует повторное использование кэш-данных (reuse data).

Динамическая оптимизация (dynamic selection) осуществляется по умолчанию при задании -О3. Компилятор генерирует одновременно последовательные и параллельные коды для циклов, подлежащих распараллеливанию, и затем оценивает их эффективность с учетом издержек времени активизации параллельных нитей, объявления приватных переменных, объединения параллельных нитей. В исполняемый модуль записывается наиболее эффективный код. Задание опции -nds отменяет динамическую оптимизацию. В этом случае все допускающие параллелизацию циклы распараллеливаются.

Препятствуют автоматической параллелизации циклов :

       do i=1,n
        a(i)=b(i)+c(i)
           .
           .
        asum=asum+a(i)
       enddo
При распараллеливании цикла для каждой нити создается временная копия переменной asum. После завершения параллельного цикла каждая нить обновляет значение глобальной переменной asum. Большинство конструкций, препятствующих локализации данных, препятствуют и их параллелизации (операторы ввода-вывола, вызовы подпрограмм в теле цикла, альясные скалярные переменные или массивы).

На Фортране можно повысить производительность, описывая первую размерность массивов (соответственно последнюю на С) так, чтобы занять целое число строк сетевого кэша (64 байта). Для этого необходимо изменить первую размерность так, чтобы она стала кратной 64. Например массивы integer*4, real*4 должны иметь первую размерность кратной 16, real*8, complex кратной 8 . После изменения размерности массивов необходимо упорядочить COMMON блоки так, чтобы массивы появлялись раньше скаляров. Требуется также упорядочить скаляры от наибольшего к наименьшему (по формату данных), чтобы они были выравнены до границы наиболее эффективно. Это рекомендуется делать везде, где возможно, и описывать все переменные независимо от того, входят ли они в COMMON блоки или нет.

Оптимизация для SPP серии может вызвать падение эффективности для С серии. Следует использовать препроцессор с опцией -fpp для выбора нужных машинно-зависимых размерностей.

8 Опции оптимизации компилятора

Данные опции дают возможность оптимизировать получаемый код. При отсутствии специального указания опция может быть использована как для С-серии так и для SPP-серии.

-blockloop n (только для SPP-серии)

Расщепление (m-1) циклов в гнезде из m вложенных циклов. Компилятор использует n как параметр расщепления на подциклы и автоматически выбирает какие циклы расщеплять (для выполнения по частям).

-cache n (только для SPP-серии)

Задание однопроцессорного кэша с прямым отображением размером n Kbytes. Компилятор использует эту информацию при определении параметра расщепления циклов.

-br (только для SPP-серии)

Разрешение оптимизации базового регистра. Эта опция по умолчанию установлена на уровне оптимизации -О1 и выше. Для запрещения оптимизации базового регистра используется -nbr.

-gr (только для SPP-серии)

Разрешение глобального распределения регистров. Эта опция установлена по умолчанию и действительна при уровнях оптимизации -О1 и выше.

-mo (только для SPP-серии)

Разрешение на генерацию многооперационных команд. Эта опция установлена по умолчанию на машинах SPP-серии; Для запрещения генерации многооперационных команд используется -nmo.

-nbr (только для SPP-серии)

Запрещение оптимизации базового регистра. По умолчанию CONVEX Fortran обеспечивает оптимизации базового регистра на уровнях оптимизации -O1 и выше.

-nga (только для SPP-серии)

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

-ngr (только для SPP-серии)

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

-ngs (только для SPP-серии)

Запрещение глобального распределения регистров для разделяемых в памяти (SM) переменных, видимых для нитей процесса . Глобальное распределение регистров разрешено по умолчанию на уровне оптимизации -О1 и выше.

-nmo (только для SPP-серии)

Запрещение генерации многооперационных команд. Многооперационные команды генерируются по умолчанию на машинах SPP-серии (и посредством опции -mo).

-no

Нет оптимизации. Эта опция устанавливается по умолчанию для С-серии.

-noblock (только для SPP-серии)

Предотвращение расщепления циклов компилятором в заданных исходных Fortran-файлах.

-nopeel

Запрещение выноса граничных итераций за тело цикла, что разрешается по умолчанию на уровнях оптимизации -О2 и -О3.

-noptst

Запрещение выноса условных операторов из тела цикла, осуществляемое путем повторения тела цикла в каждой ветви условного выражения по умолчанию при уровнях оптимизации -О2 или -О3.

-nsr

Запрещение замены элементов массива скалярами, которая разрешена по умолчанию при уровнях оптимизации -О2 и -О3.

-nuj

Запрещение раскрутки цикла с последующим объединением (jam) . На машинах серии SPP раcкрутка с обьединением имеет место по умолчанию на уровнях оптимизации -О2 и -О3.

-nur

Запрещение раскрутки цикла. На машинах серии SPP раскрутка цикла задействована по умолчанию на уровнях оптимизации -O2 и -O3.

-O0

Машинно-независимая скалярная оптимизация на уровне блоков программы (локальная).

-O1

Машинно-независимая скалярная оптимизация на уровне подпрограмм (глобальная) и глобальное распределение регистров.

-O2

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

-O3

Распараллеливание.

-peel

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

-peelall

То же, что и -peel, но позволяет расширять код беспредельно.

-ptst

Увеличение предела расширения кода при выносе условных операторов из тела цикла (на уровнях оптимизации -О2 и -О3).

-ptstall

То же, что и -ptst, но позволяет расширять код беспредельно.

-rl

Автоматическое осуществление раскрутки или динамического выбора варианта выполнения циклов на уровнях оптимизации -О2 и -О3.

-sr

Разрешение замены элементов массива скалярами. Действует по умолчанию на уровнях оптимизации -О2 и -О3.

-uj

Разрешение раскрутки цикла с последующим объединением (jam). На машинах серии SPP эта опция установлена по умолчанию. Раскрутка и jam-преобразование эффективны на уровнях -О2 и -О3.

-ujn n

Разрешение раскрутки циклов с последующим объединением (jam) и задание желаемого коэффициента раскрутки циклов n, где n --- число повторений тела цикла.

-uo

Выполнение потенциально небезопасной оптимизации: эквивалентная замена операций деления --- более медленных --- на операции умножения --- более быстрые --- может привести к ошибкам округления; замена ``дорогостоящей" операции неявного преобразования типа INTEGER переменной цикла в тип REAL на каждой итерации цикла перед присвоением ее типу REAL копированием в переменную типа REAL (заменой на тип REAL) до входа в цикл может привести к накоплению значимой ошибки ввиду особенностей внутреннего представления типа REAL.

-ur

Автоматическое осуществление раскрутки (или частичной раскрутки) циклов. На машинах SPP-серии выполняется по умолчанию на уровнях оптимизации -О2 и -О3.

-urn n

Автоматическое осуществление раскрутки (или частичной раскрутки) циклов с использованием коэффициента раскрутки n, где n --- число повторений тела цикла.

9 Смешанные опции

-align spp

Выравнивание данных в COMMON-блоках до их естественных границ (до 8 байт). Эта опция используется по умолчанию на машинах SPP-серии.

-Bdir

Поиск замещающего компилятора (fskel, fpp, errmsgf) в директории с именем dir. Обычно на SPP серии текущая директория fc используется в качестве пути поиска.

-Idir

Задание директории, где могут находиться INCLUDE-файлы (или #include при использовании препроцессора). Могут быть заданы не более 8 директорий.

-link arg

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

-nosc

Запрет вычисления условий коротких циклов.

-noU77 (только для SPP-серии)

Предотвращение добавления замыкающего символа подчеркивания (_) к именам подпрограмм в Фортран-библиотеке libU77 (или любой ее разновидности, например libU77p8). Необходимо компилировать все модули программы используя одинаковые соглашения об именах (либо -noU77, либо по умолчанию).

-o name

Присвоение name в качестве имени выполняемому файлу, созданному загрузчиком. Имя по умолчанию: a.out.

-ppu (только для SPP-серии)

Добавление символа подчеркивания (_) к концу определений внешних имен и ссылок.

-tl n

Установление максимального предела CPU времени для компиляции равным n минутам.

-vn

Вывод номера версии используемого компилятора. Вывод идет в поток stderr.

-Wsubproc,sparg[,sparg...]

Передача заданных аргументов подпроцессу subproc. Каждый аргумент (sparg) имеет форму: arg[, argvalue], где arg --- имя опции, распознаваемое подпроцессом; argvalue --- отдельный аргумент, который включается, если требуется; Следующие значения распознаются подпроцессом subproc:
                p - препроцесор;
                c - компилятор;
                o - компилятор;
                a - ассемблер;
                l - загрузчик;
Пример: -Wl, -a, archive предписывает загрузчику компоновать с архивными библиотеками.

10 Директивы оптимизации

Общая форма директив компилятору имеет вид :
          C$DIR [SPP | CSERIES]  directive [, directive ... ]
Строка директивы начинается с 1-ой позиции. Если задан один из атрибутов, определяющих тип рабочей машины (SPP или CSERIES), строка с директивой будет применяться только при компиляции для заданного типа рабочей машины. Если заданы две или более директивы, они разделяются запятыми. Директива должна помещаться на одной строке, она не может быть продолжена.

BARRIER (только для SPP-серии)

Объявление одной или нескольких переменных типа BARRIER. Используется для синхронизации нитей процессов в выбранных точках программы. Директива имеет следующую форму:
        C$DIR BARRIER(barr-name [,barr-name ...])
где barr-name --- имя объявляемой переменной типа BARRIER; Применение директивы полезно только на уровне -O3.

BEGIN_TASKS, NEXT_TASK, END_TASKS

Идентификация группы задач для независимого, параллельного выполнения. Используется для параллелизации кода вне циклов. Применение директивы эффективно только на уровне -O3. Задача --- сегмент кода программы, который может выполняться параллельно с другими задачами. Группа задач начинается с директивы BEGIN_TASKS и заканчивается директивой END_TASKS. Директива NEXT_TASK предшествует второй и всем последующим задачам в этой группе. BEGIN_TASKS директива имеет следующий синтаксис:
     C$DIR BEGIN_TASKS  [ (attribute-list) ]
где возможный параметр attribute-list (т.е. список атрибутов) классифицирует способ, которым задачи запускаются параллельно. Только один атрибут ORDERED доступен на машинах С-серии. На машинах SPP-серии attribute-list может быть одной из следующих комбинаций атрибутов:
           - ORDERED
           - NODES
           - THREADS
           - MAX_THREADS=m
           - ORDERED, NODES
           - ORDERED, THREADS
           - ORDERED, MAX_THREADS=m
           - NODES, MAX_THREADS=m
           - THREADS, MAX_THREADS=m
           - ORDERED, NODES, MAX_THREADS=m
           - ORDERED, THREADS, MAX_THREADS=m

BLOCK_LOOP (только для SPP-серии)

Использование заданного коэффициента разбиения цикла на два вложенных для непосредственно следующего за директивой цикла. Имеет синтаксис:
     C$DIR BLOCK_LOOP  [ (BLOCK_FACTOR = n) ]
где n - заданный коэффициент для разбиения цикла. Если n не задан, компилятор выбирает соответствующий коэффициент. BLOCK_LOOP эффективна только на уровнях -O2 или -O3.

BLOCK_SHARED (только для SPP-серии)

Объявление указанных размещаемых массивов как данных класса блочно-разделяемой памяти (BLOCK_SHARED).

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

Директива имеет синтаксис:

     C$DIR BLOCK_SHARED(alloc-arr  [,   alloc-arr   ...])
где alloc-arr --- размещаемый массив, описываемый до этой директивы в операторе ALLOCATABLE.

CRITICAL_SECTION (только для SPP-серии)

Помечает сегмент кода, который должен быть выполнен строго последовательно (единственной нитью) во времени. Директива имеет синтаксис:
     C$DIR CRITICAL_SECTION [ (gate-var) ]
где возможный параметр gate-var --- это ранее объявленная GATE-переменная, которая будет использоваться для контроля выполнения помеченного сегмента кода.

END_CRITICAL_SECTION

Определяет точку завершения критической секции. Директива не использует GATE-переменную и имеет синтаксис:
     C$DIR END_CRITICAL_SECTION

FAR_SHARED (только для SPP-серии)

Хранение заданного списка переменных в far-shared памяти. Директива использует следующий формат:
     C$DIR FAR_SHARED  (namelist)
где namelist --- список имен COMMON-блоков, массивов и скалярных переменных.

FAR_SHARED_POINTER (только для SPP-серии)

Размещение скрытого, сгенерированного компилятором указателя на заданную переменную в far-shared памяти, независимо от класса памяти размещаемой переменной.

Директива использует следующий формат:

     C$DIR FAR_SHARED_POINTER  (alloc-var)
где alloc-var --- имя переменной, описанной ранее в операторе ALLOCATABLE. Дополнительные директивы THREAD_PRIVATE_POINTER, NODE_PRIVATE_POINTER и \break NEAR_SHARED_POINTER помещают указатели в другие классы памяти.

GATE (только для SPP-серии)

Объявление одной (или более) переменных с типом GATE. Формат директивы следующий:
      C$DIR GATE(gate-name  [, gate-name ...])
где gate-name --- это имя объявляемой переменной типа GATE. Использование GATE полезно только на уровне -O3.

Используя GATE переменные и вспомогательные встроенные функции можно ограничить выполнение блока программы одной нитью процесса. Более подробная информация в [2].

LOOP_PARALLEL (только для SPP-серии)

Параллельное выполнение цикла, следующего непосредственно за директивой. Цикл, отмеченный такой директивой, должен иметь заданное число итераций во время инициирования цикла.

Директива LOOP_PARALLEL отличается от директивы PREFER_PARALLEL в том, что распараллеливает выполнение цикла, следующего за директивой, невзирая на имеющиеся в цикле зависимости.

Формат директивы LOOP_PARALLEL такой:

      C$DIR LOOP_PARALLEL [ (attribute-list) ]
где опция attribute-list может содержать любую из следующих комбинаций атрибутов:

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

LOOP_PRIVATE

Объявление списка переменных и/или массивов, используемых только для DO-цикла, следующего непосредственно за этой директивой. Предполагается, что переменные, объявленные с помощью LOOP_PRIVATE, не имеют зависимостей в цикле. Эти переменные не могут принимать никаких начальных и конечных значений. Директива имеет вид:
      C$DIR LOOP_PRIVATE(varlist)
где varlist --- список скалярных переменных или массивов, разделенных запятыми, которые должны использоваться только для следующего за директивой цикла.

NEAR_SHARED (только для SPP-серии)

Сохранение переменных из заданного списка в разделяемой near-shared памяти. Директива имеет следующий формат:
      C$DIR NEAR_SHARED (namelist)
где namelist --- список имен COMMON-блоков, массивов и скалярных переменных, которые должны храниться в near-shared памяти.

NEAR_SHARED_POINTER (только для SPP-серии)

Размещение скрытого, сгенерированного компилятором указателя на заданную переменную в near-shared памяти, независимо от класса динамически выделяемой памяти для переменной. Директива имеет следующий формат:
      C$DIR NEAR_SHARED_POINTER  (alloc-var)
где alloc-var --- имя переменной, описанной ранее в операторе ALLOCATABLE.

NO_BLOCK_LOOP (только для SPP-серии)

Предписывает компилятору не выполнять расщепления непосредственно следующего за директивой цикла.

NODE_PRIVATE (только для SPP-серии)

Сохранение переменных из заданного списка в node-private памяти. Директива имеет следующий формат :
     C$DIR NODE_PRIVATE (namelist)
где namelist --- список имен COMMON-блоков, массивов и скалярных переменных.

NODE_PRIVATE_POINTER (только для SPP-серии)

Размещение скрытого, сгенерированного компилятором указателя на заданную переменную в node-private памяти, независимо от класса динамически выделяемой памяти для переменной. Директива имеет следующий формат:
      C$DIR NODE_PRIVATE_POINTER  (alloc-var)
где alloc-var --- имя переменной, описанной ранее в операторе ALLOCATABLE.

NO_LOOP_DEPENDENCE (только для SPP-серии)

Игнорирование любых потенциальных зависимостей в цикле (LCDs - loop-carried dependences), обнаруживаемых компилятором в заданных массивах. Директива имеет следующий формат:
      C$DIR NO_LOOP_DEPENDENCE (namelist)
где namelist --- список массивов, потенциальные LCD-зависимости по которым будут игнорироваться компилятором. Если namelist не задан, то компилятор предполагает, что не существует никаких LCD-зависимостей в любом из массивов цикла.

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

NO_PARALLEL

Директива предписывает компилятору не распараллеливать цикл, непосредственно следующий за ней. Векторизация (доступная на машинах C-серии) не предотвращается.

NO_PEEL

Предотвращение выноса граничных итераций, использующих явно переменную цикла внутри условного оператора, из тела цикла, непосредственно следующего за директивой.

Директива отменяет задаваемый предел увеличения длины кода на всех уровнях: установленный по умолчанию, заданный при помощи -peel или -peelall опций.

Использование директивы эффективно только на уровне -О2 и выше.

NO_PROMOTE_TEST

Запрещение выноса условных операторов за тело цикла, следующего непосредственно за директивой (осуществляемое по умолчанию на уровнях -О2 или -О3 путем повторения тела цикла в каждой ветви условного выражения). Директива отменяет задаваемый предел расширения кода на всех уровнях: установленный по умолчанию, заданный при помощи -ptst или -ptstall опций.

Использование директивы эффективно только на уровне -О2 и выше.

NO_SIDE_EFFECTS

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

Формат директивы следующий :

      C$DIR NO_SIDE_EFFECTS ( func  [, func ...] )
где func --- имена определенных пользователем функций. Хотя директива может находиться в любом месте программы, эффективней ее размещать перед вызовом указанных функций. В случае отсутствия аргументов директива распространяется на все вызываемые после нее в программе функции.

Использование директивы эффективно на всех уровнях оптимизации, за исключением -no.

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

          More optimization is possible if this function call has no
          side effects.
Вызываемые в цикле функции без побочных эффектов инвариантны в следующих аспектах:

NO_UNROLL_AND_JAM

Запрещение раскрутки с последующим объединением (jam) для следующего за директивой цикла. На машинах серии SPP раcкрутка с обьединением имеет место по умолчанию на уровнях оптимизации -О2 и -О3.

Использование директивы эффективно только на уровне -О2 и выше.

ORDERED_SECTION (только для SPP-серии)

Заставляет все нити выполнять обозначенную секцию кода одной нитью одновременно и в заданном порядке (в порядке активизации нитей).

Формат директивы следующий :

       C$DIR ORDERED_SECTION (gate)
где gate задает ранее объявленную переменную GATE, которая будет использоваться для управления входом в упорядоченную (ordered) секцию. Более подробная информацию см. в [2].

END_ORDERED_SECTION (только для SPP-серии)

Определяет точку, где ordered-секция заканчивается. Не принимает параметр gate.

PEEL

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

PEEL_ALL

То же что и PEEL, при этом код программы может расширяться неограниченно.

PREFER_PARALLEL

Распараллеливание цикла, следующего непосредственно за директивой, в случае отсутствия в цикле зависимостей. Цикл, отмеченный такой директивой, должен иметь заданное число итераций во время его инициирования. Это значит, что данная директива не может использоваться в WHILE-циклах, в циклах содержащих операторы RETURN или STOP.

Директива PREFER_PARALLEL отличается от директивы LOOP_PARALLEL и директивы FORCE_PARALLEL тем, что не распараллеливает цикла, следующего за директивой, в случае существования в нем зависимостей.

Использование директивы эффективно только на уровне -О3. Директива имеет следующий формат :

      C$DIR PREFER_PARALLEL [(attribute-list)]
где возможный параметр attribute-list определяет способ, которым цикл, следующий за директивой, распараллеливается. На машинах C-серии доступны только атрибуты CHUNK_SIZE и ORDERED.

На машинах SPP-серии attribute-list может содержать одну из следующих комбинаций атрибутов:

Пример

      C$DIR PREFER_PARALLEL ( CHUNK_SIZE = 8 )
       do i=1,750
        a(i)=c(i)*sin(b(i))
       enddo
Распределяет цикл среди всех нитей порциями по 8 итераций.

PROMOTE_TEST

Вынос условных операторов за тело цикла, следующего непосредственно за директивой (осуществляемый по умолчанию на уровнях -О2 или -О3 путем повторения тела цикла в каждой ветви условного выражения). Директива увеличивает задаваемый по умолчанию предел расширения кода программы, но не безгранично.

Использование директивы эффективно только на уровне -О2 и выше.

PROMOTE_TEST_ALL

То же что и PROMOTE_TEST, при этом код программы может расширяться неограниченно.

ROW_WISE

Сообщает компилятору, что в указанных массивах осуществляется реверсия индексов, т.е. элементы массива размещаются по строкам (как в С и Ада), а не по столбцам (как в Фортране). Применение директивы может повысить эффективность доступа к памяти.

Использование директивы эффективно на всех уровнях оптимизации, включая -no.

Формат директивы следующий:

      C$DIR ROW_WISE (array_name [, array_name ...])

Примеры

       dimension a(4,1000)
       do i=1,4
        do j=1,1000           !  доступ к массиву замедляется
         a(i,j)=0             !  из-за разрывного размещения
        enddo                 !  элементов массива в памяти
       enddo                  !  ( noncontiguous memory )


        C$DIR ROW_WISE (a)
       dimension a(4,1000)
        do i=1,4               !  доступ к массиву ускоряется
        do j=1,1000           !  за счет смежного размещения
         a(i,j)=0             !  элементов массива в памяти
        enddo                 !   ( contiguous memory )
       enddo

SAVE_LAST (только для SPP-серии)

Определяет, что все переменные, объявленные директивой LOOP_PRIVATE, находящиеся в цикле, следующем непосредственно за SAVE_LAST, имеют значения соответствующие последней итерации при выходе из цикла. Если данная директива не используется, значения LOOP_PRIVATE переменных или массивов неопределены при выходе из цикла.

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

SCALAR

Предотвращениевекторизации и распараллеливания DO-цикла, следующего за данной директивой. Тело цикла может быть векторизовано или распараллелено, если более внешний цикл меняется местами с данным циклом, объявленным директивой SCALAR.

Использование директивы эффективно на всех уровнях оптимизации.

Использование директивы полезно когда число итераций цикла слишком мало для выигрыша при векторизации или параллелизации, когда вычисляемые результаты должны быть такими же как и для скалярного варианта, а также для предотвращения перестановки цикла (loop interchange).

       do 10 i=1,n               !  ( n = 2    )
       do 10 j=1,m               !  ( m = 1000 )
   10   a(i,j) = b(i,j) + c(i,j)
В данном примере компилятор осуществит перестановку местами i и j цикла для смежного размещения в памяти элементов массивов a, b, c.
      C$DIR  SCALAR
       do 10 i=1,n               !  ( n = 2    )
       do 10 j=1,m               !  ( m = 1000 )
   10   a(i,j) = b(i,j) + c(i,j)
Применение директивы SCALAR обеспечит, чтобы цикл с наибольшим числом итераций оставался самым внутренним.
      C$DIR  SCALAR
       do 10 i=1,n               !  ( n = 2 )
      C$DIR  SCALAR
       do 10 j=1,m               !  ( m = 2 )
   10   a(i,j) = b(i,j) + c(i,j)
В данном примере предотвращается векторизация и параллелизация обоих циклов, т.к. число итераций мало.

TASK_PRIVATE

Объявление ``приватным" списка переменных и/или массивов для задач, следующих непосредственно за данной директивой. В CONVEX-Фортране задачи определяются при помощи директив BEGIN_TASKS, NEXT_TASK и END_TASKS. Директива TASK_PRIVATE должна непосредственно предшествовать директиве BEGIN_TASKS.

Действие task_private переменных ограничено блоком от BEGIN_TASKS до \break END_TASKS.

Как TASK_PRIVATE могут быть описаны только статические переменные и массивы. Динамические, размещаемые и автоматические массивы не допускаются.

Использование директивы эффективно только на уровне -О3.

Директива TASK_PRIVATE имеет формат:

      C$DIR TASK_PRIVATE (varlist)
где varlist --- список переменных или массивов, разделенных запятыми, которые должны быть приватными для каждой задачи, следующей за этой директивой.

THREAD_PRIVATE (только для SPP-серии)

Предписывает хранение одной (или более) заданных переменных в thread-private памяти. Директива имеет следующий формат:
      C$DIR THREAD_PRIVATE (namelist)
где namelist --- список имен COMMON-блоков, массивов и скалярных переменных.

THREAD_PRIVATE_POINTER (только для SPP-серии)

Помещает скрытый, сгенерированный компилятором указатель на заданную переменную в thread-private память, независимо от класса памяти, в котором переменная размещается.

Директива имеет следующий формат :

       C$DIR THREAD_PRIVATE_POINTER  (alloc-var)
где alloc-var --- имя переменной, описанной ранее в операторе ALLOCATABLE.

UNROLL

Осуществление развертки цикла (замена тела цикла, следующего далее, последовательностью линейных операторов). Развертка выполняется только для скалярных циклов.

Директива имеет следующий формат:

      C$DIR UNROLL [ (UNROLL_FACTOR=n) ]
где необязательный параметр UNROLL_FACTOR определяет число повторений тела цикла n.

Использование директивы эффективно только на уровне -О2 и выше.

UNROLL_AND_JAM

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

Директива имеет следующий формат:

      C$DIR UNROLL_AND_JAM [ (UNROLL_FACTOR=n) ]
где необязательный параметр UNROLL_FACTOR определяет число повторений тела цикла n.

Использование директивы эффективно только на уровне -О2 и выше.

Литература

1. Exemplar Programing Guide, Third Edition, June 1995

2. Fortran Language Reference Eleventh Edition, October 1994

3. Fortran User's Guide, Eleventh Edition, October 1994

4. Convex MPICH User's Guide for Exemplar Systems.


The file was converted from TeX source by FunnyTeX utility by Mike Krutikov