Оптимизирующий компилятор инженерных расчетов, использующий платформу .NET

 

Пётр Иванков

1.     Введение

 

Увеличение скорости вычислений (производительности) является важной задачей программирования. По мнению многих это самая важная задача. Однако люди знакомые с профессиональным программированием знают, что, как правило, гибкость программного обеспечения является гораздо более важной, чем производительность. В данной статье рассмотрен подход, позволяющий обеспечить гибкость и высокую производительность. Примеры кода использующего данный подход расположены в проекте AstroFrame https://sourceforge.net/project/showfiles.php?group_id=159404 . Они присутствуют в любом файле загруженном после 26.07.2008.

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

К числу возражений следует отнести:

- данный поход несовместим с разработанным ранее программным обеспечением, связанным с инженерными вычислениями;

- данный поход ограничивается платформой .NET;

- специализированное программное обеспечение эффективнее универсального;

- затраты времени на освоение .NET слишком велики и лучше использовать устаревшие информационные технологии.

В данной статье приведены ответы на все эти возражения. А теперь рассмотрим суть оптимизации производительности.

 

 

 

2.     Описание компилятора

 

Технология .NET поддерживает компиляцию исходного кода во время выполнения.   Здесь уместно привести цитату из Википедии http://ru.wikipedia.org/wiki/Base_Class_Library#cite_note-0 :

============ Цитата =================

System.CodeDom 

Обеспечивает возможность создавать код и запускать его.

============Конец цитаты ===========

Теперь покажем как System.CodeDom  используется для оптимизации инженерных вычислений в проекте AstroFrame. Данный проект содержит продвинутый редактор формул имеющий возможность манипулировать с большим числом математических структур. Например на следующем рисунке изображено произведение матриц  a и b.

Произведению соответствует формула на коричневом фоне. То, что данная формула представляет собой произведение матриц обусловлено типами переменных. Переменная а  представляет собой массив Double[4, 5], а переменная b имеет тип Double[5, 6] (Типы отображаются в левом нижнем углу рисунка). В соответствии с типами формула интерпретируется как произведение матриц.  В соответствие с формулами генерируется следующий код:

            var_2[0, 0] = var_0[0, 0] * var_1[0, 0] + var_0[0, 1] * var_1[1, 0] + var_0[0, 2] * var_1[2, 0] + var_0[0, 3] * var_1[3, 0] + var_0[0, 4] * var_1[4, 0];

            var_2[0, 1] = var_0[0, 0] * var_1[0, 1] + var_0[0, 1] * var_1[1, 1] + var_0[0, 2] * var_1[2, 1] + var_0[0, 3] * var_1[3, 1] + var_0[0, 4] * var_1[4, 1];

            var_2[0, 2] = var_0[0, 0] * var_1[0, 2] + var_0[0, 1] * var_1[1, 2] + var_0[0, 2] * var_1[2, 2] + var_0[0, 3] * var_1[3, 2] + var_0[0, 4] * var_1[4, 2];

            var_2[0, 3] = var_0[0, 0] * var_1[0, 3] + var_0[0, 1] * var_1[1, 3] + var_0[0, 2] * var_1[2, 3] + var_0[0, 3] * var_1[3, 3] + var_0[0, 4] * var_1[4, 3];

            var_2[0, 4] = var_0[0, 0] * var_1[0, 4] + var_0[0, 1] * var_1[1, 4] + var_0[0, 2] * var_1[2, 4] + var_0[0, 3] * var_1[3, 4] + var_0[0, 4] * var_1[4, 4];

            var_2[0, 5] = var_0[0, 0] * var_1[0, 5] + var_0[0, 1] * var_1[1, 5] + var_0[0, 2] * var_1[2, 5] + var_0[0, 3] * var_1[3, 5] + var_0[0, 4] * var_1[4, 5];

            var_2[1, 0] = var_0[1, 0] * var_1[0, 0] + var_0[1, 1] * var_1[1, 0] + var_0[1, 2] * var_1[2, 0] + var_0[1, 3] * var_1[3, 0] + var_0[1, 4] * var_1[4, 0];

            var_2[1, 1] = var_0[1, 0] * var_1[0, 1] + var_0[1, 1] * var_1[1, 1] + var_0[1, 2] * var_1[2, 1] + var_0[1, 3] * var_1[3, 1] + var_0[1, 4] * var_1[4, 1];

            var_2[1, 2] = var_0[1, 0] * var_1[0, 2] + var_0[1, 1] * var_1[1, 2] + var_0[1, 2] * var_1[2, 2] + var_0[1, 3] * var_1[3, 2] + var_0[1, 4] * var_1[4, 2];

            var_2[1, 3] = var_0[1, 0] * var_1[0, 3] + var_0[1, 1] * var_1[1, 3] + var_0[1, 2] * var_1[2, 3] + var_0[1, 3] * var_1[3, 3] + var_0[1, 4] * var_1[4, 3];

            var_2[1, 4] = var_0[1, 0] * var_1[0, 4] + var_0[1, 1] * var_1[1, 4] + var_0[1, 2] * var_1[2, 4] + var_0[1, 3] * var_1[3, 4] + var_0[1, 4] * var_1[4, 4];

            var_2[1, 5] = var_0[1, 0] * var_1[0, 5] + var_0[1, 1] * var_1[1, 5] + var_0[1, 2] * var_1[2, 5] + var_0[1, 3] * var_1[3, 5] + var_0[1, 4] * var_1[4, 5];

            var_2[2, 0] = var_0[2, 0] * var_1[0, 0] + var_0[2, 1] * var_1[1, 0] + var_0[2, 2] * var_1[2, 0] + var_0[2, 3] * var_1[3, 0] + var_0[2, 4] * var_1[4, 0];

            var_2[2, 1] = var_0[2, 0] * var_1[0, 1] + var_0[2, 1] * var_1[1, 1] + var_0[2, 2] * var_1[2, 1] + var_0[2, 3] * var_1[3, 1] + var_0[2, 4] * var_1[4, 1];

            var_2[2, 2] = var_0[2, 0] * var_1[0, 2] + var_0[2, 1] * var_1[1, 2] + var_0[2, 2] * var_1[2, 2] + var_0[2, 3] * var_1[3, 2] + var_0[2, 4] * var_1[4, 2];

            var_2[2, 3] = var_0[2, 0] * var_1[0, 3] + var_0[2, 1] * var_1[1, 3] + var_0[2, 2] * var_1[2, 3] + var_0[2, 3] * var_1[3, 3] + var_0[2, 4] * var_1[4, 3];

            var_2[2, 4] = var_0[2, 0] * var_1[0, 4] + var_0[2, 1] * var_1[1, 4] + var_0[2, 2] * var_1[2, 4] + var_0[2, 3] * var_1[3, 4] + var_0[2, 4] * var_1[4, 4];

            var_2[2, 5] = var_0[2, 0] * var_1[0, 5] + var_0[2, 1] * var_1[1, 5] + var_0[2, 2] * var_1[2, 5] + var_0[2, 3] * var_1[3, 5] + var_0[2, 4] * var_1[4, 5];

            var_2[3, 0] = var_0[3, 0] * var_1[0, 0] + var_0[3, 1] * var_1[1, 0] + var_0[3, 2] * var_1[2, 0] + var_0[3, 3] * var_1[3, 0] + var_0[3, 4] * var_1[4, 0];

            var_2[3, 1] = var_0[3, 0] * var_1[0, 1] + var_0[3, 1] * var_1[1, 1] + var_0[3, 2] * var_1[2, 1] + var_0[3, 3] * var_1[3, 1] + var_0[3, 4] * var_1[4, 1];

            var_2[3, 2] = var_0[3, 0] * var_1[0, 2] + var_0[3, 1] * var_1[1, 2] + var_0[3, 2] * var_1[2, 2] + var_0[3, 3] * var_1[3, 2] + var_0[3, 4] * var_1[4, 2];

            var_2[3, 3] = var_0[3, 0] * var_1[0, 3] + var_0[3, 1] * var_1[1, 3] + var_0[3, 2] * var_1[2, 3] + var_0[3, 3] * var_1[3, 3] + var_0[3, 4] * var_1[4, 3];

            var_2[3, 4] = var_0[3, 0] * var_1[0, 4] + var_0[3, 1] * var_1[1, 4] + var_0[3, 2] * var_1[2, 4] + var_0[3, 3] * var_1[3, 4] + var_0[3, 4] * var_1[4, 4];

            var_2[3, 5] = var_0[3, 0] * var_1[0, 5] + var_0[3, 1] * var_1[1, 5] + var_0[3, 2] * var_1[2, 5] + var_0[3, 3] * var_1[3, 5] + var_0[3, 4] * var_1[4, 5];

 

В данном коде переменой a соответствует идентификатор var_0, а  переменной b var_1. Идентификатор var_2 соответствует выходной формуле. Мы видим, что сгенерированный код не имеет циклов. Он компилируется во время выполнения и затем исполняется. Отсутствие циклов обеспечивает высокую производительность. Приведём ещё один пример.

Формула следующего вида

Транслируется в следующий программный код.

            var_5[0] = Math.Sin((double)var_4[0]);

            var_5[1] = Math.Sin((double)var_4[1]);

            var_5[2] = Math.Sin((double)var_4[2]);

            var_5[3] = Math.Sin((double)var_4[3]);

            var_5[4] = Math.Sin((double)var_4[4]);

            var_6[0] = (var_3) * ((double)var_5[0]);

            var_6[1] = (var_3) * ((double)var_5[1]);

            var_6[2] = (var_3) * ((double)var_5[2]);

            var_6[3] = (var_3) * ((double)var_5[3]);

            var_6[4] = (var_3) * ((double)var_5[4]);

            var_7 = Math.Cos(var_3);

            var_8[0] = ((double)var_6[0]) + (var_7);

            var_8[1] = ((double)var_6[1]) + (var_7);

            var_8[2] = ((double)var_6[2]) + (var_7);

            var_8[3] = ((double)var_6[3]) + (var_7);

            var_8[4] = ((double)var_6[4]) + (var_7);

            var_10[0] = Math.Tan((double)var_9[0]);

            var_10[1] = Math.Tan((double)var_9[1]);

            var_10[2] = Math.Tan((double)var_9[2]);

            var_10[3] = Math.Tan((double)var_9[3]);

            var_10[4] = Math.Tan((double)var_9[4]);

            var_11[0] = ((double)var_8[0]) + ((double)var_10[0]);

            var_11[1] = ((double)var_8[1]) + ((double)var_10[1]);

            var_11[2] = ((double)var_8[2]) + ((double)var_10[2]);

            var_11[3] = ((double)var_8[3]) + ((double)var_10[3]);

            var_11[4] = ((double)var_8[4]) + ((double)var_10[4]);

            var_12[0] = (var_2) * ((double)var_11[0]);

            var_12[1] = (var_2) * ((double)var_11[1]);

            var_12[2] = (var_2) * ((double)var_11[2]);

            var_12[3] = (var_2) * ((double)var_11[3]);

            var_12[4] = (var_2) * ((double)var_11[4]);

            var_13[0] = Math.Exp((double)var_12[0]);

            var_13[1] = Math.Exp((double)var_12[1]);

            var_13[2] = Math.Exp((double)var_12[2]);

            var_13[3] = Math.Exp((double)var_12[3]);

            var_13[4] = Math.Exp((double)var_12[4]);

 

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

 

 

 

3.     Совместимость с внешним программным обеспечением

 

Если бы данная статья была бы адресована только профессиональным программистам, то данный раздел можно было бы не писать. Профессиональные программисты всегда обеспечивают совместимость.  О том, как обеспечивается совместимость проекта AstroFrame написано в статье посвященной определению орбит космических аппаратов:

http://www.codeproject.com/KB/cs/UniversalEnggFrmwork6.aspx

http://www.mathframe.com/articles/usef/orbitdetermination/index.html

http://sourcecode.aivietnam.net/SourceCode/tabid/54/GroupDetailId/50/ContentId/358/Default.aspx

В данной статье приводится описание экспорта в проект AstroFrame библиотек связанных с расчётом гравитационного поля Земли и динамической атмосферы. Как любой профессионально выполненный проект AstroFrame поддерживает импорт и экспорт из/в программное обеспечение совместимое с .NET. Класс совместимых с .NET программ очень широк. Это различные библиотеки COM объекты. Кроме того имеются широкие возможности конвертирования несовместимого с .NET программного обеспечения в совместимое.

4.     Широта возможностей .NET

 

Для многих .NET  является экзотикой. Однако все ведущие производители программного обеспечения давно уже эту платформу используют. Есть версии .NET совместимые с UNIX системами и с MAC/OS. Компании Oracle и IBM уже давно используют .NET для баз данных. На самом деле основные компоненты .NET такие как C# и CLI имеют сертификат ISO.

5.     Преимущества универсального программного обеспечения

 

Мнение о том, что специализированное программное обеспечение лучше универсального имеет глубокие корни, связанные с техникой. Например, в авиации невозможно нагрузить самолёт большим числом функций. Истребитель не может быть одновременно стратегическим бомбардировщиком. Однако этот довод неприменим к программному обеспечению. Колоссальные объёмы памяти современных компьютеров позволяют виртуально создавать совершенно немыслимые вещи. Один раз автор статьи показал авиатору, как виртуальный бомбардировщик летит с перегрузкой 2000g. «Не надо!!!» - воскликнул авиатор.  Этот пример наглядно показывает неприменимость технических аналогий к программному обеспечению.

С другой стороны универсальное программное обеспечение даёт много дивидендов. Например, если Вы исследуете морские ресурсы, то можно казалось бы можно ограничиться специализированными программами, с принятыми в Вашей отрасли базами данных. Однако, если Вам придётся совместить Ваше ПО с метеорологической базой данных, то скорее всего Ваше ПО будет нуждаться в 90 процентной ревизии. Упор на специализацию – характерный признак  ограниченности мышления и недальновидности.

6.     Затраты на изучение новых технологий окупаются

 

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

 

Заключение

 

В настоящее время технология .NET не нашла должного объёма применения среди инженеров. При проведении научных исследований применение .NET ещё большая редкость.  Сейчас чаще среди исследователей можно встретить Fortran, Pascal или Basic. Автор надеется, что данная статья привлечёт внимание к .NET продвинутых инженеров и исследователей.