Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Язык программирования FPTL и средства работы с ним.pdf
Скачиваний:
30
Добавлен:
28.06.2014
Размер:
226.48 Кб
Скачать

P = (id*<0>).eq;

F1 = <1>;

F2 = (id * DEC.@) . mult; DEC = (id*<1>).minus;

}

Application %factorial(6)

Интерпретатор и компилятор языка FPTL

Целью нашей работы является максимальное использование возможностей, данных нам структурой языка FPTL в области автоматического анализа и распараллеливания программ.

Мы параллельно ведем два проекта:

Кластерный интерпретатор программ FPTL

Компилятор языка FPTL

Кластерный интерпретатор

Кластерный интерпретатор позволяет выполнять программы FPTL на любом кластере машин, на которых установлен JRE 1.5 и пакеты среды выполнения языка FPTL. Связь между машинами осуществляется посредством сокетов. Мы не используем какие-либо стандартные библиотеки для параллельных вычислений (OpenMP, MPI, и т.д.) из-за различных ограничений, накладываемых ими. Интерпретатор пользуется особенностями языка FPTL для поиска параллельно вычисляемых функциональных переменных (далее – ФП). Явной возможности указать параллельно вычисляемые ФП в языке нет.

Интерпретатор имеет следующие отличительные черты:

Поддержка сетей любого размера (по кол-ву машин)

Поддержка любой пропускной способности сети

Поддержка любого кол-ва процессоров/ядер на машине (далее в статье считаем их многопроцессорными)

Автоматический поиск ФП с достаточной вычислительной сложностью (переносимых между машинами)

Функционал по поиску и использованию в процессе вычислений мемоизуемых ФП (ФП таких, что если f(a) уже было ранее вычислено, то при следующей попытке вычисления f(a) будет сразу использован сохраненный с предыдущего вызова результат)

Работа на куче, а не на стеке, что упрощает выполнение рекурсивных алгоритмов

Использование информации о загруженности как отдельной машины, так и кластера в процессе вычислений для оптимизации переноса вычисляемых ФП

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

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

Внаших планах добавление в интерпретатор следующих функций:

Оптимизация алгоритма поиска пересылаемых ФП (дает теоретическое увеличение максимального размера кластера за счет нахождения большего кол-ва задач, которые могут исполняться одновременно)

Подключение вызовов откомпилированных ФП (по сути, аналог JIT-компилятора, но вся программа компилируется в начале работы программы на серверной машине)

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

Как достигается автоматический поиск переносимых ФП?

Как было указано в описании языка, оператор звездочка является основным источником параллелизма в языке. Все его аргументы могут вычисляться параллельно. Нахождение вызова ФП в операторе звездочка является необходимым условием того, что этот вызов будет переносимым. То есть у нас переносимыми являются не ФП вообще, а именно их вызовы из конкретных мест в программе.

Вторым необходимым условием для переносимости ФП является ее достаточная сложность в результате анализа структурной сложности программы. Алгоритм оценки структурной сложности здесь приводится не будет (он еще находится в доработке), но гарантируется, что структурная сложность будет возрастать в таком порядке (F(G) – ФП F зависит от ФП G):

1.F()

2.F(G), где G()

3.F(F)

4.F(G), где G(G)

Итак далее. Фактически сложность ФП вычисляется по формуле «сложность самой сложной из ФП, от которой зависит данная» + 1, если данная ФП не рекурсивная или +2, если данная ФП рекурсивная (рекурсия любого типа – как саморекурсивная – F(F), так и взаимно рекурсивная – F(G), G(F) или F1(F2), F2(F3), F3(F1) – для любого кол-ва ФП в цепочке зависимостей).

ФП считается переносимой, если ее сложность не меньше какой-то части от сложности самой сложной ФП в программе (ФП входа в программу). На данный момент эта часть равна 80% от максимальной сложности.

Как мы находим мемоизуемые ФП?

По определению, мемоизуемая функция – это такая функция, которая за время работы программы хотя бы дважды вызывалась с одним и тем же значением ее аргумента. В функциональных программах необходимость поддержки мемоизации заметно возрастает, т.к. множественные рекурсивные вызовы функции часто приводят ко многим вызовам функции с одним и тем же ее аргументом. Одним из известных примеров функций, которые очень выгодно мемоизовать, является вычисление чисел Фибоначчи по формуле fib(n) = fib(n-1) + fib(n-2), при которой у нас будет fib(1) вызовов fib(n), fib(2) вызовов fib(n-1) и т.д. Другим менее известным, но еще более показательным примером является вычисление функции Аккермана, при котором мемоизация дает возможность программам на медленных скриптовых языках обходить реализации «в лоб», написанные на Си (заметим, что если язык дает мемоизацию задаром, то мы можем считать ее как оптимизацию сгенерированного кода, и, следовательно как преимущество над другими компиляторами).