В предыдущих разделах рассматривались следующие вопросы построения кластера и создания программ для запуска на кластере:
Следующим шагом после разработки работающей
Многопоточный подход к разработке приложений появился в программировании уже достаточно давно и широко используется для повышения производительности. С появлением многоядерных процессоров интерес к многопоточному программированию значительно усилился. Многоядерные системы прошли путь от лабораторных образцов до настольных компьютеров, доступных рядовым пользователям. В 2005 году началась массовая продажа процессоров с двумя ядрами, в 2007 - четырехъядерных процессоров. В будущем ожидается сохранение тенденции увеличения числа ядер, и можно с уверенностью сказать, что с течением времени число систем, построенных на базе многоядерных процессоров, будет постоянно увеличиваться.
В подобной ситуации остро встает вопрос об эффективном использовании многоядерных архитектур. Многопоточное программирование представляет собой очень удобный подход, позволяющий создавать приложения, способные рационально использовать процессоры с несколькими ядрами, обеспечивая их оптимальную загрузку. Уже сегодня подавляющее большинство существующих приложений использует несколько потоков: браузеры, файловые менеджеры, словари,
Необходимо, тем не менее, отметить, что разработка многопоточных приложений зачастую на порядок сложнее, чем последовательных. Новая парадигма приносит не только новые возможности, но и новые сложности. Создание эффективных многопоточных приложений - весьма трудоемкий процесс, а значит, имеется насущная необходимость в инструментальной поддержке всех стадий разработки подобных приложений, начиная с построения архитектуры и заканчивая настройкой параметров и финальной оптимизацией приложения.
В настоящее время существует достаточно много инструментов, ориентированных на поддержку многопоточного программирования. Компания Intel выделяется на общем фоне тем, что предлагает целое семейство подобных инструментов, представителем которого является Intel® Thread Profiler (
Данное семейство инструментов предназначено для поддержки всего цикла разработки многопоточных приложений. Их конечная цель - значительно ускорить процесс разработки, обеспечив разработчиков удобными и эффективными инструментами. Инструменты поддерживают различные методы создания многопоточных приложений:
На рис. 10.1 схематически представлен
(рис 10.1) Цикл разработки приложенияКак видно из диаграммы,
Центральная идея ускорения последовательных приложений достаточно проста. Необходимо выявить участки кода, в которых приложение проводит основную часть времени и оптимизировать их. Известен эвристический закон "20/80", утверждающий, что приложение проводит 80% времени работы в 20% кода. Таким образом, за счет оптимизации именно этих 20% кода можно добиться значительного повышения производительности. Как раз для поиска таких узких мест используются специальные средства, называемые профилировщиками.
Под понятием профилирование обычно понимается процесс анализа производительности приложения и учета потребляемых им ресурсов. Тем самым, профилировщик - это инструмент, при помощи которого осуществляется
Ситуация с многопоточными приложениями существенно другая, поскольку в силу вступают новые аспекты производительности, связанные с взаимодействием потоков, затратами на их создание и синхронизацию. Ускорение отдельных участков кода может не дать никакого прироста производительности, если, например, разработчик построил свое приложение таким образом, что основную часть времени потоки ожидают освобождения разделяемого ресурса. Также можно упомянуть такие распространенные в многопоточных приложениях проблемы как неравномерное распределение нагрузки и неэффективное использование примитивов синхронизации. Эти факторы могут привести к катастрофически низкой производительности приложения, вплоть до того, что многопоточная версия будет медленнее последовательной.
Таким образом, для анализа новых аспектов программной оптимизации, присущих именно многопоточным приложениям, необходимы специальные инструменты, ориентированные на обнаружение проблемных ситуаций и последующей помощи программистам в их разрешении. Одним из самых эффективных и популярных представителей инструментов такого класса является
Итак,
Таким образом,
Инструмент имеет два режима: "
Главной особенностью этого режима является возможность
Второй режим является более общим - он обеспечивает анализ
Режим "Threaded" предоставляет более богатые возможности, поэтому мы сосредоточимся на рассмотрении именно этого режима.
Минимальные требования:
При практическом использовании
Для изучения всех аспектов "реальных"
Минимальные требования:
Для анализа OpenMP-приложений и инструментации на уровне исходных кодов требуется следующее программное обеспечение:
Первое понятие, которое нам понадобится - критический путь (critical path).
Это понятие является ключом к пониманию всего процесса
Представим себе следующее многопоточное приложение: имеется основной поток, который подготавливает данные и производит их первичную обработку, а также один дочерний поток, который завершает обработку и выводит результаты. Простейшая иллюстрация этого примера - физическое моделирование, в котором основной поток занят вычислениями (например, решением разностной схемы), а дочерний - подготовкой к визуализации и непосредственно визуализацией результатов вычислений. Часто подобные взаимодействия реализуются в виде конвейера, поскольку в то время как один из потоков работает с устройствами ввода-вывода, второй поток может нагружать процессор вычислениями.
В нашем случае, как только первый поток вычислил первую порцию данных для отображения, он может начинать вычисление следующей порции, а второй поток параллельно с этим начнет визуализацию. Важно при этом обеспечить синхронизацию между потоками, чтобы не допустить уничтожения данных до того как они были визуализированы.
Ниже графически представлено взаимодействие потоков в случае, если бы требовалось выполнить только две итерации. Потокам в нашем приложении соответствуют горизонтальные линии, сплошные участки соответствуют активному состоянию потоков, а пунктирные - состоянию ожидания. Диагональные линии соответствуют сигналам, передаваемым между потоками, а проекции этих линий на ось времени соответствует временам прохождения сигналов. Кружками обозначены все события, в результате которых изменяются состояния потоков (создание/завершение, блокировка/активизация).
(рис 10.2) Пример критического пути многопоточного приложенияРассмотрим диаграмму подробнее. В начальный момент времени существовал только один поток, выполняющийся последовательно. Затем в момент времени t1 происходит создание дочернего потока, который начинает функционировать в момент времени t2, но сразу попадает в состояние ожидания, поскольку данные для визуализации еще не подготовлены. Затем в точке t3 завершаются вычисления, и дочерний поток начинает визуализацию подготовленных данных, которая продолжается с момента t4 до момента t7. Заметим, что второй поток может освободить массивы с результатами вычислений сразу после того, как переведет их в формат, необходимый для графического представления (момент времени t5 ). За счет этого и достигается распараллеливание в рассматриваемом приложении. Можно видеть, что t6 и t7
. Далее осуществляется еще одна итерация, после чего в момент времени t12 второй поток завершает свое исполнение, а в момент времени t14 завершается весь процесс.
Рассмотренная диаграмма представляет собой граф. Вершинам, как уже говорилось, соответствуют события, изменяющие состояние потоков, а ребрам - действия, выполняемые потоками (мы не учитываем пунктирные линии). При этом ребрам можно сопоставить вес, равный времени, затрачиваемому на выполнение действия. Путем приложения называется любой путь в указанном графе, соединяющий вершины, соответствующие началу и завершению приложения. Критическим путем приложения называется путь, имеющий максимальную сумму весов входящих в него ребер. Приложения со многими потоками могут иметь большое число путей, однако в нашем случае приложение имеет всего два пути, каждый из которых можно назвать критическим, так как суммы их весов совпадают.
Важность понятия
Таким образом,
При анализе
pthread_spin_lock() );Рассмотрим теперь характеристики эффективности использования аппаратных ресурсов активными потоками приложения. Для этого в
Уровни параллелизма используются для обозначения того, насколько полно потоки нагружают процессоры вычислительного узла. Так, одновременная работа двух потоков на двухъядерном процессоре означает эффективное использование его ресурсов ( full utilization ), на одноядерном - сверхиспользование ( over utilization ), а на четырехъядерном - недостаточно эффективное использование ( under utilization ).
Существует несколько типов поведения потока в активном состоянии:
Уровень параллелизма и поведение независимы друг от друга, и в совокупности они называются категориями времени (time categories) и характеризуют эффективность работы активных потоков. Собственно весь анализ производительности состоит в том, чтобы оценить каждый участок
В
(рис 10.3) Цвета, используемые для обозначения категорий времениРассмотрим приведенную таблицу подробнее, чтобы знать впоследствии, на какие цвета нужно обращать внимание прежде всего. Сначала исследуем связь числа потоков и числа ядер. Как видно из таблицы, для обозначения этого соотношения используется четыре цвета: оранжевый, красный, зеленый и синий. Зеленый цвет - признак оптимального соотношения, когда число ядер и потоков совпадает. Синий цвет используется для обозначения ситуаций, когда одновременно работает больше потоков, чем доступно ядер. Как результат, потоки могут мешать работе друг друга. Присутствие красного и оранжевого цветов в окраске
Рассмотрим теперь второй параметр - тип поведения потоков. Для обозначения типа поведения потока цветом используется изменение яркости. При этом используется простое правило: чем ярче цвет участка, тем большего внимания от разработчика он требует. Самый яркий цвет используется для указания impact -состояния потока, поскольку прежде всего необходимо сокращать время ожидания потоков друг другом. Далее по важности следует blocking -состояние, которое тоже требует пристального контроля, так как нужно минимизировать время ожидания потоков. Последним идет состояние critical path.
Имеется особая категория времени, обозначаемая желтым цветом и указывающая на непроизводительные издержки ( overhead ) в многопоточном приложении. Под непроизводительными издержками понимаются накладные расходы на
Теперь, зная о категориях времени, мы можем сказать, какие цвета будут содержаться на критическом пути нашего примера из подраздела 3.1.
(рис 10.4) Пример раскраски критического пути многопоточного приложения в соответствии с категориями времениИтак, подведем итоги:
Мы уже говорили ранее, что
Рассмотрим далее наиболее распространенные ошибки и то, как
Весьма распространенной ошибкой является неравномерное распределение нагрузки между потоками. Общее время работы приложения в значительной степени зависит от времени работы самого медленного из его потоков. Очевидно, приложение будет работать быстрее всего, если нагрузка поделена между потоками поровну - тогда они завершают работу одновременно и за минимальное время.
Типичный пример: стоит задача обработки множества заявок, трудоемкость каждой из которых заранее неизвестна. В такой ситуации не всегда правильно разделять множество заявок поровну между потоками. Дело в том, что один поток может, например, получить все сложные заявки, в результате чего он станет узким местом в приложении.
Проблема распределения вычислительной нагрузки в ряде ситуаций решается достаточно просто. Первый признак ее появления - большая доля последовательных вычислений в приложении, что легко обнаруживается при анализе
Для рассмотренного выше примера в качестве решения можно предложить не делать априорного
Аспекты производительности, связанные с синхронизацией между потоками, требуют особого внимания. Неправильно выбранная стратегия может привести к тому, что параллельная версия приложения будет выполняться даже медленнее, чем последовательная. Поэтому разработчик должен тщательно продумать используемую в его приложении модель синхронизации, и
Рассмотрим основные вопросы, связанные с синхронизацией.
Существует достаточно большое число примитивов синхронизации:
Рассмотрим, например, ситуацию, когда в приложении имеется некоторый целочисленный счетчик, являющийся глобальной переменной. При этом, если мы в приложении используем каждый раз увеличение этого счетчика на единицу, то выбор такого объекта как мьютекс является нерациональным. Гораздо эффективнее использовать InterlockedIncrement. Кроме того, при разработке модели синхронизации следует делать выбор в пользу примитивов пользовательского уровня ( CriticalSection, например), поскольку они работают быстрее, так как не генерируют системных вызовов операционной системы.
Разработчикам следует придерживаться следующей рекомендации: производить синхронизацию между потоками как можно реже. То есть необходимо сделать потоки максимально независимыми, чтобы избежать ситуаций, когда они ожидают друг друга. Слишком частое обращение нескольких потоков к разделяемому ресурсу приводит к тому, что большое количество времени потоки простаивают, находясь в состоянии ожидания.
Часто встречающаяся ситуация - это совместное использование одного и того же объекта несколькими потоками. Чтобы избежать блокировки потоков при обращении к объекту, часто используется подход, при котором каждый из потоков получает копию объекта в свое личное пользование. Так, в частности, поступают при работе нескольких пользователей с одной таблицей базы данных.
В данной ситуации
Важный момент при работе с потоками - появление дополнительных накладных расходов. Конечно, эти затраты гораздо меньше чем при управлении процессами, но все равно они могут оказаться весьма существенными. Основная рекомендация здесь состоит в следующем: потоки за время своей жизни должны совершать работу гораздо большей сложности, чем трудоемкость их собственного создания, уничтожения и управления ими.
Понятно, что если накладные расходы на управление потоками будут преобладать над их полезной деятельностью, то производительность приложения только пострадает.
Порядок работы с Intel® Thread Profiler включает в себя следующие шаги:
На первом шаге происходит подготовка приложения к профилированию - так называемая инструментация. Затем осуществляется запуск, и в процессе выполнения происходит накопление
Далее мы приведем рассмотрение процессов инструментации и
Инструментация представляет собой встраивание в приложение дополнительных вызовов, при помощи которых
Бинарная инструментация выполняется автоматически при запуске приложения из
В связи с этим, обычная процедура состоит в следующем: разработчик компилирует свое приложение со всеми необходимыми опциями, а затем использует бинарную инструментацию. Мы рассмотрим этот процесс подробнее в разделе 6.2.
Второй тип инструментации используется крайне редко. Создатели
Далее под инструментацией мы будем понимать именно бинарную инструментацию, поскольку именно с ней нам придется работать.
После того как приложение было инструментировано, можно начинать
При
Далее мы на конкретном примере познакомимся с графическим интерфейсом
Целью настоящего раздела является начальное ознакомление с инструментом
Настоящий раздел представлен в виде лабораторной работы, которую читатели могут выполнять одновременно с чтением данного документа.
Лабораторная работа проводится на учебном приложении, которое осуществляет факторизацию (разложение на простые множители) чисел из диапазона от 1 до N. Используется алгоритм, который основан на попытке деления факторизуемого числа на каждое из меньших его чисел. Если остаток от деления равен нулю, то очередной множитель запоминается, после чего производится повторная попытка деления на это же число. При нахождении каждого множителя, факторизуемое число делится на него, и алгоритм завершает работу, когда частное от очередного деления становится равным единице. Заметим, что это малоэффективный алгоритм факторизации, поэтому мы не рекомендуем использовать его при решении практических задач. Использование такого алгоритма предпринято только в учебных целях - на его примере мы сможем изучить некоторые особенности оптимизации многопоточных приложений.
Откройте проект Factorization, последовательно выполняя следующие шаги:
C:\ITPLab\Factorization,Factorization.sln или, выбрав файл, выполните команду Open.В окне Solution Explorer дважды щелкните на файле исходного кода Factorization. (рис. 10.5). После этого в рабочей области Microsoft Visual Studio появится программный код, с которым нам предстоит работать.
(рис 10.5) Открытие файла Factorization.cppПриступим к изучению приложения. В начале файла Factorization. объявлены две константы:
#define NUM_NUMBERS 100000 #define NUM_THREADS 2
Первая из них указывает количество чисел, которые будут факторизованы. В данном случае будет построено разложение для чисел от 1 до 100000. Вторая константа показывает, сколько потоков будет создано для решения этой задачи.
Также объявлен глобальный массив векторов .
vector<int> divisors[NUM_NUMBERS+1];
Он предназначен для хранения простых множителей каждого из чисел. Так, например, вектор для NUM_NUMBERS=6 будет содержать два числа: 2 и 3.
В данной лабораторной работе нас будут интересовать только функции main и factorization1.
Ознакомьтесь с кодом функции main. Он содержит объявления переменных, операции создания потоков и ожидания их завершения, а также вывод на экран, предназначенный для контроля правильности результатов.
После этого ознакомьтесь с функцией factorization1, которая представляет собой рабочую функцию потока. В ней реализуется простейшая стратегия распределения нагрузки между потоками. А именно, первый поток строит разложение для первых NUM_NUMBERS/NUM_THREADS чисел, второй - для второго массива чисел такой же длины, и так далее. Пример распределения нагрузки между двумя потоками представлен на рис. 10.6
(рис 10.6) Распределение нагрузки между потокамиСкомпилируйте и запустите приложение:
Убедитесь в правильности работы приложения по выводу на консоли.
Для того чтобы приложение можно было профилировать при помощи
После выполнения данных процедур ваше приложение готово к профилированию.
После этого
(рис 10.14) Рабочая область Intel Thread Profiler
.Результаты каждого запуска сохраняются, и вы можете работать с несколькими профилями одновременно. Доступ к профилям осуществляется через пункт меню View $$\to$$ Tuning Browser.
Далее мы по шагам изучим основные элементы графического интерфейса
В нижней части окна
(рис 10.15) Вкладка SummaryВкладка состоит из двух таблиц: первая содержит общую информацию о состоявшемся запуске (характеристики узла, время работы приложения, число потоков и т.д.), а вторая - статистику вызовов API.
Щелкните на знак "+", располагающийся возле надписи "APIs" в нижней таблице, при этом она должна принять вид, как показано на рис. 10.15. Из таблицы можно узнать, сколько времени приложение потратило на ввод/вывод, синхронизацию, непроизводительные издержки и т.д.
После того как вы ознакомитесь с приведенной информацией, вернитесь на вкладку Profile View.
Основную часть окна Profile занимает область, на которой представлен
(рис 10.16) Окно ProfileС помощью панели пользователь имеет возможность указать интересующие его характеристики поведения потоков. Для этого используются находящиеся на легенде кнопки-флажки (check-box). Первый из них, называемый Thread State, позволяет указать, интересует ли нас информация о состоянии потока. Напомним, что существуют три типа состояния: активное ( active ), активное ожидание ( spin ), ожидание ( wait ).
Выберите тип группировки по потокам, нажав на панели кнопку с изображением катушки
. После этого в окне появится информация о распределении времени в критическом пути, как показано на рис. 10.17.
(рис 10.17) Анализ состояний потоковИнформация о состоянии потоков представлена в виде столбиков зеленого цвета различной насыщенности. Бледно-зеленый цвет используется для обозначения того, что поток находился в состоянии ожидания, а темно-зеленый цвет соответствует активному состоянию потока. Из рисунка можно узнать, что два потока в нашем приложении (первый и третий столбики) большую часть времени были активны, а еще один поток в основном находился в состоянии ожидания. Нетрудно понять, что в ожидании находился основной поток нашего приложения, в то время как созданные им потоки производили разложение чисел на простые множители. Снимите флажок Thread State и убедитесь, что информация о состоянии потоков исчезнет. После этого верните флажок в исходное положение.
Следующая кнопка-флажок - Critical Path Data, при помощи которой пользователь может указать, интересует ли его разбиение
После этого верните все в исходное положение, в котором установлены флажки Critical Path Data и Concurrency, а флажок Behavior неактивен. В этой конфигурации легенды пользователь имеет возможность анализировать, насколько эффективно его приложение использует ядра процессора ( уровень параллелизма ). Здесь мы имеем дело с категориями времени, про которые было рассказано в разделе 3. В нашем случае можно увидеть, что основную часть
Легенда также несет информационную функцию. Если вы забыли, что означает тот или иной цвет, наведите курсор мыши на прямоугольник соответствующего цвета в легенде. Появится всплывающая подсказка, содержащая информацию о том, что он означает. Кроме того, если цвет считается "проблемным" (свидетельствует о неправильной организации приложения), то в подсказке будут содержаться советы для решения данной проблемы.
На легенде неизученной осталась последняя кнопка-флажок Behavior. Установите ее и снимите флажок Concurrency. В этой конфигурации пользователь имеет возможность определить поведение потока в его активном состоянии, о котором мы говорили ранее в лекции 4. В нашем случае
Установите флажки Concurrency и Behavior, чтобы получить комбинацию двух этих режимов. Тогда
Данная функция очень удобна при анализе
), то можно проанализировать эффективность работы каждого из потоков. Если же включить группировку по объектам (кнопка
), то становится доступной информация об эффективности работы с конкретным объектом (примитивом синхронизации, например).
Рассмотрим наиболее типичные варианты использования группировки. Полезно начать с варианта, когда группировки не используются. Нажмите кнопку с изображением перечеркнутого красного круга
- при этом
(рис 10.18) Изображение критического пути при отключенной группировкеЭтот режим наиболее полезен для получения информации о распределении времени в критическом пути. Используя его можно получить информацию о том, сколько процентов занимает последовательное выполнение (доля оранжевого цвета), сколько параллельное (зеленый, синий и красный цвета), а также какую часть занимают непроизводительные расходы (доля желтого цвета). Попробуйте также другие режимы группировки, анализируя каждый раз изменения
Кроме того, может оказаться полезной возможность вторичной группировки.
В первом наборе кнопок группировки нажмите кнопку с изображением катушки
, а во втором наборе - с буквами CL
. При этом
(рис 10.19) Использование вторичной группировкиВ данном случае мы имеем возможность исследовать, насколько эффективно функционировал каждый из потоков. Мы видим три группы столбиков, каждая из которых соответствует одному потоку. Столбики показывают сколько времени каждый из потоков функционировал в последовательном режиме, сколько в параллельном, а сколько в состоянии ожидания. Как можно увидеть из рисунка, третий поток (левая группа столбиков) большую часть времени работал в последовательном режиме, то есть параллельно с ним не работал ни один другой поток. Второй же поток (правая группа столбиков) практически все время работал параллельно с третьим потоком. Основной же поток приложения (центральная группа столбиков) выполнялся лишь в последовательном режиме и большую часть жизни находился в режиме ожидания дочерних потоков.
Поэкспериментируйте, определяя различные порядки группировки, попытайтесь представить ситуации, в которых они могут быть полезны.
Мы изучили различные способы представления Profile и пришло время изучить способы его анализа.
Выключите группировку, чтобы распределение
(рис 10.20) Анализ критического путиНаведите курсор мыши на столбец и щелкните левой кнопкой один раз. В нижней части окна Profile появится информация о том, сколько времени приложение функционировало в различных режимах - распределение времени по категориям.
Двойной щелчок на столбце позволяет получить более подробную информацию. Попробуем, например, получить полную информацию об участке
(рис 10.21) Обращение к исходному коду приложенияЗдесь мы можем убедиться, что за участок
Чтобы вернуться к исходному представлению
, а затем отмените всякую группировку, нажав кнопку
.
Если имеется необходимость понять, какой участок кода отвечает за некоторый участок
. После этого наведите курсор мыши на интересующий вас участок
Однако не всегда можно спуститься до уровня исходного кода - так случается, если в приложении не содержится отладочная информация. Типичная ситуация - приложение использует вызовы библиотеки, которая была получена в скомпилированном виде.
Итак, окно Profile предоставляет возможности анализа
Окно Timeline содержит три основных элемента, также как и окно Profile: рабочая область, панель инструментов и легенда. Панель инструментов в данном случае предназначена лишь для изменения масштаба временной оси.
В рабочей области окна Timeline содержится информация о поведении потоков. В верхней части рабочей области имеется временная шкала. Ниже располагаются полосы, каждая из которых соответствует одному потоку приложения.
(рис 10.22) Окно TimelineНа рис. 10.22 показано, что в нашем приложении было запущено три потока. Также можно увидеть, что один из них существовал с начала времени выполнения приложения, а два других были созданы позже. Время жизни потока равно длине полосы. Раскраска полосы не всегда одинакова - она указывает на состояния потока в различные моменты времени (см. легенду). Наведите курсор мыши на бледно-зеленую часть самой верхней полосы, соответствующей основному потоку. В появившейся подсказке будет сказано, что на этом промежутке времени поток находился в режиме ожидания.
Рассмотрим подробнее момент возникновения потоков. Для этого в рабочей области необходимо выделить область, охватывающую розовую стрелку. Наведите курсор мыши немного левее стрелки, зажмите левую клавишу и, передвиньте курсор правее стрелки, после чего отпустите кнопку мыши. Повторите увеличение еще несколько раз, пока вид в рабочей области не станет таким, как показано на рис. 10.23.
(рис 10.23) Момент создания потоковЗдесь уже можно видеть, что второй поток был создан несколько позже первого. Наведите курсор мыши на одну из розовых стрелок и убедитесь, что она соответствует вызову функции создания потока. Длина этой стрелки, спроектированная на ось времени, показывает время задержки между вызовом функции создания потока и фактическим его запуском.
Кроме того, поверх зеленых полос, указывающих состояние потоков, яркими цветами накладывается информация о критическом пути. На изучаемом нами промежутке времени имеются участки последовательного и параллельного исполнения потоков. Нетрудно понять, что оранжевый участок соответствует интервалу времени, когда в приложении существовал только один поток, а участок зеленого цвета соответствует одновременной работе двух потоков.
Вернемся к изучению трассы приложения в целом. Нажмите на панели инструментов окна . Рабочая область снова должна принять вид как на рис. 10.21.
Глядя на этот рисунок, можно сразу понять, в чем причины того, что основную часть времени наше приложение работает в последовательном режиме. Мы неравномерно распределили вычислительную нагрузку между потоками. Второй из дочерних потоков работает гораздо дольше первого, в результате чего увеличивается суммарное время работы приложения. Таким образом, если мы хотим повысить производительность нашего приложения, то первое, что мы должны сделать, это распределить нагрузку между потоками равномерно. Тогда время работы приложения сократится за счет того, что часть нагрузки второго дочернего потока будет отдана первому.
Последнее, что осталось изучить в рабочей области окна Timeline - это как из него получить доступ к исходному коду. Наведите курсор мыши на любую из полос, соответствующих дочерним потокам, и нажмите правую кнопку мыши. В появившемся контекстном меню выберите пункт Thread Creation/Entry Source View. Откроется окно с исходным кодом, отвечающим за создание потока.
Вернитесь к окну Timeline и наведите курсор на бледно-зеленую часть полосы, соответствующей основному потоку приложения, нажмите правую кнопку мыши и выберите пункт Transition Source View. Откроется окно с исходным кодом, в котором указано место ожидания основного потока (рис. 10.24).
(рис 10.24) Место ожидания основного потокаВ нашем случае это вызов функции WaitForMultipleObjects, которая необходима здесь для ожидания завершения дочерних потоков.
Вернемся к окну Timeline и рассмотрим легенду.
Общая структура и функции легенды такие же, как и в окне Profile, поэтому мы не будем останавливаться на кнопках-флажках Thread State и Critical Path Data. Вы можете поэкспериментировать с ними и проследить, как меняется вид в рабочей области окна Timeline.
Рассмотрим назначение новых кнопок-флажков. Первый из них имеет название Transitions и отвечает за отображение в рабочей области стрелок, соответствующих посылке сигналов между потоками. Снимите флажок Fork/Join - останутся три стрелки желтого цвета, показывающие посылаемые в приложении сигналы. В нашем случае это сигналы, связанные с созданием и завершением потоков.
Теперь снимите флажок Transitions и установите флажок Fork/Join. Должны появиться стрелки, указывающие на вызовы функций класса Fork/Join (создание и ожидание завершения потоков). Можно заметить, что они совпадают с желтыми стрелками, которые мы видели до этого. Единственное отличие - появление розовой стрелки, соединяющей конец полосы первого дочернего потока с полосой основного потока.
Последний флажок называется User Event. Мы не станем рассматривать его, подробная информация о нем может быть найдена в [10.7].
Таким образом, мы выяснили, что наше приложение содержит поток, большую часть времени работающий в последовательном режиме. Причины такой ситуации и способы увеличения производительности нашего примера мы рассмотрим в лабораторной работе 1.
Profile установите первичную группировку по потокам, а вторичную по уровню параллелизма. Установите соответствие между столбцами в окне Profile и полосами в окне Timeline .В предыдущих разделах рассматривались следующие вопросы построения кластера и создания программ для запуска на кластере:
Следующим шагом после разработки работающей
Многопоточный подход к разработке приложений появился в программировании уже достаточно давно и широко используется для повышения производительности. С появлением многоядерных процессоров интерес к многопоточному программированию значительно усилился. Многоядерные системы прошли путь от лабораторных образцов до настольных компьютеров, доступных рядовым пользователям. В 2005 году началась массовая продажа процессоров с двумя ядрами, в 2007 - четырехъядерных процессоров. В будущем ожидается сохранение тенденции увеличения числа ядер, и можно с уверенностью сказать, что с течением времени число систем, построенных на базе многоядерных процессоров, будет постоянно увеличиваться.
В подобной ситуации остро встает вопрос об эффективном использовании многоядерных архитектур. Многопоточное программирование представляет собой очень удобный подход, позволяющий создавать приложения, способные рационально использовать процессоры с несколькими ядрами, обеспечивая их оптимальную загрузку. Уже сегодня подавляющее большинство существующих приложений использует несколько потоков: браузеры, файловые менеджеры, словари,
Необходимо, тем не менее, отметить, что разработка многопоточных приложений зачастую на порядок сложнее, чем последовательных. Новая парадигма приносит не только новые возможности, но и новые сложности. Создание эффективных многопоточных приложений - весьма трудоемкий процесс, а значит, имеется насущная необходимость в инструментальной поддержке всех стадий разработки подобных приложений, начиная с построения архитектуры и заканчивая настройкой параметров и финальной оптимизацией приложения.
В настоящее время существует достаточно много инструментов, ориентированных на поддержку многопоточного программирования. Компания Intel выделяется на общем фоне тем, что предлагает целое семейство подобных инструментов, представителем которого является Intel® Thread Profiler (
Данное семейство инструментов предназначено для поддержки всего цикла разработки многопоточных приложений. Их конечная цель - значительно ускорить процесс разработки, обеспечив разработчиков удобными и эффективными инструментами. Инструменты поддерживают различные методы создания многопоточных приложений:
На рис. 10.1 схематически представлен
(рис 10.1) Цикл разработки приложенияКак видно из диаграммы,
Центральная идея ускорения последовательных приложений достаточно проста. Необходимо выявить участки кода, в которых приложение проводит основную часть времени и оптимизировать их. Известен эвристический закон "20/80", утверждающий, что приложение проводит 80% времени работы в 20% кода. Таким образом, за счет оптимизации именно этих 20% кода можно добиться значительного повышения производительности. Как раз для поиска таких узких мест используются специальные средства, называемые профилировщиками.
Под понятием профилирование обычно понимается процесс анализа производительности приложения и учета потребляемых им ресурсов. Тем самым, профилировщик - это инструмент, при помощи которого осуществляется
Ситуация с многопоточными приложениями существенно другая, поскольку в силу вступают новые аспекты производительности, связанные с взаимодействием потоков, затратами на их создание и синхронизацию. Ускорение отдельных участков кода может не дать никакого прироста производительности, если, например, разработчик построил свое приложение таким образом, что основную часть времени потоки ожидают освобождения разделяемого ресурса. Также можно упомянуть такие распространенные в многопоточных приложениях проблемы как неравномерное распределение нагрузки и неэффективное использование примитивов синхронизации. Эти факторы могут привести к катастрофически низкой производительности приложения, вплоть до того, что многопоточная версия будет медленнее последовательной.
Таким образом, для анализа новых аспектов программной оптимизации, присущих именно многопоточным приложениям, необходимы специальные инструменты, ориентированные на обнаружение проблемных ситуаций и последующей помощи программистам в их разрешении. Одним из самых эффективных и популярных представителей инструментов такого класса является
Итак,
Таким образом,
Инструмент имеет два режима: "
Главной особенностью этого режима является возможность
Второй режим является более общим - он обеспечивает анализ
Режим "Threaded" предоставляет более богатые возможности, поэтому мы сосредоточимся на рассмотрении именно этого режима.
Минимальные требования:
При практическом использовании
Для изучения всех аспектов "реальных"
Минимальные требования:
Для анализа OpenMP-приложений и инструментации на уровне исходных кодов требуется следующее программное обеспечение:
Первое понятие, которое нам понадобится - критический путь (critical path).
Это понятие является ключом к пониманию всего процесса
Представим себе следующее многопоточное приложение: имеется основной поток, который подготавливает данные и производит их первичную обработку, а также один дочерний поток, который завершает обработку и выводит результаты. Простейшая иллюстрация этого примера - физическое моделирование, в котором основной поток занят вычислениями (например, решением разностной схемы), а дочерний - подготовкой к визуализации и непосредственно визуализацией результатов вычислений. Часто подобные взаимодействия реализуются в виде конвейера, поскольку в то время как один из потоков работает с устройствами ввода-вывода, второй поток может нагружать процессор вычислениями.
В нашем случае, как только первый поток вычислил первую порцию данных для отображения, он может начинать вычисление следующей порции, а второй поток параллельно с этим начнет визуализацию. Важно при этом обеспечить синхронизацию между потоками, чтобы не допустить уничтожения данных до того как они были визуализированы.
Ниже графически представлено взаимодействие потоков в случае, если бы требовалось выполнить только две итерации. Потокам в нашем приложении соответствуют горизонтальные линии, сплошные участки соответствуют активному состоянию потоков, а пунктирные - состоянию ожидания. Диагональные линии соответствуют сигналам, передаваемым между потоками, а проекции этих линий на ось времени соответствует временам прохождения сигналов. Кружками обозначены все события, в результате которых изменяются состояния потоков (создание/завершение, блокировка/активизация).
(рис 10.2) Пример критического пути многопоточного приложенияРассмотрим диаграмму подробнее. В начальный момент времени существовал только один поток, выполняющийся последовательно. Затем в момент времени t1 происходит создание дочернего потока, который начинает функционировать в момент времени t2, но сразу попадает в состояние ожидания, поскольку данные для визуализации еще не подготовлены. Затем в точке t3 завершаются вычисления, и дочерний поток начинает визуализацию подготовленных данных, которая продолжается с момента t4 до момента t7. Заметим, что второй поток может освободить массивы с результатами вычислений сразу после того, как переведет их в формат, необходимый для графического представления (момент времени t5 ). За счет этого и достигается распараллеливание в рассматриваемом приложении. Можно видеть, что t6 и t7
. Далее осуществляется еще одна итерация, после чего в момент времени t12 второй поток завершает свое исполнение, а в момент времени t14 завершается весь процесс.
Рассмотренная диаграмма представляет собой граф. Вершинам, как уже говорилось, соответствуют события, изменяющие состояние потоков, а ребрам - действия, выполняемые потоками (мы не учитываем пунктирные линии). При этом ребрам можно сопоставить вес, равный времени, затрачиваемому на выполнение действия. Путем приложения называется любой путь в указанном графе, соединяющий вершины, соответствующие началу и завершению приложения. Критическим путем приложения называется путь, имеющий максимальную сумму весов входящих в него ребер. Приложения со многими потоками могут иметь большое число путей, однако в нашем случае приложение имеет всего два пути, каждый из которых можно назвать критическим, так как суммы их весов совпадают.
Важность понятия
Таким образом,
При анализе
pthread_spin_lock() );Рассмотрим теперь характеристики эффективности использования аппаратных ресурсов активными потоками приложения. Для этого в
Уровни параллелизма используются для обозначения того, насколько полно потоки нагружают процессоры вычислительного узла. Так, одновременная работа двух потоков на двухъядерном процессоре означает эффективное использование его ресурсов ( full utilization ), на одноядерном - сверхиспользование ( over utilization ), а на четырехъядерном - недостаточно эффективное использование ( under utilization ).
Существует несколько типов поведения потока в активном состоянии:
Уровень параллелизма и поведение независимы друг от друга, и в совокупности они называются категориями времени (time categories) и характеризуют эффективность работы активных потоков. Собственно весь анализ производительности состоит в том, чтобы оценить каждый участок
В
(рис 10.3) Цвета, используемые для обозначения категорий времениРассмотрим приведенную таблицу подробнее, чтобы знать впоследствии, на какие цвета нужно обращать внимание прежде всего. Сначала исследуем связь числа потоков и числа ядер. Как видно из таблицы, для обозначения этого соотношения используется четыре цвета: оранжевый, красный, зеленый и синий. Зеленый цвет - признак оптимального соотношения, когда число ядер и потоков совпадает. Синий цвет используется для обозначения ситуаций, когда одновременно работает больше потоков, чем доступно ядер. Как результат, потоки могут мешать работе друг друга. Присутствие красного и оранжевого цветов в окраске
Рассмотрим теперь второй параметр - тип поведения потоков. Для обозначения типа поведения потока цветом используется изменение яркости. При этом используется простое правило: чем ярче цвет участка, тем большего внимания от разработчика он требует. Самый яркий цвет используется для указания impact -состояния потока, поскольку прежде всего необходимо сокращать время ожидания потоков друг другом. Далее по важности следует blocking -состояние, которое тоже требует пристального контроля, так как нужно минимизировать время ожидания потоков. Последним идет состояние critical path.
Имеется особая категория времени, обозначаемая желтым цветом и указывающая на непроизводительные издержки ( overhead ) в многопоточном приложении. Под непроизводительными издержками понимаются накладные расходы на
Теперь, зная о категориях времени, мы можем сказать, какие цвета будут содержаться на критическом пути нашего примера из подраздела 3.1.
(рис 10.4) Пример раскраски критического пути многопоточного приложения в соответствии с категориями времениИтак, подведем итоги:
Мы уже говорили ранее, что
Рассмотрим далее наиболее распространенные ошибки и то, как
Весьма распространенной ошибкой является неравномерное распределение нагрузки между потоками. Общее время работы приложения в значительной степени зависит от времени работы самого медленного из его потоков. Очевидно, приложение будет работать быстрее всего, если нагрузка поделена между потоками поровну - тогда они завершают работу одновременно и за минимальное время.
Типичный пример: стоит задача обработки множества заявок, трудоемкость каждой из которых заранее неизвестна. В такой ситуации не всегда правильно разделять множество заявок поровну между потоками. Дело в том, что один поток может, например, получить все сложные заявки, в результате чего он станет узким местом в приложении.
Проблема распределения вычислительной нагрузки в ряде ситуаций решается достаточно просто. Первый признак ее появления - большая доля последовательных вычислений в приложении, что легко обнаруживается при анализе
Для рассмотренного выше примера в качестве решения можно предложить не делать априорного
Аспекты производительности, связанные с синхронизацией между потоками, требуют особого внимания. Неправильно выбранная стратегия может привести к тому, что параллельная версия приложения будет выполняться даже медленнее, чем последовательная. Поэтому разработчик должен тщательно продумать используемую в его приложении модель синхронизации, и
Рассмотрим основные вопросы, связанные с синхронизацией.
Существует достаточно большое число примитивов синхронизации:
Рассмотрим, например, ситуацию, когда в приложении имеется некоторый целочисленный счетчик, являющийся глобальной переменной. При этом, если мы в приложении используем каждый раз увеличение этого счетчика на единицу, то выбор такого объекта как мьютекс является нерациональным. Гораздо эффективнее использовать InterlockedIncrement. Кроме того, при разработке модели синхронизации следует делать выбор в пользу примитивов пользовательского уровня ( CriticalSection, например), поскольку они работают быстрее, так как не генерируют системных вызовов операционной системы.
Разработчикам следует придерживаться следующей рекомендации: производить синхронизацию между потоками как можно реже. То есть необходимо сделать потоки максимально независимыми, чтобы избежать ситуаций, когда они ожидают друг друга. Слишком частое обращение нескольких потоков к разделяемому ресурсу приводит к тому, что большое количество времени потоки простаивают, находясь в состоянии ожидания.
Часто встречающаяся ситуация - это совместное использование одного и того же объекта несколькими потоками. Чтобы избежать блокировки потоков при обращении к объекту, часто используется подход, при котором каждый из потоков получает копию объекта в свое личное пользование. Так, в частности, поступают при работе нескольких пользователей с одной таблицей базы данных.
В данной ситуации
Важный момент при работе с потоками - появление дополнительных накладных расходов. Конечно, эти затраты гораздо меньше чем при управлении процессами, но все равно они могут оказаться весьма существенными. Основная рекомендация здесь состоит в следующем: потоки за время своей жизни должны совершать работу гораздо большей сложности, чем трудоемкость их собственного создания, уничтожения и управления ими.
Понятно, что если накладные расходы на управление потоками будут преобладать над их полезной деятельностью, то производительность приложения только пострадает.
Порядок работы с Intel® Thread Profiler включает в себя следующие шаги:
На первом шаге происходит подготовка приложения к профилированию - так называемая инструментация. Затем осуществляется запуск, и в процессе выполнения происходит накопление
Далее мы приведем рассмотрение процессов инструментации и
Инструментация представляет собой встраивание в приложение дополнительных вызовов, при помощи которых
Бинарная инструментация выполняется автоматически при запуске приложения из
В связи с этим, обычная процедура состоит в следующем: разработчик компилирует свое приложение со всеми необходимыми опциями, а затем использует бинарную инструментацию. Мы рассмотрим этот процесс подробнее в разделе 6.2.
Второй тип инструментации используется крайне редко. Создатели
Далее под инструментацией мы будем понимать именно бинарную инструментацию, поскольку именно с ней нам придется работать.
После того как приложение было инструментировано, можно начинать
При
Далее мы на конкретном примере познакомимся с графическим интерфейсом
Целью настоящего раздела является начальное ознакомление с инструментом
Настоящий раздел представлен в виде лабораторной работы, которую читатели могут выполнять одновременно с чтением данного документа.
Лабораторная работа проводится на учебном приложении, которое осуществляет факторизацию (разложение на простые множители) чисел из диапазона от 1 до N. Используется алгоритм, который основан на попытке деления факторизуемого числа на каждое из меньших его чисел. Если остаток от деления равен нулю, то очередной множитель запоминается, после чего производится повторная попытка деления на это же число. При нахождении каждого множителя, факторизуемое число делится на него, и алгоритм завершает работу, когда частное от очередного деления становится равным единице. Заметим, что это малоэффективный алгоритм факторизации, поэтому мы не рекомендуем использовать его при решении практических задач. Использование такого алгоритма предпринято только в учебных целях - на его примере мы сможем изучить некоторые особенности оптимизации многопоточных приложений.
Откройте проект Factorization, последовательно выполняя следующие шаги:
C:\ITPLab\Factorization,Factorization.sln или, выбрав файл, выполните команду Open.В окне Solution Explorer дважды щелкните на файле исходного кода Factorization. (рис. 10.5). После этого в рабочей области Microsoft Visual Studio появится программный код, с которым нам предстоит работать.
(рис 10.5) Открытие файла Factorization.cppПриступим к изучению приложения. В начале файла Factorization. объявлены две константы:
#define NUM_NUMBERS 100000 #define NUM_THREADS 2
Первая из них указывает количество чисел, которые будут факторизованы. В данном случае будет построено разложение для чисел от 1 до 100000. Вторая константа показывает, сколько потоков будет создано для решения этой задачи.
Также объявлен глобальный массив векторов .
vector<int> divisors[NUM_NUMBERS+1];
Он предназначен для хранения простых множителей каждого из чисел. Так, например, вектор для NUM_NUMBERS=6 будет содержать два числа: 2 и 3.
В данной лабораторной работе нас будут интересовать только функции main и factorization1.
Ознакомьтесь с кодом функции main. Он содержит объявления переменных, операции создания потоков и ожидания их завершения, а также вывод на экран, предназначенный для контроля правильности результатов.
После этого ознакомьтесь с функцией factorization1, которая представляет собой рабочую функцию потока. В ней реализуется простейшая стратегия распределения нагрузки между потоками. А именно, первый поток строит разложение для первых NUM_NUMBERS/NUM_THREADS чисел, второй - для второго массива чисел такой же длины, и так далее. Пример распределения нагрузки между двумя потоками представлен на рис. 10.6
(рис 10.6) Распределение нагрузки между потокамиСкомпилируйте и запустите приложение:
Убедитесь в правильности работы приложения по выводу на консоли.
Для того чтобы приложение можно было профилировать при помощи
После выполнения данных процедур ваше приложение готово к профилированию.
После этого
(рис 10.14) Рабочая область Intel Thread Profiler
.Результаты каждого запуска сохраняются, и вы можете работать с несколькими профилями одновременно. Доступ к профилям осуществляется через пункт меню View $$\to$$ Tuning Browser.
Далее мы по шагам изучим основные элементы графического интерфейса
В нижней части окна
(рис 10.15) Вкладка SummaryВкладка состоит из двух таблиц: первая содержит общую информацию о состоявшемся запуске (характеристики узла, время работы приложения, число потоков и т.д.), а вторая - статистику вызовов API.
Щелкните на знак "+", располагающийся возле надписи "APIs" в нижней таблице, при этом она должна принять вид, как показано на рис. 10.15. Из таблицы можно узнать, сколько времени приложение потратило на ввод/вывод, синхронизацию, непроизводительные издержки и т.д.
После того как вы ознакомитесь с приведенной информацией, вернитесь на вкладку Profile View.
Основную часть окна Profile занимает область, на которой представлен
(рис 10.16) Окно ProfileС помощью панели пользователь имеет возможность указать интересующие его характеристики поведения потоков. Для этого используются находящиеся на легенде кнопки-флажки (check-box). Первый из них, называемый Thread State, позволяет указать, интересует ли нас информация о состоянии потока. Напомним, что существуют три типа состояния: активное ( active ), активное ожидание ( spin ), ожидание ( wait ).
Выберите тип группировки по потокам, нажав на панели кнопку с изображением катушки
. После этого в окне появится информация о распределении времени в критическом пути, как показано на рис. 10.17.
(рис 10.17) Анализ состояний потоковИнформация о состоянии потоков представлена в виде столбиков зеленого цвета различной насыщенности. Бледно-зеленый цвет используется для обозначения того, что поток находился в состоянии ожидания, а темно-зеленый цвет соответствует активному состоянию потока. Из рисунка можно узнать, что два потока в нашем приложении (первый и третий столбики) большую часть времени были активны, а еще один поток в основном находился в состоянии ожидания. Нетрудно понять, что в ожидании находился основной поток нашего приложения, в то время как созданные им потоки производили разложение чисел на простые множители. Снимите флажок Thread State и убедитесь, что информация о состоянии потоков исчезнет. После этого верните флажок в исходное положение.
Следующая кнопка-флажок - Critical Path Data, при помощи которой пользователь может указать, интересует ли его разбиение
После этого верните все в исходное положение, в котором установлены флажки Critical Path Data и Concurrency, а флажок Behavior неактивен. В этой конфигурации легенды пользователь имеет возможность анализировать, насколько эффективно его приложение использует ядра процессора ( уровень параллелизма ). Здесь мы имеем дело с категориями времени, про которые было рассказано в разделе 3. В нашем случае можно увидеть, что основную часть
Легенда также несет информационную функцию. Если вы забыли, что означает тот или иной цвет, наведите курсор мыши на прямоугольник соответствующего цвета в легенде. Появится всплывающая подсказка, содержащая информацию о том, что он означает. Кроме того, если цвет считается "проблемным" (свидетельствует о неправильной организации приложения), то в подсказке будут содержаться советы для решения данной проблемы.
На легенде неизученной осталась последняя кнопка-флажок Behavior. Установите ее и снимите флажок Concurrency. В этой конфигурации пользователь имеет возможность определить поведение потока в его активном состоянии, о котором мы говорили ранее в лекции 4. В нашем случае
Установите флажки Concurrency и Behavior, чтобы получить комбинацию двух этих режимов. Тогда
Данная функция очень удобна при анализе
), то можно проанализировать эффективность работы каждого из потоков. Если же включить группировку по объектам (кнопка
), то становится доступной информация об эффективности работы с конкретным объектом (примитивом синхронизации, например).
Рассмотрим наиболее типичные варианты использования группировки. Полезно начать с варианта, когда группировки не используются. Нажмите кнопку с изображением перечеркнутого красного круга
- при этом
(рис 10.18) Изображение критического пути при отключенной группировкеЭтот режим наиболее полезен для получения информации о распределении времени в критическом пути. Используя его можно получить информацию о том, сколько процентов занимает последовательное выполнение (доля оранжевого цвета), сколько параллельное (зеленый, синий и красный цвета), а также какую часть занимают непроизводительные расходы (доля желтого цвета). Попробуйте также другие режимы группировки, анализируя каждый раз изменения
Кроме того, может оказаться полезной возможность вторичной группировки.
В первом наборе кнопок группировки нажмите кнопку с изображением катушки
, а во втором наборе - с буквами CL
. При этом
(рис 10.19) Использование вторичной группировкиВ данном случае мы имеем возможность исследовать, насколько эффективно функционировал каждый из потоков. Мы видим три группы столбиков, каждая из которых соответствует одному потоку. Столбики показывают сколько времени каждый из потоков функционировал в последовательном режиме, сколько в параллельном, а сколько в состоянии ожидания. Как можно увидеть из рисунка, третий поток (левая группа столбиков) большую часть времени работал в последовательном режиме, то есть параллельно с ним не работал ни один другой поток. Второй же поток (правая группа столбиков) практически все время работал параллельно с третьим потоком. Основной же поток приложения (центральная группа столбиков) выполнялся лишь в последовательном режиме и большую часть жизни находился в режиме ожидания дочерних потоков.
Поэкспериментируйте, определяя различные порядки группировки, попытайтесь представить ситуации, в которых они могут быть полезны.
Мы изучили различные способы представления Profile и пришло время изучить способы его анализа.
Выключите группировку, чтобы распределение
(рис 10.20) Анализ критического путиНаведите курсор мыши на столбец и щелкните левой кнопкой один раз. В нижней части окна Profile появится информация о том, сколько времени приложение функционировало в различных режимах - распределение времени по категориям.
Двойной щелчок на столбце позволяет получить более подробную информацию. Попробуем, например, получить полную информацию об участке
(рис 10.21) Обращение к исходному коду приложенияЗдесь мы можем убедиться, что за участок
Чтобы вернуться к исходному представлению
, а затем отмените всякую группировку, нажав кнопку
.
Если имеется необходимость понять, какой участок кода отвечает за некоторый участок
. После этого наведите курсор мыши на интересующий вас участок
Однако не всегда можно спуститься до уровня исходного кода - так случается, если в приложении не содержится отладочная информация. Типичная ситуация - приложение использует вызовы библиотеки, которая была получена в скомпилированном виде.
Итак, окно Profile предоставляет возможности анализа
Окно Timeline содержит три основных элемента, также как и окно Profile: рабочая область, панель инструментов и легенда. Панель инструментов в данном случае предназначена лишь для изменения масштаба временной оси.
В рабочей области окна Timeline содержится информация о поведении потоков. В верхней части рабочей области имеется временная шкала. Ниже располагаются полосы, каждая из которых соответствует одному потоку приложения.
(рис 10.22) Окно TimelineНа рис. 10.22 показано, что в нашем приложении было запущено три потока. Также можно увидеть, что один из них существовал с начала времени выполнения приложения, а два других были созданы позже. Время жизни потока равно длине полосы. Раскраска полосы не всегда одинакова - она указывает на состояния потока в различные моменты времени (см. легенду). Наведите курсор мыши на бледно-зеленую часть самой верхней полосы, соответствующей основному потоку. В появившейся подсказке будет сказано, что на этом промежутке времени поток находился в режиме ожидания.
Рассмотрим подробнее момент возникновения потоков. Для этого в рабочей области необходимо выделить область, охватывающую розовую стрелку. Наведите курсор мыши немного левее стрелки, зажмите левую клавишу и, передвиньте курсор правее стрелки, после чего отпустите кнопку мыши. Повторите увеличение еще несколько раз, пока вид в рабочей области не станет таким, как показано на рис. 10.23.
(рис 10.23) Момент создания потоковЗдесь уже можно видеть, что второй поток был создан несколько позже первого. Наведите курсор мыши на одну из розовых стрелок и убедитесь, что она соответствует вызову функции создания потока. Длина этой стрелки, спроектированная на ось времени, показывает время задержки между вызовом функции создания потока и фактическим его запуском.
Кроме того, поверх зеленых полос, указывающих состояние потоков, яркими цветами накладывается информация о критическом пути. На изучаемом нами промежутке времени имеются участки последовательного и параллельного исполнения потоков. Нетрудно понять, что оранжевый участок соответствует интервалу времени, когда в приложении существовал только один поток, а участок зеленого цвета соответствует одновременной работе двух потоков.
Вернемся к изучению трассы приложения в целом. Нажмите на панели инструментов окна . Рабочая область снова должна принять вид как на рис. 10.21.
Глядя на этот рисунок, можно сразу понять, в чем причины того, что основную часть времени наше приложение работает в последовательном режиме. Мы неравномерно распределили вычислительную нагрузку между потоками. Второй из дочерних потоков работает гораздо дольше первого, в результате чего увеличивается суммарное время работы приложения. Таким образом, если мы хотим повысить производительность нашего приложения, то первое, что мы должны сделать, это распределить нагрузку между потоками равномерно. Тогда время работы приложения сократится за счет того, что часть нагрузки второго дочернего потока будет отдана первому.
Последнее, что осталось изучить в рабочей области окна Timeline - это как из него получить доступ к исходному коду. Наведите курсор мыши на любую из полос, соответствующих дочерним потокам, и нажмите правую кнопку мыши. В появившемся контекстном меню выберите пункт Thread Creation/Entry Source View. Откроется окно с исходным кодом, отвечающим за создание потока.
Вернитесь к окну Timeline и наведите курсор на бледно-зеленую часть полосы, соответствующей основному потоку приложения, нажмите правую кнопку мыши и выберите пункт Transition Source View. Откроется окно с исходным кодом, в котором указано место ожидания основного потока (рис. 10.24).
(рис 10.24) Место ожидания основного потокаВ нашем случае это вызов функции WaitForMultipleObjects, которая необходима здесь для ожидания завершения дочерних потоков.
Вернемся к окну Timeline и рассмотрим легенду.
Общая структура и функции легенды такие же, как и в окне Profile, поэтому мы не будем останавливаться на кнопках-флажках Thread State и Critical Path Data. Вы можете поэкспериментировать с ними и проследить, как меняется вид в рабочей области окна Timeline.
Рассмотрим назначение новых кнопок-флажков. Первый из них имеет название Transitions и отвечает за отображение в рабочей области стрелок, соответствующих посылке сигналов между потоками. Снимите флажок Fork/Join - останутся три стрелки желтого цвета, показывающие посылаемые в приложении сигналы. В нашем случае это сигналы, связанные с созданием и завершением потоков.
Теперь снимите флажок Transitions и установите флажок Fork/Join. Должны появиться стрелки, указывающие на вызовы функций класса Fork/Join (создание и ожидание завершения потоков). Можно заметить, что они совпадают с желтыми стрелками, которые мы видели до этого. Единственное отличие - появление розовой стрелки, соединяющей конец полосы первого дочернего потока с полосой основного потока.
Последний флажок называется User Event. Мы не станем рассматривать его, подробная информация о нем может быть найдена в [10.7].
Таким образом, мы выяснили, что наше приложение содержит поток, большую часть времени работающий в последовательном режиме. Причины такой ситуации и способы увеличения производительности нашего примера мы рассмотрим в лабораторной работе 1.
Profile установите первичную группировку по потокам, а вторичную по уровню параллелизма. Установите соответствие между столбцами в окне Profile и полосами в окне Timeline .Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.