company_banner

«Фортран – живее всех живых» или «Что нового у дедушки ifort»


    Как вы уже знаете, недавно вышла новая Intel Parallel Studio XE 2016, а с ней, как и полагается, новые версии всех тулов, в том числе, и Фортрановского компилятора. Он всё ещё «жив курилка», активно развивается, при это весьма востребован и используется множеством разработчиком, особенно в HPC и академической среде. Новая версия, как всегда, делает жизнь этих разработчиков чуточку легче, поддерживая новые стандарты и давая больше возможностей. Давайте посмотрим, что появилось в версии 16.0.

    Подмодули из F2008 (submodules)

    Поддержку подмодулей (submodules) из стандарта Fortran 2008 ждали уже давно, и они являются самой большой фичей языка, не поддерживаемой до последнего релиза. Теперь она реализована, причём только во втором компиляторе в мире. Кто же первый? Есть компилятор, который полностью поддерживает Fortran 2008 – это Cray.

    Так почему подмодули ждали? Сейчас покажу на наглядном примере. Итак, у нас имеется следующий код:
    module bigmod
    …
    contains
      subroutine sub1
      …<implementation of sub1>
      function func2
      …<implementation of func2>
      subroutine sub47
      …<implementation of sub47>
    …
    end module bigmod
    

    Как мы видим, у нас есть большой модуль, в котором много разных функций. Этот модуль активно используется в других файлах с помощью оператора USE:
    ! Source source1.f90
    use bigmod
    …
    сall sub1
    
    ! Source source2.f90
    use bigmod
    …
    x = func2(…)
    
    ! Source source47.f90
    use bigmod
    …
    call sub47
    

    Если нам захотелось (такое иногда случается) изменить код какой-либо функции из модуля bigmod, например sub47, то придётся пересобирать сам модуль, да ещё и все файлы, в которых он используется. Причем это произойдет даже в том случае, если интерфейс функции не изменился и, кроме того, даже если сама функция, в которую мы вносили изменения, в коде то и не вызывается вовсе, а есть только USE. В итоге для больших приложений мы получаем целую череду не всегда нужных перекомпиляций. Как бы здорово было избегать этого и пересобирать только то, что нужно. Решением этой проблемы и являются подмодули.

    Основная идея заключается в том, чтобы убрать реализацию функций из модуля. Делается это с помощью интерфейсов (interface):
    module bigmod
    …
      interface
      module subroutine sub1
    …
      module function func2
    …
      module subroutine sub47
    …
      end interface
    end module bigmod
    

    После этого, в отдельном файле, создается подмодуль bigmod_submodule и реализация функций:
    submodule (bigmod) bigmod_submod
    contains
      module subroutine sub1
      … <implementation of sub1>
    
      module function func2
      … <implementation of func2>
    
      module subroutine sub3
      … <implementation of sub3>
    
    end submodule bigmod_submod
    

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

    Интероперабельность С и Фортрана

    Так как в комитете по стандартизации Фортрана продолжают заботиться о дружбе с языком С, в последней версии появляются возможности из технической спецификации TS29113 (Further Interoperability of Fortran with C), которая будет частью стандарта F2015. Вообще, термин Техническая Спецификация (Technical Specification) появляется тога, когда есть какая то фича языка, которая ещё до конца не доделана для того, чтобы быть принята в стандарте, но вполне востребована, чтобы различные производители компиляторов начали её реализовывать. Кстати, раньше их называли TR (Technhical Report). Обычно, когда такая спецификация получает одобрение в комитете, она принимается в следующем стандарте без изменений, чтобы у тех компиляторов, которые уже реализовали поддержку, не возникло неловких ситуаций.

    Появление этой самой спецификации, в основном, мотивировано нуждами MPI3. В частности, она позволяет расширить взаимодействие между С и Фортраном в двух основных направлениях:

    • разрешает передавать в С функции указатели (pointer), перенимающие конфигурацию (assumed-shape) и выделяемые (allocatable) переменные. На данный момент, способ передачи информации о границах массивов не стандартизирован и зависит от компиляторной реализации. С этой спецификацией происходит его стандартизация. Кроме того, разработчики на С могут читать, записывать и выделять память под эти аргументы, а так же определять их и передавать в код на Фортране.
    • поддерживает интероперабельность с С типом void *, не имеющим явного типа и размерности.

    Реализуются эти задачи с помощью новых ключевых слов. Так, появляется перенимающий тип (assumed type) TYPE(*), и размерность массива (assumed rank) DIMENSION(..). Вдобавок, теперь есть дескриптор С (C descriptor). В целом, если перед вами встанет задача беспроблемного сосуществования С и Фортрана, то вы обязательно разберётесь в функциях ISO_C_BINDING модуля из стандарта Fortran2003 (про них я уже писал тут), поймёте, что в нём есть незакрытые проблемы, которые как раз решаются этой спецификацией.

    Чисто или нет?

    В том же стандарте F2008 имеется новая возможность объявлять функции как impure elemental.

    Для того, чтобы понять суть этой фичи, нужно для начала разобраться что такое элементные (elemental) и чистые(pure) функции, появившиеся ещё в стандарте F2003.

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

    pure function calculate (x)
    

    Это позволяет использовать чистые функции в параллельных конструкциях forall.

    Для всех её аргументов нужно явно указывать атрибут intent(in), который говорит, что это только входные параметры и их мы не изменяем, а в самой функции запрещено делать операции ввода/вывода, выполнять оператор STOP, использовать статические переменные (с атрибутом save). Примеры чистых функций: sin(x), length(s) и другие.

    Нечистой (impure) функцией будет являться любая функция, которая работает не с локальными переменными, или даёт различный результат вне зависимости от аргументов. Скажем, использование random() внутри чистой функции запрещено, а вот в impure в самый раз.

    Теперь перейдём к элементным функциям.

    elemental subroutine swap(a, b)
    

    Они объявляются со скалярными аргументами, но могут быть вызваны с массивами в качестве фактических аргументов. В этом случае компилятор создает цикл для вызова в нем функции, заполняя массив. По умолчанию, любая элементная функция является чистой. Таким образом, нечистые элементные (impure elemental) функции – это всё ещё элементные, но с некоторыми послаблениями, функции. В частности, в них разрешено делать операции ввода/вывода, использовать random(), статические переменные.

    Стоит отметить, что использование impure может повлиять на векторизацию, так как оптимизатор больше не знает, может ли эта функция изменять что-то «снаружи» или нет.

    OpenMP 4.1
    В посте про «плюсовый» компилятор я писал про расширение поддержки OpenMP 4.1. Конечно, в Фортране тоже теперь поддерживаются новые директивы TARGET ENTER DATA и TARGET EXIT DATA, а так же TARGET NOWAIT и TARGET DEPEND, как и в компиляторе С/С++.

    Директива omp ordered simd
    С помощью директивы omp ordered, применяемой раньше только в параллельных по задачам циклах, можно заставить потоки, которые выполнялись параллельно, достигнув блока ordered, выполняться последовательно и именно в том порядке, как это было бы в серийной версии:
    !$omp ordered [simd]
        structured code block
    !$omp end ordered
    

    Это создает своего рода зависимости между потоками. Простой пример, показывающий, как это работает. В этом случае индекс будет печататься последовательно, в возрастающем порядке:
    !$omp do ordered schedule(dynamic)
            do i=lb,ub,st
              call work(i)
            end do
            ...
            subroutine work(k)
      !$omp ordered
            write(*,*) k
      !$omp end ordered
    

    Кстати, работало это раньше только с динамической планировкой. Эта конструкция работает сейчас так же, но добавилась возможность указать опцию simd (по умолчанию идет опция threads), которая позволяет работать ещё и с SIMD циклами.

    Блокируем циклы
    Новая директива, которая работает при включенном уровне оптимизации O3, позволяет контролировать оптимизацию с разделением цикла для лучшей работы с кэш-памятью:
    !DIR$ BLOCK_LOOP [clause[[,] clause]...]
    !DIR$ NOBLOCK_LOOP
    


    Отлавливаем неинициализированные переменные
    В 15 версии компилятора появилась опция -init, позволяющая определять доступ к неинициализированным переменным в рантайме, но реализована она была только для работы со статическими переменными. Теперь они может быть использована с автоматическими, а так же выделяемыми (allocatable) массивами и указателями.
    Простенький пример:
    4  real, allocatable, dimension(:) :: A
    5
    6  ALLOCATE(A(N))
    7
    8  do i = 1, N
    9     Total = Total + A(I)
    10  enddo
    

    Собираем это дело с ключом -init=arrays,snan, при котором все переменные будут инициализирована сигнальным NaN, и получаем ошибку времени выполнения:
    $ ifort -init=arrays,snan -g -traceback sample.F90 -o sample.exe
    $ sample.exe
    forrtl: error (182): floating invalid - possible uninitialized real/complex variable.
    Image              PC                Routine            Line        Source      
    ...
    sample.exe         0000000000402E12  MAIN__                      9  sample.F90
    ...
    Aborted (core dumped)
    

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

    «На сдачу»
    Кроме всего, имеется ряд мелких улучшений, которые так же помогут разработчикам жить легче. Так, например, появилась новая опция -fpp-name, которая позволяет указать любой препроцессор. Или новый атрибут asynchronous, для аснихронной коммуникации. Есть ещё много доработок, которые как обычно, стараются делать компилятор лучше.

    Кстати, с компилятором от Интел для Фортрана уже давно поставляется специальная версия VS 2010 Shell. Она имеет некоторые ограничения (например, там совсем нет поддержки С/С++), но весьма и весьма полезна для разработчиков на Windows – не приходится выкладывать свои кровные за IDE (никаких дополнительных денежных вливаний за VS Shell не берут). Так вот, теперь версия VS обновилась до 2013, и поставляется она всё так же только с пакетом Фортрановского компилятора.

    Intel

    144,00

    Компания

    Поделиться публикацией
    Комментарии 0

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

    Самое читаемое