В предыдущих разделах рассматривались следующие вопросы построения кластера и создания программ для запуска на кластере:
Одним из основных этапов разработки программ является - отладка. Отладка параллельных программ является существенно более сложной задачей, чем отладка последовательных, поэтому в данном разделе уделено отдельное внимание отладки параллельных программ.
Ни для кого не секрет, наличие инструментов делает жизнь проще. Тезис этот находит подтверждение как в повседневной жизни - чистить одежду удобнее и эффективнее щеткой, чем руками, заворачивать гайки гораздо проще ключом, чем пальцами - так и в профессиональной деятельности - кто сейчас возьмется строить, ладно, даже не дом, пусть всего лишь баню, при помощи одного лишь топора. Естественно программисты - не исключение. Значение инструментов, облегчающих путь от анализа постановки задачи до получения решения, готового к внедрению, трудно переоценить. Данный документ представляет собой краткое описание инструмента отладки параллельных программ, разработанного корпорацией Intel и носящего название Intel® Thread
В первом разделе документа приводится назначение рассматриваемого инструмента, характеризуются области его возможного применения. Во втором дается краткая характеристика принципов работы Intel® Thread
Итак, приступим.
Процесс отладки в общем случае можно разбить на следующие шаги:
Кажется, что на первом шаге никакой инструмент не требуется. Запускаем программу и либо на некоторых исходных данных получаем неверные результаты, либо обнаруживаем, что некоторая последовательность действий по использованию программы ведет к ее "падению" или "зависанию". Однако для параллельной программы даже этот очевидный шаг может иметь существенную сложность. На практике нередко встречаются ситуации, когда неработоспособность параллельной программы проявляется один раз на сотню и более запусков. Очевидно, в этом случае инструментальная поддержка лишней не будет.
Назначение Intel® Thread
Второй шаг - поиск ошибки заключается в необходимости как можно более точной ее локализации, в идеале должна быть найдена переменная с неверным значением и/или строка кода, ведущая к краху программы. Типичный метод работы в этом пункте - использование режима трассировки в отладчике с наблюдением за состоянием переменных, регистров,
Что же делать? Быть может, наилучшее из возможных решение реализовано в
Шаг третий - выяснение причин ошибки. Задача здесь - понять, почему ошибка возникла. Отсюда во многих случаях автоматически вытекает способ ее устранения (задача шага четвертого). Можно, конечно, выяснить условия, ведущие к проявлению ошибки (некое сочетание значений переменных, например) и просто вставить в код
В результате мы получаем место потенциальной ошибки, переменную, с которой связана проблема и описание ошибки. Остается лишь освоить типовые способы борьбы с типовыми ошибками и значительная их часть будет находиться и исправляться без грандиозных усилий.
Единственное в чем
Согласно [9.2]
Приведем краткое описание каждого вида.
x операцию x = x + 3, а второй поток - операцию x = x + 5. Данные операции для каждого потока фактически разбиваются на три отдельных подоперации: считать x из памяти, увеличить x, записать x в память. В зависимости от взаимного порядка выполнения потоками подопераций финальное значение переменной x может быть больше исходного на 3, 5 или 8. Гонка данных возможна и в случае, когда один поток пишет в переменную, а остальные только читают из нее.Анализ программы, выполняемый debug не обязательна).
В процессе анализа контролируется:
Необходимо отметить, что неисполняемые участки (не вызываемые функции, ветки
Использование отладчика Intel® Thread
Активности ( Activity ) в проекте /Qtcheck. Позволяет Сборка приложения для работы с
-MT[d], -MD [d]. Данные опции автоматически устанавливаются при сборке в конфигурации debug. При сборке в конфигурации release указанные опции необходимо устанавливать вручную.-Z[i,I,7], -Od. Замечание аналогичное предыдущему пункту./fixed:no. Необходимо указывать явно.Дополнительно необходимо отметить, что при использовании ключа /Qtcheck в среде разработки (IDE) требуется указать путь к библиотекам C:\Program Files\Intel\VTune\Analyzer\Lib\.
Работа в проекта. Для его создания используется команда меню File->New Project. В главном окне мастера настройки проекта (см. рис. 9.1) необходимо выполнить всего лишь два действия. Первое - указать исполняемый файл ( ). Второе (необязательное) - указать
(рис 9.1) Мастер настройки проекта в Intel Thread Checker (версия 3.0)Рекомендуется при анализе программы, с одной стороны, использовать типичные размеры обрабатываемых данных, с другой, задавать их так, чтобы программа "убиралась" в оперативную память с учетом накладных расходов
После запуска в проекте
В случае повторного запуска активности необходимо использовать один из следующих вариантов:
на панели инструментов.
(рис 9.2) Результат анализа - список диагностикиПо каждой диагностике, выданной
(рис 9.3) Результат анализа - диагностика в исходном кодеДля начального знакомства с
Наличие у каждого потока в многопоточном приложении доступа к общему ВАП процесса позволяет потокам эффективно обмениваться данными, с одной стороны, и является основным источником ошибок, с другой. Гонки данных, они же конфликты доступа ( storage conflicts ), поджидают зазевавшегося программиста буквально на каждом шагу. Поиск таких ошибок методом "пристального взгляда" - задача весьма сложная.
Рассматриваемый пример создает 4 потока, каждый из которых увеличивает значение общей глобальной переменной globalX и использует
Откройте проект DataRaces, последовательно выполняя следующие шаги:
File выполните команду Open $$\to$$ Project/Solution…,C:\ITCLabs\DataRaces,DataRaces.dsw или, выбрав файл, выполните команду Open ;DataRaces.dsp в новый формат.После открытия проекта в окне Solution Explorer дважды щелкните на файле исходного кода DataRaces.с, как это показано на рис. 9.4. После этих действий программный код, с которым предстоит работать, будет открыт в
(рис 9.4) Открытие файла DataRaces.сИнтерес в этом небольшом файле представляет потоковая функция .
int globalX = 0;
DWORD WINAPI increment (void *arg)
{
CRITICAL_SECTION cs;
InitializeCriticalSection (cs);
EnterCriticalSection (cs);
globalX++;
LeaveCriticalSection (cs);
DeleteCriticalSection (cs);
return 0;
}
На первый взгляд код выглядит корректно. Доступ к переменной globalX защищен. Посмотрим, что покажет запуск программы.
Соберите и запустите пример, выполнив следующие действия:
/Qtcheck ;Убедитесь, что вывод на экран соответствует представленному на рис. 9.5.
(рис 9.5) Результаты работы примера DataRaces (исходный вариант)Кажется, все верно. Число потоков равно четырем. Начальное значение переменной globalX, как нетрудно убедиться, равно нулю. Таким образом, результат верен. Повторите запуск программы несколько раз и убедитесь, что результат стабильно равен 4.
Может быть, программа корректна? Подумаем, что нужно для того, чтобы гонки данных могли проявиться. Необходимо, чтобы имел место разный порядок выполнения потоков во времени. Посмотрим, возможно ли это в данном примере. Конечно же, нет! Вот участок функции main, запускающий потоки:
for (i = 0; i < NTHREADS; i++)
{
h[i] = CreateThread (0, 0, increment, NULL, 0, NULL);
}
Нетрудно понять, что потоки создаются последовательно, по мере выполнения цикла. Учитывая крайнюю простоту, а значит, и малое время выполнения потоковой функции, скорее всего и работа потоков происходит последовательно. globalX, даже если он имеет место, не успевает проявиться.
Что ж, изменим немного код. Пусть каждый поток увеличивает значение переменной globalX не один раз, а многократно.
DWORD WINAPI increment (void *arg)
{
int i;
CRITICAL_SECTION cs;
InitializeCriticalSection (cs);
for (i = 0; i < 10000; i++)
{
EnterCriticalSection (cs);
globalX++;
LeaveCriticalSection (cs);
}
DeleteCriticalSection (cs);
return 0;
}
Добавьте в код выделенные в предыдущем фрагменте строки, соберите и запустите пример. Убедитесь, что вывод на экран соответствует представленному на рис. 9.6.
(рис 9.6) Результаты работы примера DataRaces (версия с циклом) Не правда ли, неожиданно?! Вместо 40000 на экране совсем другое число! Повторите запуск несколько раз и убедитесь, что результат работы программы будет меняться. Типичная ситуация для гонки данных.
Итак, приложив некоторые усилия, мы прошли два этапа в рассмотренном в п. 1 процессе отладки. Наличие ошибки обнаружено, локализация ее также не вызывает сложностей в силу малого размера кода в примере. Осталось понять, в чем же причина ошибки. Но прежде посмотрим, как с ее обнаружением справится
Выполните следующие действия для подготовки программы к анализу.
/Qtcheck.
(рис 9.11) Установка компиляторного режима инструментацииПосле выполнения указанных действий программа готова к анализу в
После этого запустится
По окончании процесса сбора информации globalX. Краткие комментарии к диагностикам показывают, что globalX:
globalX, а второй в это время читает из нее;globalX,а второй в это время в нее пишет;globalX.Несмотря на то, что реально работало 4 потока, очевидно, что для демонстрации ошибки достаточно двух. Именно так
(рис 9.14) Результат анализа примера DataRaces - DiagnosticsПри наличии отладочной информации
(рис 9.15) Результат анализа примера DataRaces - Source ViewКомментарий в красном поле над исходными текстами описывает ситуацию. В представленном на рисунке случае имеет место
Осталась самая малость - понять, в чем причина гонки данных в рассматриваемом примере, и устранить ее. Сделать это на самом деле не так уж сложно. Противоречие с тем фактом, что обращение потоков к переменной globalX происходит внутри CRITICAL_SECTION cs ). Этот объект объявлен внутри потоковой функции, а значит, является локальным для каждого потока. То есть, когда один поток захватывает cs, к которому остальные потоки не имеют доступа.
Исправить ситуацию можно несколькими способами, но в любом из них объявление объекта cs нужно вынести из потоковой функции , а инициализацию секции и main.
Возможный корректный вариант представлен ниже.
#include <stdio.h>
#include <windows.h>
#define NTHREADS 4
int globalX = 0;
CRITICAL_SECTION cs;
DWORD WINAPI increment (void *arg)
{
EnterCriticalSection (cs);
globalX++;
LeaveCriticalSection (cs);
return 0;
}
int main (int argc, char *argv[])
{
HANDLE h[NTHREADS];
DWORD rc;
int i;
printf ("START\n");
InitializeCriticalSection (cs);
for (i = 0; i < NTHREADS; i++)
{
h[i] = CreateThread (0, 0, increment, NULL, 0, NULL);
}
rc = WaitForMultipleObjects (NTHREADS, h, TRUE, INFINITE);
DeleteCriticalSection (cs);
printf ("TOTAL = %d\n", globalX);
printf ("STOP\n");
}
Ни для кого не секрет, наличие инструментов делает жизнь проще. Перефразируем: наличие Intel® Thread
В предыдущих разделах рассматривались следующие вопросы построения кластера и создания программ для запуска на кластере:
Одним из основных этапов разработки программ является - отладка. Отладка параллельных программ является существенно более сложной задачей, чем отладка последовательных, поэтому в данном разделе уделено отдельное внимание отладки параллельных программ.
Ни для кого не секрет, наличие инструментов делает жизнь проще. Тезис этот находит подтверждение как в повседневной жизни - чистить одежду удобнее и эффективнее щеткой, чем руками, заворачивать гайки гораздо проще ключом, чем пальцами - так и в профессиональной деятельности - кто сейчас возьмется строить, ладно, даже не дом, пусть всего лишь баню, при помощи одного лишь топора. Естественно программисты - не исключение. Значение инструментов, облегчающих путь от анализа постановки задачи до получения решения, готового к внедрению, трудно переоценить. Данный документ представляет собой краткое описание инструмента отладки параллельных программ, разработанного корпорацией Intel и носящего название Intel® Thread
В первом разделе документа приводится назначение рассматриваемого инструмента, характеризуются области его возможного применения. Во втором дается краткая характеристика принципов работы Intel® Thread
Итак, приступим.
Процесс отладки в общем случае можно разбить на следующие шаги:
Кажется, что на первом шаге никакой инструмент не требуется. Запускаем программу и либо на некоторых исходных данных получаем неверные результаты, либо обнаруживаем, что некоторая последовательность действий по использованию программы ведет к ее "падению" или "зависанию". Однако для параллельной программы даже этот очевидный шаг может иметь существенную сложность. На практике нередко встречаются ситуации, когда неработоспособность параллельной программы проявляется один раз на сотню и более запусков. Очевидно, в этом случае инструментальная поддержка лишней не будет.
Назначение Intel® Thread
Второй шаг - поиск ошибки заключается в необходимости как можно более точной ее локализации, в идеале должна быть найдена переменная с неверным значением и/или строка кода, ведущая к краху программы. Типичный метод работы в этом пункте - использование режима трассировки в отладчике с наблюдением за состоянием переменных, регистров,
Что же делать? Быть может, наилучшее из возможных решение реализовано в
Шаг третий - выяснение причин ошибки. Задача здесь - понять, почему ошибка возникла. Отсюда во многих случаях автоматически вытекает способ ее устранения (задача шага четвертого). Можно, конечно, выяснить условия, ведущие к проявлению ошибки (некое сочетание значений переменных, например) и просто вставить в код
В результате мы получаем место потенциальной ошибки, переменную, с которой связана проблема и описание ошибки. Остается лишь освоить типовые способы борьбы с типовыми ошибками и значительная их часть будет находиться и исправляться без грандиозных усилий.
Единственное в чем
Согласно [9.2]
Приведем краткое описание каждого вида.
x операцию x = x + 3, а второй поток - операцию x = x + 5. Данные операции для каждого потока фактически разбиваются на три отдельных подоперации: считать x из памяти, увеличить x, записать x в память. В зависимости от взаимного порядка выполнения потоками подопераций финальное значение переменной x может быть больше исходного на 3, 5 или 8. Гонка данных возможна и в случае, когда один поток пишет в переменную, а остальные только читают из нее.Анализ программы, выполняемый debug не обязательна).
В процессе анализа контролируется:
Необходимо отметить, что неисполняемые участки (не вызываемые функции, ветки
Использование отладчика Intel® Thread
Активности ( Activity ) в проекте /Qtcheck. Позволяет Сборка приложения для работы с
-MT[d], -MD [d]. Данные опции автоматически устанавливаются при сборке в конфигурации debug. При сборке в конфигурации release указанные опции необходимо устанавливать вручную.-Z[i,I,7], -Od. Замечание аналогичное предыдущему пункту./fixed:no. Необходимо указывать явно.Дополнительно необходимо отметить, что при использовании ключа /Qtcheck в среде разработки (IDE) требуется указать путь к библиотекам C:\Program Files\Intel\VTune\Analyzer\Lib\.
Работа в проекта. Для его создания используется команда меню File->New Project. В главном окне мастера настройки проекта (см. рис. 9.1) необходимо выполнить всего лишь два действия. Первое - указать исполняемый файл ( ). Второе (необязательное) - указать
(рис 9.1) Мастер настройки проекта в Intel Thread Checker (версия 3.0)Рекомендуется при анализе программы, с одной стороны, использовать типичные размеры обрабатываемых данных, с другой, задавать их так, чтобы программа "убиралась" в оперативную память с учетом накладных расходов
После запуска в проекте
В случае повторного запуска активности необходимо использовать один из следующих вариантов:
на панели инструментов.
(рис 9.2) Результат анализа - список диагностикиПо каждой диагностике, выданной
(рис 9.3) Результат анализа - диагностика в исходном кодеДля начального знакомства с
Наличие у каждого потока в многопоточном приложении доступа к общему ВАП процесса позволяет потокам эффективно обмениваться данными, с одной стороны, и является основным источником ошибок, с другой. Гонки данных, они же конфликты доступа ( storage conflicts ), поджидают зазевавшегося программиста буквально на каждом шагу. Поиск таких ошибок методом "пристального взгляда" - задача весьма сложная.
Рассматриваемый пример создает 4 потока, каждый из которых увеличивает значение общей глобальной переменной globalX и использует
Откройте проект DataRaces, последовательно выполняя следующие шаги:
File выполните команду Open $$\to$$ Project/Solution…,C:\ITCLabs\DataRaces,DataRaces.dsw или, выбрав файл, выполните команду Open ;DataRaces.dsp в новый формат.После открытия проекта в окне Solution Explorer дважды щелкните на файле исходного кода DataRaces.с, как это показано на рис. 9.4. После этих действий программный код, с которым предстоит работать, будет открыт в
(рис 9.4) Открытие файла DataRaces.сИнтерес в этом небольшом файле представляет потоковая функция .
int globalX = 0;
DWORD WINAPI increment (void *arg)
{
CRITICAL_SECTION cs;
InitializeCriticalSection (cs);
EnterCriticalSection (cs);
globalX++;
LeaveCriticalSection (cs);
DeleteCriticalSection (cs);
return 0;
}
На первый взгляд код выглядит корректно. Доступ к переменной globalX защищен. Посмотрим, что покажет запуск программы.
Соберите и запустите пример, выполнив следующие действия:
/Qtcheck ;Убедитесь, что вывод на экран соответствует представленному на рис. 9.5.
(рис 9.5) Результаты работы примера DataRaces (исходный вариант)Кажется, все верно. Число потоков равно четырем. Начальное значение переменной globalX, как нетрудно убедиться, равно нулю. Таким образом, результат верен. Повторите запуск программы несколько раз и убедитесь, что результат стабильно равен 4.
Может быть, программа корректна? Подумаем, что нужно для того, чтобы гонки данных могли проявиться. Необходимо, чтобы имел место разный порядок выполнения потоков во времени. Посмотрим, возможно ли это в данном примере. Конечно же, нет! Вот участок функции main, запускающий потоки:
for (i = 0; i < NTHREADS; i++)
{
h[i] = CreateThread (0, 0, increment, NULL, 0, NULL);
}
Нетрудно понять, что потоки создаются последовательно, по мере выполнения цикла. Учитывая крайнюю простоту, а значит, и малое время выполнения потоковой функции, скорее всего и работа потоков происходит последовательно. globalX, даже если он имеет место, не успевает проявиться.
Что ж, изменим немного код. Пусть каждый поток увеличивает значение переменной globalX не один раз, а многократно.
DWORD WINAPI increment (void *arg)
{
int i;
CRITICAL_SECTION cs;
InitializeCriticalSection (cs);
for (i = 0; i < 10000; i++)
{
EnterCriticalSection (cs);
globalX++;
LeaveCriticalSection (cs);
}
DeleteCriticalSection (cs);
return 0;
}
Добавьте в код выделенные в предыдущем фрагменте строки, соберите и запустите пример. Убедитесь, что вывод на экран соответствует представленному на рис. 9.6.
(рис 9.6) Результаты работы примера DataRaces (версия с циклом) Не правда ли, неожиданно?! Вместо 40000 на экране совсем другое число! Повторите запуск несколько раз и убедитесь, что результат работы программы будет меняться. Типичная ситуация для гонки данных.
Итак, приложив некоторые усилия, мы прошли два этапа в рассмотренном в п. 1 процессе отладки. Наличие ошибки обнаружено, локализация ее также не вызывает сложностей в силу малого размера кода в примере. Осталось понять, в чем же причина ошибки. Но прежде посмотрим, как с ее обнаружением справится
Выполните следующие действия для подготовки программы к анализу.
/Qtcheck.
(рис 9.11) Установка компиляторного режима инструментацииПосле выполнения указанных действий программа готова к анализу в
После этого запустится
По окончании процесса сбора информации globalX. Краткие комментарии к диагностикам показывают, что globalX:
globalX, а второй в это время читает из нее;globalX,а второй в это время в нее пишет;globalX.Несмотря на то, что реально работало 4 потока, очевидно, что для демонстрации ошибки достаточно двух. Именно так
(рис 9.14) Результат анализа примера DataRaces - DiagnosticsПри наличии отладочной информации
(рис 9.15) Результат анализа примера DataRaces - Source ViewКомментарий в красном поле над исходными текстами описывает ситуацию. В представленном на рисунке случае имеет место
Осталась самая малость - понять, в чем причина гонки данных в рассматриваемом примере, и устранить ее. Сделать это на самом деле не так уж сложно. Противоречие с тем фактом, что обращение потоков к переменной globalX происходит внутри CRITICAL_SECTION cs ). Этот объект объявлен внутри потоковой функции, а значит, является локальным для каждого потока. То есть, когда один поток захватывает cs, к которому остальные потоки не имеют доступа.
Исправить ситуацию можно несколькими способами, но в любом из них объявление объекта cs нужно вынести из потоковой функции , а инициализацию секции и main.
Возможный корректный вариант представлен ниже.
#include <stdio.h>
#include <windows.h>
#define NTHREADS 4
int globalX = 0;
CRITICAL_SECTION cs;
DWORD WINAPI increment (void *arg)
{
EnterCriticalSection (cs);
globalX++;
LeaveCriticalSection (cs);
return 0;
}
int main (int argc, char *argv[])
{
HANDLE h[NTHREADS];
DWORD rc;
int i;
printf ("START\n");
InitializeCriticalSection (cs);
for (i = 0; i < NTHREADS; i++)
{
h[i] = CreateThread (0, 0, increment, NULL, 0, NULL);
}
rc = WaitForMultipleObjects (NTHREADS, h, TRUE, INFINITE);
DeleteCriticalSection (cs);
printf ("TOTAL = %d\n", globalX);
printf ("STOP\n");
}
Ни для кого не секрет, наличие инструментов делает жизнь проще. Перефразируем: наличие Intel® Thread
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.