РУКОВОДСВО ПОЛЬЗОВАТЕЛЯ

Linux Edition. Revision B
перевод Балуева А. Н.

Оглавление

  • Глава 4. Мультипроцессорная обработка (только версия PRO)

    Глава 4. Мультипроцессорная обработка (только версия PRO)

    В начало страницы
    
     В этой главе описываются методы параллельного исполнения программы на Fortran. program in
     Параллельная обработка  Fortran-программ носит название мультипроцессорной
    обработки или мультипроцессинга.
    

    Обзор мультипроцессинга

    В начало страницы
    
    В настоящем документе мультипроцессинг означает, что одна программа
    выполняется на двух или более процессорах, которые могут работать независимо и
    одновременно. Здесь это не означает одновременного выполнения двух или более
    программ. Рассмотрим следующий код:
    
                     do i = 1, 50000
                       a(i) = b(i) + c(i)
                     end do
    
    Различные итерации цикла  DO выполняются на разных процессорах в одно и то же
    время.
    
           CPU 1:
    
                     do i1 = 1, 25000
                       a(i1) = b(i1) + c(i1)
                     end do
    
           CPU 2:
    
                     do i2 = 25001, 50000
                       a(i2) = b(i2) + c(i2)
                     end do
    
    

    Увеличение производительности

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

    Что препятствует увеличению производительности

    В начало страницы
    
    Увеличение скорости при использовании мультипроцессинга с помощью LF95 PRO
    происходит от расщепления циклов по доступным процессорам.  В число
    препятствий увеличения производительности входят:
    
    * Накладные расходы на инициализацию и управление подпроцессами с
    вспомогательными процессорами.
    * Отсутствие больших массивов и циклов, их обрабатывающих.
    * Программы, интенсивно использующие не вычисления, а ввод/вывод.
    * Возможность получения неправильных результатов.
    * Циклы, которые нельзя параллелизовать.
    
    Эти препятствия рассматриваются в следующих ниже разделах.
    

    Накладные расходы

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

    Отсутствие больших массивов

    В начало страницы
    
    Если ваша программа не тратит большую часть своего времени на интенсивные
    вычислительные циклы, то нет и адекватной возможности для разделения его между
    процессорами.  Она, вероятно, будет столь же быстро работать и без
    парраллелизации. Например, если половину времени ваша программа тратит на
    циклы, которые можно параллелизовать, то максимальная экономия времени за счет
    использования двух процессоров составит 25%.  И если программа при
    последовательном исполнении берет две минуты и половина этого времени тратится
    на циклы, которыые можно параллелизовать, то оптимальное параллельное
    исполнительное время будет одна минута и 30 секунд.
    
    

    Программы с интенсивным вводом/выводом

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

    Возможность некорректных результатов

    В начало страницы
     
    Возможность парралелизации некоторых циклов может быть установлена
    компилятором без помощи программиста.  Однако, многие циклы связаны
    зависимостью данных, которая препятствует автоматической параллелизации
    из-за возможности ошибок в результатах. По этой причине  LF95 PRO содержит
    строки контроля за оптимизацией (см. "Строка контроля оптимизации" на стр.73)
    и  директивы OpenMP (см. "OpenMP" на стр. 85), с которыми программист может
    передать информацию, необходимую компилятору для параллелизации циклов, без
    которой правильная параллелизация невозможна.
    

    Другие непараллелизуемые циклы

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

    Аппаратура для мультипроцессинга

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

    Автоматическая параллелизация

    В начало страницы
    
    При автоматической параллелизации циклы  DO и операции с массивами
    параллелизуются без участия программиста в модификации программы. Это упрощает
    передачу исходных программ на другие обрабатывающие системы, если эти
    программы удовлетворяют стандартам Fortran.
    LF95 PRO имеет средство, называемое Строка управления оптимизацией (сокращенно
    OCL), которое помогает проводить автоматическую параллелизацию.
    OCL используются программистом для идентификации конструктов, которые могут
    исполняться параллельно (см."Строка управления оптимизацией" на стр. 73, где
    есть примеры).  Поскольку  OCL имеет форму комментариев  Fortran, программы с
    ними соответствуют стандартам и могут компилироваться компиляторами, не
    использующими OCL.
    

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

    В начало страницы
    
    Имеются четыре таких параметра. Это --parallel, --threads, --threadstack и
    --ocl.  Они описаны в "Параметры компилятора и компоновщика" на стр. 14.
    
    

    Переменные окружения

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

    Переменная окружения PARALLEL

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

    Замечание:

    В начало страницы
    
    Если --threads указан во время компиляции, значение  PARALLEL должно быть
    равно аргументу параметра --threads и количество активных  CPU должно быть
    больше или равно аргументу параметра  --threads.  Если переменная окружения
    PARALLEL не установлена, то аргумент параметра --threads должен быть таким же,
    как количество активных CPU.
    

    Переменная окружения THREAD_STACK_SIZE

    В начало страницы
    
    Когда переменная THREAD_STACK_SIZE установлена, она определяет размер стека в
    килобайтах для каждого стека подпроцесса (thread stack).  Локальные переменные
    в циклах DO и операциях с массивами кладутся на стек.  Может оказаться
    необходимым увеличить размер стека, если этих переменных много.
    По умолчанию размер стека для каждого подпроцесса такой же, как размер
    исполнительного.  Параметр компилятора --threadstack и переменная окружения
    THREAD_STACK_SIZE могут менять размер стека для каждого подпроцесса. Параметр
    компилятора --threadstack подавляет переменную окружения THREAD_STACK_SIZE.
    
    		 Примеры компиляции и исполнения
    
                             % lf95 --parallel --ocl test1.f
                             % a.out
    
    В приведенном  примере  автоматической параллелизации и оптимизации
    управляющие строки (OCL) действуют во время компиляции.  Эта программа
    выполняется с использованием всех активных CPU в конфигурации.
    
                    % lf95 --parallel test2.f
                    5001-i: "test2.f", line 2: параллелизуется цикл DO с индексом i.
                    % setenv PARALLEL 2
                    % a.out
                    % setenv PARALLEL 4
                    % a.out
    Во втором примере переменная окружения PARALLEL устанавливается на 2 и
    программа исполняется с двумя CPU. Затем переменная окружения PARALLEL
    устанавливается на 4 и программа исполняется с четырьмя CPU.
    

    Детали мультипроцессинга

    В начало страницы
    
    В этом разделе мультипроцессинг описан более подробно.
    

    Объекты автоматической параллелизации

    В начало страницы
     
    Операторами, подвергаемыми автоматической параллелизации, служат циклы DO
    (включая вложенные циклы DO) и операторы над массивами (выражения-массивы и
    присваивания массивам).
    
    Сечения циклов (Loop Slicing)
    Автоматическая параллелизация может разрезать циклы DO на несколько частей.
    Требуемое для исполнения время уменьшается за счет исполнения частей  DO
    параллельно.
              do i = 1, 50000
                a(i) = b(i) + c(i)
              end do
    Разные итерации цикла  DO могут исполняться на разных CPU одновременно.
    
    CPU 1:
              do i1 = 1, 25000
                a(i1) = b(i1) + c(i1)
              end do
    CPU 2:
              do i2 = 25001, 50000
                a(i2) = b(i2) + c(i2)
              end do
    
    

    Операции над массивами и автоматическая параллелизация

    В начало страницы
    
    Автоматическая параллелизация применяется также к операторам с массивами
    (выражениям с массивами и присваиванием массивам).
    
              integer a(1000), b(1000)
              a = a + b
    
    Половина операций делается на одном CPU и половина -- на другом.
    
    CPU 1:
              a(1:500) = a(1:500) + b(1:500)
    CPU 2:
              a(501:1000) = a(501:1000) + b(501:1000)
        
        

    Автоматическое разрезание (Slicing) циклов компилятором

    В начало страницы
    
    LF95 параллелизует цикл  DO, если порядок ссылок на данные останется таким же
    как при последовательном исполнении.  LF95 обеспечивает тот же результат
    работы мультипроцессорной программы, что и при последовательном ее исполнении.
    
    Следующий пример содержит цикл  DO, к которому нельзя применить разрезание на
    части.  В этом цикле  DO , когда параметр цикла  I равен  5001, необходимо
    иметь значение элемента массива A(5000).
    
    
                              do i = 2,10000
                                a(i) = a(i-1) + b(i)
                              end do
    
    Следующее разрезание нельзя применить к предыдущему коду:
                    CPU 1:
                              do i = 2,5000
                                a(i) = a(i-1) + b(i)
                              end do
                    CPU 2:
                              do i = 5001, 10000
                                a(i) = a(i-1) + b(i)
                              end do
    
     A(5000) недоступно для CPU2, и цикл так разрезать нельзя.
    
    

    Перестановки в циклах и автоматическое разрезание

    В начало страницы
     
    Если разрезается вложенный (nested) цикл DO, LF95 пытается параллелизовать,
    если возможно, самый внешний цикл. LF95 выбирает цикл DO, который можно
    разрезать и переставляет его с возможно более внешним циклом. Целью этого
    является уменьшение накладных расходов мультипроцессинга и увеличение
    исполнительной производительности.
     Следующая картина показывает пример перестановки во вложенном цикле.
    Можно разрезать внутренний цикл с параметром J. Частота мультипроцессорного
    управления может быть уменьшена перестановкой его с внешним циклом.
    
                              do i = 2, 10000
                                do j = 1, 10
                                   a(i,j) = a(i-1,j) + b(i,j)
                                end do
                              end do
    
    После перестановки циклов получим:
                              do j = 1, 10
                                do i = 2, 10000
                                   a(i,j) = a(i-1,j) + b(i,j)
                                end do
                              end do
    
    После параллелизации получим:
    
                    CPU 1:
                              do j = 1, 5
                                do i = 2, 10000
                                   a(i,j) = a(i-1,j) + b(i,j)
                                end do
                              end do
    
    
    
                    CPU 2:
                              do j = 6, 10
                                do i = 2, 10000
                                 a(i,j) = a(i-1,j) + b(i,j)
                                end do
                              end do
    
    

    Распределение циклов и автоматическое разрезание

    В начало страницы
    
    В следующем примере ссылки на массив A не могут быть разрезаны, потому что
    порядок ссылок на данные будет отличаться от порядка ссылок при
    последовательном исполнении.  Массив B можно разрезать, потомку что порядок
    ссылки на данные остается тот же, что и при последовательном исполнении. В
    этом случае оператор , в котором определяется массив A и оператор, где
    определяется массив  B , разделены в двух циклах  DO, и тот цикл  DO, где
    определяется массив B, параллелизуется.
    
    do i = 1, 10000
                a(i) = a(i-1) + c(i)
                b(i) = b(i) + c(i)
              end do
    
    С распределением циклов это превращается в:
              do i = 1, 10000
                a(i) = a(i-1) + c(i)
              end do
    
              do i = 1, 10000
                b(i) = b(i) + c(i)
              end do
    Затем второй цикл параллелизуется:
    CPU 1:
              do i = 1, 5000
                b(i) = b(i) + c(i)
              end do
    CPU 2:
              do i = 5001, 10000
                b(i) = b(i) + c(i)
              end do
    
    

    Слияние (Fusion) циклов и автоматическое разрезание циклов

    В начало страницы
    
    В следующем примере имеются два последовательных цикла DO с одинаковым
    управлением. В этом случае  дополнительное управление циклом и частоту
    управления мультипроцессингом можно сократить за счет слияния двух циклов в
    один.
    
    
                              do i = 1, 10000
                                a(i) = b(i) + c(i)
                              end do
    
                              do i = 1, 10000
                                d(i) = e(i) + f(i)
                              end do
    После слияния циклов получаем:
    
                              do i = 1, 10000
                                a(i) = b(i) + c(i)
                                d(i) = e(i) + f(i)
                              end do
    После параллелизации это превращается в:
    
                    CPU 1:
    
                              do i = 1, 5000
                                a(i) = b(i) + c(i)
                                d(i) = e(i) + f(i)
                              end do
                    CPU 2:
    
                              do i = 5001, 10000
                                a(i) = b(i) + c(i)
                                d(i) = e(i) + f(i)
                              end do
    
    

    Приведение (Reduction) циклов

    В начало страницы
    
    Приведение циклов разрезает циклы DO, изменяя порядок операций (сложения,
    умножения и т.д.). Заметим, что приведение циклов может быть причиной
    небольших изменений в результатах.
    Оптимизация приведением циклов применяется при наличии в цикле одной из
    следующих операций:
    
                    * SUM: S=S+A(I)
                    * PRODUCT: P=P*A(I)
                    * DOT PRODUCT: P=P+A(I)*B(I)
                    * MIN: X=MIN(X,A(I))
                    * MAX: Y=MAX(Y,A(I))
                    * OR: N=N.OR. A(I)
                    * AND: M=M.AND.A(I)
    
    Следующий пример иллюстрирует приведение циклов и их автоматическое
    разрезание.
    
    
    
              sum = 0
              do i = 1, 10000
                  sum = sum + a(i)
              end do
    Параллелизация происходит так:
    CPU 1:
              sum1 = 0
              do i = 1, 5000
                  sum1 = sum1 + a(i)
              end do
    CPU 2:
              sum2 = 0
              do i = 5001, 10000
                  sum2 = sum2 + a(i)
              end do
    Частичные суммы складываются:
              sum = sum + sum1 + sum2
    
    

    Ограничения на разрезание циклов

    В начало страницы
     
    Следующие типы циклов не могут подвергаться разрезанию.
    1. Циклы, в которых не прогнозируется уменьшение временных затрат.
    2. Циклы, содержащие операции типа, не подходящего для разрезания циклов.
    3. Циклы, содержащие ссылки на процедуры.
    4. Слишком сложные циклы.
    5. Циклы, содержащие операторы ввода/вывода.
    6. Циклы, в которых порядок ссылки на данные может измениться относительно
     порядка при последовательном исполнении.
    
    

    Отладка

    В начало страницы
    
    Мульти-подпроцессные программы не могут отлаживаться с помощью fdb.
    

    Строка управления оптимизацией

    В начало страницы
    
    LF95 допускает употребление строк управления оптимизацией (OCL) для
    ориентировки автоматической параллелизации. Такие строки дают эффект, когда
    указаны оба параметра --parallel и --ocl.
    
    

    Спецификатор управления оптимизацией

    В начало страницы
    
    Строки OCL имеют несколько функций, зависящих от спецификатора управления
    оптимизацией.
    

    Синтаксис OCL

    В начало страницы
    
    Колонки 1-5 строки управления оптимизацией (OCL) должны быть такими:
    "!OCL ".  Затем следуют один или более спецификаторов управления оптимизацией:
              !OCL i [,i] ....
    где каждое i есть один из спецификаторов управления оптимизацией: SERIAL,
    PARALLEL, DISJOINT, TEMP, или  INDEPENDENT (см. "Спецификатор управления
    оптимизацией" на стр. 73).
    
    

    Позиция OCL

    В начало страницы
    
    Позиция OCL зависит от спецификатора контроля оптимизации.
    OCL для автоматической параллелизации может располагаться в тотальной (total)
    позиции или в позиции цикла. Названные позиции определяются так:
      * Тотальная позиция:  начало любой программной единицы.
      * Позиция цикла: непосредственно перед циклом DO.  Вместе с тем, более одной
     OCL можно указывать в позиции цикла и строки комментария можно помещать
    между OCL и циклом DO.
    
    !ocl serial <------------------ тотальная позиция
                                      subroutine sub(b, c, n)
                                      integer a(n), b(n), c(n)
                                      do i = 1, n
                                         a(i) = b(i) + c(i)
                                      end do
                                      print*, fun(a)
                             !ocl parallel <---------------- позиция цикла
                                      do i = 1, n
                                         a(i) = b(i) * c(i)
                                      end do
                                      print*, fun(a)
                                      end
    
    
    Автоматическая параллелизация и спецификаторы управления  оптимизацией
    Спецификатор управления  оптимизацией становится бесполезным для цикла DO
    который не годится (is not a target) для разрезания цикла, даже если указан
    спецификатор управления оптимизацией для автоматической параллелизации.
    

    Спецификаторы управления оптимизацией

    В начало страницы
    
    Для облегчения автоматической параллелизации можно использовать следующие
    спецификаторы управления оптимизацией:
                    * SERIAL
                    * PARALLEL
                    * DISJOINT
                    * TEMP
                    * INDEPENDENT
    
    
    
    SERIAL
    Этот спецификатор используется для запрещения разрезания цикла.
    
    Например, если программист знает, что последовательное исполнение цикла  DO
    происходит быстрее чем параллельное исполнение, возможно, потому, что счетчик
    цикла всегда будет небольшим, он может указать спецификатор  SERIAL для
    этого цикла.
    
    Syntax:
               !OCL SERIAL
    
    Спецификатор SERIAL может быть указан в позиции цикла или в тотальной позиции.
    Эффект от  SERIAL зависит от его позиции.
    
    * В позиции цикла
    
    SERIAL запрещает разрезание цикла  DO, соответствующего OCL (и любого
    вложенного  цикла).
    
    * В тотальной позиции
    
    SERIAL запрещает разрезание всех циклов в программной единице, содержащей OCL.
    
    В следующей программе, если цикл 2 не должен разрезаться, разрезание циклов
    можно предотвратить указанием SERIAL. Буквы  p слева в исходной программе
    отмечают параллелизуемые операторы.
    
                p       do j = 1, 10
                p          do i = 1, l        ! <----------- loop 1
                p              a1(i,j) = a1(i,j) + b1(i,j)
                p              c1(i,j) = c1(i,j) + d1(i,j)
                p              e1(i,j) = e1(i,j) + f1(i,j)
                p              g1(i,j) = g1(i,j) + h1(i,j)
                p          end do
                p       end do
    
                p       do j=1, 10
                p          do i=1, m       ! <------------ loop 2
                p              a2(i,j) = a2(i,j) + b2(i,j)
                p              c2(i,j) = c2(i,j) + d2(i,j)
                p              e2(i,j) = e2(i,j) + f2(i,j)
                p              g2(i,j) = g2(i,j) + h2(i,j)
                p          end do
                p       end do
    
    
    
                p         do j=1, 10
                p           do i=1, n      ! <------------ loop 3
                p              a3(i,j) = a3(i,j) + b3(i,j)
                p              c3(i,j) = c3(i,j) + d3(i,j)
                p              e3(i,j) = e3(i,j) + f3(i,j)
                p              g3(i,j) = g3(i,j) + h3(i,j)
                p           end do
                p         end do
                p         do j = 1, 10
                p           do i = 1, l      ! <------------ loop 1
                p              a1(i,j) = a1(i,j) + b1(i,j)
                p              c1(i,j) = c1(i,j) + d1(i,j)
                p              e1(i,j) = e1(i,j) + f1(i,j)
                p              g1(i,j) = g1(i,j) + h1(i,j)
                p           end do
                p         end do
                     !ocl serial
                          do j = 1, 10
                            do i = 1, m      ! <------------ loop 2
                               a2(i,j) = a2(i,j) + b2(i,j)
                               c2(i,j) = c2(i,j) + d2(i,j)
                               e2(i,j) = e2(i,j) + f2(i,j)
                               g2(i,j) = g2(i,j) + h2(i,j)
                            end do
                          end do
                p         do j = 1, 10
                p           do i = 1, n      <-------------- loop 3
                p              a3(i,j) = a3(i,j) + b3(i,j)
                p              c3(i,j) = c3(i,j) + d3(i,j)
                p              e3(i,j) = e3(i,j) + f3(i,j)
                p              g3(i,j) = g3(i,j) + h3(i,j)
                p           end do
                p         end do
    
                    PARALLEL
    Спецификатор PARALLEL используется для  обращения (reverse) эффекта от  SERIAL
    и разрешения нарезания циклов.
    
                    Syntax: !OCL PARALLEL
    Спецификатор PARALLEL может размещаться в позиции цикла или тотальной позиции.
    Действие  PARALLEL зависит от его позиции.
    * Случай позиции цикла
    
    
    
    PARALLEL разрешает нарезание циклов для цикла DO, соответствующего OCL (и
    любых вложенных в него циклов),
    * Случай тотальной позиции
    PARALLEL допускает нарезание циклов для всех циклов в программе, содержащей
    OCL. В следующем примере, если только loop 2 должен разрезаться, он может
    нарезаться при указании PARALLEL вместе с SERIAL, как это показано.
    Буква P слева в исходной программе отмечает параллелизуемые операторы.
             !ocl serial <------------ total position
                               ...
                        do j = 1, 10
                           do i = 1, l        ! <----------- loop 1
                               a1(i,j) = a1(i,j) + b1(i,j)
                               c1(i,j) = c1(i,j) + d1(i,j)
                               e1(i,j) = e1(i,j) + f1(i,j)
                               g1(i,j) = g1(i,j) + h1(i,j)
                           end do
                        end do
             !ocl parallel
              p         do j = 1, 10
              p            do i = 1, m        ! <----------- loop 2
              p                a2(i,j) = a2(i,j) + b2(i,j)
              p                c2(i,j) = c2(i,j) + d2(i,j)
              p                e2(i,j) = e2(i,j) + f2(i,j)
              p                g2(i,j) = g2(i,j) + h2(i,j)
              p            end do
              p         end do
                       do j = 1, 10
                           do i = 1, n        ! <----------- loop 3
                               a3(i,j) = a3(i,j) + b3(i,j)
                               c3(i,j) = c3(i,j) + d3(i,j)
                               e3(i,j) = e3(i,j) + f3(i,j)
                               g3(i,j) = g3(i,j) + h3(i,j)
                           end do
                        end do
    
    DISJOINT
    Спецификатор DISJOINT  указывает, что порядок ссылок на данные (ссылок на
    массивы в цикле DO) остается неизменным при выполнении последовательно и
    параллельно. В результате можно нарезать цикл DO, который в противном случае
    не нарезался бы, так как компилятор был бы не в состоянии определить
    порядок ссылок на данные.
    
    
    
                    Syntax: !OCL DISJOINT [ (a [,a]...) ]
    Здесь  "a" есть имя массива, для которого нарезание цикла возможно.
    Символ обобщения (wild-card) можно использовать в "a".  Если имя массива
    опущено, DISJOINT действует только в области цикла DO.  См. синтаксис
    Wild Card в "Спецификация Wild Card" на стр. 81.
    Спецификатор  DISJOINT может размещаться в позиции цикла или в тотальной
    позиции. Действие  DISJOINT зависит от его позиции.
    * В позиции цикла
    DISJOINT поощряет нарезание цикла DO, соответствующего OCL (и всех вложенных
    циклов).
    * В тотальной позиции
    DISJOINT поощряет нарезание циклов для всех циклов в программной единице.
    Рассмотрим следующий код:
                             do j = 1, 1000
                                    do i = 1, 1000
                                      a(i,l(j)) = a(i,l(j)) + b(i,j)
                                    end do
                             end do
    Так как индексное выражение массива  A есть элемент другого массива L(J), то
    система не может определить, возникнут ли конфликты при разрезке A.
    Поэтому система не будет нарезать внешний цикл DO. Если программист знает,
    что при нарезке A конфликтов не будет, внешний цикл DO будет нарезан, если
    DISJOINT будет использован, как показано в примере ниже. Буква P показывает
    в левой части исходного кода параллелезуемые операторы.
    
                             !ocl disjoint(a)
                               p              do j = 1, 1000
                               p               do i = 1, 1000
                               p                 a(i,l(j)) = a(i,l(j)) + b(i,j)
                               p               end do
                               p              end do
    Замечание:
    Если массив, который не может нарезаться, отмечен по ошибке спецификатором
     DISJOINT, LF95 может проделать неправильное разрезание цикла и результат
    программы может оказаться неверным.
    TEMP
    Указатель TEMP используется для извещения системы, что перечисленные
    переменные используют временно в цикле DO. В результате исполнительная
    производительность параллелизованного цикла DO может быть улучшена.
    
    
    Синтаксис:
               !OCL TEMP [ (s [,s]...) ]
    
    Здесь  "s" есть имя переменной, используемой временно в цикле DO. Указание
    символа обобщения (wild card) возможно в "s".  Если имя переменной опущено,
    TEMP становится эффективным для всех скалярных переменных в области цикла DO.
    См. "Спецификация символа обобщения" на стр. 81  по поводу синтаксиса символа
    обобщения.
    
    Спецификатор  TEMP может размещаться в позиции цикла или в тотальной позиции.
    Действие  TEMP зависит от его позиции.
    
    * В позиции цикла.
    
    TEMP указывает, что переменные в цикле DO, соответствующего OCL, есть
    временные переменные.
    
    * В тотальной позиции
    
    TEMP указывает, что переменные всех циклов в программной единице, содержащей
    OCL, есть временные переменные.
    
    В примере, приведенном ниже, вследствие того, что  T есть переменная  common,
    LF95 должен предполагать, что переменная T используется в подрутине SUB даже
    если  T используется только в цикле DO.  LF95 добавляет код для гарантии, что
    T имеет правильное значение в конце параллелизации цикла DO. Буква  P слева
    в исходной программе отмечает параллелизуемые операторы.
    
                         common t
                                   .
                                   .
                                   .
                  p      do j = 1, 50
                  p         do i = 1, 1000
                  p            t = a(i,j) + b(i,j)
                  p            c(i,j) = t + d(i,j)
                  p         end do
                  p      end do
                                   .
                                   .
                                   .
                         call sub
    
    Если программист знает, что значение T в конце цикла DO не нужно в подрутине
    SUB, программист может поставить спецификатор TEMP с T, как это показано в
    следующем коде.  В результате улучшается исполнительная производительность
    так как инструкция, которая поправляет значение T, становится лишней в конце
    цикла DO
    
    
    
                                         common t
                                                   .
                                                   .
                                                   .
                               !ocl temp(t)
                                p        do j = 1, 50
                                p          do i = 1, 1000
                                p                t = a(i,j) + b(i,j)
                                p                c(i,j) = t + d(i,j)
                                p          end do
                                p        end do
                                                   .
                                                   .
                                                   .
                                         call sub
    Замечание:
    Если переменная, которая не используется как временная, описана в
    спецификаторе TEMP по ошибке, LF95 может сделать некорректную нарезку цикла и
    результат программы может быть неверным.
    
    INDEPENDENT
    Спецификатор INDEPENDENT используется для указания компилятору LF95, что
    параллельное исполнение совпадает с последовательным даже если процедура
    вызывается из цикла DO.  Поэтому цикл DO, содержащий процедуру, пригоден
    для нарезания.
                    Синтаксис:
                               !OCL INDEPENDENT [ (e [,e]...) ]
    Здесь  "e" есть имя процедуры, которая не препятствует разрезанию цикла.
    В "e" можно использовать символ обобщения. Если имя процедуры опущено,
    INDEPENDENT действует для всех процедур в пределах цикла DO.
    См. "Спецификация символа обобщения" на стр. 81.
    Заметим, что процедура e должна компилироваться с параметром --parallel.
    Спецификатор INDEPENDENT может размещаться в позиции цикла и в тотальной
    позиции.
    Действие INDEPENDENT зависит от его позиции.
    * В позиции цикла INDEPENDENT разрешает нарезание соответствующего цикла  DO
    (и всех вложенных циклов).
    * В тотальной позиции INDEPENDENT разрешает нарезание всех циклов в программе,
    содержащей OCL.  Рассмотрим следующий код:
    
    
    
             do i = 1, 10000
                j = i
                a(i) = fun(j)
             end do
                    ..
             end
             function fun(j)
             fun = sqrt(real(j**2+3*j+6))
             end
    В приведенной программе, так как процедура "FUN" вызывается в цикле DO,
    система не может определить, параллелизуем ли цикл DO. Если программист знает,
    что никаких проблем не будет при  нарезании цикла, вызывающего процедуру "FUN",
    он может сообщить системе о возможности нарезки спецификатором INDEPENDENT,
    как это показано в следующем ниже коде. Буква  P в левой части исходной
    программы отмечает параллелизуемые операторы.
                   !ocl independent(fun)
              p           do i = 1,1000
              p             j = i
              p             a(i) = fun(j)
              p           end do
                                 ..
                          end
                          function fun(j)
                          fun = sqrt(real(j**2+3*j+6))
                          end
    Замечание:
    Если процедура, которая не может быть нарезана, описана  спецификатором
    INDEPENDENT по ошибке, LF95 может проделать неправильную нарезку цикла, что
    приведет к ошибке в результатах.
    
    Спецификация с символам обобщения (Wild Card Specification)
    В операнде следующих спецификаторов управления оптимизацией можно указывать
    имена переменных или процедур с помощью символа обобщения:
    * DISJOINT
    * TEMP
    * INDEPENDENT
    Указание с символом обобщения есть комбинация специальных символов с
    алфавитно-цифровыми символами.  Эффект будет тот же самый, как и при  указании
    всех имен процедур и переменных, которые соответствуют выражению с  wild card.
    Имеются два символа  wild card, "*" и "?", и они соответствуют следующим
    цепочкам символов.
       "*" соответствуют всякой символьной цепочке из одной  или более букв
    и цифр.
    
    
      "?" соответствуют любой отдельной букве или цифре. Спецификация с wild card
    не может содержать более одного  символа  wild card
                             !ocl temp(w*)
    В этом примере  w* соответствует любой переменной, начинающейся с w и имеющей
    длину два или более символов. Например, переменные с именами  work1, w2,
    и work3 включены в эту спецификацию.
                             !ocl disjoint(a?)
    В этом примере  a? соответствует любому двух символьному имени массива,
    имеющему на первом месте букву  a. Например, имена массивов  a1, a2 и  aa
    включены в эту спецификацию.  А имя abc в нее не входит, так как его длина
    не равна двум.
                             !ocl independent(sub?)
    В этом примере  sub? соответствует имени процедуры с длиной четыре и началом
    sub. Например, имена процедур sub1, sub2, sub9 включены в эту спецификацию.
    

    Замечания по параллелизации

    В начало страницы
    
    В этом разделе поясняются некоторые особенности средств параллелизации.
    
    --threads
    Когда количество CPU, работающих параллельно, указывается в параметре
    компилятора --threads,  аргумент параметра  --threads должен иметь то же самое
    значение, что и переменная окружения PARALLEL.  Если переменная окружения
    PARALLEL не установлена, значение аргумента параметра --threads должно быть
    тем же, что и количество CPU, активных во время исполнения. Приведенный ниже
    пример иллюстрирует неправильное применение параметра компилятора --threads,
    когда число активных CPU есть четыре.  Если для --threads указано неверное
    значение, результаты исполнения могут оказаться неверными. В приведенном
    примере с ошибкой значение N и значение PARALLEL различны.
                             % setenv PARALLEL 2
                             % lf95 --parallel --threads 4 a.f
    В следующем примере результаты исполнения могут быть неверными, если число
    активных  CPU не равно двум.
                             % lf95 --parallel --threads 2 a.f
    
    

    Мультиобработка вложенных циклов DO

    В начало страницы
    
    Если имеется параллелизованный цикл  DO в процедуре, которая вызывается из
    другого параллелизованного цикла DO, возникает гнездо параллелизованных
    циклов DO.  Программа, которая содержит такие циклы DO, не должна
    компилироваться с параметром --threads.
    
    
    Следующий пример -- такой, в котором параллелизованный цикл  DO должен
    выполняться последовательно.  Если исходная программа, которая  содержит такие
    циклы  DO,  компилируется с параметром компилятора --threads, результат может
    быть неверным.
    
              file: a.f
              !ocl independent(sub)
                         do i = 1,100         ! <------ выполняется параллельно
                             j = i
                             call sub(j)
                         end do
                                  :
                         end
                         subroutine sub(n)
                                  :
                         do i = 1, 10000 ! < ----- должен исполняться последовательно
                             a(i) = 1 / b(i)**n
                         end do
                                  :
                         end
    
    Результат может быть неправильным, если исходная программа a.f компилируется
    следующим образом:
    
              % lf95 --parallel --threads 4 a.f  (неверное применение)
    
    Для предотвращения такой ошибки указывайте  !OCL SERIAL в процедуре, которая
    вызывается из параллелизованного цикла DO:
    
                    !ocl serial
                      subroutine sub(n)
                             :
                      do i = 1,10000
                          a(i) = 1 / b(i)**n
                      end do
                             :
                      end
    
    

    Эффект приведения циклов

    В начало страницы
    
    Когда --parallel указано как параметр компиляции, результат исполнения может
    отличаться от результата последовательного исполнения. Причина в том, что
    в результате приведения цикла порядок операций при параллельном исполнении
    может отличаться от порядка при серийном исполнении. Следующий пример
    иллюстрирует оптимизацию при приведении циклов.
    
             sum = 0
              do i = 1, 10000
                sum = sum + a(i)
              end do
    При параллелизации имеем:
    
                     CPU 1:
                              sum1 = 0
                              do i = 1, 5000
                                sum1 = sum1 + a(i)
                              end do
                    CPU 2:
                              sum2 = 0
                              do i = 5001, 10000
                                sum2 = sum2 + a(i)
                              end do
    
    Затем частные суммы складываются:
    
                    sum = sum + sum1 + sum2
    
    Переменная SUM аккумулирует значения от A(1) до A(10000) в порядке
    последовательного исполнения.
    При параллельном вычислении SUM1 аккумулирует значения от  A(1) до A(5000)
    и SUM2 аккумулирует значения  от A(5001) до A(10000) в то же самое время.
    После этого сумма  SUM1 и SUM2 добавляется к SUM. Оптимизация приведения
    циклов может внести  побочный эффект (в силу округления) в результат
    исполнения, потому что порядок сложения элементов массива отличается от
    последовательного сложения.
    

    Неправильное использование OCL

    В начало страницы
     
    В следующей программе указывается по ошибке DISJOINT для массива A. Результат
    исполнения будет неверным, если массив A разрезается, потому что порядок
    ссылок на массив A отличается от порядка при последовательном исполнении.
    
                                   !ocl disjoint(a)
                                   do i = 2,10000
                                        a(i) = a(i-1) + b(i)
                                   end do
    
    В следующей программе по ошибке указано TEMP для переменной T.  Правильное
    значение не будет присвоено переменной last, потому что LF95 не гарантирует
    правильное значение переменной T в конце цикла DO.
                              !ocl temp(t)
                                   do i = 1, 1000
                                        t = a(i) + b(i)
                                        c(i) = t + d(i)
                                   end do
                                   last = t
    
    Следующая программа указывает по ошибке INDEPENDENT для процедуры SUB.
    Результат выполнения может оказаться неверным, если массив  A будет нарезаться,
    так как порядок ссылок на данные в массиве A отличен от ссылок при
    последовательном исполнении.
    
    
    
                        common a(1000), b(1000)
                  !ocl independent(sub)
                        do i = 2, 1000
                           a(i) = b(i) + 1.0
                           call sub(i-1)
                        end do
                         ...
                        end
                        subroutine sub(j)
                        common a(1000)
                        a(j) = a(j) + 1.0
                        end
    
    

    Мультипроцессинг для операторов I/O и ссылок на встроенные процедуры

    В начало страницы
    
    Если имеется оператор  I/O, то встроенная процедура или ссылка на функцию,
    которые не подходят для нарезки цикла в процедуре, вызываемой в
    параллелизуемом цикле DO, могут привести к неправильным результатам.
    Исполнительная производительность программы с мультипроцессингом может
    уменьшиться вследствие  избытка параллельных действий. В итоге результат
    оператора I/O может отличаться от результата при последовательном исполнении.
    Приведем пример, в котором оператор I/O находится в процедуре, которая
    вызывается в параллелизованном  цикле DO.
    
                  file: a.f
                   !ocl independent(sub)
                      do i = 1, 100
                         j = i
                         call sub(j)
                      end do
                           :
                      end
                      recursive subroutine sub(n)
                           :
                      print*, n
                           :
                      end
    
    
                    OpenMP
         В этом разделе описывается параллелизация с помощью  OpenMP. По поводу
    специальной не относящейся к реализации (non-implementation-specific)
    информации см.  OpenMP Fortran specification, содержащею ее и LF95 в формате
    PDF. Подробную информацию об OpenMP см. в http://www.openmp.org/
    Предполагается, что читатель имеет общее понятие об OpenMP. Ее использование в
    реализации LF95 описано ниже.
    

    Компиляция

    В начало страницы
    Имеются четыре параметра компилятора для OpenMP-параллелизации.  Это --openmp,
     --cpus, --threadstack и --threadheap.  Эти параметры описаны в  "Параметры
    компилятора и компоновщика" на стр. 14.
    

    Переменные окружения

    В начало страницы
     
    OpenMP имеет несколько специальных переменных окружения, описанных в
    документации OpenMP  в http://www.openmp.org.  Вместе со своими переменными
    окружения  эта реализация имеет:
    
                THREAD_STACK_SIZE          16   THREAD_STACK_SIZE   2048
    
    Пользователь может указывать размер стека для каждого подпроцесса (thread),
    используя переменную окружения THREAD_STACK_SIZE. Максимальный размер стека
    для подпроцесса в Linux есть  2048 Kbytes. Параметр компилятора --threadstack
    подавляет эту переменную.
    

    Спецификации реализации

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

    Количество подпроцессов (Threads)

    В начало страницы
      
    Если нет указаний о количестве их в служебных рутинах  OMP_SET_NUM_THREADS
    или в переменных окружения  OMP_NUM_THREADS, то количество совпадает с числом
    доступных CPU.
    

    Предложение SCHEDULE

    В начало страницы
     
    Если предложение SCHEDULE опущено, по умолчанию действует SCHEDULE(STATIC).
    

    Переменная окружения _SCHEDULE

    В начало страницы
     
    Если опущена переменная окружения OMP_SCHEDULE, то директивы  DO или
     PARALLEL DO, имеющие по расписанию тип RUNTIME, по умолчанию относятся
    к SCHEDULE(STATIC).
    

    Операторы ASSIGN и Assigned GO TO

    В начало страницы
    
    Оператор ASSIGN в пределах блока  OpenMP  не могут ссылаться на метку
    оператора, расположенного вне блока директив  OpenMP. Таким образом, на метку
    оператора в директивном блоке  OpenMP не может ссылаться оператор ASSIGN,
    расположенный вне директивного блока OpenMP. Переход внутрь или из области
    директивного блока с помощью присвающего
    оператора  GO TO не поддерживается.
    

    Дополнительные функции и операторы в директиве ATOMIC и предложении REDUCTION

    В начало страницы
    
    Следующие встроенные функции и операторы могут указываться в директиве  ATOMIC
    или предложении REDUCTION.
    
    Встроенные функции : AND, OR
    Операторы: .XOR., .EOR.
    

    Конструкт FORALL

    В начало страницы
     
    В конструкте  FORALL директивы  OpenMP употреблять нельзя.
    
    THREADPRIVATE
    
    При использовании директивы THREADPRIVATE заданный блок common должен быть
    определен одинаково во всех программных единицах.  Блок common указывает, что
    THREADPRIVATE не может увеличивать свой размер.
    
    IF Clause для директивы PARALLEL
    Когда значение IF clause для директивы PARALLEL ложно, директива PARALLEL
    игнорируется. Поэтому никакой группы подпроцессов не создается. Однако,
    директива  PARALLEL продолжает действовать.
    
    Подставляемые (Inline) расширения
    Следующие процедуры не расширяются при подстановке:
    
    * Определенные пользователем процедуры, которые содержат  OpenMP директивы
    Fortran.
    * Определенные пользователем процедуры, на которые ссылаются в директивах
    OpenMP.
    
    Встроенные процедуры, вызываемые из параллельной области.
    Переменная в главной процедуре, на которую ссылается  внутренняя процедура,
    вызванная в параллельной области, рассматривается как  SHARED даже если она
    приватизирована в параллельной области.
    
    
                             :i = 1                ! это совместное  i
                             !$omp parallel private(i)
                             i = 2                 ! частное i
                             print*, i             ! частное i
                             call proc             ! частное i
                             !$omp end parallel
                             contains
                                  subroutine proc()
                                  :                ! совместное i
                                  print*, i ! i is shared
                                  :                ! совместное i  
                                  end subroutine
                             :
    

    Переменная DO для последовательного цикла DO в параллельной области

    В начало страницы
    
    Когда переменная DO последовательного цикла DO в параллельной области
    помечается как "SHARED", она приватизируется в области действия цикла DO.
                             !$omp parallel shared(i)
                             i = 1                      ! i совместное
                             do i = 1, n                ! i частное
                                  :                     ! i частное
                             end do                     ! i частное
                             print*, i                  ! i совместное
                             !$omp end parallel
                             !$omp parallel private(i)
                             i = 1                      ! i частное
                             do i = 1, n                ! i частное
                                  :                     ! i частное
                             end do                     ! i частное
                             print*, i                  ! i частное
                             !$omp end parallel
    

    Оператор-функция

    В начало страницы
      
    Переменная, которая фигурирует в оператор-функции, не может иметь атрибутов
     PRIVATE, FIRSTPRIVATE, LASTPRIVATE, REDUCTION или  THREADPRIVATE.
    

    Объект группы Namelist

    В начало страницы
     
     Переменная, объявленная как объект группы namelist, не может иметь атрибуты
     PRIVATE, FIRSTPRIVATE, LASTPRIVATE, REDUCTION или THREADPRIVATE.
    

    Материализация параллельной области

    В начало страницы
      
    Внутренние процедуры есть  SCHEDULE(STATIC). Порожденная внутренняя процедура
    имеет имя "_n_", где  n есть порядковый номер.
    

    Автоматическая параллелизация с OpenMP

    В начало страницы
     
    Параметры --openmp и --parallel могут быть указаны одновременно.  Параметр
    --parallel игнорируется в каждой программной единице, которая содержит
    директивы  OpenMP.
    

    Отладка

    В начало страницы
     
    Много-подпроцессные программы нельзя отлаживать с помощью fdb.
                  
    

<<< Оглавление Страницы: 4  5 >>>