Опубликован: 22.04.2008 | Доступ: свободный | Студентов: 527 / 50 | Оценка: 4.50 / 4.75 | Длительность: 06:55:00
Специальности: Программист
Лекция 5:

Программирование на языке MC#

Аннотация: В данной лекции приведены практические примеры программирования на языке MC#. Рассматриваются несколько методов нахождения чисел из последовательности Фибоначчи, а также уделено внимание практической реализации метода Эратосфена для нахождения простых чисел

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

5.1. Вычисление чисел Фибоначчи

Последовательность чисел Фибоначчи есть бесконечный ряд из натуральных чисел

a0, a1, a2, a3, . . .

таких, что

a0 = 1,
a1 = 1, и
ai = ai-1 + ai-2,  для  i >=  2.

Построим параллельную программу, находящую n -ое ( n >= 0 ) число в ряду Фибоначчи, т.е., элемент an последовательности.

Первый вариант такой программы будет иметь рекурсивную структуру, соответствующую формуле определения чисел Фибоначчи. Основной вычислительный метод этой программы будет объявлен асинхронным, и будет возвращать вычисленный результат по каналу, переданному ему в качестве одного из входных аргументов. С другой стороны, в рекурсивных вызовах внутри этого метода будут использоваться каналы для получения значений от методов, вызванных рекурсивно.

Класс Fib, содержащий основной вычислительный метод Compute, может иметь следующий вид:


Главный класс для этой программы может выглядеть следующим образом:


Упражнение 1. Показать, почему при рекурсивных вызовах функции Compute необходимо создание новых объектов класcа Fib.

( Подсказка: рассмотреть как будут использоваться каналы ic1 и ic2 в программе, в которой опущено создание таких объектов, например, при вызове функции Compute с n = 3).

Легко видеть, что приведенный вариант параллельной программы является очень неэффективным, поскольку в нем порождается 2n асинхронных вызовов метода Compute, каждый из которых выполняет очень мало вычислительных операций: фактически, порождает два дополнительных вызова и передает результат по каналу. Очевидно, что в этом случае эффект от параллельного исполнения методов будет перекрыт накладными расходами на их порождение.

Избежать порождения асинхронных вызовов функции Compute для малых по величине аргументов n можно путем введения "локального" вычисления соответствующей функции для малых n:


Приведенный выше вариант является более эффективным, чем первый, но и он обладает существенным недостатком: теперь async-методы для больших n выполняют очень мало вычислительных операций.

Сократить общее количество порождаемых рекурсивно async-методов и более равномерно распределить по ним вычислительную нагрузку позволяет следующий, "линейный" вариант программы Fib:


Приведенные выше рассуждения и варианты программы Fib переносятся и на варианты этой же программы, но для распределенного исполнения (т.е., с заменой async на movable ). При этом, для получения максимального ускорения программист должен подобрать оптимальное значение константы threshold.

Ниже приведены графики времени выполнения последовательной программы и распределенного, "линейного" варианта программы Fib с threshold = 36. Причем количество используемых процессоров в распределенном варианте определялось как n - 34 (для n >= 35 ). Тестовые замеры проводились на кластере с процессорами AMD Athlon(TM) MP 1800+.

Время вычисления N-го числа Фибоначчи "линейным" алгоритмом.

Рис. 5.1. Время вычисления N-го числа Фибоначчи "линейным" алгоритмом.

Полные тексты вариантов программы Fib приведёны в приложении [ "Программирование на языке MC#" ].

5.2. Обход бинарного дерева

Если структура данных задачи организована в виде дерева, то его обработку легко распараллелить путем обработки каждого поддерева отдельном async- ( movable- ) методом.

Предположим, что мы имеем следующее определение (в действительности, сбалансированного) бинарного дерева в виде класса BinTree:


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



Естественно, что для повышения эффективности этой программы, а именно, для получения ускорения при исполнении ее на параллельной архитектуре, необходимо внести в нее усовершенствования, аналогичные тем, которые были сделаны для программы Fib.

Следует также отметить, что в случае распределенного варианта этой программы, при вызове movable -метода Sum, к объекту класса BinTree, являющемуся аргументом этого метода, будут применяться процедуры сериализации/десериализации при переносе вычислений на другой компьютер. (В действительности, с точки зрения Runtime-языка MC#, поддерживающей распределенное исполнение программ, канал также является обычным объектом, к которому будут применяться процедуры сериализации/десериализации).

Полный текст данной программы приведён в приложении [ "Программирование на языке MC#" ].

Упражнение 2. Предположим, что имеется класс Tree, внутренними полями которого являются поле value, хранящее значение, связанное с корнем данного дерева, и поле subtrees, являющееся массивом объектов класса Tree.

Написать параллельную программу на MC#, суммирующую значения из всех вершин заданного дерева.

( Подсказка: для получения значений от рекурсивно порождаемых методов, воспользуйтесь одним каналом).