`

СПЕЦИАЛЬНЫЕ
ПАРТНЕРЫ
ПРОЕКТА

Архив номеров

Как изменилось финансирование ИТ-направления в вашей организации?

Best CIO

Определение наиболее профессиональных ИТ-управленцев, лидеров и экспертов в своих отраслях

Человек года

Кто внес наибольший вклад в развитие украинского ИТ-рынка.

Продукт года

Награды «Продукт года» еженедельника «Компьютерное обозрение» за наиболее выдающиеся ИТ-товары

 

Научные вычисления: архитектуры, форматы, инструментарий. Часть 3

+11
голос

Предыдущие две части обзора касались весьма универсальных, но имеющих четко определенную область применения в научных приложениях инструментальных средств. Наступила пора перейти к вещам более "приземленным" -- обладающим завершенной функциональностью программным системам, созданным "в научной среде для потребностей научной среды". Именно в таком случае, когда разработчики являются и потребителями, достигается почти идеальное соответствие между показателями программы и требованиями.
Возвращаясь к первой части статьи, с момента публикации которой прошло достаточное для того, чтобы забыть мелкие нюансы, время, автор спешит напомнить читателю -- выбор рассматриваемых программ совершенно субъективен. Отсутствие в этом цикле статей упоминания о некоторой программе X вовсе не означает, что программа X недостойна такого упоминания. И все же критерии отбора претендентов на роль "героя" очередного повествования остаются неизменными: удачное сочетание простоты изучения и функциональности, "легковесность" (немаловажным фактором в определении которой является минимизация зависимости от сторонних библиотек, что обеспечивает высокую живучесть проекта), доступность и maturity -- этап зрелости в жизненном цикле любой программы. Этот же перечень требований на "более человеческом" языке звучит вполне логично: прикладная программа хороша тогда, когда она обеспечивает достижение заданных пользователем целей при фиксированных затратах усилий последнего на ее изучение без потребности в "копошении" с деталями "сырой" и неотработанной реализации.

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

Yorick

Научные вычисления архитектуры, форматы, инструментарий. Часть 3
Я знал его, Горацио. Это был человек бесконечного остроумия, неистощимый на выдумки.
В. Шекспир

Приверженность ученых к "мрачновато звучащим" названиям, заимствованным из классической литературы, зачастую порождала подозрительное отношение к по-настоящему хорошим разработкам (достаточно вспомнить исторические факты религиозных волнений по поводу знаменитых daemons в ОС Unix, массовую негативную реакцию на целый ворох ассоциирующихся с адом Данте имен в ОС Inferno). В случае с Yorick подобного не произошло, да и не могло произойти -- ведь пользователями этого замечательного продукта являются сами ученые. Так что оставим в стороне обсуждение жутковатого логотипа и вспомним только, что шекспировский Йорик все-таки был "неистощим на выдумки". Как и его программный "тезка", в чем мы убедимся впоследствии.

Научные вычисления архитектуры, форматы, инструментарий. Часть 3
Рис. 1
Научные вычисления архитектуры, форматы, инструментарий. Часть 3
Рис. 2
Научные вычисления архитектуры, форматы, инструментарий. Часть 3
Рис. 3
Научные вычисления архитектуры, форматы, инструментарий. Часть 3
Рис. 4
Итак, о чем же пойдет речь? Yorick -- весьма компактная программная среда, предназначенная для комплексного решения научно-инженерных вычислительных задач. Архитектура Yorick, ориентированная на язык (language-oriented), весьма тривиальна для научных приложений, и мы о ней не раз писали ранее. Но и при такой тривиальности Yorick обладает целым рядом весьма специфичных для разработок подобного класса особенностей.

Во-первых, раз мы говорим о языковой архитектуре, следовательно, основой системы должен быть или компилятор, или интерпретатор. Как и большинство программ своего класса, Yorick основана на интерпретаторе. "Ну вот, -- возмущенно спросит читатель, -- и чем это отличается от описанного в прошлой главе Nickle?". На уровне идеи -- ничем. Или почти ничем -- как уже упоминалось, Nickle предназначен для быстрого прототипирования научных задач (со всеми вытекающими отсюда следствиями). Yorick же задумана как языковая система, с помощью которой задачи решаются. А это значит, что интерпретатор Yorick должен обладать исключительно высоким быстродействием. Практика данное предположение полностью подтверждает.

Во-вторых, термин "комплексный" в определении Yorick означает, что программа располагает не только развитыми вычислительными способностями, но и мощными интегрированными подсистемами: интерактивной графической, импорта/экспорта данных в научных форматах.

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

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

Теперь настало время и оправдаться за субъективность выбора именно Yorick еще раз: GNU Octave -- продукт, без сомнения, отличный, но требующий для достижения функциональности Yorick дополнительных сложных программ визуализации результатов, Matlab -- прекрасный коммерческий пакет, заслуженно популярный и описанный в море книг и журнальных статей. Yorick же, изначально разработанная одним человеком (Дэвидом Манро из знаменитых Ливерморских лабораторий) и впоследствии дорабатываемая обширным научным сообществом, даже без детального анализа уже известных фактов, очевидно, является воплощением перечисленных выше требований.

Первые шаги

Раз речь идет об ориентированной на язык архитектуре, значит, изучения "еще одного" (это словосочетание столь популярно в англоязычной IT терминологии, что образовало устойчивую аббревиатуру YA -- yet another) языка не избежать. Это расплата за простоту реализации (большая часть интерпретаторов давным-давно не программируется вручную, а автоматически генерируется специальными инструментальными программами), мобильность и живучесть всего проекта в целом. Впрочем, создатель Yorick, как и большинство авторов специализированных языков, пошел по проторенному пути -- "YA-язык" должен быть максимально синтаксически похож на один из распространенных языков программирования. В случае с Yorick этим языком-прообразом является вездесущий C. Но требования реальности внесли свои коррективы в "схожесть" Yorick с C, которые можно наблюдать в первом работоспособном примере:

func wave(Ph, Q)

u = 0.5/Q;
omega = sqrt(1.-u*u);
return sin(omega*Ph)*exp(-u*Ph);

Первое отличие от C, которое бросается в глаза, -- явное описание функции ключевым словом "func". Второе -- обычное для интерпретируемых языков, заключается в нетребовательности к описаниям типов переменных. Во всем остальном эта программа очень похожа на свой C-аналог, что обеспечивает минимизацию затрат времени на освоение "экзотического синтаксиса". Зато в части семантики Yorick действительно "неистощим на выдумки".

Добавим в конец предыдущего примера две строки такого содержания:

wave(1.5,3);
wave( [.5,1,1.5,2,2.5],3 ) ;

Сохраним получившийся текст в файл с именем; например demo0.i (расширение .i -- принятое для Yorick-программ), и заставим интерпретатор Yorick выполнить нашу первую программу с помощью команды

yorick -batch demo0.i

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

0.775523
[0.435436,0.705823,0.775523,0.659625,0.41276]

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

Работа с массивами -- настоящая "изюминка" Yorick, блестяще реализованная с точки зрения как пользователя, так и программиста-разработчика высокопроизводительного вычислительного ПО. Для "пользователя" (этот термин закавычен потому, что к "пользователю" подобной системы предъявляются слишком обширные требования) Yorick предусматривает синтаксически элементарный механизм спецификации операций с массивами, отражающий требования прикладных областей, в которых традиционно используются мощные вычислительные пакеты. Здесь без иллюстраций фрагментами кода не обойтись:

arr_1 = [1,2,3,4,5] ;
arr_2 = [2,4,6,8,10] ;
arr_1*arr_2 ;

В этом примере мы объявили два одномерных массива (вектора) и указали Yorick найти их произведение. При подобной форме записи интерпретатор Yorick выполняет поэлементное перемножение массивов; сохранив этот фрагмент в файл с именем demo01.i, запустим интерпретатор и получим соответствующий результат:

yorick -batch demo01.i
[2,8,18,32,50]

Но для обширных прикладных областей, где математический аппарат линейной алгебры является основным инструментом, этих возможностей катастрофически недостаточно, а перекладывать в высокоуровневом вычислительном пакете на плечи "непрограммирующего программиста" эффективную реализацию непростых алгоритмов значит одновременно и сузить область применений программы, и понизить ее уровень... В Yorick с помощью простого синтаксического механизма аннотации присутствует встроенная реализация ключевых операций линейной алгебры -- внешнего и внутреннего произведений. Изменим наш файл demo01.i, добавив в его конец две строки следующего содержания:

arr_1(+)*arr_2(+) ;
arr_1(-,)*arr_2(,-);

Результат выполнения этой программы будет выглядеть так:

[2,8,18,32,50]
110
[[2,4,6,8,10],[4,8,12,16,20],[6,12,18,24,30],
[8,16,24,32,40],[10,20,30,40,50]]

Второе значение результата -- внутреннее произведение (скалярное значение ), третье -- внешнее, являющееся в данном случае матрицей. Впрочем, на этом особенности массивов Yorick не заканчиваются -- есть еще один аспект их применения, кроющийся в часто используемой форме представления функции -- табличной. В этом случае массив хранит аргументы и соответствующие им значения функции, и часто возникает задача найти некоторое промежуточное значение, явно не присутствующее в таблице. И здесь массивы Yorick помогают поддержкой встроенной функции интерполяции -- interp, освобождающей программиста от потребности в очередной раз "изобретать колесо". Даже процедура создания массива, с элементарным представителем которой мы уже познакомились (оператором инициализации перечислением [...]), реализована в Yorick в полном соответствии с требованиями реальных вычислительных задач: так, вызов процедуры span с параметрами (Start,End,Num) создает одномерный массив, инициализированный определенным количеством (Num) значений, равномерно заполняющих диапазон от Start до End, ее специальная форма -- spanl -- делает закон изменения инициализирующих значений логарифмическим, и наконец, с помощью вызова функции array можно изменить размерность целого массива или его фрагмента-среза. Возможность простого и удобного описания срезов массивов -- еще одна приятная особенность Yorick -- для ее использования требуется минимальный синтаксис, например:

arr_1 = [1,2,3,4,5] ;
arr_2 = arr_1(2:4) ;

Полученный с помощью этой "программы" результат вполне предсказуем при учете еще одного семантического отличия Yorick от C -- индексация массивов здесь всегда начинается с 1, а не с 0, а расположение массивов в памяти соответствует правилам по сей день непревзойденного для решения вычислительных задач Fortran:

[2,3,4]

Напротив, в "обычных" для языка программирования возможностях Yorick достаточно приближен к С -- в языке присутствуют и привычные операторы изменения потока выполнения программы (ветвления if...else, циклов for и while), и обычные в C правила видимости (локальные и глобальные переменные практически с теми же синтаксисом и семантикой).

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

Дело в том, что сам разработчик Yorick на вопрос "Что делает систему такой быстрой?" уверенно отвечает: синтаксис операций работы с массивами. Чтобы понять, в чем действительно дело, стоит "заглянуть" краем глаза во внутреннюю организацию системы.

Технологически Yorick реализована как достаточно простая виртуальная машина с одним указателем стека и программным счетчиком (счетчиком команд). Каждая "машинная команда" Yorick представляет собой указатель на функцию, выполняющую соответствующие семантике команды инструкции. Естественно, реализующие систему команд виртуальной машины функции написаны на языке C и работают очень быстро. Но все-таки при этом остаются большие накладные расходы, например на организацию проверки типов операндов, используемых в операции, -- именно по этой причине скалярные вычисления с помощью Yorick не радикально отличаются по производительности от аналогичных систем (хотя Yorick в большинстве случаев быстрее из-за тщательно "вылизанной" за годы интенсивной эксплуатации реализации). А вот введение операций с массивами на языковом уровне, оказывается, способно изменить многое...

Предположим, что мы хотим найти значение такого выражения:

R = c + b•X + X•Y,

где R, X, Y -- массивы (векторы), а c и b -- скалярные значения.

В "сильно похожих" на C императивных языках для выполнения такой операции потребуется написать нечто подобное:

for (i=0; i< размерность_массивов; i++)
R[i] = c + b*X[i] + X[i]*Y[i] ;

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

То же самое выражение, записанное на Yorick в виде

R = c + b*X + X*Y ;

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

for (i=0; i< размерность_массивов; i++) Temp[i]=X[i]*Y[i] ;
for (i=0; i< размерность_массивов; i++) Temp1[i] = b*X[i] ;
for (i=0; i< размерность_массивов; i++) Temp2[i]=Temp[i]+
Temp1[i] ;
for (i=0; i< размерность_массивов; i++) R[i] = c + Temp2[i] ;

где Temp, Temp1 и Temp2 -- области памяти для хранения результатов промежуточных вычислений.

Как показала практика реализации Yorick, такое "пошаговое" выполнение, даже с учетом неизбежных накладных расходов на инициализацию дополнительных областей памяти, уступает по быстродействию сгенерированному оптимизирующим компилятором коду для программы в "C-стиле" всего в 5 раз -- это очень высокий показатель для интерпретируемых языков, обычно "отстающих" от своих компилируемых собратьев на два порядка.


Визуализация

Встроенная графическая подсистема Yorick, несмотря на наличие некоторых ограничений, обладает достаточными для многих реальных задач быстродействием и функциональностью. Она основана на девяти базовых примитивах -- вызовах соответствующих функций библиотеки Yorick. Самая простая из них -- plg, "рисующая" множество линейных сегментов, заданное двумя одномерными массивами координат X и Y соответственно. Для уже использованных в предыдущих примерах массивов arr_1 и arr_2 результатом выполнения следующей "программы"

arr_1 = [1,2,3,4,5] ; arr_2 = [2,4,6,8,10] ;
plg, arr_1, arr_2 ;

станет "информативная" картинка, представленная на рис. 1. Впрочем, в данном случае нам важна не информативность, а элементарность действий, с помощью которых мы добились результата. Кроме того, в перечне базовых графических примитивов Yorick далеко не все так примитивно -- вызов pls "рисует" контурную карту (карту изолиний) заданной функции, вызовы plm и plf отвечают за визуализацию сеточного представления поверхности, описываемой функцией, вызов plv выводит изображение двухмерного векторного поля... Сами по себе такие "примитивы" трудно назвать примитивными, но Yorick располагает куда более развитыми, основанными на их возможностях, встроенными графическими операциями. К ним можно отнести управление палитрами, отображающими уровни значений (обязательная для задач научной визуализации функция), тонкую программную настройку систем координат, сеточного представления и т. д. И, что главное, Yorick позволяет вводить "стили" -- высокоуровневые описания сложных процедур форматирования, пригодные к повторному использованию.

Упомянутое "ограничение" графической подсистемы Yorick заключается в том, что трехмерная графика в программе реализована на уровне... самих скриптов на Yorick. И при этом назвать ее медленной трудно. На вполне приличной как для тестовой задачи программе моделирования колебаний мембраны, возбужденной ударом, в которой используется подсистема 3D-графики (рис. 2--4), Yorick показывает скорость расчетов и графического вывода результатов порядка 160 кадров/с на процессоре Intel Pentium III 750 MHz.


Интеграция

Как уже говорилось выше, Yorick допускает расширение функциональности за счет "пристегивания" быстрых ресурсоемких программ, написанных на языках C и Fortran. В этом случае такие программы становятся вызовами языка Yorick, доступными из скриптов. Но это только одна сторона медали -- ведь самой системе совершенно безразлично, чем именно будет расширена ее функциональность. Естественно, что такая "неприхотливость" позволила давным-давно (Yorick -- разработка весьма почтенного возраста) включить в перечень расширений вызовы интерфейсов программных шин, что превращает Yorick в элемент системы распределенных вычислений. Не обойдена вниманием и поддержка научных форматов данных -- в Yorick-программы можно импортировать файлы HDF и NetCDF.

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

Напечатать Отправить другу

Читайте также

 
 
IDC
Реклама

  •  Home  •  Рынок  •  ИТ-директор  •  CloudComputing  •  Hard  •  Soft  •  Сети  •  Безопасность  •  Наука  •  IoT