понедельник, 23 января 2017 г.

Ламповый теплый Fortran

Introduction

Инженер на любом языке программирования программирует на Fortran.

Это шутка, но, как обычно, только отчасти. :)

Fortran – старейший язык высокого уровня, задуманный изначально как FORmula TRANslator для ученых и инженеров. Fortran создавался в IBM начиная с 1954 года суровым чуваком Джоном Бэкусом.


Формально первым языком высокого уровня считается Планкалкюль (1945), компилятор которого, правда, был создан только в 2000. Fortran в 1950-х был не просто изобретен (точнее – «интуитивно нащупан»), но сразу реализован. Он – первый «по-честноку».

Благодарные ученые и инженеры активно используют Fortran до сих пор, несмотря на очевидную его моральную устарелость. (Сейчас XXI век, четвертая пятилетка). Мы тоже не исключение. На Fortran меня подсадили в 2009 году седовласые профессора кафедры «Прикладная математика» петербургского Политеха, с которыми мы вместе создавали одну нетривиальную загогулину. С тех пор слезть с него не удается никак.

В рейтинге популярности языков программирования Fortran находится на 28 месте, между F# и Lua. Современные мейнстримные программисты заливаются хохотом, когда им говоришь про Fortran. (Боюсь, они представляют его весьма условно, в стандарте приблизительно 77). А зря. Среди куда более узкой аудитории – ученых и инженеров Fortran занимает если уже и не первое, то точно одно из ведущих мест.

Причин продолжения использования Fortran несколько:

  1. Огромная, накопленная за пол века база инженерно-научного кода.

  2. Скорость. Программы на Fortran – очень быстрые. Компиляторы Fortran чрезвычайно эффективны, а используемые математические библиотеки алгоритмически отточены как лезвия бритв. Кроме того, современный Fortran очень эффективно позволяет распараллеливать вычисления.

  3. Удобство работы с матрицами и вообще с математикой. В Си-подобных языках этого можно добиться только с помощью ряда библиотек, здесь – все «из коробки».

  4. Он простой как топор и императивный как танк. Инженеру/ученому не надо забивать голову программистскими премудростями (объектно-ориентированными или функциональными парадигмами, либо особенностями распределения памяти, указателями и т.д., и т.п.). Можно просто писать свои сладкие формулы.

  5. Он компилируемый. Программы очень просто кому-нибудь отдавать безо всяких ваших виртуальных машин.

  6. Как ни странно, он развивается и поддерживается. Стандарты регулярно обновляются (последний – 2015), компиляторы – совершенствуются.

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

Если высокая скорость не требуется, лучше взять более современный язык «сверхвысокого» уровня: Python с библиотеками NumPy и SciPy или специализированный математический язык Mathematica или MatLAB (или свободные аналоги; для последнего – Octave или SciLAB). На них разработка «научного» проекта будет в разы быстрей и прозрачней, правда все будет работать чертовки по-черепашьи.

Часто проекты так и приходится создавать в два этапа:

  1. Прототипирование на «высоком и прозрачном» языке типа Python.

  2. Реализация на быстром Fortran (или С/С++).

В последние годы научным сообществом активно разрабатывается новый современный язык Julia, претендующий на замену Fortran. Он позволяет писать как «прозрачные» прототипы, так и быструю реализацию критичных участков. Julia, родившаяся в 2012, – многообещающий и очень интересный язык, который лично я изучать пока только начал. О нем еще будут посты.

Компиляторы Fortran

Живых компиляторов Fortran сегодня, как ни странно, не меньше десятка.

Лучший активно развивающийся коммерческий компилятор для Windows и Linux – вероятно, Intel Fortran. С ним «из коробки» идет мощнейшая математическая библиотека MKL, включающая известные пакеты линейной алгебры BLAS и LAPACK, а также супер-эффективный пакет для работы с разряженными матрицами PARDISO и прочие вкусности. (За дополнительные деньги можно приобрести дополнительные или альтернативные библиотеки, такие как знаменитая IMSL). С Intel Fortran поставляется очень мощный профайлер, позволяющий смотреть, на каких участках кода как тратится время (и другие параметры) и оптимизировать код. В Windows Intel Fortran ставится на MS Visual Studio и работает с линкером от Майкрософт.

Вторым лидером рынка вроде бы считается PGI Fortran (для Linux and Mac доступен во Free edition) – тоже монстр, на который, я правда особо и не смотрел. Еще есть Absoft Pro Fortran, IBM XL Fortran (для Linux и AIX), EKOPath (Linux), Lahey/Fujitsu Fortran, NAG Fortran, Oracle Solaris Studio (для Solaris и Linux, кажется недавно стала бесплатной) и другие.

Довольно интересный коммерческий компилятор – Silverfrost FTN95, бесплатный для персонального использования или «в целях оценки». Единственное неудобство – каждый раз при запуске скомпилированной программы появляется баннер. С FTN95 поставляется приятная легкая Plato IDE. Вообще, вся система производит впечатление очень легкой и быстрой.

Лучший свободный компилятор – GFortran из набора компиляторов GCC, разрабатываемых в рамках проекта GNU. GFortran активно совершенствуется «свободным сообществом».

Еще неплохой свободный компилятор был G95, но похоже загнулся в 2013.

Итого, наш выбор – GFortran, поэтому о нем – поподробнее.

Установка GFortran в Windows

На январь 2017 последняя стабильная версия GCC с GFortran – 6.3, заканчивается работа над седьмой версией.

GFortran – разумеется, кроссплатформенный. Для Windows его можно получить и установить разными способами:

  1. Из родного «набора свободных средств разработки под Windows для настоящих минималистов» :) – проекта MinGW или отпочковавшегося от него проекта MinGW-w64, реализующего преимущества 64-битной архитектуры. Или из проекта CygWin. Все три варианта – не самые простые пути. Что надо скачать и как это настроить – все довольно запутанно.

  2. Поставить GFortran вместе с хорошей свободной кроссплатформенной IDE Code::Blocs. Скачиваем с их сайта и устанавливаем файл codeblocks-16.01mingw_fortran-setup.exe (версия 16.01 на сегодня последняя). Возможно, это наиболее простой путь. Недостаток – GFortran будет несколько устаревшей (текущая версия в сборке 4.9.2) и только в разрядности 32. Зато поставил – и можно сразу переходить к сладким формулам в интуитивно понятном окружении IDE.

  3. От ребят, именующих себя Equation Solution, в лице китайца Джен-Чинга Ляо (www.equation.com). Это бывший профессор колумбийского университета, еженедельно собирающий самую актуальную версию дистрибутива GCC для разрядности как 32, так и 64. Сборка дополнена отладчиком и собственными библиотеками профессора Ляо, из которых особый интерес для нас представляют LAIPE2 (решение СЛАУ с распараллеливанием) и JCL (перенумерация строк и столбцов разряженных матриц для приведения их к ленточной форме). Это хорошие библиотеки, оказавшиеся для меня очень кстати. Из дистрибутива профессора Ляо GCC устанавливается очень легко через инсталлятор. Необходимые пути автоматом прописываются в PATH. У Ляо в принципе, все хорошо. С его дистрибутивами я жил какой-то время, единственное, там немножко все «собрано на коленке». Например, в комплекте нет dll (предполагается только статическая линковка библиотек gcc), получаемые exe в последних версиях стали странным образом разбухать, его собственные библиотеки – иногда глючат и т.п. :) Как обычно, Open Source – такой Open Source. :)

  4. Из специальной сборки компиляторов GCC для Windows с сайта TDM-GCC. Это очень хорошие и продуманные сборки, включающие полный набор нужных средств. К сожалению, здесь GCC тоже несколько устаревший (текущая версия 5.1), но сейчас этот вариант – мой фаворит. Скачиваем с их странички файл tdm64-gcc-5.1.0-2.exe для архитектуры 64 или tdm-gcc-5.1.0-3.exe для 32. На версии 64 можно (в том числе) компилировать свои программы и для 32-разрядных машин, т.е. две версии ставить не нужно. Устанавливается все элементарно через инсталлятор. Единственное, ему надо указать «Устанавливать все пакеты», т.к. по умолчанию компилятор фортран как «экзотика» пропускается. Инсталлятор прописывает путь к компиляторам в PATH.

Последний вариант, TDM-GCC, – очень хороший, за одним «но». В версиях 5.3 (и 4.9) допущен досадный баг, из-за которого не работает оператор фортрана Open. :) (Open Source – такой Open Source). Об этом не написал в баг-листе только ленивый. Надо скачать с их сайта старую версию 4.8 (там надо найти gcc-4.8.1-tdm64-2-fortran.zip) и заменить из нее файлы libgfortran_64-3.dll и libgfortran-3.dll, а также libgfortran.a, libgfortran.dll.a, libgfortran.spec, libgfortranbegin.a. Причем последние файлы имеются в 64 и 32-разрядных версиях, их надо аккуратно разложить по соответствующим папкам lib или lib32.

То же относится и к текущей версии 4.9 GFortran в CodeBlocs (он по сути там тот же самый из TDM-GCC).

Надеюсь, это исправят. В остальном все работает как часы.

В дистрибутивах от Ляо этой проблемы нет.

Компиляция в GFortran

Из IDE

IDE для того и нужны, чтобы не думать, а нажимать кнопочки. :) Все интуитивно понятно без дополнительных пояснений.

Из командной строки

Допустим, мы хотим запустить «чистый компилятор», не пользуясь IDE. Пишем программу в текстовом файле, например Hello.f08:

program Hello
    print *, "Здавствуй, мир! Hello, Space Boy!"
    call execute_command_line("Pause")
endprogram

Компилируем из командной строки:

gfortran Hello.f08 -o Hello.exe

Если пути в PATH «подхватились», после этого получается файл Hello.exe, запуск которого приведет к выводу приветствия на консоль (или «кракозябров» из-за несовпадения кодировки). Если пути в PATH не прописаны, перед gfortran надо указать к нему путь – все точно также будет работать.

Опция -o задает выходной файл.

Чтобы статически сгенерировать exe, не завязанный ни на какие внешние .dll-библиотеки, нужно добавить опцию -static:

gfortran Hello.f08 -static -o Hello.exe

О других опциях – см. ниже.

Далее. Допустим, проект состоит из нескольких модулей. Пусть, например, это три файла: Module1.f08, Module2.f08 и MainProgram.f08 с соответствующими текстами:

module Module1 
    contains 
    function Square(x)  ! Вычисление квадрата x
        real :: x, Square
        Square = x**2
    endfunction
endmodule
module Module2
    use Module1 
    contains 
    subroutine PrintSquare(y)  ! Печать квадрата y 
        real :: y
        print *, "Квадрат числа:", Square(y)
    endsubroutine
endmodule
program MainProgram ! Супер-программа «Нахождение квадрата числа»
    use Module1 ; use Module2
    real :: z = 3.   ! Наше число
    call PrintSquare(z)
    print *, "Проверка:", Square(z)
    call execute_command_line("Pause")
endprogram

Пример составлен так, чтобы смоделировать зависимости модулей друг от друга, показанные на схеме:




Компилируется такой проект, чтобы не нарушался порядок зависимостей, следующим образом:

gfortran -c Module1.f08
gfortran -c Module2.f08
gfortran -c MainProgram.f08
gfortran Module1.o Module2.o MainProgram.o -static -o MyProgram.exe

Сначала исходные файлы компилируются в объектные .o (опция -c), а затем они линкуются в результирующий .exe.

Компиляция с GNU Make

Работать с командной строкой, когда фалов проекта много, не слишком удобно (даже если все прописывать в *.bat или *.cmd). Задачу автоматизации сборки берет на себя IDE и/или олдскульная технология составления Make-файлов, которые можно легко вызывать на исполнение, например, из любимого текстового редактора. Преимущество Make, в частности, в том, что каждый раз будут перекомпилироваться не все файлы проекта, а только те, которые изменились.

Если фортран был поставлен из дистрибутива TDM-GCC или CodeBlocs, утилита Make находится в папке bin под именем mingw32-make.exe. Для простоты скопируем (продублируем) ее под именем просто make.exe. В дистрибутиве от Ляо она сразу называется make.exe.

Если на машине инсталлировано несколько сред программирования, могут стоять и различные мэйки, которые также могут вызываться по команде make не совсем к месту. Убедимся, что так вызывается именно GNU Make, набрав make -v. Это должно выдавать приблизительно следующее:

GNU Make 3.82.90
Built for i686-pc-mingw32
Copyright (C) 1988-2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

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

Make-файл состоит из правил и переменных. Правила имеют следующий синтаксис:

цель1 цель2 ...: реквизит1 реквизит2 ...
    команда1
    команда2
    ...

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

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

Простейший Makefile для программы из одного модуля Hello.f08 (наш первый пример) может выглядеть так:

PRG_NAME = Hello

all: 
    gfortran $(PRG_NAME).f08 -o $(PRG_NAME).exe
    $(PRG_NAME).exe

clean:
    rm -rf *.o *.mod $(PRG_NAME).exe

Здесь вначале определена переменная PRG_NAME, значение которой затем «дословно» подставляется в команды – везде, где написано $(PRG_NAME).

Далее определены две цели: all и clean, без реквизитов, но зато с командами.

Если из рабочего каталога вызвать

make all

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

make

Можно также указать требуемый мейк-файл явно, тогда он может называться как нам угодно:

make -f MyMakeFile all

Запуск Make с другой целью, clean, сотрет все промежуточные файлы проекта:

make clean

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

# 1. Флаги компилятора, меняемые оперативно (отладка, оптимизация):

# Быстрая компиляция без лишних вопросов:
# MY_FLAGS = -w -O0 

# Компиляция для отладки с проверками времени выполнения и предупреждениями:
MY_FLAGS = -Og -Wall -Wno-tabs -fcheck=all

# Компиляция для релиза с оптимизацией:
# MY_FLAGS = -w -Ofast -mfpmath=sse -ftree-vectorize -funroll-all-loops

# 2. Флаги компилятора - общие для всех модулей и вариантов компиляции:
FLAGS = -ffree-line-length-512 -freal-4-real-8 

# 3. Название программы и исходных файлов (без расш.) в порядке компиляции:
PRG_NAME = ..\\bin\\MyProgram
F1 = Module1
F2 = Module2
F3 = MainProgram

# 4. Компилятор фортран (здесь можно прописать путь):
CF = gfortran

# 5. Главная цель - сформировать программу и потом ее запустить:
all: $(PRG_NAME)
    $(PRG_NAME).exe

# 6. Для этого нужно достичь другой цели - слинковать объектные файлы в exe:
$(PRG_NAME): $(F1).o $(F2).o $(F3).o 
    $(CF) $^ $(FLAGS) $(MY_FLAGS) -o $@.exe 

# 7. А для этого нужно скомпилировать каждый файл из исходников:
$(F1).o: $(F1).f08 
    $(CF) -c $(FLAGS) $(MY_FLAGS) $^ 

$(F2).o: $(F2).f08 
    $(CF) -c $(FLAGS) $(MY_FLAGS) $^ 

$(F3).o: $(F3).f08 
    $(CF) -c $(FLAGS) $(MY_FLAGS) $^ 

# 8. Альтернативная цель - очистка проекта от промежуточных файлов:
clean:
    rm -rf *.o *.mod $(PRG_NAME).exe

Возможно, это не лучший стиль написания Make, но для меня он «родной» и наглядный.

В главной цели all перед командой запуска файла мы указали реквизит, одноименный с названием программы:

all: $(PRG_NAME)
    $(PRG_NAME).exe

Make – как хороший солдат, любой ценой (но желательно наименьшей), должен выполнить поставленную перед ним цель. А заключается она в исполнении команд, при этом обязательным условием перед их исполнением является наличие реквизита. В качестве реквизита основной цели мы указали необходимость достижения «дочерней» цели, правило для которой описали так:

$(PRG_NAME): $(F1).o $(F2).o $(F3).o 
    $(CF) $^ $(FLAGS) $(MY_FLAGS) -o $@.exe 

Оно требует наличия еще трех реквизитов – готовых объектных файлов $(F1).o $(F2).o $(F3).o. Если они имеются, должна быть выполнена команда по их линковке в exe.

Выражение $@ означает «подставить сюда текущую цель», а $^ – «подставить сюда текущий реквизит».

Цели, связанные с данными реквизитами, мы описали так:

$(F1).o: $(F1).f08 
    $(CF) -c $(FLAGS) $(MY_FLAGS) $^ 

Это значит, что для достижения очередной цели надо, чтобы присутствовал файл $(F1).f08, после чего его надо скомпилировать в объектный файл.

Make, как любой нормальный боец, чрезвычайно ленив. Если какая-то цель была уже выполнена, он не будет рваться достигать ее еще раз. Если у него уже есть скомпилированный объектный файл, он его компилировать повторно не будет. Но он – все же очень исполнительный и прилежный боец. Если файл исходника был изменен позже объектного файла, Make старательно это исправит, перекомпилировав объектный файл снова.

Здесь находится перевод официальной справки по GNU Make.

Здесь – хороший материал «Эффективное использование GNU Make» от Владимира Игнатова.

Некоторые опции компилятора

При компиляции и линковке GFortran’у можно указать огромную кучу опций – все они описаны в справках по GFortran и GСС. Перечислю здесь некоторые «типовые» (используемые мной в практике):

Опция Пояснение
-static Указывается при линковке. Собирает весь код зависимых библиотек в один exe-файл. Его можно будет отдавать и переносить без дополнительных dll.
-shared-libgcc Наоборот, dll от GCC – отлинковать динамически. exe-файл будет маленький, но без установленных в доступности PATH нескольких «родных» dll ничего работать не будет.
-m32 Если установлен 64-разрядный GFortran, по умолчанию проект компилируется как 64-разрядное приложение, этой опцией можно попросить сделать версию для 32-разрядных машин.
-O0, -O1, -O2 или -O3 или -Ofast Уровни оптимизации, кода. С -O0 сама компиляция будет быстрой (удобно при разработке), но код – медленный. -O3 или -Ofast – для релиза. -Og – оптимизация для отладки.
-mfpmath=sse -ftree-vectorize -funroll-all-loops Дополнительные оптимизации для релиза (но без фанатизма, для современных процессоров можно навернуть больше).
-fdollar-ok -llaipe2 -lneuloop4 -ljcl Опции для подключения библиотек от Ляо. Первая разрешает доллар в именах функций как у него принято, остальные просто подключают библиотеки laipe2, neuloop4, jcl.
-fopenmp Подключает библиотеку OpenMP для параллельных вычислений.
-fimplicit-none Запрещает неявное определение типов по первой букве у переменных (i – integer и т.д.). Эквивалентно указанию в каждой процедуре implicit none, где их можно забыть и поиметь проблемы с переменными, которые забыли определить явно.
-ffree-line-length-512 Позволяет писать длинные строки в коде (с современными мониторами трудно умещать код в 72 символа, считающиеся ранее максимальной шириной кода).
-freal-4-real-8 Считать все вещественные числа типа real(4), которые в фортране по-умолчанию можно задавать как «просто» real, числами с двойной точностью real(8) (8 байт). На мой взгляд, для большинства задач сейчас лучше использовать 8 байт, а с этой опцией будет меньше визуального мусора типа real(8) :: r = 1.0_8, будет просто real :: r = 1.0.
-w Подавлять все синтактические предупреждения
-Wall -Wno-tabs -fcheck=all Наоборот, выводить все предупреждения (кроме абсурдных), а также выполнять все проверки времени выполнения (выходы за пределы индексов в массивах и т.д., и т.п.)
-ggdb Генерировать информацию для отладчика gdb
-pg Генерировать информацию для профайлера gprof

О расширениях исходных файлов

Расширение файлов по традиции отражает стандарт языка: в наших примерах оно f08 – стандарт 2008. Это дань традиции: компилятор без доп. опций понимает f08, f03, f95, f90 одинаково, как файлы в свободном формате. Для старого фиксированного формата используются расширения f, for и др.

Если расширение начинается с большой буквы (F08, F95 и т.п.), а также если оно fpp, то файл будет сначала пропускаться через предпроцессор – в нем можно будет писать директивы компилятору с синтаксисом gcc-Си, например:

#define Compile_R16
! ...

#if defined Compile_R16
    real*16 :: MyReal
#else
    real*8  :: MyReal
#endif 

Подключение математических библиотек к GFortran

BLAS и LAPACK

BLAS и LAPACK – классические библиотеки линейной алгебры, которые не совсем очевидно подключаются к GFortran в Windows. Здесь описан процесс подключения, если вы работаете c Visual Studio с линкером от Майкрософт, который подключает библиотеки *.dll, используя *.lib. Родной линкер GFortran использует библиотеки в архивированном формате *.a.

Как подключить BLAS и LAPACK к GFortran в Windows:

  1. Скачиваем с официального сайта архив lapack-3.7.0.tgz. (На сегодня последняя версия 3.7.0). Распаковываем архив в папку lapack-3.7.0.

  2. В подпапке INSTALL находим файл make.inc.gfortran, копируем его в корневую папку (lapack-3.7.0) и переименовываем его в просто make.inc.

  3. Идем в каталог \BLAS\SRC (например, в файловом менеджере FAR) и набираем в нем команду make (GNU make должен быть подключен, см. выше). После компиляции ряда файлов в корневой папке (lapack-3.7.0) должен появиться файл librefblas.a.

  4. Аналогично, идем в каталог \SRC и набираем в нем команду make all. После компиляции ряда файлов (довольно долгой) в корне должен появиться файл liblapack.a.

  5. Переписываем файлы librefblas.a и liblapack.a туда, где GCC хранит свои библиотеки. В зависимости от установленного дистрибутива это папки вроде \i686-pc-mingw32\lib, \x86_64-w64-mingw32\lib или \MinGW\lib (в CodeBlocks).

  6. Если мы работаем с дистрибутивом разрядности 64 (например от TDM-GCC) и хотим по опции -m32 уметь компилировать наши программы для 32-разрядных машин, весь процесс придется повторить, скомпилировав librefblas.a и liblapack.a с опцией -m32 (придется дописать ее в make-файл), а потом положить 32-разрядные версии библиотек в соответствующую папку (у TDM-GCC это \x86_64-w64-mingw32\lib32).

  7. Все!

Протестируем. Решим систему линейных алгебраических уравнений (СЛАУ)

\[ \mathbf{A}\cdot\mathbf{X}=\mathbf{B}, \]

где \(\mathbf{A}\) – матрица коэффициентов, \(\mathbf{X}\) – вектор неизвестных и \(\mathbf{B}\) – вектор правых частей.

Пусть матрица \(\mathbf{A}\) – плотная, общего вида. Для СЛАУ с такими матрицами в подойдет LAPACK-процедура dgesv. Первая буква «d» означает вариант для вещественных чисел двойной точности, real(8). Пусть \(\mathbf{A}\) имеет размер \(10 \times 10\) с заполнением случайными числами от \(0\) до \(100\), а вектор \(\mathbf{B}\) заполнен числами \(100 \dots 1000\).

Наберем в файле test.f08:

program LAPACK_test
    implicit none 
    integer, parameter      ::  n = 10    ! Размерность матриц
    real(8), dimension(n,n) ::  A         ! Матрица коэффициентов
    real(8), dimension(n,1) ::  B, X      ! Векторы правых частей / результатов
    integer                 ::  i         ! Вспомог. индекс
    integer, dimension(n)   ::  ipiv      ! Вспомог. матрица для процедуры LAPACK
    integer                 ::  info      ! Инфо об успешности решения
    character(100)          ::  FRM       ! Формат для вывода матриц
    !----------------------------------------------------------------------------
    print *, "Проверка LAPACK. Решение СЛАУ А*X=B с плотной матрицей общего вида"
    call random_number(A);  A = 100*A     ! Заполняем матрицу А 
    forall(i = 1:n) B(i,1) = 100*i        ! Заполняем вектор B
    write(FRM, "(a,i10,a)") "(",n,"f8.2)" ! Формируем формат для вывода матриц
    print *, "Матрица коэффициентов A:"
    print FRM, A
    print *, "Вектор правых частей B:"
    print FRM, B
    X = B
    call dgesv(n, 1, A, n, ipiv, X, n, info)  ! Вызов LAPACK для решения СЛАУ
    print *, "Решение:"
    print FRM, X
    print *, "Успешность решения (флаг «info»):", info
endprogram

Компилируем, подключая библиотеки lapack / refblas:

gfortran test.f08 -llapack -lrefblas -static -o test.exe

Получаем:

Проверка LAPACK. Решение СЛАУ А*X=B с плотной матрицей общего вида
Матрица коэффициентов A:
  99.76   56.68   96.59   74.79   36.74   48.06    7.38    0.54   34.71   34.22
  21.80   13.32   90.05   38.68   44.55   66.19    1.61   65.09   64.64   32.30
  85.57   40.13   20.69   96.85   59.84   67.30   45.69   33.00   10.04   75.55
  60.57   71.90   89.73   65.82   15.07   61.23   97.87   99.91   25.68   55.09
  65.90   55.40   97.78   90.19   65.79   72.89   40.25   92.86   14.78   67.45
  76.96   33.93   11.58   61.44   82.06   94.71   73.11   49.76   37.48   42.15
  55.29   99.79   99.04   74.63   95.38    9.33   73.40   75.18   94.68   70.62
  81.38   55.86    6.17   48.04   59.77   13.75   58.74   52.00   88.59   30.38
  66.97   66.49   50.37   26.16    7.66   10.12   54.93   37.56    1.51   79.29
  62.09   77.36   95.36   11.42   31.85   59.68    4.82   11.42   21.60   10.06
Вектор правых частей B:
 100.00  200.00  300.00  400.00  500.00  600.00  700.00  800.00  900.00 1000.00
Решение:
  -8.79   16.14   12.52    3.00  -14.42   -0.18    4.31   -2.40    5.41   -1.10

Библиотеки дядюшки Ляо

В дистрибутиве GCC профессора Ляо (с сайта www.equation.com) имеются очень неплохие библиотеки:

  • NEOLOOP – для параллельных вычислений;

  • LAIPE2 – решение СЛАУ с матрицами коэффициентов различного вида, включая симметричные положительно определенные ленточные или просто разряженные, представляющие для нас особый интерес (почему – об этом потом). Решение выполняется с распараллеливанием процессов.

  • JCL – перенумерация строк и столбцов разряженных матриц для приведения их к ленточной форме.

Если нужно установить эти библиотеки в другие дистрибутивы, делаем следующее:

  1. Находим в дистрибутиве от Ляо файлы библиотек: libneuloop4.a, libneuloop6.a, liblaipe2.a, libjcl.a.

  2. Переписываем их в место, где хранит основные библиотеки ваш фортран.

Как обычно, если мы работаем с 64-битной версией GCC с желанием сохранить возможность компиляции программ под 32, это надо сделать для обеих версий библиотек, положив их в lib и lib32.

Компиляция программ с этими библиотеками выполняется с опциями:

-fdollar-ok -llaipe2 -lneuloop4 -ljcl

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

Чтобы подпрограммы LAIPE2 заработали, надо предварительно вызвать процедуру laipe$use с указанием числа ядер процессора, которые вы хотите использовать (иначе не «заведется» – об этом Ляо забыл написать в справке).

Библиотеки NEOLOOP и LAIPE2 работают только на Windows 7 и более старших.

IDE и редакторы для GFortran

Для серьезной работы с языком хорошо иметь как IDE, со всем богатством возможностей, так и текстовый редактор (может быть, не один), позволяющий «что-нибудь сварганить» по-быстрому или подправить. Честно говоря, я IDE почему-то запускаю реже текстового редактора, с которым намного лучше постигается Дзен.

Какие есть варианты для GFortran в Windows?

IDE Code::Blocks

Это самый простой вариант начать работать в Fortran, пользуясь свободным ПО. Все ставится из коробки и заводится с пол-оборота. По сравнению с альтернативными IDE – относительно легкая. Тут есть все что нужно: автодополнение кода, подсказки, навигация по объектам программы, отладчик, профайлер, минимальные средства рефакторинга.


Когда-то я «щупал» ifort + Visual Studio, там таких удобных подсказок и умного автодополнения и близко не было (это не претензия к студии – в других языках все очень круто, но не в Intel Fortan). Т.е. в определенном смысле CodeBlocks – даже лучше. (Может конечно я отстал от жизни, последних версий ifort + VS не видел).

На 64-разрядных машинах желательно поставить 64-разрядный компилятор GFortran (см. выше) и подключить его в качестве основного (все делается мышкой в настройках элементарно).

Вывод сообщений ваших программ не в консоль, в окно редактора (чтобы не было проблем с кодировками) можно сделать через меню Tools, создав новый инструмент, указав для него:

Executable: ${TARGET_OUTPUT_BASENAME}
Working directory: ${TARGET_OUTPUT_DIR}
Launch tool hidden with standart output redirect

IDE Eclipse с расширением Photran

Eclipse – знаменитая мощнейшая IDE для множества языков программирования. Для Fortran есть расширение Photran, работающее в том числе c GCC. Доступно здесь.

Eclipse – очень тяжелая штука. Я побаловался, но дальше она как-то не прижилась.

Прочие IDE

Еще можно посмотреть в сторону легонькой Geany.

Не уверен, что своими силами можно толком настроить GFortran c Visual Studio. Есть коммерческий пакет, где это сделано – Lahey/Fujitsu Fortran. Это не наш путь.

Текстовые редакторы

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

Emacs и Vim

Седовласые старики. Очень опытные, мудрые и крутые, но найти с ними общий язык – очень сложно. Я пока не сумел, хотя пару десятков попыток делал. :)

Sublime Text, Atom, VS Code

Энергичные мужики. Моложавые, хипстерской внешности. На этих редакторах получается сделать «почти IDE».

Atom – открытый, мощный, но жирный и неторопливый. Использую его для некоторых других языков, Фортран как-то на нем не прижился.

Sublime Text – мой выбор. Мощный, быстрый, правда не открытый и не бесплатный (70 долларов, хотя незарегистрированной версией можно пользоваться сколько угодно).


VS Code – удачная попытка Microsoft написать бесплатный Sublime. Очень приятный. Все намереваюсь на него перейти, но руки пока не доходят.

SciTE, NotePad++, AkelPad

Эдакие продвинутые юнцы. Меленькие, легкие, дерзкие, но далеко не такие мощные, как товарищи из первых двух групп. Не «почти IDE».

Раньше я очень любил SciTE, последнее время перешел в AkelPad. Он крошечный, работает со сверхзвуковой скоростью. Очень просто настраивается подсветка. Все, чего только только можно желать, реализуется на скриптах на простом человеческом JavaScript.


С GFortran нетрудно настроить любой редактор. Привожу некоторые скриншоты работы приведенной выше программы по тестированию правильности установки LAPACK.

Литература по Fortran

Лучшая книга на русском языке – О. В. Бартеньев «Современный Фортран». Она настолько полная и и исчерпывающая, что книги остальных авторов – не нужны. Хорош также его же трехтомник по библиотеке IMSL. Даже если вы не применяете IMSL, там много полезной теории. (На самом деле книги Бартеньева – по сути качественный перевод полной документации по Фортрану и IMSL и от минимума «отсебятины» они только лучше). Жаль только, что его книги несколько устарели, но это совсем не критично – Фортран развивается не так быстро.

Здесь – GFortran Wiki.

Здесь – PDF-справка по GFortran. Очень неплоха. Коротко и по делу. Для работы нужен второй PDF по GCC в целом.

Здесь – очень хорошее краткое описание языка в стандарте 95 в википедии. Если вы пишете на других языках, прочитав это, станете гуру в Фортране за каких-нибудь пару часов. :)

Здесь, в открытом доступе, – прекрасная справка по Intel Fortran, в основной части (в части самого языка) подходящая и для GFortran.

Здесь, аналогично, – прекрасная документация по Intel MKL, в основной части подходящая для BLAS и LAPACK.

Что прекрасно в Fortran

  1. Работа с матрицами.

С матрицами (например, A, B, C) можно работать как с переменными:

A = 2*B + sin(C)

К матрицам (поэлементно) могут быть применены любые чистые функции (как синус в примере).

Вырезки и сечения матриц:

B[1:5,1:5] = A[1:10:2,1:10:2]

В отличие от MatLAB-подобных языков, шаг - третий, а не второй элемент триплета.

Конструкция forall. Например, заполним матрицу \(n \times n\) единицами по диагонали (сделаем ее единичной):

A = 0 
forall(i = 1:n) A(i,i) = 1

Конструкция where. Например, так можно обнулить все отрицательные компоненты матрицы B:

where(B < 0) B = 0

С матрицами, вообще, множество удобных встроенных функций – практически на все случаи жизни.

Матрицы могут быть многомерными.

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

  2. Типы, включая комплексные числа. Удобные древовидные пользовательские типы (структуры данных).

  3. Как ни странно, форматный ввод-вывод, к которому привыкаешь, но который я также отмечу как одно из самых ужасных свойств языка. :)

Что ужасно в Fortran

Современный Fortran позволяет писать более-менее сносный код, если не пользоваться устаревшими конструкциями языка, поддерживаемыми ради совместимости, например:

  • типизацией переменных по умолчанию на основе первых букв их имен;

  • разными точками входа в процедуры;

  • переходами goto с метками;

  • указателями и прочими средствами работы с памятью «напрямую» и т.д.

Иногда чешутся руки, наоборот, это все использовать и писать программы аутентично «индийские». В идеале можно выйти на абсолютный Brainfuck. :)

Не только эти, но и в принципе многие конструкции языка, конечно же, устарели. Что особенно бесит:

  • Работа со строками.

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

Специфичны преобразования других типов данных в строку и обратно. Чтобы преобразовать, например, вещественное число r в строку str или наоборот, приходится делать это через операторы ввода-вывода:

write(str, "(f8.3)") r  ! str <- r в формате f8.3
read(str,*) r           ! str -> r

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

str = trim(str1) // trim(str2)  ! Если строки выровнены влево
str = trim(AdjustL(str1)) // trim(AdjustL(str2)) ! Если не выровнены :)

Разумеется, в Fortran нет никаких регулярных выражений и других «вкусностей», к которым в современных языках мы успели уже «прикипеть».

  • Форматный ввод-вывод – очень мясной. Он был удобен для терминалов, но сейчас это – кошмарный анахронизм:
print "(a,i4,a,10f8.3)", "Шаг:", i, "Вектор:", V

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

  • Нет списков и вообще структур данных с изменяемой длиной «на лету». (Динамически размещаемые массивы, конечно же, есть).

  • Переусложненная система типов. Вещественные числа разной длины пишутся с указанием разновидности (KIND) весьма специфично:

real     :: r1 = 1.     ! По умолчанию real – это real(4)
real(8)  :: r2 = 1._8
real(16) :: r3 = 1._16

С другой стороны, KIND можно определить на уровне директивы компилятора и везде использовать просто real и числа без этих «_8»

  • Мало средств «декомпозиции и абстракции» для поддержания структуры больших проектов.

  • Избыточность языка, связанная с историей и совместимостью снизу вверх.

  • Наследуемый код, написанный за пол века «учеными и инженерами» – в подавляющей массе просто чудовищен. Чуть менее, чем весь созданный код. :) Почему так – загадка. Прямой вины языка в этом нет. Потемки – душа ученого.

Вместо Conclusions

В этой импровизированной статье пока не был раскрыт ключевой вопрос абстракта «как заставить себя в XXI веке писать на Fortran?». :)

Мейнстримовый C++ предлагает аналогичную скорость.

Лаконичный Python предоставляет неслыханную мощь и ясность/читаемость кода. Одна строчка идет за 5-10 Фортрана.

Julia совсем наступает на пятки, предлагая почти ту же мощь и прозрачность, что и Python, и вполне сравнимую с Fortran скорость.

На самом деле, у меня нет разумного ответа на сакраментальный вопрос, кроме лишь одного: Fortran надо любить. :) Как дедушку и стареющего отца.

Каждый раз, открывая текстовый редактор с Фортраном, чувствуешь, что «подключаешься» к чему-то ретрофутуристично-прекрасному. :) Будущее, как видели его в 60-х годах, космические полеты, покорение звезд. Элементарные частицы, атомные реакторы. Шуршащие пленкой вычислительные машины. Папиросы профессоров «Прикладной математики». Все это преломляется в вашем редакторе, хочется открывать его еще и еще. :) А на современных машинах все эти отблески проносятся с бешенной скоростью… Ну и конечно, по совокупности качеств, с чего «статья» начиналась…

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

2 комментария:

  1. Спасибо за очень интересный и информативный обзор. Я использую старый Compaq Visual Fortran, думаю со временем перейти на что то более современное.

    ОтветитьУдалить
  2. Спасибо за статью, но забыли про тип REAL(10) - 80-битовое вещественное число, который также поддерживается в GNU-фортране наряду с типами REAl(4), REAL(8) и REAL(16). Вместо опции компилятора -freal-4-real-8 можно задать опцию -freal-4-real-10, и тогда все вещественные переменные и числовые константы в программе будут иметь не двойную, а расширенную точность (80 бит), что соответствует 19-значной десятичной разрядности. Если же есть старая программа, в которой понапиханы всякие implicit real*8(a-h,o-z) и горячо любимые буквы "D" во все числовые константы, то можно эту программу откомпилировать ничего в ней не меняя с опцией -freal-8-real-10, и точность вычислений будет на три порядка выше.

    ОтветитьУдалить