МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РОССИЙСКОЙ ФЕДЕРАЦИИ Федеральное государственное бюджетное образовательное учреждение

advertisement
МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РОССИЙСКОЙ
ФЕДЕРАЦИИ
Федеральное государственное бюджетное образовательное учреждение
высшего
профессионального образования
«КУБАНСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ»
(ФГБОУ ВПО «КубГУ»)
Кафедра вычислительных технологий
«МНОГОПОТОЧНОЕ ПРОГРАММИРОВАНИЕ С
ИСПОЛЬЗОВАНИЕМ СТАНДАРТА С++ 11 НА ПРИМЕРЕ
ПРОГРАММЫ ОЦЕНКИ СТРУКТУРЫ AD-HOC СЕТИ»
А.В. Березанцев
Краснодар 2014
СОДЕРЖАНИЕ
ВВЕДЕНИЕ ........................................................................................................... 3
1 Что такое параллелизм и зачем он нужен. .................................................. 5
1.1 Немного о распараллеливании приложений.................................................. 5
1.2 Средства управления потоками. ..................................................... 8
2 Разработка многопоточного ПО для исследования характеристик сети .... 9
2.1 Анализ задачи. ........................ 9
2.2 Проектирование. ........................................................................ 9
2.3 Стандарт С++ 11. ....................................................................... 10
2.4 Модель. ....................................................................................... 11
ЗАКЛЮЧЕНИЕ .................................................................................. 21
СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ .................. 22
ПРИЛОЖЕНИЕ А ................................................................. 23
Класс node. ............................................................................. 23
ПРИЛОЖЕНИЕ Б ........................................................................ 24
Класс grafe. ....................................................................... 24
ПРИЛОЖЕНИЕ В ................................................................... 25
Класс T_Averaged_Components. ....................................... 25
ПРИЛОЖЕНИЕ Г ............................................................. 26
Класс T_Bridges ..................................................................... 26
ПРИЛОЖЕНИЕ Д ............................................................. 27
Реализация методов класса grafe. ....................... 27
ПРИЛОЖЕНИЕ Е ........................................................... 34
Подпрограмма многопоточного запуска .......................... 34
3
ВВЕДЕНИЕ
Телекоммуникационная отрасль – это та отрасль, без технологий
которой сегодня не может полноценно функционировать практически ни
одна организация, служба или предприятие. Таким образом, благосостояние,
эффективная работа государства – напрямую зависят от состояния его
информационной развитости. От того, какие услуги, с каким уровнем
качества и доступности могут обеспечить существующие сегодня
телекоммуникационные технологии.
Сегодня мы можем наблюдать стремительный рост количества
мобильных устройств у населения и, соответственно, потенциальных
пользователей, желающих иметь быстрый беспроводной доступ в Интернет,
причем в любой точке и без разрыва сеансов связи.
В прошлой работе были получены результаты:
Разработана программа, позволяющая исследовать имеющуюся
беспроводную ad hoc сеть, представленную неориентированным графом, на
предмет повреждений структуры сети и возможности восстановить
поврежденную структуру, а так же снизить риск нарушения структуры путем
выявления ненадежных участков сети – мостов.
Проведены испытания, показавшие, что при увеличении числа узлов в
сети, расположенных на фиксированной площади, растет и количество
ненадежных каналов связи, при разрыве которых сеть потеряет целостность,
распавшись на несколько несвязанных сегментов. При этом, количество
таких каналов возрастает в 2 раза при увеличении концентрации сетевых
узлов в 3 раза. По результатам испытаний установлено, что сбор информации
для восстановления структуры сети при числе узлов, большем чем 2000,
будет иметь
,
как верхнюю оценку времени и, если N ≤ 2000, то
временная оценка не больше, чем
,
.
Программа работала, с точки зрения ресурсозатрат, далеко не
оптимально. Главный недостаток разработанного продукта – длительное
4
время выполнения, достигавшее 5 часов (на некоторых конфигурациях – до 8
часов).
5
1 Что такое параллелизм и зачем он нужен.
1.1 Немного о распараллеливании приложений.
Первый способ распараллелить приложение – запустить несколько
однопоточных процессов, исполняемых одновременно. Процессы могут
обмениваться данными с помощью стандартных средств межпроцессной
коммуникации (файлы, сигналы, сокеты…). Благодаря использованию
стандартных системных механизмов коммуникации процессов код
получается более безопасным, чем при использовании потоков. Также,
процессы можно распределять по локальной сети между многими
компьютерами, значительно повышая вычислительную эффективность.
Однако, за безопасность приходится платить повышенными накладными
расходами на запуск процесса. В случаях, когда эти расходы слишком
велики, а процессы не планируется исполнять по сети, стоит рассмотреть
распараллеливание по потокам.
Второй способ. Несколько потоков. Потоки запускаются в рамках
общего процесса. Обмен данными происходит через общую разделяемую
память процесса. Этот способ характеризуется меньшими накладными
расходами на содержание нескольких потоков и коммуникацию со стороны
ОС. Однако, этот подход имеет свои недостатки. В частности, общая
разделяемая память потоков требует тщательного понимания возможных
ситуаций обращения потоков к ней, ибо непродуманное взаимодействие
потоков через общую память может обернуться весьма неприятными
последствиями – от задержек в работе программы до «мертвых блокировок».
Мертвая блокировка – ситуация в многопоточной среде, когда
несколько потоков находятся в состоянии бесконечного ожидания ресурсов,
занятых самими этими потоками.
Применение параллелизма в приложениях.
Разделение обязанностей, улучшение структуры кода программы.
Типичный случай – когда программа должна работать с несколькими
6
разнородными потоками данных одновременно (файловая система,
звуковой/видео канал, интерфейс пользователя). Если обработка всего этого
выполняется в одном потоке, то придется организовывать слежение за
действиями пользователя и переключение между соответствующими
задачами, что существенно усложнит понимание и разработку продукта.
Применение же нескольких потоков (на число ядер процессоров не
ориентируемся, ибо важна ясность структуры, а не скорость) позволит
разнести структуру приложения на несколько относительно независимых
частей, каждая из которых обрабатывает свою часть данных отдельно, при
необходимости связываясь с другими потоками (при чтении файла
произошла ошибка – идем к потоку интерфейса и показываем сообщение).
Обработчики взаимодействий потоков упростят отладку, т.к. не нужно искать
по всему коду места переключения задач.
Повышение производительности.
В 1965 году один из основателей Intel Гордон Мур обнаружил
закономерность: появление новых моделей микросхем наблюдалось спустя
примерно год после предшественников, при этом количество транзисторов в
них возрастало каждый раз приблизительно вдвое. Мур пришел к выводу,
что при сохранении этой тенденции мощность вычислительных устройств за
относительно короткий промежуток времени может вырасти
экспоненциально. Это наблюдение получило название — закон Мура.
В 1975 году Гордон Мур внёс в свой закон коррективы, согласно
которым удвоение числа транзисторов будет происходить каждые два года.
Однако, из-за физических ограничений, накладываемых на
вычислительные схемы (в т.ч. отвод выделяемого тепла и потери тока),
чтобы получить возможность задействовать на практике ту дополнительную
вычислительную мощность, которую предсказывает закон Мура, стало
необходимо задействовать параллельные вычисления. На протяжении
многих лет, производители процессоров постоянно увеличивали тактовую
частоту и параллелизм на уровне инструкций, так что на новых процессорах
7
старые однопоточные приложения исполнялись быстрее без каких-либо
изменений в программном коде. Сейчас по разным причинам производители
процессоров предпочитают многоядерные архитектуры, и для получения
всей выгоды от возросшей производительности ЦП программы должны
переписываться в соответствующей манере. Поэтому для повышения
эффективности от многоядерных, процессорных систем программы
необходимо проектировать как набор параллельных задач.
Используются два способа такого распараллеливания:
а. Распараллеливание по задачам (задача разбивается на части,
выполняющиеся параллельно)
б. Распараллеливание по данным (каждый поток выполняет
обработку своей части общих данных).
Рисунок 1 – Распараллеливание по задачам.
Рисунок 2 – Распараллеливание по данным.
8
Параллелизм не стоит применять, если выигрыш в производительности
параллельной версии относительно однопоточной перекрывается
накладными расходами, либо же неоправданным усложнением кода
программы, делающим ее дальнейшую разработку и сопровождение
затруднительным.
1.2 Средства управления потоками.
В случае, когда потоки имеют некоторые общие данные, которые они
могут изменять независимо друг от друга, могут произойти весьма
неприятные события – если объект изменяется одним потоком, и тут же
другой считывает из него же данные – хорошего мало, вплоть до аварийного
завершения работы приложения. Во избежание таких ситуаций, доступ к
общим данным обычно закрывается мьютексом, который позволяет
блокировать доступ к объекту, пока его изменение не завершено.
9
2 Разработка многопоточного ПО для исследования характеристик сети
2.1 Анализ задачи.
Необходимо разработать программный продукт, позволяющий:
· Принимать информацию о сети, сгенерированную определенным
образом.
· Строить на основе принятых данных о сети ее графовое
представление, необходимое для проведения дальнейших
исследований.
Провести исследование сети на предмет наличия в ней изолированных
групп узлов и мостов.
Проанализировать возможность восстановления полной структуры
сети (в случае, если изолированные группы узлов присутствуют).
Организовать параллельное исполнение программы, используя
возможности стандарта С++ 11.
Оценить полученный прирост производительности.
2.2 Проектирование.
В качестве языка реализации программы выбран язык C++.
С++ – язык общего назначения. За исключением, может быть,
некоторых деталей, он целиком содержит в себе язык С. Язык С++
расширяется введением средств, предназначенных для гибкого создания
необходимых типов. Программист получает возможность доопределять
свою задачу, определив новые типы, которые наиболее точно соответствуют
его задаче. Такой подход к построению программных продуктов принято
называть абстракцией данных. Информация о типах содержится в некоторых
объектах типов, определенных пользователем. Если этот подход применяется
10
правильно, то код программы становится короче и понятнее, а дальнейшее
сопровождение программного продукта упрощается.
Ключевым понятием С++ является класс. Класс - это определяемый
пользователем тип.
Классы обеспечивают удобную работу с данными пользователя:
создание интерфейсов (дающих пользователю необходимые методы доступа
и работы с данными – и не более), инициализацию определенными
начальными значениями, возможность перегрузки операций, механизмы
контроля распределения памяти.
Язык и стандартные библиотеки С++ разрабатывались с расчетом на
переносимость. Имеющиеся реализации языка С++ будут работать в
большинстве систем, поддерживающих С. В программах на С++ можно
использовать библиотеки С.
С был выбран как основа для С++ по нескольким причинам. Среди них:
универсальность, краткость; соответствие большинству задач,
заключающихся в написании системного программного обеспечения;
переносимость программных продуктов (4).
2.3 Стандарт С++ 11.
Введение стандарта С++ 11 расширяет возможности программиста в
многопоточном программировании. Работать с потоками можно как
вручную, используя std::thread (управляя запуском, числом потоков,
параметрами ожидания), так и с помощью вызова функции через
std::async. Функция, вызванная вторым способом, может быть запущена
в отдельном потоке (при необходимости).
Число одновременно работающих потоков программы задается
пользователем, учитываю проверку на корректность – не более, чем число
ядер в системе. Иначе произойдет существенный спад производительности
вследствие повышения накладных расходов на избыточное контекстное
переключение ядер процессора.
11
Стандарт С++ 11 предоставляет 4 вида мьютексов:
(необходимо подключение #include <mutex> )
· mutex — повторный захват одним и тем же потоком не
контролируется
· recursive_mutex — повторные захваты тем же потоком допустимы,
каждый такой захват учитывается счетчиком
· timed_mutex — нет контроля повторного захвата тем же потоком,
поддерживается захват мьютекса с тайм-аутом;
· recursive_timed_mutex — повторные захваты тем же потоком
допустимы, ведётся счётчик таких захватов, поддерживается захват
мьютекса с тайм-аутом.
Сбор результирующих данных программой производится в глобальную
структуру MyRes, представляющую собой вектор результирующих строк.
Запись в структуру не вызывает взаимоблокировок потоков вследствие того,
что запускаемые одновременно потоки гарантированно не завершатся
одновременно (если не произойдет ошибки, в этом случае поток завершится
без обращения к структуре) – соответственно, каждый поток к моменту
завершения получает доступ к незаблокированной структуре MyRes без
необходимости согласовывать свой доступ с другими потоками и дожидаться
ее разблокировки. Таким образом, отпадает необходимость использовать
механизм мьютексов-семафоров для синхронизации потоков, и, тем более –
писать собственный мьютекс.
2.4 Модель.
Пусть есть граф G=(V,E).
Информация о сети генерируется как набор вершин ∈
объектов
класса node (приложение А), имеющих координаты расположения на
плоскости xu,yu и радиус приема/передачи радиосигнала rangeu.
12
По этому набору программой вычисляется множество E ребер,
соединяющих пары вершин графа.
Ребро между u и v определяется, как возможность узлов обмениваться
информацией без участия дополнительных передатчиков. Для этого оба узла
должны находиться в зоне радиовидимости друг друга, т.е. расстояние
(
−
)+ (
− ) должно быть меньше, чем rangeu и rangev.
Сформулируем условие окончательно:
Ребро e(u,v) добавляется в граф G тогда и только тогда, когда
(
−
)+ (
− ) < min
, ∈(-
,-)
Узлы сети заданы вектором объектов класса node (приложение А).
Проведя анализ расстояний, получим список смежности графа.
Таким образом, получаем граф G=(V,E), являющийся моделью
структуры данной сети. В программе представлен объектом класса grafe
(приложение Б)
Недостаток такой модели заключается в том, что при высокой степени
связности графа (на практике это может выражаться в большом скоплении
узлов сети в пределах прямой видимости. Схема связей в этом случае
стремится к «каждый к каждому») алгоритм поиска мостов, основанный на
рекурсивном обходе, вызовет переполнение стека вызовов. Однако, в
современных условиях вероятность наступления такой ситуации очень мала
(т.к. потребуется собрать свыше 30000 устройств внутри круга радиуса r ≤
100 м., препятствий обмену радиосигналом между устройствами быть не
должно).
Алгоритмы.
Для поиска связных компонент используется итеративная форма
обхода графа в глубину.
Описание алгоритма итеративного обхода в глубину:
Пусть вершины графа перенумерованы i=1…n, n – общее число
вершин.
13
Поиск компонент связности.
Для нахождения всех групп изолированных узлов сети,
представленных компонентами связности в графовой модели, необходимо
обойти граф в глубину. Каждый обход из непосещенной вершины графа
будет возвращать множество вершин, находящихся в одной компоненте
связности со стартовой вершиной. Если таковая компонента одна, то граф
является связным, изолированных узлов в сети нет. Иначе в векторе
Components после завершения просмотра графа получим номера компонент
связности и список вершин, входящих в каждую компоненту.
Поскольку обходятся все ребра графа, временная оценка алгоритма
О(|V|+|E|).
Компоненты связности представлены в классе grafe полем vector<vector
<int>> Components.
Сведения о восстановлении структуры хранятся в виде расстояний
между парами компонент UCi , UCj; , = 1$$$,$#$ , k – число компонент.
На рисунке 3. изображен граф G сети с 6 узлами. Узлы 4 и 5 находятся
в одной компоненте связности, остальные узлы изолированы (находятся в
компонентах связности, состоящих из 1 узла).
14
Рисунок 3 – Пример графа ad hoc сети.
Поскольку для установки связи со всеми узлами компоненты UCi
достаточно установить связь хотя бы с одним ее узлом, то представим группу
узлов, входящих в компоненту UCi, как один узел ACi, имеющий
характеристики, средние по всем узлам компоненты UCi:
%&' =(
)
*+
,.' (1)
%&' =()
*+
,.' (2)
-%&' =(-)
*+
,.' (3)
Где ni – число вершин в i-й компоненте.
Для показанного на рис. 6. Графа после преобразования получим вид,
изображенный на рисунке 7 , некоторые компоненты перенумерованы для
удобства.
15
Рисунок 4 – Граф ad hoc сети после преобразования компоненты
связности.
Эти данные хранятся в векторе объектов класса
T_Averaged_Components (приложение В).
Поиск мостов.
Из определения моста, приведенного выше, следует, что ребра (u,v),
являющиеся мостами в данной сетевой модели, имеют в ней особое значение.
Соответственно, таким участкам следует уделить внимание в виде
повышения их надежности (усиление аппаратуры конкретных узлов,
соединенных мостом), либо добавления дополнительных мостов,
связывающих сегменты, содержащие u и v для снижения вероятности
разрыва структуры сети в случае выхода из строя моста (u, v).
Для того чтобы найти мосты в графе, необходимо для каждого ребра:
А) Удалить его из графа.
Б) Проверить, не увеличилось ли число компонент связности
16
В) Если увеличилось – добавляем это ребро в вектор, хранящий
информацию о мостах.
Информация о найденных мостах хранится в grafe::Bridges – векторе
объектов класса T_Bridges (приложение Г).
Координаты и область радиовидимости узла (x ,y, range) задаются
псевдослучайными числами и ограничены диапазонами:
∈ 10,1003, ∈
10,1003,- ∈ 10,1003 . Псевдослучайное задание величины range
имитирует влияние препятствий на прохождение радиосигнала между
узлами.
Для выяснения зависимости появления мостов в сети от плотности
расположения узлов, расположенных на фиксированной площади, были
проведены серии из 50 тестов на графовых моделях сетей случайного
построения из 500, 1500, 4500 и 13500 узлов.
Обозначим среднее число мостов B(N), среднее время поиска мостов
T(N), время, затраченное на поиск одного моста T1(N). N – число вершин в
тестируемом графе.
Результаты тестирования представлены в таблице 2.
Таблица 2.
Результаты оценки величин B(N), T(N), T1(N) для проведенных серий
испытаний.
N = 500 N = 1500 N = 4500 N = 13500
B(N) 6 13 26 48
T(N), мс. 0,96 4,3 38 339
T1(N), мс. 0,16 0,33 1,46 7,06
Более наглядно итоги показаны на рисунках 5 и 6.
17
Рисунок 5 – Итоговый график, показывающий изменение среднего
количества мостов при увеличении плотности размещения узлов в сети.
Рисунок 6 – Итоговый график, показывающий изменение времени,
затраченного на поиск одного моста при увеличении плотности размещения
узлов в сети.
Видно, что с ростом количества узлов в сети, при фиксированной
площади для размещения, растет среднее число появляющихся мостов и
время, необходимое на отыскание одного такого моста. Если в модели с 500
0
10
20
30
40
50
60
0 5000 10000 15000
Среднее число мостов в соответствующей серии
испытаний
Среднее число
мостов
Верхняя оценка
среднего числа
мостов
0
1
2
3
4
5
6
7
8
0 2000 4000 6000 8000 10000 12000 14000 16000
18
узлами оно составляет всего лишь 0,16 мс., то уже в тестах с 4500 узлами
поднимается до 1,46 мс. на 1 мост. Однако, сильнее всего время отыскания
моста выросло в последнем случае. Для троекратного увеличения N (по
сравнению с случаем 4500 узлов) наблюдаю увеличение времени отыскания
каждого моста в 4,8 раза (7,06 мс. на 1 мост).
Также, при троекратном увеличении концентрации узлов сети, среднее
число мостов повышается только в 2 раза.
Для того, чтобы говорить о корректности данных, собранных
многопоточной программой, оценим отличия в асимптотике вероятности
появления мостов в случае однопоточной (О) и многопоточной (М)
программ:
Рисунок 7 – сравнение данных о вероятности появления мостов.
Как видно, отличия незначительны. Поэтому можно говорить о
корректности собранных данных.
Тестирование среднего числа мостов дало следующие результаты:
0
0,001
0,002
0,003
0,004
0,005
0,006
0,007
0,008
0,009
0 2000 4000 6000 8000 10000 12000
(М)
(О)
Рисунок
Результаты, полученные
следующей рекурсивной
Пусть a= F(n
Тогда остальные результаты
Для получения верхней
F
n/2 .
В результате применения
получен следующий результат
Рисунок
многопоточной
0
20
40
60
80
100
120
140
0 2000 4000
0
2
4
6
8
10
12
Однопоточная
19
8 – оценка среднего количества мостов
Результаты программой, хорошо
функцией:
/3
можно оценить как a7
оценки достаточно будет рассчитать
параллельной архитектуры
времени работы , мин.:
9 – Сравнение временных затрат однопоточной
версий программы.
6000 8000 10000 12000
Среднее
оценка
Многопоточная
Сравнение временных затрат
Однопоточная
Многопоточная
мостов.
приближаются
! a'89 a;
a!
программы был
и
число мостов
20
Таблица 2.
Сравнение производительности многопоточной и однопоточной
программ.
Однопоточная, мин. Многопоточная, 2 потока,
мин.
Прирост, %.
11,12 5,8 47,84
Листинг подпрограмм, использованных в экспериментах, приведен в
приложении Д.
21
ЗАКЛЮЧЕНИЕ
Основные результаты и выводы работы:
Разработана программа, позволяющая исследовать имеющуюся
беспроводную ad hoc сеть, представленную неориентированным графом, на
предмет повреждений структуры сети и возможности восстановить
поврежденную структуру, а так же снизить риск нарушения структуры путем
выявления ненадежных участков сети – мостов.
Проведены испытания, показавшие, что при увеличении числа узлов в
сети, расположенных на фиксированной площади, растет и количество
ненадежных каналов связи, при разрыве которых сеть потеряет целостность,
распавшись на несколько несвязанных сегментов. При этом, количество
таких каналов возрастает в 2 раза при увеличении концентрации сетевых
узлов в 3 раза.
Рассмотрен стандарт многопоточного программирования С++ 11 на
примере конкретной программы. Оценка прироста производительности
составила ~48%.
22
СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ
1. Емеличев В.А, Мельников О.И., Сарванов В.И., Тышкевич Р.И. Лекции по
теории графов. // М: Наука, 1990. с. 36.
2. Басакер Р, Саати Т. Перевод с английского. Конечные графы и сети. // М:
Наука. 1973. с. 25.
3. Харари Ф. Теория графов. Перевод с английского. // М: Едиториал УРСС.
2003. с. 27, 42.
4. Страуструп Б. Язык программирования С++. // Второе дополненное
издание. 1997. с. 14 – 17.
5. Домнин Л.Н. Элементы теории графов.// Пенза: изд-во ПГУ, 2007.
6. Зыков А.А. Основы теории графов.// М.: Вузовская книга. 2004.
7. Уильямс Э. Параллельное программирование на С++ в действии.//М.:ДМК
Пресс, 2012.
23
ПРИЛОЖЕНИЕ А
Класс node.
/*Вершина. Имеет координаты x,y и радиус действия передатчика range*/
class node
{
public:
float x,y;
float range;
node::node(float x, float y, float range)
{
this->x = x;
this->y = y;
this->range = range;
};
};
24
ПРИЛОЖЕНИЕ Б
Класс grafe.
class grafe
{
public:
vector<vector <int> > edgelist;// список смежности.
vector<int> Marker;//массив посещенных врешин
int size; //число вершин
vector<vector <int>> Components; // Хранит компоненты связности
графа
vector<int> comp; // временный вектор для сборки Components
vector<node*> GNodes; //Вектор, хранит указатели на все
имеющиеся узлы сети
vector<node*> AverageComponents;//Указатели на узлы
среднестатистических координат в компонентах
vector<T_Averaged_Components*> UCComponents; //Информация о
возможностях связать компоненты
vector<T_Bridges*> Bridges; //Ребра-мосты
vector<pair<int,int>> IRepair;/*пары вершин для прокладки ребер
между компонентами*/
int diam; //диаметр графа
grafe (int n);
~grafe(void){};
int DFS1(int); // Поиск в глубину в чистом виде.
void grafe::bridgeUtil(int u, bool visited[], int disc[], int
low[], int parent[]); //вспомогательная подпрограмма поиска мостов
void bridge(); //основная подпрограмма поиска мостов
int GDDFS_Iter(int v1);//итеративный DFS, поиск диаметра графа.
int GDiamIter(); //Поиск диаметра графа, основная подпрограмма
int GUComponents_Iter();//проверка связности графа на итеративном
обходе.
//----------------------------------------------void EListPrint();//Печать списка смежности графа
void UCComponentsPrint();//Печать компонент связности
int BridgesPrint();//Печать мостов
void PMayRepair(); //Информация о возможности восстановления
float Range_Between(node *, node *); // Дистанция между узлами
сети.
};
25
ПРИЛОЖЕНИЕ В
Класс T_Averaged_Components.
class T_Averaged_Components
{
public:
pair<int, node*> Node1;//Номер первого узла/компоненты,
pair<int, node*> Node2; //Номер второго узла/компоненты
float mydistance;
T_Averaged_Components(node * P1, node* P2, float dist,int num1,
int num2)
{
this->Node1.first=num1;
this->Node1.second = P1;
this->Node2.first=num2;
this->Node2.second=P2;
this->mydistance = dist;
}
};
26
ПРИЛОЖЕНИЕ Г
Класс T_Bridges
class T_Bridges
{
public:
int v1,v2;
T_Bridges(int x,int y)
{
this->v1 = x;
this->v2 = y;
}
};
27
ПРИЛОЖЕНИЕ Д
Реализация методов класса grafe.
grafe::grafe(int n)
{
size = n;
diam=0;
for (int i = 0; i<n; i++)
{
Marker.push_back(0);
edgelist.push_back(vector <int> ());
Components.push_back(vector <int> ());
}
}
//---------------Печать списка смежности графа-----------------------void grafe::EListPrint()
{
cout << "Ребра: " << endl;
int v,u;
for(int i=0; i<(int)edgelist.size(); i++)
{
for(int j=0; j<(int)edgelist[i].size(); j++)
{
u=i+1;
v=edgelist[i][j]+1;
cout << "(" << u << " <-> " << v <<"); \n";
}
cout << endl;
}
}
int grafe::DFS1(int v)
{
int tmp=0; //tmp, is result
int mtmp=0; //max for tmp
comp.push_back(v+1);
Marker[v] = 1; //вершина посещена
for (vector<int>::iterator i = this->edgelist[v].begin(); i !=
this->edgelist[v].end(); ++i)
{
if ( Marker[*i] != 1 )
{
DFS1(*i);
28
}
}
return 1;
}
//----------------------------------------------------------void grafe::bridgeUtil(int u, bool visited[], int disc[], int low[],
int parent[])
{
static int time = 0;
// Отметка о посещении вершины
visited[u] = true;
// Инициализуем минимальное значение и счетчик посещения
disc[u] = low[u] = ++time;
// Пройдем по всем вершинам, смежным с данной
vector<int>::iterator i;
for (i = edgelist[u].begin(); i != edgelist[u].end(); ++i)
{
int v = *i; // выбранная v смежна с u
// Если v не посещена, повторим поиск для нее
if (!visited[v])
{
parent[v] = u;
bridgeUtil(v, visited, disc, low, parent);
/*Убедимся, что поддерево с корнем v имеет связь с одним
из предков u*/
low[u] = min(low[u], low[v]);
/*Если вершины в разных компонентах связности, то
ребро - мост*/
if (low[v] > disc[u])
{
// cout << u+1 <<" <-> " << v+1 << endl;
this->Bridges.push_back(new T_Bridges(u+1, v+1));
}
}
// Обновим значения.
else if (v != parent[u])
low[u] = min(low[u], disc[v]);
}
}
void grafe::bridge()
{
29
// Отметим все вершины как непосещенные
bool *visited = new bool[this->size];
int *disc = new int[this->size];
int *low = new int[this->size];
int *parent = new int[this->size];
// Проинициализуем массив предков и массив посещений
for (int i = 0; i < this->size; i++)
{
parent[i] = NIL;// NIL = -1, т.к. изначально предков нет.
visited[i] = false;
}
// Вызов рекурсивной функции поиска мостов. Корень поддерева i-я
вершина.
for (int i = 0; i < this->size; i++)
if (!visited[i] )
bridgeUtil(i, visited, disc, low, parent);
}
int grafe::BridgesPrint()
{
if (this->Bridges.size()>0)
{
cout << "Найдено "<< this->Bridges.size() << " мостов,
формат вывода \nv1 <-> v2 \n";
for (int i = 0; i < this->Bridges.size(); i++)
{
cout << " " << this->Bridges[i]->v1 << " - " << this>Bridges[i]->v2 << endl;
}
}
else
cout << "Мостов не обнаружено. \n";
return 0;
}
//------------------Дистанция между узлами--------------------float grafe::Range_Between(node *P1, node *P2)
{
return (float)sqrt(pow(P1->x - P2->x, 2) + pow(P1->y - P2>y, 2));
};
//-----------------Печать компонент связности------------------void grafe::UCComponentsPrint()
{
if (this->Components.size()-this->size > 1)
{
int kol=0;
cout << "\nКомпоненты связности\n";
30
for (int i = 0; i < (int)Components.size(); i++)
{
if (Components[i].size() > 0)
{
kol ++;
cout << kol<<"-я: \n";
for (int j = 0; j < (int)Components[i].size(); j++)
{
cout << Components[i][j]<<"
("<<GNodes[Components[i][j]-1]->x<<" , "<<GNodes[Components[i][j]-1]>y<<"); R="<<GNodes[Components[i][j]-1]->range<< endl;
}
cout << endl;
}
}
}
}
//Информация о возможности восстановления
void grafe::PMayRepair()
{
if (this->Components.size()-this->size > 1)
{
int numcomponent=0;
int kol;
for (int i = 0; i < (int)Components.size(); i++)
{
if((Components[i].size()>0) )
{
numcomponent++;
kol=0;
float midx=0, midy=0,midrange=0;
for (int j = 0; j < (int)Components[i].size(); j++)
{
kol++;
midx+=GNodes[Components[i][j]]->x;
midy+=GNodes[Components[i][j]]->y;
midrange+=GNodes[Components[i][j]]->range;
}
midx=midx/kol;
midy=midy/kol;
midrange/=kol;
AverageComponents.push_back(new
node(midx,midy,midrange));
31
}
}
cout << "Усредненные данные компонент: \n";
for (int i = 0; i < (int)AverageComponents.size(); i++)
{
cout << i+1<<" ("<<AverageComponents[i]->x<<" ,
"<<AverageComponents[i]->y<<"); R="<<AverageComponents[i]->range<<
endl;
}
double min=99999.0;
for (int i = 0; i < (int)AverageComponents.size()-1; i++)
{
for (int j = i+1; j < (int)AverageComponents.size(); j++)
{
UCComponents.push_back(new
T_Averaged_Components(AverageComponents[i],
AverageComponents[j],Range_Between(AverageComponents[i],
AverageComponents[j]),i+1,j+1));
}
}
/*cout << "UCComponents << " << UCComponents.size() << endl;
for (int i = 0; i < UCComponents.size(); i++)
{
cout << UCComponents[i]->mydistance << " ";
}
cout << endl;*/
T_Averaged_Components * minimal=new
T_Averaged_Components(nullptr,nullptr,(float)99999.9,0,0);
for (int i = 0; i < (int)UCComponents.size(); i++)
{
if (UCComponents[i]->mydistance < minimal->mydistance)
{
minimal=UCComponents[i];
}
}
}
}
// обход итеративный, для поиска диаметра графа. Вспомогательная
//подпрограмма.
int grafe::GDDFS_Iter(int v1)
{
stack<int> s;
s.push(v1);
32
comp.push_back(v1);
int v;
int maxdiam=0;
int diamtmp=0;
while (!s.empty())
{
if (diamtmp > maxdiam)
maxdiam = diamtmp;
v = s.top();
s.pop();
//diamtmp=1;
for (int i = 0; i < (int)edgelist[v].size(); i++)
{
if (!Marker[edgelist[v][i]] )
{
s.push(edgelist[v][i]);
Marker[edgelist[v][i]] = 1;
diamtmp++;
}
}
};
return maxdiam-1;
}
//Основная подпрограмма поиска диаметра графа.
int grafe::GDiamIter()
{
int maxd=0;
int tmp=0;
for (int i = 0; i < (int)Marker.size(); i++)
{
Marker[i]=0;
}
for (int i = 0; i < this->size; i++)
{
tmp=GDDFS_Iter(i);
if (tmp > maxd)
maxd=tmp;
}
return maxd;
}
//Итератиный поиск несвязных компонент, основная подпрограмма.
int grafe::GUComponents_Iter()
{
int kol = 0;
for (int i = 0; i < (int)Marker.size(); i++) Marker[i]=0;
this->Components.clear();
33
for (int i = 0; i < (int)Marker.size(); i++)
{
if (!Marker[i])
{
comp.clear();
GDDFS_Iter(i, this->comp);
if (comp.size() >0)
{
Components.push_back(comp);
kol++;
}
}
}
return kol;
};
34
ПРИЛОЖЕНИЕ Е
Подпрограмма многопоточного запуска
void parstart(int &rangMin, int &rangStep,int &rangMax, int &maxX, int
&maxY, int &maxR, int &idTst, int &Nrept)
{
vector<vector <int>> param;
vector <thread> MyThreads;
SYSTEM_INFO sysinfo;
GetSystemInfo(&sysinfo);
int CPUcount = sysinfo.dwNumberOfProcessors;
//cout << endl << CPUcount << endl;
maxCoreUse = CPUcount; // сколько ядер макс. можно занять
(потоков запустить одновременно)
cout << "Доступно " << CPUcount <<" ядер ЦПУ, сколько занять под
приложение? ";
cin >> maxCoreUse;
if (maxCoreUse > CPUcount)
{
maxCoreUse = CPUcount;
cout << "Превышено рекомендуемое предельное значение.
Максимум запускаемых одновременно потоков установлен в " <<
maxCoreUse
<< endl;
}
int numthcom = (rangMax - rangMin) / rangStep;
int N2 = numthcom / maxCoreUse;
cout << "N2 =" << N2;
//system("pause");
//Обработка потоков 1
int k = 0;
int inum;
for (unsigned int j = 0; j < N2; ++j)
{
cout << "J = " << j+1 << "[] -- ";
for (unsigned int it=0; it < maxCoreUse; ++it)
{
inum = it+1 + k*maxCoreUse;
cout << inum << " ( " <<rangMin+rangStep*(it +
(j)*maxCoreUse) << " ) ";
}
cout << endl;
}
int vv =0;
unsigned int time1=clock();
unsigned int time2=time1;
35
for (unsigned int j = 0; j < N2; ++j)
{
time1 = clock();
for (unsigned int it=0; it < maxCoreUse; ++it)
{
inum = it+1 + j*maxCoreUse;
vv = rangMin+rangStep*(it + (j)*maxCoreUse);
param.push_back(vector<int>());
param[inum-1].push_back(vv);
param[inum-1].push_back(maxX);
param[inum-1].push_back(maxY);
param[inum-1].push_back(maxR);
param[inum-1].push_back(idTst);
param[inum-1].push_back(Nrept);
param[inum-1].push_back(it+1 + k*maxCoreUse);
param[inum-1].push_back(rand());
MyThreads.push_back( thread (fthread, param[inum-1]));
}
//жду завершения работы пачки потоков
for (int i=0; i< (int)MyThreads.size(); ++i)
{
MyThreads[i].join();
MyThreads[i].~thread();
time2 = clock();
cout << "поток закрыт успешно; Время работы = " <<
timeformatted(time1,time2) << endl;
}
MyThreads.clear();
++k;
cout << endl;
}
int Nm = numthcom - k*N2;
//если вдруг остались
if (k*N2 < numthcom)
{
//cout << k*N2 << " " << numthcom;
time1 = clock();
param.clear();
for (unsigned int it=0; it < Nm ; ++it)
{
inum = it+1 + N2*maxCoreUse;
vv = rangMin+rangStep*(it + N2*maxCoreUse);
//cout << vv << " " << it+1 + k*maxCoreUse << endl;
param.push_back(vector<int>());
param[it].push_back(vv);
36
param[it].push_back(maxX);
param[it].push_back(maxY);
param[it].push_back(maxR);
param[it].push_back(idTst);
param[it].push_back(Nrept);
param[it].push_back(it+1 + k*maxCoreUse);
param[it].push_back(rand());
MyThreads.push_back( thread (fthread, param[it]));
}
for (int i=0; i< (int)MyThreads.size(); ++i)
{
MyThreads[i].join();
MyThreads[i].~thread();
time2 = clock();
cout << "поток закрыт успешно; Время работы = " <<
timeformatted(time1,time2) << endl;
}
MyThreads.clear();
}
}
Download