Очень давно хотел написать о том, на какой стадии сейчас находится один из «прародителей» популярных сегодня языков программирования. Да, я говорю о Фортране. Попытаюсь разрушить стереотип, который существует в умах многих разработчиков – что Фортран древний язык без каких либо перспектив, и уж точно никто на нём уже не пишет. Наоборот, он очень активно эволюционирует и уже давно предлагает богатый набор различных функциональностей, закреплённых в стандартах, вполне, кстати, сопоставимых с тем же С/С++.
От «старого» 77-го Фортрана осталось уже не так и много… в 95 стандарте мы могли вполне себе создавать свои типы данных, динамически выделять и очищать память, работать с указателями, перегружать функции и операторы и много чего ещё. По сути, от С своим набором средств он отличается несильно. Тем не менее, я не хочу пытаться сравнивать языки – это вопрос философии. Скажу только, что фортрановский компилятор Intel пользуется очень большим спросом, и, собственно, приобретается даже активнее, чем тот же С++.
Цель же данного поста – рассказать о текущем состоянии дел. Собственно, Фортран на сегодняшний день — параллельный язык, и стал он таковым после принятия стандарта Fortran 2008, в котором появились Coarray’и.
Итак, обо всё по порядку. За основу была взята модель программирования SPMD (Single Program MultipleData). Если вы знакомы с MPI, то суть та же – мы пишем наше приложение, копии которого будут выполняться определенное количество раз параллельно. При этом у каждой копии имеются свои локальные данные. Те данные, к которым необходим доступ из разных копий, описываются с помощью специального синтаксиса, именуемого как Coarray.
Для понимания достаточно привести простой Hello World пример:
program hello
write(*,*) "Hello world"
end program hello
Собственно, самый обычный код. Вот только скомпилировав его с ключиком –coarray (компилятором Intel), мы увидим «приветы» из нескольких разных копий программы, или, в терминах Coarray, из разных Image’ей (образов). Причём их число можно контролировать, например, через ключ –coarray-num-images=x, или переменную окружения FOR_COARRAY_NUM_IMAGES. Понятно, что существует способ определять, в каком образе происходит выполнение. Усложним наш пример:
program hello_image
write(*,*) "Hello from image ", this_image(), "out of ", num_images()," total images“
end program hello_image
После запуска мы увидим что-то похожее на это:
Hello from image 1 out of 4 total images
Hello from image 4 out of 4 total images
Hello from image 2 out of 4 total images
Hello from image 3 out of 4 total images
Очевидно, что наше приложение было выполнено 4 раза (4 копии/образа). Имея эти данные о Coarray, мы в принципе, уже способны создавать параллельные приложения.
Вот только весьма бестолковые, потому что нет ответа на главный вопрос – как же быть с данными? Для этого вводят очень простой и понятный синтаксис:
real, codimension[*] :: x
real :: y[*]
Квадратные скобки говорят нам о том, что мы используем Сoarray.
В данном примере это просто скаляры, которые всё так же доступны в каждой копии программы. Но теперь мы можем обратиться к значению этого скаляра в нужной нам копии (образе).
Например, написав y[2] мы обратимся к значению y в образе 2. Это открывает нам возможности для «настоящей» параллельной работы с данными.
Естественно, существует ряд логичный ограничений, накладываемых на Сoarray’и, как, например, любая попытка связать объект Сoarray с другим объектом через указатели, или передача объектов Сoarray в С код.
Давайте рассмотрим ещё несколько примеров, считая, что мы уже объявили ранее переменную x как Сoarray:
x = 42.0
В данном случае, мы оперируем с локальной для образа переменной х.
Как только в нашем коде появляются квадратные скобки – это явный указатель на то, что происходит доступ с переменной в другом образе программы:
x[3] = 42.0 ! задаёт значение х равное 42 в образе 3
x = x[1] ! присваиваем локальной для текущего образа переменной х значение из образа 1
x[i] = x[j] !присваиваем переменной х в образе I значение переменной х из образа j
Что хорошо в Coarray, так это то, что в отличие от чистого MPI, мы не заботимся о посылке или приеме сообщений. Всё это на плечах реализации (которая уже и использует тот же MPI). Но мы «над» этим. Кроме того, код будет работать как на системах с распределённой памятью, так и на системах с общей. Достаточно только поменять ключ на coarray=shared или coarray=distributed.
Раз уж у нас появились данные в разных копиях нашей программы, логично предположить, что должны быть и средства для их синхронизации. Конечно, они имеются. Это, например, конструкция SYNC ALL, синхронизирующая все образы. Есть ещё и SYNC IMAGES(), позволяющая синхронизировать только определенные образы.
Ещё один пример:
integer, codimension[*] :: fact
integer :: i, factorial
fact = this_image()
SYNC ALL
if ( this_image() == 1 ) then
factorial = 1
do i = 1, num_images()
factorial = factorial * fact[i]
end do
write(*, *) num_images(), 'factorial is ', factorial
end if
Естественно, не самый быстрый способ посчитать факториал, зато хорошо иллюстрирующий суть работы с Сoarray.
Для начала, объявляем fact как Сoarray, и далее в каждом образе мы присваиваем значение, равное номеру образа. Перед тем, как мы перемножим все значения, нужно убедиться, что они уже присвоены, поэтому используем SYNC ALL. И в образе с номером 1, таком якобы «мастер-образе», вычисляем факториал.
В итоге мы получили весьма эффективное средство – часть языка, позволяющее создавать параллельные приложения для систем с различной организацией памяти. Естественно, в компиляторной поддержке и реализации Coarray основная трудность – это производительность. На данный момент она до сих пор остается не самой сильной стороной… но здесь и открываются большие перспективы для различных компиляторов.
Я же заканчиваю своё краткое описание относительно «новых плюшек» из последнего принятого стандарта. Надеюсь, было не очень скучно смотреть Фортрановский код. Если же отзывы покажут обратное и проснётся живой интерес по данной теме, то я не откажу вам в удовольствии и продолжу тему. А сейчас всем большое спасибо за внимание.
Автор: ivorobts