Оптимизация параллельной программы
10.5. Основные концепции и понятия профилирования
10.5.1. Понятие критического пути
Первое понятие, которое нам понадобится - критический путь (critical path). Это понятие является ключом к пониманию всего процесса профилирования, поэтому очень важно хорошо усвоить его. Прежде чем дать формальное определение этого понятия, попытаемся понять его суть на примере.
Представим себе следующее многопоточное приложение: имеется основной поток, который подготавливает данные и производит их первичную обработку, а также один дочерний поток, который завершает обработку и выводит результаты. Простейшая иллюстрация этого примера - физическое моделирование, в котором основной поток занят вычислениями (например, решением разностной схемы), а дочерний - подготовкой к визуализации и непосредственно визуализацией результатов вычислений. Часто подобные взаимодействия реализуются в виде конвейера, поскольку в то время как один из потоков работает с устройствами ввода-вывода, второй поток может нагружать процессор вычислениями.
В нашем случае, как только первый поток вычислил первую порцию данных для отображения, он может начинать вычисление следующей порции, а второй поток параллельно с этим начнет визуализацию. Важно при этом обеспечить синхронизацию между потоками, чтобы не допустить уничтожения данных до того как они были визуализированы.
Ниже графически представлено взаимодействие потоков в случае, если бы требовалось выполнить только две итерации. Потокам в нашем приложении соответствуют горизонтальные линии, сплошные участки соответствуют активному состоянию потоков, а пунктирные - состоянию ожидания. Диагональные линии соответствуют сигналам, передаваемым между потоками, а проекции этих линий на ось времени соответствует временам прохождения сигналов. Кружками обозначены все события, в результате которых изменяются состояния потоков (создание/завершение, блокировка/активизация).
Рассмотрим диаграмму подробнее. В начальный момент времени существовал только один поток, выполняющийся последовательно. Затем в момент времени t1 происходит создание дочернего потока, который начинает функционировать в момент времени t2, но сразу попадает в состояние ожидания, поскольку данные для визуализации еще не подготовлены. Затем в точке t3 завершаются вычисления, и дочерний поток начинает визуализацию подготовленных данных, которая продолжается с момента t4 до момента t7. Заметим, что второй поток может освободить массивы с результатами вычислений сразу после того, как переведет их в формат, необходимый для графического представления (момент времени t5 ). За счет этого и достигается распараллеливание в рассматриваемом приложении. Можно видеть, что потоки работают параллельно на промежутке времени между моментами t6 и t7 . Далее осуществляется еще одна итерация, после чего в момент времени t12 второй поток завершает свое исполнение, а в момент времени t14 завершается весь процесс.
Рассмотренная диаграмма представляет собой граф. Вершинам, как уже говорилось, соответствуют события, изменяющие состояние потоков, а ребрам - действия, выполняемые потоками (мы не учитываем пунктирные линии). При этом ребрам можно сопоставить вес, равный времени, затрачиваемому на выполнение действия. Путем приложения называется любой путь в указанном графе, соединяющий вершины, соответствующие началу и завершению приложения. Критическим путем приложения называется путь, имеющий максимальную сумму весов входящих в него ребер. Приложения со многими потоками могут иметь большое число путей, однако в нашем случае приложение имеет всего два пути, каждый из которых можно назвать критическим, так как суммы их весов совпадают.
Важность понятия критического пути определяется тем, что сумма весов ребер в критическом пути равна времени выполнения приложения. Из этого факта следует следующий принцип: необходимо оптимизировать прежде всего те участки приложений, которые вошли в критический путь, поскольку именно они определяют производительность.
Таким образом, профилирование и оптимизация приложения неразрывно связаны с процессом анализа критического пути. Критический путь - это первое, на что нужно обращать внимание, поскольку не входящие в него участки приложения вносят меньший вклад в суммарное время работы приложения. Далее мы увидим, что ITP позволяет строить критический путь приложения и предоставляет различные средства для его анализа.
10.5.2. Состояния потоков
При анализе критического пути пользуются таким понятием как состояние потока (thread state). А именно, поток может находиться в трех состояниях:
- Активное состояние (active) - поток исполняется в настоящее время;
- Состояние активного ожидания (spin) - постоянно повторяемая проверка состояния блокировки(например, при использовании pthread_spin_lock() );
- Состояние ожидания (wait) - ожидание завершения какой-либо блокирующей операции (например, операции ввода-вывода).
10.5.3. Понятие категорий времени
Рассмотрим теперь характеристики эффективности использования аппаратных ресурсов активными потоками приложения. Для этого в ITP используются два параметра: уровень параллелизма (concurrency) и поведение (behavior).
Уровни параллелизма используются для обозначения того, насколько полно потоки нагружают процессоры вычислительного узла. Так, одновременная работа двух потоков на двухъядерном процессоре означает эффективное использование его ресурсов ( full utilization ), на одноядерном - сверхиспользование ( over utilization ), а на четырехъядерном - недостаточно эффективное использование ( under utilization ).
Существует несколько типов поведения потока в активном состоянии:
- Сдерживание (impact) - ситуация, когда поток сдерживает выполнение другого потока (например, поток занял мьютекс, освобождения которого ожидают другие потоки);
- Блокировка (blocking) - приостановка потока в результате вызова блокирующих функций (например, операций ввода/вывода);
- Критический путь (critical path) - ситуация, когда поток выполняет участок кода, входящий в критический путь, при отсутствии других ожидающих потоков.
Уровень параллелизма и поведение независимы друг от друга, и в совокупности они называются категориями времени (time categories) и характеризуют эффективность работы активных потоков. Собственно весь анализ производительности состоит в том, чтобы оценить каждый участок критического пути по этим двум параметрам и определить наиболее проблемные участки приложения.
В ITP используется несколько видов представления критического пути, каждое из которых мы впоследствии подробно изучим. Для удобства анализа каждый участок критического пути окрашивается в определенный цвет в зависимости от уровня параллелизма и поведения потока на этом участке. На рис. 10.3 представлена сводная таблица используемых цветов. n обозначает число активных потоков приложения, а p - число процессоров (ядер).
Рассмотрим приведенную таблицу подробнее, чтобы знать впоследствии, на какие цвета нужно обращать внимание прежде всего. Сначала исследуем связь числа потоков и числа ядер. Как видно из таблицы, для обозначения этого соотношения используется четыре цвета: оранжевый, красный, зеленый и синий. Зеленый цвет - признак оптимального соотношения, когда число ядер и потоков совпадает. Синий цвет используется для обозначения ситуаций, когда одновременно работает больше потоков, чем доступно ядер. Как результат, потоки могут мешать работе друг друга. Присутствие красного и оранжевого цветов в окраске критического пути чаще всего свидетельствует о необходимости перестройки приложения. Эти цвета означают, что используются не все ядра процессора и можно получить дополнительное ускорение за счет более эффективного распараллеливания.
Рассмотрим теперь второй параметр - тип поведения потоков. Для обозначения типа поведения потока цветом используется изменение яркости. При этом используется простое правило: чем ярче цвет участка, тем большего внимания от разработчика он требует. Самый яркий цвет используется для указания impact -состояния потока, поскольку прежде всего необходимо сокращать время ожидания потоков друг другом. Далее по важности следует blocking -состояние, которое тоже требует пристального контроля, так как нужно минимизировать время ожидания потоков. Последним идет состояние critical path.
Имеется особая категория времени, обозначаемая желтым цветом и указывающая на непроизводительные издержки ( overhead ) в многопоточном приложении. Под непроизводительными издержками понимаются накладные расходы на создание потоков, управление и синхронизацию между потоками. Другими словами, эта величина показывает количество процессорного времени, израсходованного на поддержку работы потоков. Чаще всего большая доля желтого цвета в критическом пути свидетельствует о чрезмерно частом вызове функций синхронизации и необходимости перепроектирования приложения.
Теперь, зная о категориях времени, мы можем сказать, какие цвета будут содержаться на критическом пути нашего примера из подраздела 3.1.
Рис. 10.4. Пример раскраски критического пути многопоточного приложения в соответствии с категориями времени
Итак, подведем итоги: профилирование многопоточного приложения основано на построении критического пути и его анализе с точки зрения эффективности использования процессора. Для этого выделяют категории времени, часть из которых считается проблемными и свидетельствует о неправильной организации многопоточного приложения. Деятельность разработчика, таким образом, состоит в нахождении проблемных участков критического пути, выявлении причин их появления (соотнесение с исходным кодом) и в последующем их разрешении.