Анализ эффективности функционального программирования на языке F# для многоядерных процессоров
Анализ существующих функциональных языков: история, семейства, преимущества. Анализ эффективности параллельного программирования для задачи обработки графического представления фрактальных функций. Программа умножения матриц, обработки изображения.
Рубрика | Программирование, компьютеры и кибернетика |
Вид | дипломная работа |
Язык | русский |
Дата добавления | 12.01.2016 |
Размер файла | 2,5 M |
Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже
Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.
open System.IO
open System.Net
open Microsoft.FSharp.Control.WebExtensions
let getPage (url:string) =
async {
let req = WebRequest.Create(url)
let! res = req.AsyncGetResponse()
use stream = res.GetResponseStream()
use reader = new StreamReader(stream)
let! result = reader.AsyncReadToEnd()
return result
}
Построитель асинхронного потока операций, работает следующим образом. Встретив оператор let! или do!, он начинает выполнять операцию асинхронно, при этом метод, начинающий асинхронную операцию, получит оставшуюся часть блока async в качестве функции обратного вызова. После завершения асинхронной операции переданный callback продолжит выполнение асинхронного потока операций, но, возможно, уже в другом потоке операционной системы (для выполнения кода используется пул потоков). В результате код выглядит так, как будто он выполняется последовательно. То, что может быть с легкостью записано внутри блока async с использованием циклов и условных операторов, достаточно сложно реализуется с использованием обычной техники, требующей описания множества callback-методов и передачей состояния между их вызовами.
MailboxProcessor -- это класс из стандартной библиотеки F#, реализующий один из паттернов параллельного программирования. MailboxProcessor является агентом, обрабатывающим очередь сообщений, которые поставляются ему извне при помощи метода Post. Вся конкурентность поддерживается реализацией класса, который содержит очередь с возможностью одновременной записи несколькими писателями и чтения одним единственным читателем, которым является сам агент [17].
let agent = MailboxProcessor.Start(fun inbox ->
async {
while true do
let! msg = inbox.Receive()
printfn "message received: '%s'" msg
})
2.2 Анализ эффективности программирования на F# для многоядерных процессоров
Для исследования эффективности программирования на языке F# была выбрана одна из базовых операций работы процессора - умножение матриц для чисел с плавающей точкой. Одним из критериев является тот, что умножение матриц легко поддается распараллеливанию. Были выбраны алгоритмы эффективного умножения, которые также способствуют написанию параллельной программы. Для сравнения, аналогичные программы были написаны на императивных языках С++ и C#.
Существует множество алгоритмов перемножения матриц: алгоритм Штрассена, алгоритм Пана, алгоритм Бини, алгоритмы Шёнхаге, алгоритм Копперсмита-Винограда, алгоритмы с использованием теории групп.
Данные алгоритмы предусматривают оптимизацию по способу умножения, но существуют также алгоритмы, оптимизирующие доступ к памяти и позволяющие лучше распараллеливать задачу. Среди таких алгоритмов можно выделить:
ленточный алгоритм;
алгоритм Фокса;
алгоритм Кэннона;
Z-преобразование (Morton-order);
В программе для анализа эффективности параллельного программирования использовались алгоритмы Штрассена и Z-преобразования матриц. Рассмотрим их подробней.
Пусть A, B - две квадратные матрицы над кольцом R. Матрица C получается по формуле:
Если размер умножаемых матриц n не является натуральной степенью двойки, мы дополняем исходные матрицы дополнительными нулевыми строками и столбцами. При этом мы получаем удобные для рекурсивного умножения размеры, но теряем в эффективности за счёт дополнительных ненужных умножений.
Разделим матрицы A, B и C на равные по размеру блочные матрицы
где
Однако с помощью этой процедуры нам не удалось уменьшить количество умножений. Как и в обычном методе, нам требуется 8 умножений.
Теперь определим новые матрицы
которые затем используются для выражения Ci,j. Таким образом, нам нужно всего 7 умножений на каждом этапе рекурсии. Элементы матрицы C выражаются из Pk по формулам
Итерационный процесс продолжается n раз, до тех пор пока матрицы Ci,j не выродятся в числа (элементы кольца R). На практике итерации останавливают при размере матриц от 32 до 128 и далее используют обычный метод умножения матриц. Это делают из-за того, что алгоритм Штрассена теряет эффективность по сравнению с обычным на малых матрицах из-за дополнительных копирований массивов [22].
Z-преобразование, или код Мортона (рис.1) впервые был предложен в 1966 году Г.М. Мортоном, он представляет собой пространственную кривую, часто использующуюся в информатике. Благодаря сохраняющему локальность поведению он используется в структурах данных для перевода многомерной информации в одно измерение. Z-значение точки легко вычисляется с помощью бинарного представления значений координат. Как только данные отсортированы в этом порядке, к ним может быть применена любая одномерная структура, например деревья двоичного поиска, B-деревья, списки или хэш-таблицы.
Рис. 1 - Графическое представление Z-преобразования
Для исследования были написаны программы, реализующие умножение матриц на языках С++ и С# с использованием OpenMP и Parallel Extensions, а также на языке F# с использованием асинхронных выражений. Исследование проводилось на двухъядерном процессоре Inter Core 2 Duo, 2.4 Ghz, 3Mb Cache, 3Gb оперативной памяти, среда Visual Studio 2008 Team System, операционная система Microsoft Windows Vista Home Edition, библиотека для параллельных вычислений ParallelExtensions_Jun08CTP, сборка F# 1.9.6.16.
Для исследования были реализованы следующие алгоритмы умножения матриц:
Классический ().
Классический с использованием Z-преобразования.
Штрассена с использованием Z-преобразования.
Результаты измерения времени работы этих алгоритмов для выбранных языков программирования представлены на рисунках 2-6. На рисунке 7 для сравнения был добавлен график зависимости времени выполнения программы от размера матриц для встроенного метода умножения матриц библиотеки Math платформы .NET.
Рис. 2 - График зависимости времени выполнения от размера матриц для классического алгоритма умножения
Рис. 3 - График зависимости времени выполнения от размера матриц для параллельной версии классического алгоритма умножения
Рис. 4 - График зависимости времени выполнения от размера матриц для классического алгоритма умножения с использованием Z-преобразования матриц
Рис. 5 - График зависимости времени выполнения от размера матриц для параллельной версии классического алгоритма умножения с использованием Z-преобразования матриц
Рис. 6 - График зависимости времени выполнения от размера матриц для алгоритма Штрассена с использованием Z-преобразования матриц
Рис. 7 - График зависимости времени выполнения от размера матриц для параллельной версии алгоритма Штрассена с использованием Z-преобразования матриц
После проведенного эксперимента проанализируем полученные результаты:
При обычном и обычном параллельном умножениях .NET языки показали одинаковую производительность, чего и следовало ожидать. Здесь и далее С++ намного опережает их по производительности, что доказывает целесообразность использования для приложений, требующих высокой скорости вычислений компилируемых языков, а F# и C# - интерпретируемые языки. .NET совместимые языки компилируются во второй, кроссплатформенный язык, который называется CIL (Common Intermediate Language). А уже зависимая от платформы среда CLR (Common Language Runtime) компилирует CIL в машинный код, который может быть выполнен на данной платформе.
Умножение с использованием Z-преобразования матриц лучше проходит на С#, чем на F#, но значительно хуже распараллеливается (всего 20-25%).
Массивы располагаются линейно в оперативной памяти, благодаря Z-преобразованию доступ к ним осуществляется последовательно, ниже процент cache-misses, это приводит к уменьшению времени выполнения умножений.
Использование метода Штрассена, как и ожидалось, привело к ускорению работы программы.
В умножении с использованием метода Штрассена F# показывает лучшие результаты, чем C#. Следует полагать, что это происходит благодаря отходу от императивного способа создания новых матриц и использования связывания имен let.
На основе полученных результатов можно сделать вывод, что язык F# позволяет эффективно распараллеливать и решать такую стандартную задачу как умножение матриц, обходя при этом по эффективности другой язык работающий на платформе .NET - C#, но уступая языку С++, работающему с неуправляемой памятью.
3. АНАЛИЗ ЭФФЕКТИВНОСТИ ФУНКЦИОНАЛЬНОГО ПРОГРАММИРОВАНИЯ НА ЯЗЫКЕ F# ДЛЯ РЕШЕНИЯ ЗАДАЧИ ОБРАБОТКИ ИЗОБРАЖЕНИЙ ДЛЯ МНОГОЯДЕРНЫХ ПРОЦЕССОРОВ
3.1 Описание задачи
Задача умножения матриц является достаточно абстрактной и не привязана к конкретной проблеме из реального мира, а для исследования эффективности определенного языка необходимо учитывать возможность его применения на практике. В связи с этим для исследования нужно было найти задачу, обладающую следующими свойствами:
Актуальность. Для того чтобы определить язык как эффективный, необходимо убедиться, что он может решать актуальные задачи и получать убедительные результаты.
Реализуемость. Для анализа языка задача не должна быть слишком сложной, но она должна охватывать базовые требования, которые должны обязательно реализовываться в современном языке.
Возможность распараллеливания. Поскольку анализ языка проводится в частности для многоядерных процессоров, нужно убедиться в том, что язык позволяет эффективно распределять нагрузку на данный тип вычислительных систем.
Наглядность. Хорошей практикой для описания новых языков является реализация наглядных примеров, так как это способствует восприятию его как альтернативы существующим языкам.
В архиве Microsoft был найден набор таких примеров от 2009-12-09. Описание примеров можно найти в источнике [22]. Из этих примеров для исследования была выбрана задача построения и обработки классического множества Мандельброта.
В математике множество Мандельброта -- это фрактал, определённый как множество точек С на комплексной плоскости, для которых итеративная последовательность
не уходит на бесконечность.
Впервые множество Мандельброта было описано в 1905 году Пьером Фату (Pierre Fatou), французским математиком, работавшим в области аналитической динамики комплексных чисел. Фату изучал рекурсивные процессы вида
Начав с точки на комплексной плоскости, можно получить новые точки, последовательно применяя к ним эту формулу. Такая последовательность точек называется орбитой при преобразовании
Фату нашел, что орбита при этом преобразовании показывает достаточно сложное и интересное поведение. Существует бесконечное множество таких преобразований -- своё для каждого значения. Основываясь на своих расчётах, он доказал, что орбита точки, лежащей на расстоянии больше 2 от начала координат, всегда уходит в бесконечность.
Фату никогда не видел изображений, которые мы сейчас знаем как изображения множества Мандельброта, потому что необходимое количество вычислений невозможно провести вручную. Профессор Бенуа Мандельброт был первым, кто использовал для этого компьютер.
Фракталы были описаны Мандельбротом в 1975 году в его книге «Les Objets Fractals: Forme, Hasard et Dimension» («Фрактальные объекты: форма, случайность и размерность») [24].
Фрактал (лат. fractus -- дробленый, сломанный, разбитый) -- сложная геометрическая фигура, обладающая свойством самоподобия, то есть составленная из нескольких частей, каждая из которых подобна всей фигуре целиком. В более широком смысле под фракталами понимают множества точек в евклидовом пространстве, имеющие дробную метрическую размерность (в смысле Минковского или Хаусдорфа), либо метрическую размерность, строго большую топологической [25].
Одной из идей, выросших из открытия фрактальной геометриим, была идея нецелых значений для количества измерений в пространстве. Конечно, мы не можем осознать четырехмерные вещи, хотя Lucky Tesseract и активно работает в этом направлении. Мандельброт назвал нецелые измерения такие как 2.76 фрактальными измерениями. Обыкновенная евклидова геометрия утверждает, что пространство ровное и плоское. Свойства такого пространства такого пространства задают точки, линии, углы, треугольники, кубы, сферы, тетраэдры и т. д.
Мандельброт верил, что действительный ландшафт пространства не ровный и что в нашем мире нет ничего, что было бы совершенно плоским, круглым, то есть все фрактально. Следовательно, объект, имеющий точно 3 измерения невозможен. Поэтому концепция фрактального измерения была нужна для измерения степени неровности вещей.
Первыми открытыми фракталами были т.н. детерминированные фракталы. Их отличительной чертой является свойство самоподобия, обусловленное особенностями метода их генерации.
Такие фракталы называются классическими, геометрическими фракталами или линейными фракталами. Эти фракталы обычно формируются начиная с инициатора -- фигуры, к которой применяется определенный основной рисунок. Во всех детерминированных фракталах, само-подобие проявляется на всех уровнях. Это значит, что независимо от того насколько вы приближаете фрактал, вы увидите все тот же узор. Для сложных фракталов, которые будут рассмотрены позже, это не так. Детерминистские фракталы образуются в процессе, называемом итерацией, которая применяет основной рисунок к инициатору, после чего применяет его к результату и так далее. Из примеров можно выделить решетку Серпинского (рис. 8), фрактал Серпинского (рис. 9),крест Коха (рис. 10), фрактал Мандельброта (рис. 11), колбасу Минковского (рис. 12), пятиугольник Дарера (рис. 13), кривую Гильберта (рис 14), кривую Дракона (рис 15). Их фрактальные размерности находятся в пределах от 1.26 до 2.0.
Большая часть встречающихся сегодня фракталов не являются детерминированными. Они не линейны и не собраны из повторяющихся геометрических форм. Такие фракталы называются сложными. Если увеличить малую область фрактала до размера большей области то получатся два изображения, которые будут очень похожи в деталях, но они не будут полностью идентичными.
Детерминистские фракталы являются линейными, тогда как сложные фракталы таковыми не являются. Будучи нелинейными, эти фракталы генерируются нелинейными алгебраическими уравнениями.
Из самых известных недетерминированных фракталов можно выделить множества Мандельброта (рис. 16) и Жулиа (рис. 17).
Большинство фракталов, которые можно найти на сегодняшний день, представлены в богатой цветовой схеме. Возможно, фрактальные изображения получили такое большое эстетическое значение именно благодаря этой особенности. После того, как уравнение посчитано, начинается применение цвета. Если результаты остаются стабильными, или колеблются вокруг определенного значения, точка обычно принимает черный цвет. Если значение на том или ином шаге стремится к бесконечности, точку закрашивают в другой цвет, может быть в синий или красный. Во время этого процесса, компьютер назначает цвета для всех скоростей движения.
Фракталы находят все большее и большее применение в науке. Основная причина этого заключается в том, что они описывают реальный мир иногда даже лучше, чем традиционная физика или математика. Вот несколько примеров:
Наиболее полезным использованием фракталов в компьютерной науке является фрактальное сжатие данных. В основе этого вида сжатия лежит тот факт, что реальный мир хорошо описывается фрактальной геометрией. При этом, картинки сжимаются гораздо лучше, чем это делается обычными методами (такими как jpeg или gif). Другое преимущество фрактального сжатия в том, что при увеличении картинки, не наблюдается эффекта пикселизации (увеличения размеров точек до размеров, искажающих изображение). При фрактальном же сжатии, после увеличения, картинка часто выглядит даже лучше, чем до него.
Изучение турбулентности в потоках очень хорошо подстраивается под фракталы. Турбулентные потоки хаотичны и поэтому их сложно точно смоделировать. И здесь помогает переход к из фрактальному представлению, что сильно облегчает работу инженерам и физикам, позволяя им лучше понять динамику сложных потоков.
При помощи фракталов также можно смоделировать языки пламени.
Пористые материалы хорошо представляются в фрактальной форме в связи с тем, что они имеют очень сложную геометрию. Это используется в нефтяной науке.
Для передачи данных на расстояния используются антенны, имеющие фрактальные формы, что сильно уменьшает их размеры и вес.
Фракталы используются для описания кривизны поверхностей. Неровная поверхность характеризуется комбинацией из двух разных фракталов.
Биосенсорные взаимодействия и биения сердца в медицине.
Моделирование хаотических процессов, в частности при описании моделей популяций в биологии.
Применение фракталов в стеганографии и для шифрования данных.
Сегодня Мандельброт и другие ученые, такие как Клиффорд А. Пикковер (Clifford A. Pickover), Джеймс Глейк (James Gleick) или Г. О. Пейтген (H.O. Peitgen) пытаются расширить область фрактальной геометрии так, чтобы она могла быть применена практически ко всему в мире, от предсказания цен на рынке ценных бумаг до совершения новых открытий в теоретической физике [26].
Тем не менее и сама задача построения фракталов не завершилась а продолжила развитие в 3D-масштабах. Далее приведены примеры изображений, построенных с использованием фракталов.
Рис. 18 - 3D-изображения, построенные при помощи фракталов
Анализ эффективности параллельного программирования на F# для задачи обработки графического представления фрактальных функций
Задача графической реализации фрактального множества была выбрана не случайно, ведь, как и в большинстве задач по обработке изображений, в данной присутствует возможность для распараллеливания вычислений по данным. В программе было использовано распараллеливание по независимым рядам пикселей, что позволило равномерно распределить нагрузку между ядрами процессоров.
Для исследования эффективности использования языка F#, было реализовано 2D-изображение множества Мандельброта на 256 цветах в окне Windows. В качестве дополнительного функционала были реализованы такие функции как масштабирование окна, приближение и удаление картинки, восстановление первоначального приближения, перемещение по изображению.
Для тестирования было использовано следующее оборудование:
Intel Core 2 Duo, 2.4 Ghz, 3Mb Cache, 3Gb оперативной памяти, среда Visual Studio 2010 Ultimate, операционная система Microsoft Windows Vista Home Edition 32x.
QuadCore Intel Core i7, 2.6 Ghz, 6Mb Cache, 6Gb оперативной памяти, среда Visual Studio 2010 Ultimate, операционная система Microsoft Windows 7 Home Premium 64x.
Для тестирования исходное изображение десятикратно увеличивалось и сравнивалось время его перерисовки для языков F#, C++/CLR, C#.
Так выглядит окно программы после применения нескольких увеличений.
Рис. 19 - Окно программы Interactive Fractal
В результате исследования были представлены несколько графиков, которые отображают количество обработанных пикселей в миллисекунду на 10 шагах приближения множества Мандельброта.
Результаты тестирования на первом оборудовании:
Рис. 20 - Скорость обработки пикселей на десяти шагах приближения для последовательной версии программы
Рис. 21 - Скорость обработки пикселей на десяти шагах приближения для параллельной версии программы
Рис. 22 - Коэффициент ускорения работы программы на десяти шагах приближения
Результаты тестирования на втором оборудовании:
Рис. 23 - Скорость обработки пикселей на десяти шагах приближения для последовательной версии программы
Рис. 24 - Скорость обработки пикселей на десяти шагах приближения для параллельной версии программы
Рис. 25 - Коэффициент ускорения работы программы на десяти шагах приближения
По полученным результатам можно сделать c выводы:
Результаты отличаются для двух- и четырехъядерных процессоров. Лучшие результаты язык F# показывает при тестировании параллельной версии программы на четырех ядрах.
Использование языка F# не дает выигрыша в скорости выполнения программы.
Так как все языки компилируются в CLI, результаты работы программы обработки изображения примерно одинаковы.
F# показывает лучший коэффициент ускорения на многоядерном процессоре, чем другие языки
В ряде тестов ускорение при работе нескольких ядер превышало их количество.
Проанализируем код программ для получения объяснения результатов эксперимента.
С++
RenderImageData^ rrd = gcnew RenderImageData(position, width, height);
if (parallel)
{
ParallelOptions ^options = gcnew ParallelOptions();
options->CancellationToken = cancellationToken;
Parallel::For(0, rrd->ImageHeight, options, gcnew Action<int>(rrd, &RenderImageData::RenderRow));
}
else
{
for (int row = 0; row < rrd->ImageHeight; row++)
{
cancellationToken.ThrowIfCancellationRequested();
rrd->RenderRow(row);
}
}
…
Data = gcnew array<Byte>(Pixels);
void RenderRow(int row)
{
double initialY = row * RowToYTranslation + Top;
for (int col = 0, currentPixelIndex = row * ImageWidth; col < ImageWidth; col++, currentPixelIndex++)
{
Complex c = *(gcnew Complex(col * ColToXTranslation + Left, initialY));
Complex z = c;
for (int iteration = 0; iteration < MaxIterations; iteration++)
{
if (z.Magnitude > 4)
{
Data[currentPixelIndex] = iteration;
break;
}
z = (z * z) + c;
}
}
}
Видно, что основные вычисления происходят в циклах по высоте изображения, ширине и количеству итераций для определения цвета точки. Для осуществления распараллеливания используется конструкция for класса Parallel библиотеки TPL. Также очевиден и недостаток такой реализации - все процессы обращаются к общей структуре, что теоретически допускает возможность конфликтов между записывающими потоками. Из неудобств написания программы нужно выделить отсутствие поддержки IntelliSense, необходимость инициализации и выделения памяти массиву данных до его использования.
C#
if (parallelRendering)
{
var options = new ParallelOptions { CancellationToken = cancellationToken };
Parallel.For(0, imageHeight, options, row =>
{
double initialY = row * rowToYTranslation + top;
fixed (byte* ptr = data)
{
byte* currentPixel = &ptr[row * imageWidth];
for (int col = 0; col < imageWidth; col++, currentPixel++)
{
Complex c = new Complex(col * colToXTranslation + left, initialY);
Complex z = c;
for (int iteration = 0; iteration < maxIterations; iteration++)
{
if (z.Magnitude > 4)
{
*currentPixel = (byte)iteration;
break;
}
z = (z * z) + c;
}
}
}
});
}
else
{
for (int row = 0; row < imageHeight; row++)
{
cancellationToken.ThrowIfCancellationRequested();
double initialY = row * rowToYTranslation + top;
fixed (byte* ptr = data)
{
byte* currentPixel = &ptr[row * imageWidth];
for (int col = 0; col < imageWidth; col++, currentPixel++)
{
Complex c = new Complex(col * colToXTranslation + left, initialY);
Complex z = c;
for (int iteration = 0; iteration < maxIterations; iteration++)
{
if (z.Magnitude > 4)
{
*currentPixel = (byte)iteration;
break;
}
z = (z * z) + c;
}
}
}
};
}
Этот же способ был реализован и на C#, что в конечном итоге приводит к тем же проблемам. Кроме того, используется unsafe(небезопасный) код с указателями. Из-за того большая часть кода повторяется дважды, программа становится громоздкой и неудобной для повторного использования. Повторное написание кода вызвано использованием многих переменных класса, т.е. состояния программы.
F#
let rec drawBit (z : Complex) c curIter =
match curIter with
x when x = maxIterations ->byte 0
|_ ->
match z with
x when x.Magnitude > 4.0 -> byte curIter
|_ -> drawBit (z*z+c) c (curIter+1)
let getbyte initialY j =
cancellationToken.ThrowIfCancellationRequested()
let c = new Complex((float j) * colToXTranslation + left, initialY)
drawBit c c 0
let data =
if parallelRendering then
Array.Parallel.init imageHeight (fun i -> Array.init newImageWidth (fun j -> getbyte ((float i) * rowToYTranslation + top) j))
else
Array.init imageHeight (fun i -> Array.init newImageWidth (fun j -> getbyte ((float i) * rowToYTranslation + top) j))
Версия на F# имеет следующие отличия:
Отсутствуют операторы присваивания, что автоматически устраняет проблему конфликта между процессами
Циклы заменяются рекурсией, которая, в силу того, что она «хвостовая» компилируется обратно в цикл
Легче производить функциональную декомпозицию, код становится короче
Параллельная версия отличается от последовательной одним словом
Отсутствует необходимость инициализировать массив до его заполнения, так как эти операции можно выполнить одновременно
Результаты эксперимента несколько различаются для 2х и 4х ядерного процессора, наиболее эффективно проявляет себя параллельная версия программы на 4х ядерном процессоре
В этом эксперименте также проявился эффект сверхлинейного ускорения - скорость работы программы при подключении двух и четырех ядер увеличивалась более чем в два и четыре раза соответственно. Это объясняется использованием общего кэша L2 на процессоре Core 2 Duo и L3 на процессоре i7. Можно выделить следующие преимущества систем с общим кэшем:
Увеличивается коэффициент использования кэша.
Уменьшается сложность согласования кэшей.
Уменьшается количество промахов кэша.
Уменьшается расход памяти - одни и те же данные для нескольких процессоров записываются единожды.
Уменьшается нагрузка на шину данных - проблемы доступа к данным разрешаются на уровне кэша [27].
Так как массив бит для изображения в памяти располагается линейно, то достигается высокий процент попаданий в кэш, что серьезно увеличивает производительность уже для двухъядерного процессора. Для четырехъядерного процессора это приводит к сверхлинейному ускорению, невозможному без участия кэша.
ЗАКЛЮЧЕНИЕ
В результате проделанной работы можно утверждать, что язык F# является эффективным .NET языком программирования для реализации параллельных вычислений или параллельной обработки данных. В эксперименте с умножением матриц по алгоритму Штрассена с использованием Z-порядка реализация на F# оказалась эффективней другого .NET языка C#. В эксперименте с оконным приложением для обработки изображения недетерминированного фрактала язык показал лучший коэффициент ускорения при переходе с одного ядра на четыре, при этом обеспечивая безопасный доступ потоков различных ядер к общим данным. Кроме того, возможности асинхронной работы потоков, интеграции с другими .NET приложениями, интерактивного выполнения кода, статическая типизация, функциональная основа делают его незаменимым инструментов в руках профессионального разработчика. Поддержка своих механизмов и библиотек .NET для параллельного программирования для многоядерных процессоров позволяет F# успешно конкурировать с другими мощными языками поддерживающими возможности параллельной разработки.
ЛИТЕРАТУРА
1. http://www.ict.edu.ru/ft/005135//ch3.pdf
2. http://ru.wikipedia.org/wiki/Язык_функционального_программирования
3. http://ru.wikipedia.org/wiki/Препроцессор
4. http://ru.wikipedia.org/wiki/Lisp
5. http://progopedia.ru/language/ml/
6. http://oops.math.spbu.ru/~msk/languages.functional.html
7. http://en.wikipedia.org/wiki/Hope_(programming_language)
8. http://progopedia.ru/language/miranda/
9. http://ru.wikipedia.org/wiki/Haskell
10. http://www.intuit.ru/department/pl/haskel98/1/
11. http://ru.wikipedia.org/wiki/Охрана_(программирование)
12. http://www.uchi-it.ru/7/2/3.html
13. http://www.rsdn.ru/article/funcprog/fp.xml
14. http://ru.wikipedia.org/wiki/Функциональное_программирование
15. http://msdn.microsoft.com/ru-ru/library/bb669144.aspx
16. http://habrahabr.ru/blogs/net/71656/
17. http://fprog.ru/2010/issue5/maxim-moiseev-et-al-fsharp-intro/
18. http://www.realcoding.net/article/view/2764
19. http://www.dartmouth.edu/~rc/classes/intro_mpi/parallel_prog_compare.html
20. http://msdn.microsoft.com/en-us/library/dd460693.aspx
21. http://ru.wikipedia.org/wiki/Алгоритм_Штрассена
22. http://blogs.msdn.com/b/pfxteam/archive/2009/12/09/9934811.aspx
23. http://ru.wikipedia.org/wiki/Множество_Мандельброта
24. http://ru.wikipedia.org/wiki/Фрактал
25. http://www.chaostarantula.narod.ru/ToC/1.htm
26. http://drdobbs.com/embedded-systems/196902836
27. Tomas Petricek,Jon Skeet Functional Programming for the Real World/Manning/2009
28. Сошников Д.В. - лекции (“Функциональное программирование”)/2008/Lect1, видео (“F# : Новый язык программирования .NET”)/Платформа/ 2009
29. Тим Мэттсон, Андрей Чернышев Введение в технологии параллельного программирования /Intel® Software Network/2009
30. Don Syme, Adam Granicz, and Antonio Cisternino Expert F#/Apress/2010
ПРИЛОЖЕНИЕ 1
Тексты программ на F#
А. Программа для умножения матриц
#light//включение light-синтаксиса
open System
open System.Threading
/// Измерение времени выполнения функции
let Time f =
let timer = System.Diagnostics.Stopwatch.StartNew ()
let y = f ()
printfn "Time taken = %O" timer.Elapsed
y
let MatrixMultiplyDemo () =
/// Обыкновенное умножение
let Multiply (A: float[,]) (B: float[,])(C: float[,]) =
let n = Array2D.length1 A
for i = 0 to (n-1) do
for j=0 to n-1 do
for k=0 to n-1 do
C.[i,j] <- C.[i,j] + A.[i,k] * B.[k,j]
C
/// Простое умножение
let MultiplyX (A: matrix) (B: matrix) =
(fun x y -> x * y) A B //анонимная функция
/// Быстрое умножение с использованием алгоритма Parallel-FX
let PFXMultiply (A: float[,]) (B: float[,]) (C: float[,]) =
let n = Array2D.length1 A
let RowTask i =
for j=0 to n-1 do
for k=0 to n-1 do
C.[i,j] <- C.[i,j] + A.[i,k] * B.[k,j]
//далее идет инструкия из Parallel Extensions .NET
Parallel.For(0, (n-1), new Action<int>(RowTask))
C
///Создание Z-матриц
let CreateZMatrix (A: float[,]) (B: float[,]) (C: float[,] ) =
let n = Array2D.length1 A
let zMaskSize = int(Math.Log(float(n*n),float(2)))
let DecalcZ z =
let mutable z1 = z
let mutable i = 0
let mutable j = 0
let mutable bit = 1
while (bit <= z1) do
j <- j ||| (z1 &&& bit)
z1 <- z1 >>> 1
i <- i||| (z1 &&& bit)
bit <- bit <<< 1
(i,j) //возвращается tuple
let f i (M: float[,]) =
let (i1,j1)=DecalcZ i
M.[i1,j1]
let zA=Array.init (1<<<zMaskSize) (fun i -> f i A)
let zB=Array.init (1<<<zMaskSize) (fun i -> f i B)
let mutable zC=Array.init (1<<<zMaskSize) (fun i -> f i C)
(zA,zB,zC) //массив zC теперь изменяемый
///параллельное Z-умножение
let PZMatrixMultiply (zA:float[]) (zB:float[]) (zC:float[]) =
let n=Array.length zA
let ZCode = 0
let MaskBits = int(Math.Log(float(n),float(2)))
let rec zMultiply (aZCode,aMaskBits) (bZCode,bMaskBits) (cZCode,cMaskBits)=
if aMaskBits >=2 then
let GetSubcells (zCode, maskBits) = //разбиение на 4 ячейки
let subMaskBits = maskBits - 2
let zI =
[|
(zCode , subMaskBits)
(zCode ||| (1<<<subMaskBits), subMaskBits)
(zCode ||| (2<<<subMaskBits), subMaskBits)
(zCode ||| (3<<<subMaskBits), subMaskBits)
|]
zI
let c1sub = GetSubcells (aZCode,aMaskBits)
let c2sub = GetSubcells (bZCode,aMaskBits)
let cRsub = GetSubcells (cZCode,aMaskBits)
zMultiply c1sub.[0] c2sub.[0] cRsub.[0]
zMultiply c1sub.[1] c2sub.[2] cRsub.[0]
zMultiply c1sub.[0] c2sub.[1] cRsub.[1]
zMultiply c1sub.[1] c2sub.[3] cRsub.[1]
zMultiply c1sub.[2] c2sub.[0] cRsub.[2]
zMultiply c1sub.[3] c2sub.[2] cRsub.[2]
zMultiply c1sub.[2] c2sub.[1] cRsub.[3]
zMultiply c1sub.[3] c2sub.[3] cRsub.[3]
else
zC.[cZCode] <- zA.[aZCode] * zB.[bZCode] + zC.[cZCode]
let PzMultiply (aZCode,aMaskBits) (bZCode,bMaskBits) (cZCode,cMaskBits)=
if aMaskBits >=2 then
let GetSubcells (zCode, maskBits) =
let subMaskBits = maskBits - 2
let zI =
[|
(zCode , subMaskBits)
(zCode ||| (1<<<subMaskBits), subMaskBits)
(zCode ||| (2<<<subMaskBits), subMaskBits)
(zCode ||| (3<<<subMaskBits), subMaskBits)
|]
zI
let c1sub = GetSubcells (aZCode,aMaskBits)
let c2sub = GetSubcells (bZCode,aMaskBits)
let cRsub = GetSubcells (cZCode,aMaskBits)
let tasks = [//разбиение на асинхронные потоки
async
{
zMultiply c1sub.[0] c2sub.[0] cRsub.[0]
zMultiply c1sub.[1] c2sub.[2] cRsub.[0]
}
async
{
zMultiply c1sub.[0] c2sub.[1] cRsub.[1]
zMultiply c1sub.[1] c2sub.[3] cRsub.[1]
}
async
{
zMultiply c1sub.[2] c2sub.[0] cRsub.[2]
zMultiply c1sub.[3] c2sub.[2] cRsub.[2]
}
async
{
zMultiply c1sub.[2] c2sub.[1] cRsub.[3]
zMultiply c1sub.[3] c2sub.[3] cRsub.[3]
}
]
tasks |> Async.Parallel |> Async.RunSynchronously |>ignore
else
zC.[cZCode] <- zA.[aZCode] * zB.[bZCode] + zC.[cZCode]
//вычисление на последнем уровне
PzMultiply (ZCode,MaskBits)(ZCode,MaskBits)(ZCode,MaskBits)
zC
///простое Z-умножение
let ZMatrixMultiply (zA:float[]) (zB:float[]) (zC:float[]) =
let n=Array.length zA
let ZCode = 0
let MaskBits = int(Math.Log(float(n),float(2)))
let rec zMultiply (aZCode,aMaskBits) (bZCode,bMaskBits) (cZCode,cMaskBits)=
if aMaskBits >=2 then
let GetSubcells (zCode, maskBits) =
let subMaskBits = maskBits - 2
let zI =
[|
(zCode , subMaskBits)
(zCode ||| (1<<<subMaskBits), subMaskBits)
(zCode ||| (2<<<subMaskBits), subMaskBits)
(zCode ||| (3<<<subMaskBits), subMaskBits)
|]
zI
let c1sub = GetSubcells (aZCode,aMaskBits)
let c2sub = GetSubcells (bZCode,bMaskBits)
let cRsub = GetSubcells (cZCode,cMaskBits)
zMultiply c1sub.[0] c2sub.[0] cRsub.[0]
zMultiply c1sub.[1] c2sub.[2] cRsub.[0]
zMultiply c1sub.[0] c2sub.[1] cRsub.[1]
zMultiply c1sub.[1] c2sub.[3] cRsub.[1]
zMultiply c1sub.[2] c2sub.[0] cRsub.[2]
zMultiply c1sub.[3] c2sub.[2] cRsub.[2]
zMultiply c1sub.[2] c2sub.[1] cRsub.[3]
zMultiply c1sub.[3] c2sub.[3] cRsub.[3]
else
zC.[cZCode] <- zA.[aZCode] * zB.[bZCode] + zC.[cZCode]
zMultiply (ZCode,MaskBits)(ZCode,MaskBits)(ZCode,MaskBits)
zC
///Умножение с использование метода Штрассена
let StrassenMultiply (zA:float[]) (zB:float[]) (zC:float[]) =
let n=Array.length zA
let ZCode = 0
let MaskBits = int(Math.Log(float(n),float(2)))
let rec Strassen (aZCode,aMaskBits) (bZCode,bMaskBits) (cZCode,cMaskBits)=
let GetSubcells (zCode, maskBits) =
let subMaskBits = maskBits - 2
let zI =
[|
(zCode , subMaskBits)
(zCode ||| (1<<<subMaskBits), subMaskBits)
(zCode ||| (2<<<subMaskBits), subMaskBits)
(zCode ||| (3<<<subMaskBits), subMaskBits)
|]
zI
if aMaskBits >= 3 then
let c1sub = GetSubcells (aZCode,aMaskBits)
let c2sub = GetSubcells (bZCode,aMaskBits)
let cRsub = GetSubcells (cZCode,aMaskBits)
Strassen c1sub.[0] c2sub.[0] cRsub.[0]
Strassen c1sub.[1] c2sub.[2] cRsub.[0]
Strassen c1sub.[0] c2sub.[1] cRsub.[1]
Strassen c1sub.[1] c2sub.[3] cRsub.[1]
Strassen c1sub.[2] c2sub.[0] cRsub.[2]
Strassen c1sub.[3] c2sub.[2] cRsub.[2]
Strassen c1sub.[2] c2sub.[1] cRsub.[3]
Strassen c1sub.[3] c2sub.[3] cRsub.[3]
else
let a0code=aZCode
let a1code=aZCode ||| 1
let a2code=aZCode ||| 2
let a3code=aZCode ||| 3
let b0code=bZCode
let b1code=bZCode ||| 1
let b2code=bZCode ||| 2
let b3code=bZCode ||| 3
let c0code=cZCode
let c1code=cZCode ||| 1
let c2code=cZCode ||| 2
let c3code=cZCode ||| 3
let p1=(zA.[a0code]+zA.[a3code])*(zB.[b0code]+ zB.[b3code]);
let p2=(zA.[a2code]+zA.[a3code])* zB.[b0code];
let p3=zA.[a0code] *(zB.[b1code]- zB.[b3code]);
let p4=zA.[a3code] *(zB.[b2code]- zB.[b0code]);
let p5=(zA.[a0code]+zA.[a1code])* zB.[b3code];
let p6=(zA.[a2code]-zA.[a0code])*(zB.[b0code]+ zB.[b1code]);
let p7=(zA.[a1code]-zA.[a3code])*(zB.[b2code]+ zB.[b3code]);
zC.[c0code] <- p1+p4-p5+p7+zC.[c0code];
zC.[c1code] <- p3+p5+zC.[c1code];
zC.[c2code] <- p2+p4+zC.[c2code];
zC.[c3code] <- p1-p2+p3+p6+zC.[c3code];
Strassen (ZCode,MaskBits)(ZCode,MaskBits)(ZCode,MaskBits)
zC
///Параллельная версия умножения с использованием метода штрассена
let ParStrassenMultiply (zA:float[]) (zB:float[]) (zC:float[]) =
let n=Array.length zA
let ZCode = 0
let MaskBits = int(Math.Log(float(n),float(2)))
let rec Strassen (aZCode,aMaskBits) (bZCode,bMaskBits) (cZCode,cMaskBits)=
let GetSubcells (zCode, maskBits) =
let subMaskBits = maskBits - 2
let zI =
[|
(zCode , subMaskBits)
(zCode ||| (1<<<subMaskBits), subMaskBits)
(zCode ||| (2<<<subMaskBits), subMaskBits)
(zCode ||| (3<<<subMaskBits), subMaskBits)
|]
zI
if aMaskBits >= 3 then
let c1sub = GetSubcells (aZCode,aMaskBits)
let c2sub = GetSubcells (bZCode,aMaskBits)
let cRsub = GetSubcells (cZCode,aMaskBits)
Strassen c1sub.[0] c2sub.[0] cRsub.[0]
Strassen c1sub.[1] c2sub.[2] cRsub.[0]
Strassen c1sub.[0] c2sub.[1] cRsub.[1]
Strassen c1sub.[1] c2sub.[3] cRsub.[1]
Strassen c1sub.[2] c2sub.[0] cRsub.[2]
Strassen c1sub.[3] c2sub.[2] cRsub.[2]
Strassen c1sub.[2] c2sub.[1] cRsub.[3]
Strassen c1sub.[3] c2sub.[3] cRsub.[3]
else
let a0code=aZCode
let a1code=aZCode ||| 1
let a2code=aZCode ||| 2
let a3code=aZCode ||| 3
let b0code=bZCode
let b1code=bZCode ||| 1
let b2code=bZCode ||| 2
let b3code=bZCode ||| 3
let c0code=cZCode
let c1code=cZCode ||| 1
let c2code=cZCode ||| 2
let c3code=cZCode ||| 3
let p1=(zA.[a0code]+zA.[a3code])*(zB.[b0code]+ zB.[b3code]);
let p2=(zA.[a2code]+zA.[a3code])* zB.[b0code];
let p3=zA.[a0code] *(zB.[b1code]- zB.[b3code]);
let p4=zA.[a3code] *(zB.[b2code]- zB.[b0code]);
let p5=(zA.[a0code]+zA.[a1code])* zB.[b3code];
let p6=(zA.[a2code]-zA.[a0code])*(zB.[b0code]+ zB.[b1code]);
let p7=(zA.[a1code]-zA.[a3code])*(zB.[b2code]+ zB.[b3code]);
zC.[c0code] <- p1+p4-p5+p7+zC.[c0code];
zC.[c1code] <- p3+p5+zC.[c1code];
zC.[c2code] <- p2+p4+zC.[c2code];
zC.[c3code] <- p1-p2+p3+p6+zC.[c3code];
let rec ParStrassen (aZCode,aMaskBits) (bZCode,bMaskBits) (cZCode,cMaskBits)=
let GetSubcells (zCode, maskBits) =
let subMaskBits = maskBits - 2
let zI =
[|
(zCode , subMaskBits)
(zCode ||| (1<<<subMaskBits), subMaskBits)
(zCode ||| (2<<<subMaskBits), subMaskBits)
(zCode ||| (3<<<subMaskBits), subMaskBits)
|]
zI
if aMaskBits >= 3 then
let c1sub = GetSubcells (aZCode,aMaskBits)
let c2sub = GetSubcells (bZCode,aMaskBits)
let cRsub = GetSubcells (cZCode,aMaskBits)
let tasks = [
async
{
Strassen c1sub.[0] c2sub.[0] cRsub.[0]
Strassen c1sub.[1] c2sub.[2] cRsub.[0]
}
async
{
Strassen c1sub.[0] c2sub.[1] cRsub.[1]
Strassen c1sub.[1] c2sub.[3] cRsub.[1]
}
async
{
Strassen c1sub.[2] c2sub.[0] cRsub.[2]
Strassen c1sub.[3] c2sub.[2] cRsub.[2]
}
async
{
Strassen c1sub.[2] c2sub.[1] cRsub.[3]
Strassen c1sub.[3] c2sub.[3] cRsub.[3]
}
]
tasks |> Async.Parallel |> Async.RunSynchronously |>ignore
else
let a0code=aZCode
let a1code=aZCode ||| 1
let a2code=aZCode ||| 2
let a3code=aZCode ||| 3
let b0code=bZCode
let b1code=bZCode ||| 1
let b2code=bZCode ||| 2
let b3code=bZCode ||| 3
let c0code=cZCode
let c1code=cZCode ||| 1
let c2code=cZCode ||| 2
let c3code=cZCode ||| 3
let p1=(zA.[a0code]+zA.[a3code])*(zB.[b0code]+ zB.[b3code]);
let p2=(zA.[a2code]+zA.[a3code])* zB.[b0code];
let p3=zA.[a0code] *(zB.[b1code]- zB.[b3code]);
let p4=zA.[a3code] *(zB.[b2code]- zB.[b0code]);
let p5=(zA.[a0code]+zA.[a1code])* zB.[b3code];
let p6=(zA.[a2code]-zA.[a0code])*(zB.[b0code]+ zB.[b1code]);
let p7=(zA.[a1code]-zA.[a3code])*(zB.[b2code]+ zB.[b3code]);
zC.[c0code] <- p1+p4-p5+p7+zC.[c0code];
zC.[c1code] <- p3+p5+zC.[c1code];
zC.[c2code] <- p2+p4+zC.[c2code];
zC.[c3code] <- p1-p2+p3+p6+zC.[c3code];
ParStrassen (ZCode,MaskBits)(ZCode,MaskBits)(ZCode,MaskBits)
zC
let n = 512 //1024,2048,4096
let mutable rand= new Random()
let rand1 = ref rand
let Generate i j (rand: Random ref) =
rand.Value <- new Random(i+j)
let x = rand.Value.NextDouble()
x
let A = Array2D.init n n (fun i j -> Generate i j rand1)
let B = Array2D.init n n (fun i j -> Generate i j rand1)
let C = Array2D.create n n 0.0
printf "[Lib multiply of %dx%d matrix] " n n
let x =
let A1= Math.Matrix.of_array2 A
let B1= Math.Matrix.of_array2 B
Time (fun () -> MultiplyX A1 B1)|>ignore
System.GC.Collect()
let (Za,Zb,Zc) = CreateZMatrix A B C
let Zc1= Array.copy Zc
let Zc2= Array.copy Zc
let Zc3= Array.copy Zc
printf "[Ordinary multiply of %dx%d matrix] " n n
Time (fun () -> Multiply A B C) |> ignore
printf "[Fast multiply of %dx%d matrix] " n n
Time (fun () -> PFXMultiply A B C)|>ignore
printf "[Z multiply of %dx%d matrix] " n n
let x0 =Math.Vector.of_array (Time(fun () ->ZMatrixMultiply Za Zb Zc))
printf "[Parallel Z multiply of %dx%d matrix] " n n
let x1 =Math.Vector.of_array (Time(fun () ->PZMatrixMultiply Za Zb Zc1))
printf "[Strassen multiply of %dx%d matrix] " n n
let x2 =Math.Vector.of_array (Time(fun () ->StrassenMultiply Za Zb Zc2))
printf "[Parallel Strassen multiply of %dx%d matrix] " n n
let x3 =Math.Vector.of_array (Time(fun () ->ParStrassenMultiply Za Zb Zc3))
let error = (x0-x2)+(x1-x3)
printf "error = "
Console.Write(Array.sum (Math.Vector.to_array error))
do
let PrintWithHeading s =
let line = String.replicate(s |> String.length) "="
printfn "%s\n%s" s line
printfn "RFE 2010\nVladimir Shchur\n"
PrintWithHeading "Matrix Multiply"
MatrixMultiplyDemo ()
printfn "\n\nPress any key to finish"
ПРИЛОЖЕНИЕ 2
Программа по обработке графического изображения множества Мандельброта
Program.fs - файл запуска Windows-приложения
module Program =
open System
open System.Windows.Forms
open MandelbrotFormModule
#if COMPILED
[<STAThread>]
do Application.EnableVisualStyles()
do Application.SetCompatibleTextRenderingDefault(false)
do Application.Run(new MandelbrotForm())
#endif
MandelbrotForm.fs -в этом файле производятся обработка пользовательских событий и вывод изображения на экран
module MandelbrotFormModule
open System
open System.Windows.Forms
open System.Drawing
open System.Drawing.Imaging
open System.Diagnostics
open System.Runtime.InteropServices
open System.Threading
open System.Threading.Tasks
open System.Collections.Concurrent
open System.IO
open MandelbrotGeneratorModule
/// <summary>Describes the bounds of the fractal to render.</summary>
let mutable _mandelbrotWindow = MandelbrotPosition( 2.9, 2.27, -0.75, 0.006)
/// <summary>The last known size of the main window.</summary>
let mutable _lastWindowSize = Size.Empty
/// <summary>The last known position of the mouse.</summary>
let mutable _lastMousePosition = Point(0,0)
/// <summary>The most recent cancellation source to cancel the last task.</summary>
let mutable _lastCancellation = new CancellationTokenSource()
/// <summary>The last time the image was updated.</summary>
let mutable _lastUpdateTime = DateTime.MinValue
/// <summary>Whether the left mouse button is currently pressed on the picture box.</summary>
let mutable _leftMouseDown = false
/// <summary>
/// The format string to use for the main form's title; {0} should be set to the number of
/// pixels per second rendered.
/// </summary>
let _formTitle = "Interactive Fractal F# ({0}x) - PPMS: {1} - Time: {2}";
/// <summary>Whether to use parallel rendering.</summary>
let mutable _parallelRendering = false;
type MandelbrotForm() as this =
inherit Form(ClientSize = new System.Drawing.Size(521, 452),
AutoScaleDimensions = new System.Drawing.SizeF(8.0f, 16.0f),
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font,
Margin = new System.Windows.Forms.Padding(4, 4, 4, 4),
Name = "MainForm",
Text = "Mandelbrot in F#")
let pictureBox = new PictureBox(BackColor = System.Drawing.Color.Black,
Dock = System.Windows.Forms.DockStyle.Fill,
Location = Point(0, 0),
Margin = Padding(4, 4, 4, 4),
Name = "mandelbrotPb",
Size = Size(410, 369),
SizeMode = System.Windows.Forms.PictureBoxSizeMode.CenterImage,
TabIndex = 0,
TabStop = false)
let statusStrip = new StatusStrip( Location = Point(0, 427),
Name = "statusStrip",
Padding = Padding(1, 0, 19, 0),
Size = Size(521, 25),
TabIndex = 2,
Text = "statusStrip")
let toolStripStatusLabel = new ToolStripStatusLabel(Name = "toolStripStatusLabel1",
Size = Size(386, 20),
Text = "P: parallel mode, S: sequential mode, Double-click: zoom")
let countProc flag =
match flag with
| false -> 1
| true -> Environment.ProcessorCount
let UpdateImageAsync () =
// If there's currently an active task, cancel it! We don't care about it anymore.
if not (_lastCancellation.Equals(null))
then _lastCancellation.Cancel()
// Get the current size of the picture box
let renderSize = pictureBox.Size
// Keep track of the time this request was made. If multiple requests are executing,
// we want to only render the most recent one available rather than overwriting a more
// recent image with an older one.
let timeOfRequest = DateTime.UtcNow;
// Start a task to asynchronously render the fractal, and store the task
// so we can cancel it later as necessary
_lastCancellation <- new CancellationTokenSource()
let token = _lastCancellation.Token
// Start a task to asynchronously render the fractal
let task =
async{
// For diagnostic reasons, time how long the rendering takes
let sw = Stopwatch.StartNew()
// Render the fractal
let bmp = Create _mandelbrotWindow renderSize.Width renderSize.Height token _parallelRendering
if not (bmp.Equals(null))
then sw.Stop()
let ppms = (Convert.ToDouble(renderSize.Width * renderSize.Height)) / (double) sw.ElapsedMilliseconds;
// Update the fractal image asynchronously on the UI
// If this image is the most recent, store it into the picture box
// making sure to free the resources for the one currently in use.
// And update the form's title to reflect the rendering time.
if (timeOfRequest > _lastUpdateTime)
then //pictureBox.Image.Dispose()
pictureBox.Image <- bmp
//_lastUpdateTime <- timeOfRequest
this.Text <- String.Format(_formTitle,
countProc _parallelRendering,
ppms.ToString("F2"), sw.ElapsedMilliseconds.ToString())
let file = new FileStream("D:/Result/F#.txt", FileMode.Append)
let strw = new StreamWriter(file)
strw.WriteLine(this.Text)
strw.Flush()
strw.Close()
// If the image isn't the most recent, just get rid of it
else bmp.Dispose()
}
Async.StartAsTask(task,cancellationToken = token) |> ignore
// Add Handlers
do this.SuspendLayout()
do this.Controls.Add(pictureBox)
do statusStrip.Items.Add(toolStripStatusLabel)|>ignore
do this.Controls.Add(statusStrip)
do pictureBox.VisibleChanged.AddHandler(new EventHandler(fun s e -> this.pictureBox_VisibleChanged (s,e)))
do pictureBox.MouseDoubleClick.AddHandler(new MouseEventHandler(fun s e -> this.pictureBox_DoubleClick(s,e)))
do pictureBox.MouseDown.AddHandler(new MouseEventHandler(fun s e -> this.pictureBox_MouseDown(s,e)))
do pictureBox.MouseUp.AddHandler(new MouseEventHandler(fun s e -> this.pictureBox_MouseUp(s,e)))
do pictureBox.MouseMove.AddHandler(new MouseEventHandler(fun s e -> this.pictureBox_MouseMove(s,e)))
do pictureBox.Resize.AddHandler(new EventHandler(fun s e -> this.pictureBox_Resize(s,e)))
do this.ResumeLayout()
member this.pictureBox_VisibleChanged (s, e) =
if (pictureBox.Visible)
then _lastWindowSize <- this.Size
UpdateImageAsync |> ignore
member this.pictureBox_DoubleClick (s, e) =
// Center the image on the selected location
_mandelbrotWindow.CenterX <- _mandelbrotWindow.CenterX + (float (e.X - (pictureBox.Width / 2)) / (float pictureBox.Width)) * _mandelbrotWindow.Width
_mandelbrotWindow.CenterY <- _mandelbrotWindow.CenterY + (float (e.Y - (pictureBox.Height / 2)) / (float pictureBox.Height)) * _mandelbrotWindow.Height
// If the left mouse button was used, zoom in by a factor of 2; if the right mouse button, zoom
// out by a factor of 2
let factor =
if (e.Button = MouseButtons.Left)
then 0.5
else 2.0;
_mandelbrotWindow.Width <- _mandelbrotWindow.Width*factor
_mandelbrotWindow.Height <- _mandelbrotWindow.Height*factor
// Update the image
UpdateImageAsync()
member this.pictureBox_MouseDown (s, e) =
// Record that mouse button is being pressed
if (e.Button = MouseButtons.Left)
then _lastMousePosition <- e.Location
_leftMouseDown <- true
member this.pictureBox_MouseUp (s, e) =
// Record that the mouse button is being released
if (e.Button = MouseButtons.Left)
then _lastMousePosition <- e.Location
_leftMouseDown <- false
member this.pictureBox_MouseMove (s, e) =
// Determine how far the mouse has moved. If it moved at all...
let delta = new Point(e.X - _lastMousePosition.X, e.Y - _lastMousePosition.Y)
if not (delta = Point.Empty)
then // And if the left mouse button is down...
if (_leftMouseDown)
then
// Determine how much the mouse moved in fractal coordinates
let fractalMoveX = (float delta.X) * _mandelbrotWindow.Width / (float pictureBox.Width)
let fractalMoveY = (float delta.Y) * _mandelbrotWindow.Height / (float pictureBox.Height)
// Shift the fractal window accordingly
_mandelbrotWindow.CenterX <- _mandelbrotWindow.CenterX - fractalMoveX
_mandelbrotWindow.CenterY <- _mandelbrotWindow.CenterY - fractalMoveY
// And update the image
UpdateImageAsync()
// Record the new mouse position
_lastMousePosition <- e.Location
member this.pictureBox_Resize (s, e) =
// If the window has been resized
if not (this.Size = _lastWindowSize)
then
// Scale the mandelbrot image by the same factor so that its visual size doesn't change
if not (_lastWindowSize.Width = 0)
then
let xFactor = (float this.Size.Width) / (float _lastWindowSize.Width)
_mandelbrotWindow.Width <- _mandelbrotWindow.Width * xFactor
if not (_lastWindowSize.Height = 0)
then
let yFactor = (float this.Size.Height) / (float _lastWindowSize.Height)
_mandelbrotWindow.Height <- _mandelbrotWindow.Height * yFactor
// Record the new window size
_lastWindowSize <- this.Size
// Update the image
UpdateImageAsync()
override this.OnKeyDown e =
// Handle the key pressed
base.OnKeyDown e
match e.KeyCode with
Keys.R ->
_mandelbrotWindow <- new MandelbrotPosition( 2.9, 2.27, -0.75, 0.006)
use tempForm = new MandelbrotForm()
let xFactor = (float this.Size.Width) / (float tempForm.Width)
let yFactor = (float this.Size.Height) / (float tempForm.Height)
_mandelbrotWindow.Width <- _mandelbrotWindow.Width * xFactor
_mandelbrotWindow.Height <- _mandelbrotWindow.Height * yFactor
UpdateImageAsync()
|Keys.S ->
_parallelRendering <- false
UpdateImageAsync()
|Keys.P ->
_parallelRendering <- true
UpdateImageAsync()
|_ -> ()
MandelbrotGenerator.fs -в этом файле производятся расчеты цвета пикселей для построения множества Мандельброта
module MandelbrotGeneratorModule
#nowarn "9"
open System
open System.IO
open System.Windows.Forms
open System.Drawing
open System.Drawing.Imaging
open System.Diagnostics
open System.Runtime.InteropServices
open System.Threading
open System.Threading.Tasks
open System.Numerics
open Microsoft.FSharp.NativeInterop
open Microsoft.FSharp.Collections
open System.Collections.Concurrent
/// <summary>Represents the bounds and location of the mandelbrot fractal to be rendered.</summary>
type public MandelbrotPosition =
struct
val mutable public Width: float
val mutable public Height: float
val mutable public CenterX: float
val mutable public CenterY: float
new(width: float, height: float, centerX: float, centerY: float) =
{ Width = width ; Height = height; CenterX = centerX; CenterY = centerY }
end
/// <summary>Create the color palette to be used for all fractals.</summary>
/// <returns>A 256-color array that can be stored into an 8bpp Bitmap's ColorPalette.</returns>
let CreatePaletteColors : Color[] =
let paletteColors : Color[] = Array.init 256 (fun i -> Color.FromArgb(0, i * 5 % 256, i * 5 % 256))
paletteColors
/// <summary>The 256 color palette to use for all fractals.</summary>
let _paletteColors = CreatePaletteColors
/// <summary>Copy our precreated color palette into the target Bitmap.</summary>
Подобные документы
Приобретение теоретических и практических навыков программирования на языке Паскаль. Математическая формулировка задачи и выбор метода обработки информации. Разработка алгоритма и его описание. Описание программы. Форма представления исходных данных.
курсовая работа [224,3 K], добавлен 11.02.2016Создание информационной системы обработки матриц. Общая характеристика программного обеспечения, которое реализует выполнение заданных функций. Программа разработана с использованием среды визуального программирования Delphi 7 и языка Object Pascal.
курсовая работа [373,4 K], добавлен 14.01.2011Изучение организации диалоговой программы и закрепления основных элементов программирования на языке Паскаль и Си (Delphi, C++ Builder). Описание представления информации в программах на языках высокого уровня. Сравнительная характеристика Delphi и C++.
курсовая работа [3,1 M], добавлен 27.02.2015Задачи цифровой обработки изображений. Методы пороговой сегментации. Создание программы представления рисунка в виде матрицы и применения к нему пороговой обработки. Разработка интерфейса программы загрузки и фильтрации изображения с выбранным порогом.
курсовая работа [2,0 M], добавлен 12.11.2012Обзор существующих моделей параллельного программирования, основные средства отладки эффективности MPI-программ, общие проблемы всех средств трассировки. Создание экспериментальной системы отладки эффективности MPI-программ, этапы работы анализатора.
дипломная работа [767,2 K], добавлен 14.10.2010Анализ затрат и прибыли. Создание программного проекта для решения задачи о прибыли и убытках на языке программирования C#. Использование функций и переменных, компиляция программы. Алгоритмы и структуры данных. Тестирование программного обеспечения.
курсовая работа [1,2 M], добавлен 03.01.2015Понятия структурного программирования и алгоритма решения задачи. Краткая история развития языков программирования от машинных до языков ассемблера и языков высокого уровня. Процедурное программирование на C#. Методы и программы для моделирования.
учебное пособие [1,7 M], добавлен 26.10.2010Особенности способов описания языков программирования. Язык программирования как способ записи программ на ЭВМ в понятной для компьютера форме. Характеристика языка Паскаль, анализ стандартных его функций. Анализ примеров записи арифметических выражений.
курсовая работа [292,0 K], добавлен 18.03.2013Характеристика языков программирования: краткая история, хронология. Основные виды языков программирования: ассемблер; бейсик. Создание и использование формул в Excel. Применение операторов в формулах. Использование функций в Excel. Сайт дома отдыха.
отчет по практике [139,1 K], добавлен 03.06.2011Обзор основных используемых языков программирования (С++, Java, Pascal). Анализ существующих методов шифрования паролей. Основные понятия объектно-ориентированного программирования. Реализация приложения для генерирования паролей на языке Object Pascal.
курсовая работа [822,4 K], добавлен 07.07.2012