Россия, г. Саранск |
Отладка параллельной программы
В предыдущих разделах рассматривались следующие вопросы построения кластера и создания программ для запуска на кластере:
- аппаратной основы кластера;
- системного обеспечения, необходимого для работы кластера;
- оценка эффективности построенного кластера;
- среды разработки параллельных программ.
Одним из основных этапов разработки программ является - отладка. Отладка параллельных программ является существенно более сложной задачей, чем отладка последовательных, поэтому в данном разделе уделено отдельное внимание отладки параллельных программ.
Ни для кого не секрет, наличие инструментов делает жизнь проще. Тезис этот находит подтверждение как в повседневной жизни - чистить одежду удобнее и эффективнее щеткой, чем руками, заворачивать гайки гораздо проще ключом, чем пальцами - так и в профессиональной деятельности - кто сейчас возьмется строить, ладно, даже не дом, пусть всего лишь баню, при помощи одного лишь топора. Естественно программисты - не исключение. Значение инструментов, облегчающих путь от анализа постановки задачи до получения решения, готового к внедрению, трудно переоценить. Данный документ представляет собой краткое описание инструмента отладки параллельных программ, разработанного корпорацией Intel и носящего название Intel® Thread Checker.
В первом разделе документа приводится назначение рассматриваемого инструмента, характеризуются области его возможного применения. Во втором дается краткая характеристика принципов работы Intel® Thread Checker. В третьем и четвертом разделах приводится информация, необходимая для подготовки пользовательского проекта и инструмента для анализа. Пятый раздел посвящен вопросам сбора и анализа данных, полученных в результате работы Intel® Thread Checker. В шестом возможности инструмента рассмотрены на простом примере, входящем в поставку Intel® Thread Checker.
Итак, приступим.
9.1. Назначение Intel Thread Checker
Процесс отладки в общем случае можно разбить на следующие шаги:
- определение факта наличия ошибки;
- поиск (локализация) ошибки;
- выяснение причин ошибки;
- определение способа устранения ошибки;
- устранение ошибки.
Кажется, что на первом шаге никакой инструмент не требуется. Запускаем программу и либо на некоторых исходных данных получаем неверные результаты, либо обнаруживаем, что некоторая последовательность действий по использованию программы ведет к ее "падению" или "зависанию". Однако для параллельной программы даже этот очевидный шаг может иметь существенную сложность. На практике нередко встречаются ситуации, когда неработоспособность параллельной программы проявляется один раз на сотню и более запусков. Очевидно, в этом случае инструментальная поддержка лишней не будет.
Назначение Intel® Thread Checker (ITC) - поиск мест с возможным недетерминированным поведением многопоточной программы, написанной как на основе библиотеки потоков (Windows или POSIX threads), так и с использование технологии OpenMP. Соответственно ITC может быть использован как под операционными системами семейства Windows, так и под различными ОС семейства Linux. Принципы поиска ошибок рассмотрены ниже, здесь же укажем, что ITC неплохо справляется с задачей обнаружения факта ошибки в программе, даже если эта ошибка в текущем варианте исполнения программы и не проявила себя.
Второй шаг - поиск ошибки заключается в необходимости как можно более точной ее локализации, в идеале должна быть найдена переменная с неверным значением и/или строка кода, ведущая к краху программы. Типичный метод работы в этом пункте - использование режима трассировки в отладчике с наблюдением за состоянием переменных, регистров, стека вызова и т.д. "Плохая новость" - для многопоточных программ режим трассировки практически неприменим, поскольку автоматически меняет характер их выполнения, а значит, скрывает места, которые могут приводить к проблемам во время реальной работы. Кстати говоря, даже типичный способ локализации ошибки расстановкой операторов печати по тексту программы в этом случае нужно использовать с большой осторожностью - печать также вносит синхронизацию в выполнение программы.
Что же делать? Быть может, наилучшее из возможных решение реализовано в ITC. ITC не есть привычный всем отладчик с режимами трассировки, наблюдения и т.д. ITC выполняет анализ программы сам, без участия программиста, причем анализируется не только выполненный "прогон" программы, а все возможные варианты ее выполнения. В результате выясняются и показываются программисту места в программе, в которых содержатся ошибки (с той или иной долей вероятности, в большинстве случаев близкой к 100%).
Шаг третий - выяснение причин ошибки. Задача здесь - понять, почему ошибка возникла. Отсюда во многих случаях автоматически вытекает способ ее устранения (задача шага четвертого). Можно, конечно, выяснить условия, ведущие к проявлению ошибки (некое сочетание значений переменных, например) и просто вставить в код заплатку именно для этого случая. Данный вариант мы здесь не рассматриваем. На этом шаге ITC помогает тем, что каждое найденное им проблемное место сопровождает комментарием, содержащим тип ошибки: гонка данных, несинхронизированный доступ к переменной, тупик и т.д.
В результате мы получаем место потенциальной ошибки, переменную, с которой связана проблема и описание ошибки. Остается лишь освоить типовые способы борьбы с типовыми ошибками и значительная их часть будет находиться и исправляться без грандиозных усилий.
Единственное в чем ITC совсем не может помочь - это шаг пятый. Устранять найденную ошибку все-таки придется программисту самостоятельно.
9.2. Возможности Intel Thread Checker
Согласно [9.2] ITC обнаруживает ошибки следующих видов: гонки данных ( data races ), тупики ( deadlocks ), потоки в состоянии ожидания ( stalled threads ), потерянные сигналы ( lost signals ), заброшенные замки ( abandoned locks ).
Приведем краткое описание каждого вида.
- Гонки данных. Возникают, когда несколько потоков работают с разделяемыми данными и конечный результат зависит от соотношения скоростей потоков. Пусть, например, один поток выполняет над общей переменной x операцию x = x + 3, а второй поток - операцию x = x + 5. Данные операции для каждого потока фактически разбиваются на три отдельных подоперации: считать x из памяти, увеличить x, записать x в память. В зависимости от взаимного порядка выполнения потоками подопераций финальное значение переменной x может быть больше исходного на 3, 5 или 8. Гонка данных возможна и в случае, когда один поток пишет в переменную, а остальные только читают из нее.
- Тупики. Взаимная блокировка потоков, ожидающих наступление некоторого события для продолжения работы. Типичный пример тупика, когда нулевой поток занял для использования ресурс 1 и ожидает предоставления ему ресурса 2, а первый поток занял ресурс 2 и ожидает предоставления ему ресурса 1.
- Потоки в состоянии ожидания. Одно из состояний потока в многозадачной операционной системе - ожидание. Поток переходит в него, когда для продолжения выполнения ему требуется наступление некоторого внешнего события. Если пребывание потока в этом состоянии продолжается слишком долго, ITC рапортует об ошибке типа stalled thread. Интервал времени, по истечении которого выдается данная диагностика, может быть задан в настройках ITC.
- Потерянные сигналы. Возникают, когда поток ожидает наступление некоторого события, произошедшего прежде, чем поток пришел в состояние готовности к его приему и обработке. В результате поток никогда не сможет выйти из состояния ожидания.
- Заброшенные замки. Возникают в ситуации, когда поток захватил некоторый ресурс (критическую секцию, мьютекс) и был снят с выполнения по той или иной причине. В результате ресурс не может быть освобожден. Если он требуется другому потоку, это приведет к бесконечному ожиданию.
9.3. Принцип сбора информации
Анализ программы, выполняемый ITC, основан на процедуре инструментации. Инструментация - вставка обращений к библиотеке ITC для записи действий, потенциально способных привести к ошибкам: работа с памятью, вызовы операций синхронизации и работа с потоками [9.2]. Может выполняться автоматически на уровне исполняемого модуля (а также dll-библиотеки) и/или по указанию программиста на уровне исходного кода. Для достоверности получаемых результатов крайне желательно, чтобы во время сборки анализируемой программы была выключена оптимизация (сборка в конфигурации debug не обязательна).
В процессе анализа контролируется:
- доступ к памяти;
- операции синхронизации;
- операции создания потоков.
Необходимо отметить, что неисполняемые участки (не вызываемые функции, ветки условных переходов и т.д.) никак не проверяются, то есть под анализ не подпадают.
9.4. Подготовка программы для анализа
Использование отладчика Intel® Thread Checker возможно в двух режимах:
- Бинарная инструментация программы - осуществляется автоматически в момент запуска Активности ( Activity ) в проекте ITC. Рекомендуется в случае, если отсутствует доступ к исходным кодам или невозможна повторная сборка программы с нужными ITC настройками.
-
Компиляторная инструментация - при сборке анализируемой программы необходимо указать ключ компилятора /Qtcheck. Позволяет ITC предоставить информацию о найденных ошибках с указанием имен переменных, с которыми эти ошибки связаны.
Сборка приложения для работы с ITC предполагает установку следующих опций проекта (или настроек в make-файле):
- Компиляция потоко-безопасного кода: -MT[d], -MD[d]. Данные опции автоматически устанавливаются при сборке в конфигурации debug. При сборке в конфигурации release указанные опции необходимо устанавливать вручную.
- Использование debug опций: -Z[i,I,7], -Od. Замечание аналогичное предыдущему пункту.
- Связывание с ключом /fixed:no. Необходимо указывать явно.
Дополнительно необходимо отметить, что при использовании ключа /Qtcheck в среде разработки (IDE) требуется указать путь к библиотекам ITC. Обычно этот путь имеет вид C:\Program Files\Intel\VTune\Analyzer\Lib\.
9.5. Создание проекта в Intel Thread Checker
Работа в ITC выполняется в рамках проекта. Для его создания используется команда меню File->New Project. В главном окне мастера настройки проекта (см. рис. 9.1) необходимо выполнить всего лишь два действия. Первое - указать исполняемый файл ( Launch an application ). Второе (необязательное) - указать аргументы командной строки.
Рекомендуется при анализе программы, с одной стороны, использовать типичные размеры обрабатываемых данных, с другой, задавать их так, чтобы программа "убиралась" в оперативную память с учетом накладных расходов ITC, которые могут быть довольно значительными.
9.6. Сбор и анализ данных
После запуска в проекте ITC активности (при создании проекта это происходит автоматически) начинается инструментация исполняемого модуля, указанного для анализа, и используемых им динамических библиотек. Затем модуль запускается и начинается процесс анализа. По завершении ITC формирует окно с информацией о найденных ошибках и подозрительных местах. Возможный его вид указан на рис. 9.2.
В случае повторного запуска активности необходимо использовать один из следующих вариантов:
- выбрать пункт меню Activity Run,
- нажать F5,
- нажать кнопкуна панели инструментов.
По каждой диагностике, выданной ITC, в случае если сборка выполнялась с приведенными в разделе 4 настройками, может быть получена дополнительная информация (см. раздел 7), вид которой показан на рис. 9.3.