SlideShare a Scribd company logo
1 of 37
Download to read offline
Неудачная попытка сравнить PVS-
Studio (VivaMP) и Intel C/C++ ("Parallel
Lint")
Автор: Андрей Карпов

Дата: 07.10.2009


Аннотация
Изначально статья должна была носить название "Сравнение диагностических возможностей PVS-
Studio (VivaMP) и Intel C/C++ Compiler ("Parallel Lint"). Отсутствие на данный момент достаточного
количества информации о "Parallel Lint" ограничило возможности автора, и статья представляет
собой предварительный вариант сравнения. Полный вариант статьи с корректным сравнением
будет доступен читателю в будущем. Обратите внимание, что содержание статьи актуально на
момент публикации. В дальнейшем возможно изменение диагностических возможностей обоих
продуктов.


Краткая справочная информация
PVS-Studio (VivaMP) - статический анализатор параллельного Си/Си++ кода, в котором
используется технология параллельного программирования OpenMP. Анализатор позволяет
диагностировать различные виды ошибок, приводящих к некорректной или неэффективной
работе OpenMP программ. Например, анализатор способен выявить некоторые ошибки
синхронизации, обнаружить выход исключений за границы параллельных регионов и так далее.
VivaMP входит в состав программного продукта PVS-Studio и представляет собой модуль
расширения к среде Visual Studio 2005/2008 [1].

Intel C/C++ Compiler ("Parallel Lint") - подсистема статического анализа параллельного OpenMP
кода, встроенная в компилятор Intel C/C++ начиная с версии 11.1. Для использования Intel C/C++
"Parallel Lint" необходимо создать специальную конфигурацию проекта, но в остальном работа с
Intel C/C++ "Parallel Lint" мало чем отличается от обычной работы с компилятором Intel C/C++.
Компилятор встраивается в среду Visual Studio 2005/2008. Более подробно с подсистемой "Parallel
Lint" можно познакомиться, посмотрев вебинар Дмитрия Петунина "Static Analysis and Intel C/C++
Compiler ("Parallel Lint" overview)".


Введение
Мне как разработчику анализатора VivaMP было крайне интересно сравнить его диагностические
возможности с возможностями подсистемы "Parallel Lint", реализованной в Intel C/C++.
Дождавшись, когда Intel C/C++ версии 11.1 стал доступен для скачивания, и найдя время для
начала исследований, я приступил к сравнению инструментов. К сожалению, это изучение и
сравнение не увенчалось успехом, о чем свидетельствует название статьи. Но я уверен, что
разработчики найдут в этой статье много интересной информации.
Для начала исследований я решил испробовать Intel C/C++ на демонстрационном примере
ParallelSample, входящем в состав PVS-Studio и содержащем ошибки связанные с использованием
OpenMP. Программа ParallelSample содержит 23 паттерна ошибок, которые обнаруживаются
анализатором VivaMP. Анализ этого проекта должен подтвердить, что анализаторы осуществляют
схожую диагностику и их сравнение корректно.


Подготовка к проверке ParallelSample с помощью Intel C++ "Parallel
Lint"
Скачивание и установка пробной версии Intel C/C++ Compiler версии 11.1.038 прошли легко и не
вызвали никаких затруднений. Немного удивил размер дистрибутива, равный почти гигабайту. Но
это понятно, учитывая, что я выбрал для скачивания самый полный вариант, включающий в себя
MKL, TBB, поддержку IA64 и так далее. После завершения установки я решил, прежде чем
приступить к анализу, собрать и запустить программу ParallelSample.

Но с компиляцией демонстрационного примера ParallelSample возникли сложности. Компилятор
выдал сообщение "Catastrophic error: unable to obtain mapped memory (see pch_diag.txt)", как
показано на рисунке N1.




      Рисунок N1. Сообщение компилятора, выданное при попытке собрать приложение
                                     ParallelSample.

При этом файл pch_diag.txt я у себя не обнаружил и обратился за помощью к Google. И почти сразу
обнаружил ветку на форуме Intel, посвященную этой проблеме. Ошибка компиляции связана с
использованием предкомпилируемых pch-файлов. В чем именно состоит проблема и как
аккуратно изменить настройки, чтобы оставить действующей подсистему предкомпилируемых
заголовочных файлов, я разбираться не стал. Для такого маленького проекта как ParallelSample это
не имеет значения, и я просто отключил в проекте использование предкомпилируемых
заголовков (см. рисунок N2).
Рисунок N2. Отключение предкомпилируемых заголовков в настройках проекта.

После этого проект был успешно собран, хотя сразу запустить его не удалось (см. рисунок N3).
Рисунок N3. Ошибка при первом запуске ParallelSample.

Это произошло потому, что я забыл прописать в окружение пути до необходимых DLL. Для этого
следует воспользоваться пунктами "C++ Build Environment for applications running on IA-32" и "C++
Build Environment for applications running on Intel(R) 64", доступными через меню "Пуск", как
показано на рисунке N4.
Рисунок N4. Установка окружения для работоспособности приложений, собранных с
                                использованием Intel C++.

Модификация окружения стала последним шагом, после которого приложение ParallelSample
стало успешно компилироваться, запускаться и работать. Теперь можно приступить к самому
интересному - к запуску "Parallel Lint".

Наиболее удобным вариантом использования Intel C++ "Parallel Lint" является создание отдельной
конфигурации проекта. Дело в том, что нельзя одновременно получить исполняемый файл и
диагностируемые сообщения от Intel C++ "Parallel Lint". Можно получить что-то одно. Постоянно
менять настройки, чтобы собирать EXE-файл или чтобы получить диагностику от "Parallel Lint" -
весьма неудобно. Следует пояснить такое поведение.

В "классическом" режиме компилятор создает объектные файлы с кодом, которые затем
объединяются с помощью редактора связей (компоновщик). В результате процесс компиляции
выглядит как показано на рисунке N5.
Рисунок N5. Стандартный режим работы компилятора и компоновщика.

Для глубокого анализа параллельного кода необходимо владеть информацией обо всех модулях
программы. Это позволяет понять, используется ли какая-то функция из одного модуля (cpp-
файла) в параллельном режиме из другого модуля (cpp-файла), как показано на рисунке N6.




                       Рисунок N6. Межмодульное взаимодействие.
Чтобы собрать всю необходимую информацию, Intel C++ использует следующую стратегию.
Компилятор по-прежнему создает *.obj файлы, но это не объектные файлы, а файлы, содержащие
данные, необходимые для последующего анализа. Именно поэтому сборка исполняемого EXE-
файла невозможна. Когда компилятор обработает все *.cpp файлы, вместо редактора связей
(компоновщика) начинает работать анализатор кода. Опираясь на данные, содержащиеся в *.obj
файлах, он диагностирует потенциальные проблемы и выводит соответствующие сообщения об
ошибках. В целом процесс анализа можно представить, как показано на рисунке 7..




      Рисунок N7. Сбор данных и последующая их обработка анализатором "Parallel Lint".

Создание новой конфигурации осуществляется с помощью "Configuration Manager", как показано
на рисунках N8-N10:
Рисунок N8. Запускаем Configuration Manager.
Рисунок N9. Создаем новую конфигурацию с именем "Intel C++ Parallel Lint", на основе
                               существующей.
Рисунок N10. Делаем вновь созданную конфигурацию активной.

Следующий шаг - непосредственная активация анализатора "Parallel Lint". Для этого необходимо
перейти во вкладку "Diagnostics" в настройках проекта (см. рисунок N11).
Рисунок N11. Вкладка "Diagnostics" в настройках проекта, где можно активировать "Parallel
                                             Lint".

Данная страница настроек содержит пункт "Level of Source Code Parallelization Analysis", задающий
уровень анализа параллельного кода. Всего имеется три варианта, как показано на рисунке N12.
Рисунок N12. Выбор уровня диагностики параллельных ошибок.

Я выставил максимальный третий уровень диагностики (см. рисунок N13). Включать анализ
заголовочных файлов (Analyze Include Files) я не стал, так как проект ParallelSample содержит
ошибки только в *.cpp файлах.




                   Рисунок N13. Выбран максимальный уровень диагностики.


Анализ проектов и сравнение анализаторов
Подготовка к началу анализа завершена, теперь можно запустить компиляцию проекта, чтобы
получить диагностические сообщения. Именно это и было осуществлено, в результате чего был
получен список сообщений. Приводить его здесь нет смысла, тем более что он дополнительно
содержит предупреждения, не относящиеся к OpenMP.

Из 23 трех примеров, содержащих ошибки, Intel C++ обнаружил 5 из них. Также он обнаружил
одну дополнительную ошибку, возникающую в некорректной функции и не диагностируемую
VivaMP. Количество ложных срабатываний - 2.

Также я попробовал VivaMP на примере parallel_lint, входящем в состав дистрибутива Intel C++. В
этом примере содержатся 6 ошибок. Здесь совпадение было полным. И VivaMP, и Intel C++
обнаружили эти 6 ошибок.

Подведем промежуточный итог. Полное совпадение диагностики на примере parallel_lint (из
дистрибутива Intel C++) и совпадение диагностики 5 из 23 ошибок на примере ParallelSample (из
дистрибутива PVS-Studio) говорит о том, что выбран верный путь. Данные анализаторы подобны, и
их сравнение в сфере анализа параллельного OpenMP кода корректно.

Я приготовился к тому, чтобы начать сравнивать анализатор PVS-Studio (VivaMP) и Intel C++
("Parallel Lint"). Сравнение я планировал осуществить на основании следующих паттернов ошибок:

    1. Все паттерны ошибок, которые диагностируются VivaMP и примеры которых представлены
       в проекте ParallelSample.
    2. Паттерны ошибок, которые мне известны, но пока не реализованы в анализаторе VivaMP.
       О подобных ошибках можно узнать из статей: "32 подводных камня OpenMP при
       программировании на Си++" [2] и OpenMP и статический анализ кода [3].
    3. Все паттерны ошибок, которые диагностируются Intel C++ ("Parallel Lint") и описание
       которых должно содержаться в документации к компилятору Intel C++.

Хороший план сравнения. Но меня ждало большое разочарование. Третий пункт оказался
невозможным для исследования. Я не смог найти в документации Intel C++ описание тех
проверок, которые осуществляет Parallel Lint. Поиск в интернете также не дал результатов. Мне
удалось обнаружить только разрозненные примеры, демонстрирующие некоторые возможности
Parallel Lint.

Тогда я обратился за помощью в сообщество разработчиков Intel, задав вопрос в форуме, где
можно подробнее ознакомиться с Parallel Lint. Ответ был неутешителен:

Presently, there's no such list in our current product release, other than what's presented in the compiler
documentation under Parallel Lint section. But, we are developing description and examples for all
messages, planned as of now for our next major release. Will keep you updated as soon as the release
containing such a list is out. Appreciate your patience till then.

Резюмируя - описание возможностей "Parallel Lint" и примеров нет, и в ближайшее время не
будет. Я оказался перед выбором, оставить сравнение и продолжить его, когда появится
документация на возможности "Parallel Lint" или провести неполное сравнение, а в дальнейшем
пополнить его. Поскольку была проделана уже существенная часть работы, я решил все-таки
продолжить написание статьи. В ней я проведу некорректное сравнение анализаторов. И
подчеркиваю это. В том числе и названием самой статьи.

Сравнение будет осуществляться на доступных мне на данный момент данных. В будущем, когда
данных будет больше, я создам вторую версию этой статьи, где возможности Intel C++ будут
учтены более полно. Сейчас сравнение будет осуществлено на основании следующих паттернов
ошибок:

   1. Все паттерны ошибок, которые диагностируются VivaMP и примеры которых представлены
      в проекте ParallelSample.
   2. Паттерны ошибок, которые мне известны, но пока не реализованы в анализаторе VivaMP.
      О подобных ошибках можно узнать из статей: "32 подводных камня OpenMP при
      программировании на Си++" и "OpenMP и статический анализ кода".
   3. Разрозненные примеры паттернов ошибок, которые входят в состав демонстрационного
      примера parallel_lint, а также те, которые мне удалось собрать из статей о "Parallel Lint",
      найденных мной в интернете.

В таблице N1 представлены результаты некорректного сравнения анализаторов VivaMP и "Parallel
Lint". После таблицы будут даны пояснения к каждому из пунктов сравнения. Еще раз подчеркну,
что результаты сравнения некорректны. Данные в таблице представлены однобоко. Рассмотрены
все ошибки, обнаруживаемые VivaMP и только часть ошибок, обнаруживаемых Intel C++ ("Parallel
Lint"). Возможно, Intel C++ ("Parallel Lint") обнаруживает еще 500 неизвестных мне паттернов
OpenMP ошибок, и тем самым на порядок превосходит возможности VivaMP.
Таблица N1. Результаты некорректного (неполного) сравнения анализаторов PVS-Studio
                             (VivaMP) и Intel C++ ("Parallel Lint").


Пояснение к пунктам сравнения, представленным в таблице N1


Пункт 1. Забытая директива parallel
Ошибка возникает, когда забыта директива parallel. Эта ошибка относится к классу ошибок
допускаемых по невнимательности и приводит к неожиданному поведению кода. Пример, где
цикл не будет распараллелен:

#pragma omp for

for(int i = 0; i < 100; i++) { ... }

При этом само по себе отсутствие ключевого слова "parallel" в паре, например со словом "for",
вовсе не означает ошибку. Если директива "for" или "sections" находятся внутри параллельной
секции, заданной директивой "parallel", то такой код не является подозрительным:

#pragma omp parallel

{

    #pragma omp for

    for(int i = 0; i < 100; i++)

      ...

}

Диагностика:

PVS-Studio (VivaMP): диагностируется как ошибка V1001.

Intel C++ (Parallel Lint): не диагностируется.


Пункт 2. Забытая директива omp
Ошибка возникает, когда явно забыта директива omp. Ошибка выявляется даже просто при
компиляции кода (без использования Parallel Lint), но все-таки также учтем ее в нашем сравнении.
Ошибка относится к классу ошибок по невнимательности и приводит к неожиданному поведению
кода. Пример:

#pragma single

Диагностика:

PVS-Studio (VivaMP): диагностируется как ошибка V1002.

Intel C++ (Parallel Lint): диагностируется как warning #161.
Пункт 3. Забытая директива for
Иногда программист может забыть вписать директиву "for", что повлечет к выполнению двух
циклов, а не их распараллеливанию. Разумно предупредить программиста, что код содержит
потенциальную ошибку. Пример подозрительного кода:

#pragma omp parallel num_threads(2)

for(int i = 0; i < 2; i++)

    ...

Диагностика:

PVS-Studio (VivaMP): диагностируется как ошибка V1003.

Intel C++ (Parallel Lint): не диагностируется.


Пункт 4. Множественное распараллеливание циклов
Код, содержащий множественное распараллеливание, потенциально может содержать ошибку
или приводить к неэффективному использованию вычислительных ресурсов. Если внутри
параллельной секции используется параллельный цикл, то, скорее всего, это избыточно и только
снизит производительность. Пример кода, где возможно логично сделать внутренний цикл не
параллельным:

#pragma omp parallel for

for(int i = 0; i < 100; i++)

{

    #pragma omp parallel for

    for(int j = 0; j < 100; j++)

      ...

}

Диагностика:

PVS-Studio (VivaMP): диагностируется как ошибка V1004.

Intel C++ (Parallel Lint): не диагностируется.


Пункт 5. Забытые order директивы
Опасным следует считать совместное использование директив "for" и "ordered", если затем
внутри цикла заданного оператором for не используется директива "ordered". Пример:

#pragma omp parallel for ordered

for(int i = 0; i < 4; i++)

{
foo(i);

}

Диагностика:

PVS-Studio (VivaMP): диагностируется как ошибка V1005.

Intel C++ (Parallel Lint): не диагностируется.


Пункт 6. Не подключен заголовочный файл <omp.h>
Отсутствие включения заголовочного файла <omp.h> в файле, где используются директивы
OpenMP, например такие как "#pragma omp parallel for" теоретически может привести к ошибке и
является плохим стилем.

Если программа использует OpenMP, то она должна импортировать vcomp.lib/vcompd.lib. В
противном случае произойдет ошибка на этапе выполенния, если вы используете компилятор
Visual C++. Импорт этой библиотеки осуществляется в файле omp.h. Поэтому, если в проекте явно
не указан импорт нужных библиотек, то #include <omp.h> должно присутствовать хотя бы в одном
из файлов проекта. Или импорт библиотек должен быть явно задан в настройках проекта.

В PVS-Studio данное диагностическое сообщение имеет низкий приоритет и активно только в
"Pedantic Mode". Поэтому не будет зря вас беспокоить.

Но несмотря на всю свою редкость, это настоящая ошибка, и именно поэтому она попала в
сравнение. Приведу практический пример. Открываем проект parallel_lint, входящий в состав Intel
C++. Компилируем его с использованием Visual C++. Запускаем и получаем ошибку при запуске
исполняемого файла, как показано на рисунке N14.
Рисунок N14. Результат запуска проекта Primes_omp1, собранного компилятором Visual C++
                                             2005.

Причина - забытый #include <omp.h>. Замечу, что если собрать проект с использованием Intel C++,
ошибок не возникает.

Диагностика:

PVS-Studio (VivaMP): диагностируется как ошибка V1006.

Intel C++ (Parallel Lint): не диагностируется.


Пункт 7. omp_set_num_threads внутри параллельной секции
Некорректным является вызов функции omp_set_num_threads внутри параллельной секции,
заданного директивой "parallel". В Си++ это приводит к ошибкам во время выполнения
программы и ее аварийному завершению. Пример:

#pragma omp parallel

{

    omp_set_num_threads(2);

}

Диагностика:

PVS-Studio (VivaMP): диагностируется как ошибка V1101.

Intel C++ (Parallel Lint): не диагностируется.


Пункт 8. Нечетность количества блокировок и разблокировок
Опасным следует считать нечетное использование функций omp_set_lock, omp_set_nest_lock,
omp_unset_lock и omp_unset_nest_lock внутри параллельной секции. Пример ошибочного кода:

#pragma omp parallel sections

{
#pragma omp section

    {

        omp_set_lock(&myLock);

    }

}

Диагностика:

PVS-Studio (VivaMP): диагностируется как ошибка V1102.

Intel C++ (Parallel Lint): не диагностируется.


Пункт 9. omp_get_num_threads в арифметических операциях
Крайне опасным может являться использование omp_get_num_threads в некоторых
арифметических выражениях. Пример некорректного кода:

int lettersPerThread = 26 / omp_get_num_threads();

Ошибка заключается в том, что если omp_get_num_threads вернет, например, значение 4, то
произойдет деление с остатком. В результате часть объектов не будет обработано. С более
подробным примером можно ознакомиться в статье "32 подводных камня OpenMP при
программировании на Си++" или в демонстрационном примере Parallel Sample, входящий в состав
дистрибутива PVS-Studio.

Другие выражения, использующие omp_get_num_threads, могут быть вполне корректны. Пример,
не вызывающий вывод диагностических сообщений в PVS-Studio:

bool b = omp_get_num_threads() == 2;

Диагностика:

PVS-Studio (VivaMP): диагностируется как ошибка V1103.

Intel C++ (Parallel Lint): не диагностируется.


Пункт 10. omp_set_nested внутри параллеьной секции
Некорректным является вызов функции omp_set_nested внутри параллельной секции, заданной
директивой "parallel". Пример:

#pragma omp parallel

{

    omp_set_nested(2);

}

Но если функция omp_set_nested находится во вложенном блоке, созданном директивой "master"
или "single", то такой код вполне корректен:
#pragma omp parallel

{

    #pragma omp master

    {

        omp_set_nested(2);

    }

}

Диагностика:

PVS-Studio (VivaMP): диагностируется как ошибка V1104.

Intel C++ (Parallel Lint): не диагностируется.


Пункт 11. Параллельное использование общих ресурсов
Опасным следует считать незащищенное использование функций в параллельных секциях, если
эти функции используют общие ресурсы. Примеры функций: printf, функции OpenGL.

Пример опасного кода:

#pragma omp parallel

{

    printf("abcd");

}

Пример безопасного кода, когда вызов функции защищен:

#pragma omp parallel

{

    #pragma omp critical

    {

        printf("abcd");

    }

}

Диагностика:

PVS-Studio (VivaMP): частично диагностируется как ошибка V1201.

Intel C++ (Parallel Lint): частично диагностируется как error #12158.
Диангоностика описана как частичная, по следующим причинам:

В VivaMP список опасных функций далеко не полон и требует расширения.

К сожалению, анализатор Parallel Lint выдает ложное предупреждение, когда вызов функции
защищен. По поводу полноты списка функций, к сожалению, ничего не известно.


Пункт 12. flush для указателя
Директива flush служит для того, чтобы потоки обновили значения общих переменных. При
использовании flush для указателя высока вероятность того, что программист ошибся, считая, что
произойдет обновление данных по адресу, на которое ссылается указатель. Обновится значение
именно переменной, содержащей указатель. Более того, в стандарте OpenMP явно сказано, что
переменная-аргумент директивы flush не должна быть указателем.

Пример:

int *t;

...

#pragma omp flush(t)

Диагностика:

PVS-Studio (VivaMP): диагностируется как ошибка V1202.

Intel C++ (Parallel Lint): не диагностируется.


Пункт 13. Использование threadprivate
Директива threadprivate является крайне опасной директивой и ее использование рационально
только в крайних случаях. По возможности, стоит избегать использования данной директивы.
Более подробно с опасностью threadprivate можно познакомится в статье "32 подводных камня
OpenMP при программировании на Си++" [2].

Пример опасного кода:

#pragma omp threadprivate(var)

Диагностика:

PVS-Studio (VivaMP): диагностируется как ошибка V1203.

Intel C++ (Parallel Lint): не диагностируется.


Пункт 14. Состояния гонки. Статические переменные
Одна из ошибок состояния гонки. Недопустима инициализация статической переменной в
параллельной секции без специальной защиты. Пример:

#pragma omp parallel num_threads(2)

{
static int cachedResult = ComputeSomethingSlowly();

    ...

}

Корректный пример с использованием защиты:

#pragma omp parallel num_threads(2)

{

    #pragma omp critical

    {

        static int cachedResult = ComputeSomethingSlowly();

        ...

    }

}

Диагностика:

PVS-Studio (VivaMP): диагностируется как ошибка V1204.

Intel C++ (Parallel Lint): диагностируется как warning #12246.


Пункт 15. Состояние гонки. Общая переменная
Одна из ошибок состояния гонки. Два или более потока модифицируют одну общую переменную,
которая специально не защищена. Пример:

int a = 0;

#pragma omp parallel for num_threads(4)

for (int i = 0; i < 100000; i++)

{

    a++;

}

Корректный пример с защитой:

int a = 0;

#pragma omp parallel for num_threads(4)

for (int i = 0; i < 100000; i++)

{

#pragma omp atomic
a++;

}

Диагностика:

PVS-Studio (VivaMP): диагностируется как ошибка V1204.

Intel C++ (Parallel Lint): диагностируется как warning #12246, warning #12247, warning #12248.


Пункт 16. Состояние гонки. Доступ к массиву
Одна из ошибок состояния гонки. Два или более потока пытаются работать с одними и теми же
элементами массива, используя для вычисления индексов различные выражения. Пример:

    int i;

    int factorial[10];

    factorial[0]=1;

    #pragma omp parallel for

    for (i=1; i < 10; i++) {

    factorial[i] = i * factorial[i-1];

    }

Диагностика:

PVS-Studio (VivaMP): не диагностируется.

Intel C++ (Parallel Lint): диагностируется как warning #12246.


Пункт 17. Состояние гонки. Опасная функция
Одна из ошибок состояния гонки. Два или более потока вызывают функцию, принимающую в
качестве формального аргумента значение по неконстантной ссылке или неконстантному
указателю. При вызове такой функции в параллельной секции в нее передается общая
переменная. Никакой защиты при этом не используется.

Пример потенциально опасного кода:

void dangerousFunction(int& param);

void dangerousFunction2(int* param);

int a = 0;

#pragma omp parallel num_threads(4)

{

    #pragma omp for
for (int i = 0; i < 100000; i++)

    {

        dangerousFunction(a);

        dangerousFunction2(&a);

    }

}

Диагностика:

PVS-Studio (VivaMP): диагностируется как ошибка V1206.

Intel C++ (Parallel Lint): не диагностируется.


Пункт 18. Состояние гонки. Модификация объекта
Одна из ошибок состояния гонки. Два или более потока вызывают неконстантную функцию класса
у общего объекта. Никакой защиты при этом не используется.

Пример потенциально опасного кода:

    MyClass obj;

    #pragma omp parallel for num_threads(2)

    for (int i = 0; i < 100000; i++)

    {

        obj.nonConstMethod();

    }

Диагностика:

PVS-Studio (VivaMP): диагностируется как ошибка V1207.

Intel C++ (Parallel Lint): не диагностируется.


Пункт 19. Приватные указатели
Опасным следует считать применение директив "private", "firstprivate" и "threadprivate" к
указателям (не массивам).

Пример опасного кода:

int *arr;

#pragma omp parallel for private(arr)
Если переменная будет указателем, каждый поток получит по локальной копии этого указателя, и
в результате все потоки будут работать через него с общей памятью. Высока вероятность, что в
коде имеется ошибка.

Пример безопасного кода, где каждый поток работает со своим собственным массивом:

int arr[4];

#pragma omp parallel for private(arr)

Диагностика:

PVS-Studio (VivaMP): диагностируется как ошибка V1209.

Intel C++ (Parallel Lint): не диагностируется.


Пункт 20. Неосторожное применение lastprivate
Директива lastprivate после параллельной секции присваивает переменной значение из
лексически последней секции, либо из последней итерации цикла. Код, в котором в последней
секции не модифицируется помеченная переменная, по всей видимости, ошибочен.

Пример:

#pragma omp sections lastprivate(a)

{

    #pragma omp section

    {

        a = 10;

    }

    #pragma omp section

    {

    }

}

Диагностика:

PVS-Studio (VivaMP): диагностируется как ошибка V1210.

Intel C++ (Parallel Lint): не диагностируется.


Пункт 21. Бессмысленный flush. Локальные переменные
Бессмысленным следует считать использование директивы flush для локальных переменных
(объявленных в параллельной секции), а также переменных помеченных как threadprivate, private,
lastprivate, firstprivate.
Директива flush не имеет для перечисленных переменных смысла, так как эти переменные всегда
содержат актуальные значения. И дополнительно снижает производительность кода.

Пример:

int a = 1;

#pragma omp parallel for private(a)

for (int i = 10; i < 100; ++i) {

    #pragma omp flush(a);

    ...

}

Диагностика:

PVS-Studio (VivaMP): диагностируется как ошибка V1211.

Intel C++ (Parallel Lint): не диагностируется.


Пункт 22. Бессмысленный flush. Присутсвует неявный flush
Неэффективным следует считать использование директивы "flush" там, где оно выполняется
неявно. Случаи, в которых директива "flush" присутствует неявно и в ее использовании нет
смысла:

    •     в директиве barrier;
    •     при входе и при выходе из секции директивы critical;
    •     при входе и при выходе из секции директивы ordered;
    •     при входе и при выходе из секции директивы parallel;
    •     при выходе из секции директивы for;
    •     при выходе из секции директивы sections;
    •     при выходе из секции директивы single;
    •     при входе и при выходе из секции директивы parallel for;
    •     при входе и при выходе из секции директивы parallel sections;

Диагностика:

PVS-Studio (VivaMP): не диагностируется.

Intel C++ (Parallel Lint): не диагностируется.


Пункт 23. Исключения. Явный throw
Согласно спецификации OpenMP, все исключения должны быть обработаны внутри параллельной
секции. Выход исключения за пределы параллельной секции приведет к сбою в программе и,
скорее всего, к ее аварийному останову.

Пример недопустимого кода:
try {

    #pragma omp parallel for num_threads(4)

    for(int i = 0; i < 4; i++)

    {

        throw 1;

    }

}

catch (...)

{

}

Корректный код:

size_t errCount = 0;

#pragma omp parallel for num_threads(4) reduction(+: errCount)

for(int i = 0; i < 4; i++)

{

    try {

        throw 1;

    }

    catch (...)

    {

        ++errCount;

    }

}

Диагностика:

PVS-Studio (VivaMP): диагностируется как ошибка V1301.

Intel C++ (Parallel Lint): не диагностируется.


Пункт 24. Исключения. Оператор new
Согласно спецификации OpenMP, все исключения должны быть обработаны внутри параллельной
секции. Оператор "new" в случае ошибки выделения памяти генерирует исключение, и это
необходимо учесть при его использовании в параллельных секциях.
Пример ошибочного кода:

try {

    #pragma omp parallel for num_threads(4)

    for(int i = 0; i < 4; i++)

    {

        float *ptr = new (MyPlacement) float[1000];

        delete [] ptr;

    }

}

catch (std::bad_alloc &)

{

}

Более подробную информацию на эту тему можно найти в следующих записях блога
разработчиков PVS-Studio:

    1. OpenMP и исключения (exceptions)
    2. Обработка исключений внутри параллельных секций

Диагностика:

PVS-Studio (VivaMP): диагностируется как ошибка V1302.

Intel C++ (Parallel Lint): не диагностируется.


Пункт 25. Исключения. Функции
Согласно спецификации OpenMP, все исключения должны быть обработаны внутри параллельной
секции. Если в параллельной секции используется функция, помеченная как явно бросающая
исключения, то исключение должно быть обработано внутри параллельной секции.

Пример ошибочного кода:

void MyThrowFoo() throw(...)

{

    throw 1;

}

try {

    #pragma omp parallel for num_threads(4)

    for(int i = 0; i < 4; i++)
{

        MyThrowFoo();

    }

}

catch (...)

{

}

Диагностика:

PVS-Studio (VivaMP): диагностируется как ошибка V1303.

Intel C++ (Parallel Lint): не диагностируется.


Пункт 26. Забытая инициализация. omp_init_lock
Ошибочным следует считать использование переменных типа omp_lock_t / omp_nest_lock_t без
их предварительной инициализации в функциях omp_init_lock / omp_init_nest_lock. Под
использованием понимается вызов функции omp_set_lock и так далее.

Пример ошибочного кода:

omp_lock_t myLock;

#pragma omp parallel num_threads(2)

{

    omp_set_lock(&myLock);

    omp_unset_lock(&myLock);

}

Диагностика:

PVS-Studio (VivaMP): не диагностируется.

Intel C++ (Parallel Lint): не диагностируется.


Пункт 27. Забытая инициализация. Локальные переменные
Ошибочным следует считать использование переменных, объявленных в параллельном регионе
локальными с использованием директив "private" и "lastprivate" без их предварительной
инициализации. Пример:

int a = 0;

#pragma omp parallel private(a)
{

    a++;

}

Диагностика:

PVS-Studio (VivaMP): не диагностируется.

Intel C++ (Parallel Lint): диагностируется как error #12361.


Пункт 28. Забытая инициализация после параллельного региона
Ошибочным следует считать использование переменных после параллельного участка кода, к
которым применялась директива "private", "threadprivate" или "firstprivate". Перед дальнейшим
использованием они должны быть вновь инициализированы.

Пример некорректного кода:

#pragma omp parallel private(a)

{

    ...

}

b = a;

Диагностика:

PVS-Studio (VivaMP): не диагностируется.

Intel C++ (Parallel Lint): диагностируется как error #12352, error #12358.


Пункт 29. Отсутствие конструктора копирования
Опасным следует считать применение директив "firstprivate" и "lastprivate" к экземплярам
классов, в которых отсутствует конструктор копирования.

Диагностика:

PVS-Studio (VivaMP): не диагностируется.

Intel C++ (Parallel Lint): не диагностируется.


Пункт 30. Неэффективное использование критических секций
Неэффективным следует считать использование критических секций или функций класса
omp_set_lock, там где достаточно директивы "atomic". Директива atomic работает быстрее, чем
критические секции, поскольку некоторые атомарные операции могут быть напрямую заменены
командами процессора. Следовательно, эту директиву желательно применять везде, где
требуется защита общей памяти при элементарных операциях. К таким операциям, согласно
спецификации OpenMP, относятся операции следующего вида:

    •   x binop= expr
    •   x++
    •   ++x
    •   x--
    •   --x

Здесь х - скалярная переменная, expr - выражение со скалярными типами, в котором не
присутствует переменная х, binop - не перегруженный оператор +, *, -, /, &, ^, |, <<, или >>. Во всех
остальных случаях применять директиву atomic нельзя (это проверяется компилятором).

Вообще, с точки зрения убывания быстродействия, средства защиты общих данных от
одновременной записи располагаются так: atomic, critical, omp_set_lock.

Пример неэффективного кода:

#pragma omp critical

{

    e++;

}

Диагностика:

PVS-Studio (VivaMP): не диагностируется.

Intel C++ (Parallel Lint): не диагностируется.


Пункт 31. Бессмысленная защита локальных переменных
Любая защита памяти от одновременной записи замедляет выполнение программы, будь то
атомарная операция, критическая секция или блокировка. Следовательно, в тех случаях, когда она
не нужна, эту защиту лучше не использовать.

Переменную не нужно защищать от одновременной записи в следующих случаях:

если переменная является локальной для потока (а также если она участвует в выражении
threadprivate, firstprivate, private или lastprivate);

если обращение к переменной производится в коде, который гарантированно выполняется
только одним потоком (в параллельной секции директивы master или директивы single).

Пример, где защита записи в переменную "p" не имеет смысла:

#pragma omp parallel for private(p)

for (i=1; i<10; i++)

{
#pragma omp critical

    p = 0;

    ...

}

Диагностика:

PVS-Studio (VivaMP): не диагностируется.

Intel C++ (Parallel Lint): не диагностируется.


Пункт 32. Лишний reduction
Иногда в коде может использоваться директива reduction, хотя указанные в ней переменные не
меняются в параллельном регионе. Это может свидетельствовать как об ошибке, так и просто о
том, что какая-то директива или переменная была забыта и не удалена в процессе рефакторинга
кода.

Пример, где не используется переменная abcde:

#pragma omp parallel for reduction (+:sum, abcde)

for (i=1; i<999; i++)

{

    sum = sum + a[i];

}

Диагностика:

PVS-Studio (VivaMP): не диагностируется.

Intel C++ (Parallel Lint): диагностируется как error #12283.


Заключение
Хотя сравнение двух анализаторов пока нельзя назвать законченным, эта статья позволяет
читателю узнать о многих паттернах параллельных OpenMP-ошибок и о методах их выявления на
самых ранних этапах разработки. Возможность выявить ошибку на этапе кодирования является
сильным преимуществом методики статического анализа, так как эти ошибки могут с большим
трудом выявляться на этапе тестирования или вовсе не выявляться весьма продолжительное
время.

Рассмотренные инструменты PVS-Studio (VivaMP) и Intel C/C++ ("Parallel Lint") будут крайне
полезны разработчикам параллельных программ. Какой инструмент выбрать - зависит от
разработчика. К сожалению, эта статья не дает ответ на этот вопрос. Пока можно сформулировать
преимущества каждого из анализаторов следующим образом:

Преимущества анализатора Intel C/C++ ("Parallel Lint"):
•   анализатор входит в состав компилятора, что позволяет разработчикам, использующим
       Intel C/C++, без каких либо дополнительных вложений или усилий производить
       верификацию параллельного кода;
   •   анализатор собирает вместе данные по всем модулям программы и затем целостно их
       обрабатывает, что позволяет осуществлять некоторые сложные алгоритмы диагностики;
   •   авторитет и опыт разработчиков Intel C++ в области параллельных решений не вызывает
       сомнений.

Преимущества анализатора PVS-Studio (VivaMP):

   •   возможность проверить проект, разрабатываемый в Visual Studio 2005/2008 без
       необходимости его предварительной адаптации для сборки компилятором Intel C++;
   •   разработчики, в том числе и автор статьи, доступны для оперативного общения и могут
       быстро реализовать в анализаторе те возможности, которые необходимы пользователям;
   •   в состав анализатора входит подробное описание всех паттернов ошибок с примерами,
       интегрирующимися в справочную систему MSDN (доступной в online режиме здесь:
       http://www.viva64.com/content/PVS-Studio-help-ru/contents-ru.html);
   •   интерфейс PVS-Studio позволяет интерактивно фильтровать диагностические сообщения
       несколькими методами, сохранять и загружать список предупреждений;
   •   VivaMP является узкоспециализированным, активно развивающимся анализатором, с
       постоянно растущей базой паттернов параллельных ошибок.


Библиографический список
   1. Евгений Рыжков. Учебное пособие по PVS-Studio. http://www.viva64.com/art-4-1-
      1796251700.html
   2. Алексей Колосов, Евгений Рыжков, Андрей Карпов. 32 подводных камня OpenMP при
      программировании на Си++. http://www.viva64.com/art-3-1-464379766.html
   3. Андрей Карпов, Евгений Рыжков. OpenMP и статический анализ кода.
      http://www.viva64.com/art-3-1-1177384583.html

More Related Content

What's hot

Урок 7. Проблемы выявления 64-битных ошибок
Урок 7. Проблемы выявления 64-битных ошибокУрок 7. Проблемы выявления 64-битных ошибок
Урок 7. Проблемы выявления 64-битных ошибокTatyanazaxarova
 
20120218 model checking_karpov_lecture02
20120218 model checking_karpov_lecture0220120218 model checking_karpov_lecture02
20120218 model checking_karpov_lecture02Computer Science Club
 
Специфика разработки и тестирования статического анализатора
Специфика разработки и тестирования статического анализатораСпецифика разработки и тестирования статического анализатора
Специфика разработки и тестирования статического анализатораAndrey Karpov
 
Теория языков программирования некоторые слайды к лекциям
Теория языков программирования некоторые слайды к лекциямТеория языков программирования некоторые слайды к лекциям
Теория языков программирования некоторые слайды к лекциямSergey Staroletov
 
ПРИМЕНЕНИЕ ГЕНЕТИЧЕСКИХ АЛГОРИТМОВ К ГЕНЕРАЦИИ ТЕСТОВ ДЛЯ АВТОМАТНЫХ ПРОГРАММ
ПРИМЕНЕНИЕ ГЕНЕТИЧЕСКИХ АЛГОРИТМОВ К ГЕНЕРАЦИИ ТЕСТОВ ДЛЯ АВТОМАТНЫХ ПРОГРАММПРИМЕНЕНИЕ ГЕНЕТИЧЕСКИХ АЛГОРИТМОВ К ГЕНЕРАЦИИ ТЕСТОВ ДЛЯ АВТОМАТНЫХ ПРОГРАММ
ПРИМЕНЕНИЕ ГЕНЕТИЧЕСКИХ АЛГОРИТМОВ К ГЕНЕРАЦИИ ТЕСТОВ ДЛЯ АВТОМАТНЫХ ПРОГРАММITMO University
 
20090720 hpc exercise1
20090720 hpc exercise120090720 hpc exercise1
20090720 hpc exercise1Michael Karpov
 
C++ осень 2012 лекция 6
C++ осень 2012 лекция 6C++ осень 2012 лекция 6
C++ осень 2012 лекция 6Technopark
 
Сравнение статического анализа общего назначения из Visual Studio 2010 и PVS-...
Сравнение статического анализа общего назначения из Visual Studio 2010 и PVS-...Сравнение статического анализа общего назначения из Visual Studio 2010 и PVS-...
Сравнение статического анализа общего назначения из Visual Studio 2010 и PVS-...Tatyanazaxarova
 
Трепещи, мир! Мы выпустили PVS-Studio 4.00 с бесплатным анализатором общего н...
Трепещи, мир! Мы выпустили PVS-Studio 4.00 с бесплатным анализатором общего н...Трепещи, мир! Мы выпустили PVS-Studio 4.00 с бесплатным анализатором общего н...
Трепещи, мир! Мы выпустили PVS-Studio 4.00 с бесплатным анализатором общего н...Tatyanazaxarova
 
Установка PC-Lint и его использование в Visual Studio 2005
Установка PC-Lint и его использование в Visual Studio 2005Установка PC-Lint и его использование в Visual Studio 2005
Установка PC-Lint и его использование в Visual Studio 2005Tatyanazaxarova
 
C++ CoreHard Autumn 2018. Что не умеет оптимизировать компилятор - Александр ...
C++ CoreHard Autumn 2018. Что не умеет оптимизировать компилятор - Александр ...C++ CoreHard Autumn 2018. Что не умеет оптимизировать компилятор - Александр ...
C++ CoreHard Autumn 2018. Что не умеет оптимизировать компилятор - Александр ...corehard_by
 
подпрограммы в языке программирования паскаль
подпрограммы в языке программирования паскальподпрограммы в языке программирования паскаль
подпрограммы в языке программирования паскальArtem German
 
PostSharp - Threading Model Library
PostSharp - Threading Model LibraryPostSharp - Threading Model Library
PostSharp - Threading Model LibraryAndrey Gordienkov
 
C++ теория
C++ теорияC++ теория
C++ теорияtank1975
 

What's hot (20)

Урок 7. Проблемы выявления 64-битных ошибок
Урок 7. Проблемы выявления 64-битных ошибокУрок 7. Проблемы выявления 64-битных ошибок
Урок 7. Проблемы выявления 64-битных ошибок
 
20120218 model checking_karpov_lecture02
20120218 model checking_karpov_lecture0220120218 model checking_karpov_lecture02
20120218 model checking_karpov_lecture02
 
Специфика разработки и тестирования статического анализатора
Специфика разработки и тестирования статического анализатораСпецифика разработки и тестирования статического анализатора
Специфика разработки и тестирования статического анализатора
 
Теория языков программирования некоторые слайды к лекциям
Теория языков программирования некоторые слайды к лекциямТеория языков программирования некоторые слайды к лекциям
Теория языков программирования некоторые слайды к лекциям
 
ПРИМЕНЕНИЕ ГЕНЕТИЧЕСКИХ АЛГОРИТМОВ К ГЕНЕРАЦИИ ТЕСТОВ ДЛЯ АВТОМАТНЫХ ПРОГРАММ
ПРИМЕНЕНИЕ ГЕНЕТИЧЕСКИХ АЛГОРИТМОВ К ГЕНЕРАЦИИ ТЕСТОВ ДЛЯ АВТОМАТНЫХ ПРОГРАММПРИМЕНЕНИЕ ГЕНЕТИЧЕСКИХ АЛГОРИТМОВ К ГЕНЕРАЦИИ ТЕСТОВ ДЛЯ АВТОМАТНЫХ ПРОГРАММ
ПРИМЕНЕНИЕ ГЕНЕТИЧЕСКИХ АЛГОРИТМОВ К ГЕНЕРАЦИИ ТЕСТОВ ДЛЯ АВТОМАТНЫХ ПРОГРАММ
 
20090720 hpc exercise1
20090720 hpc exercise120090720 hpc exercise1
20090720 hpc exercise1
 
C++ осень 2012 лекция 6
C++ осень 2012 лекция 6C++ осень 2012 лекция 6
C++ осень 2012 лекция 6
 
Сравнение статического анализа общего назначения из Visual Studio 2010 и PVS-...
Сравнение статического анализа общего назначения из Visual Studio 2010 и PVS-...Сравнение статического анализа общего назначения из Visual Studio 2010 и PVS-...
Сравнение статического анализа общего назначения из Visual Studio 2010 и PVS-...
 
лек5 6
лек5 6лек5 6
лек5 6
 
лек1
лек1лек1
лек1
 
Трепещи, мир! Мы выпустили PVS-Studio 4.00 с бесплатным анализатором общего н...
Трепещи, мир! Мы выпустили PVS-Studio 4.00 с бесплатным анализатором общего н...Трепещи, мир! Мы выпустили PVS-Studio 4.00 с бесплатным анализатором общего н...
Трепещи, мир! Мы выпустили PVS-Studio 4.00 с бесплатным анализатором общего н...
 
Tbb лр1
Tbb   лр1Tbb   лр1
Tbb лр1
 
Установка PC-Lint и его использование в Visual Studio 2005
Установка PC-Lint и его использование в Visual Studio 2005Установка PC-Lint и его использование в Visual Studio 2005
Установка PC-Lint и его использование в Visual Studio 2005
 
C++ CoreHard Autumn 2018. Что не умеет оптимизировать компилятор - Александр ...
C++ CoreHard Autumn 2018. Что не умеет оптимизировать компилятор - Александр ...C++ CoreHard Autumn 2018. Что не умеет оптимизировать компилятор - Александр ...
C++ CoreHard Autumn 2018. Что не умеет оптимизировать компилятор - Александр ...
 
206297
206297206297
206297
 
подпрограммы в языке программирования паскаль
подпрограммы в языке программирования паскальподпрограммы в языке программирования паскаль
подпрограммы в языке программирования паскаль
 
PostSharp - Threading Model
PostSharp - Threading ModelPostSharp - Threading Model
PostSharp - Threading Model
 
PostSharp - Threading Model Library
PostSharp - Threading Model LibraryPostSharp - Threading Model Library
PostSharp - Threading Model Library
 
Багдатов Методы автоматического выявления плагиата в текстах компьютерных про...
Багдатов Методы автоматического выявления плагиата в текстах компьютерных про...Багдатов Методы автоматического выявления плагиата в текстах компьютерных про...
Багдатов Методы автоматического выявления плагиата в текстах компьютерных про...
 
C++ теория
C++ теорияC++ теория
C++ теория
 

Similar to Неудачная попытка сравнить PVS-Studio (VivaMP) и Intel C/C++ ("Parallel Lint")

Сравнение PVS-Studio с другими анализаторами кода
Сравнение PVS-Studio с другими анализаторами кодаСравнение PVS-Studio с другими анализаторами кода
Сравнение PVS-Studio с другими анализаторами кодаTatyanazaxarova
 
VivaMP - инструмент для OpenMP
VivaMP - инструмент для OpenMPVivaMP - инструмент для OpenMP
VivaMP - инструмент для OpenMPTatyanazaxarova
 
Регулярное использование статического анализа кода в командной разработке
Регулярное использование статического анализа кода в командной разработкеРегулярное использование статического анализа кода в командной разработке
Регулярное использование статического анализа кода в командной разработкеTatyanazaxarova
 
Реклама PVS-Studio - статический анализ кода на языке Си и Си++
Реклама PVS-Studio - статический анализ кода на языке Си и Си++Реклама PVS-Studio - статический анализ кода на языке Си и Си++
Реклама PVS-Studio - статический анализ кода на языке Си и Си++Andrey Karpov
 
60 антипаттернов для С++ программиста
60 антипаттернов для С++ программиста60 антипаттернов для С++ программиста
60 антипаттернов для С++ программистаAndrey Karpov
 
Тестирование параллельных программ
Тестирование параллельных программТестирование параллельных программ
Тестирование параллельных программTatyanazaxarova
 
Использование анализатора PVS-Studio в процессе инкрементальной сборки в Micr...
Использование анализатора PVS-Studio в процессе инкрементальной сборки в Micr...Использование анализатора PVS-Studio в процессе инкрементальной сборки в Micr...
Использование анализатора PVS-Studio в процессе инкрементальной сборки в Micr...Tatyanazaxarova
 
Continous Integration
Continous IntegrationContinous Integration
Continous IntegrationGetDev.NET
 
Статический анализ и ROI
Статический анализ и ROIСтатический анализ и ROI
Статический анализ и ROITatyanazaxarova
 
Облегчаем процесс разработки с помощью статического анализа кода: Наш опыт
Облегчаем процесс разработки с помощью статического анализа кода: Наш опытОблегчаем процесс разработки с помощью статического анализа кода: Наш опыт
Облегчаем процесс разработки с помощью статического анализа кода: Наш опытAndrey Karpov
 
Константин Книжник: статический анализ, взгляд со стороны
Константин Книжник: статический анализ, взгляд со стороныКонстантин Книжник: статический анализ, взгляд со стороны
Константин Книжник: статический анализ, взгляд со стороныTatyanazaxarova
 
Как мы тестируем анализатор кода
Как мы тестируем анализатор кодаКак мы тестируем анализатор кода
Как мы тестируем анализатор кодаTatyanazaxarova
 
инструменты параллельного программирования
инструменты параллельного программированияинструменты параллельного программирования
инструменты параллельного программированияAlexander Petrov
 
C++ теория
C++ теорияC++ теория
C++ теорияtank1975
 
C++ теория
C++ теорияC++ теория
C++ теорияtank1975
 
оп.05 основы программирования
оп.05 основы программированияоп.05 основы программирования
оп.05 основы программированияStepan1234
 
PVS-Studio. Статический анализатор кода. Windows/Linux, C/C++/C#
PVS-Studio. Статический анализатор кода. Windows/Linux, C/C++/C#PVS-Studio. Статический анализатор кода. Windows/Linux, C/C++/C#
PVS-Studio. Статический анализатор кода. Windows/Linux, C/C++/C#Andrey Karpov
 

Similar to Неудачная попытка сравнить PVS-Studio (VivaMP) и Intel C/C++ ("Parallel Lint") (20)

Сравнение PVS-Studio с другими анализаторами кода
Сравнение PVS-Studio с другими анализаторами кодаСравнение PVS-Studio с другими анализаторами кода
Сравнение PVS-Studio с другими анализаторами кода
 
TAP
TAPTAP
TAP
 
VivaMP - инструмент для OpenMP
VivaMP - инструмент для OpenMPVivaMP - инструмент для OpenMP
VivaMP - инструмент для OpenMP
 
Регулярное использование статического анализа кода в командной разработке
Регулярное использование статического анализа кода в командной разработкеРегулярное использование статического анализа кода в командной разработке
Регулярное использование статического анализа кода в командной разработке
 
Реклама PVS-Studio - статический анализ кода на языке Си и Си++
Реклама PVS-Studio - статический анализ кода на языке Си и Си++Реклама PVS-Studio - статический анализ кода на языке Си и Си++
Реклама PVS-Studio - статический анализ кода на языке Си и Си++
 
60 антипаттернов для С++ программиста
60 антипаттернов для С++ программиста60 антипаттернов для С++ программиста
60 антипаттернов для С++ программиста
 
Тестирование параллельных программ
Тестирование параллельных программТестирование параллельных программ
Тестирование параллельных программ
 
Использование анализатора PVS-Studio в процессе инкрементальной сборки в Micr...
Использование анализатора PVS-Studio в процессе инкрементальной сборки в Micr...Использование анализатора PVS-Studio в процессе инкрементальной сборки в Micr...
Использование анализатора PVS-Studio в процессе инкрементальной сборки в Micr...
 
Continous Integration
Continous IntegrationContinous Integration
Continous Integration
 
Статический анализ и ROI
Статический анализ и ROIСтатический анализ и ROI
Статический анализ и ROI
 
Облегчаем процесс разработки с помощью статического анализа кода: Наш опыт
Облегчаем процесс разработки с помощью статического анализа кода: Наш опытОблегчаем процесс разработки с помощью статического анализа кода: Наш опыт
Облегчаем процесс разработки с помощью статического анализа кода: Наш опыт
 
Константин Книжник: статический анализ, взгляд со стороны
Константин Книжник: статический анализ, взгляд со стороныКонстантин Книжник: статический анализ, взгляд со стороны
Константин Книжник: статический анализ, взгляд со стороны
 
лек12
лек12лек12
лек12
 
Как мы тестируем анализатор кода
Как мы тестируем анализатор кодаКак мы тестируем анализатор кода
Как мы тестируем анализатор кода
 
инструменты параллельного программирования
инструменты параллельного программированияинструменты параллельного программирования
инструменты параллельного программирования
 
PVS-Studio vs Chromium
PVS-Studio vs ChromiumPVS-Studio vs Chromium
PVS-Studio vs Chromium
 
C++ теория
C++ теорияC++ теория
C++ теория
 
C++ теория
C++ теорияC++ теория
C++ теория
 
оп.05 основы программирования
оп.05 основы программированияоп.05 основы программирования
оп.05 основы программирования
 
PVS-Studio. Статический анализатор кода. Windows/Linux, C/C++/C#
PVS-Studio. Статический анализатор кода. Windows/Linux, C/C++/C#PVS-Studio. Статический анализатор кода. Windows/Linux, C/C++/C#
PVS-Studio. Статический анализатор кода. Windows/Linux, C/C++/C#
 

More from Tatyanazaxarova

Урок 27. Особенности создания инсталляторов для 64-битного окружения
Урок 27. Особенности создания инсталляторов для 64-битного окруженияУрок 27. Особенности создания инсталляторов для 64-битного окружения
Урок 27. Особенности создания инсталляторов для 64-битного окруженияTatyanazaxarova
 
Урок 26. Оптимизация 64-битных программ
Урок 26. Оптимизация 64-битных программУрок 26. Оптимизация 64-битных программ
Урок 26. Оптимизация 64-битных программTatyanazaxarova
 
Урок 25. Практическое знакомство с паттернами 64-битных ошибок
Урок 25. Практическое знакомство с паттернами 64-битных ошибокУрок 25. Практическое знакомство с паттернами 64-битных ошибок
Урок 25. Практическое знакомство с паттернами 64-битных ошибокTatyanazaxarova
 
Урок 24. Фантомные ошибки
Урок 24. Фантомные ошибкиУрок 24. Фантомные ошибки
Урок 24. Фантомные ошибкиTatyanazaxarova
 
Урок 23. Паттерн 15. Рост размеров структур
Урок 23. Паттерн 15. Рост размеров структурУрок 23. Паттерн 15. Рост размеров структур
Урок 23. Паттерн 15. Рост размеров структурTatyanazaxarova
 
Урок 21. Паттерн 13. Выравнивание данных
Урок 21. Паттерн 13. Выравнивание данныхУрок 21. Паттерн 13. Выравнивание данных
Урок 21. Паттерн 13. Выравнивание данныхTatyanazaxarova
 
Урок 20. Паттерн 12. Исключения
Урок 20. Паттерн 12. ИсключенияУрок 20. Паттерн 12. Исключения
Урок 20. Паттерн 12. ИсключенияTatyanazaxarova
 
Урок 19. Паттерн 11. Сериализация и обмен данными
Урок 19. Паттерн 11. Сериализация и обмен даннымиУрок 19. Паттерн 11. Сериализация и обмен данными
Урок 19. Паттерн 11. Сериализация и обмен даннымиTatyanazaxarova
 
Урок 17. Паттерн 9. Смешанная арифметика
Урок 17. Паттерн 9. Смешанная арифметикаУрок 17. Паттерн 9. Смешанная арифметика
Урок 17. Паттерн 9. Смешанная арифметикаTatyanazaxarova
 
Урок 16. Паттерн 8. Memsize-типы в объединениях
Урок 16. Паттерн 8. Memsize-типы в объединенияхУрок 16. Паттерн 8. Memsize-типы в объединениях
Урок 16. Паттерн 8. Memsize-типы в объединенияхTatyanazaxarova
 
Урок 15. Паттерн 7. Упаковка указателей
Урок 15. Паттерн 7. Упаковка указателейУрок 15. Паттерн 7. Упаковка указателей
Урок 15. Паттерн 7. Упаковка указателейTatyanazaxarova
 
Урок 13. Паттерн 5. Адресная арифметика
Урок 13. Паттерн 5. Адресная арифметикаУрок 13. Паттерн 5. Адресная арифметика
Урок 13. Паттерн 5. Адресная арифметикаTatyanazaxarova
 
Урок 11. Паттерн 3. Операции сдвига
Урок 11. Паттерн 3. Операции сдвигаУрок 11. Паттерн 3. Операции сдвига
Урок 11. Паттерн 3. Операции сдвигаTatyanazaxarova
 
Урок 10. Паттерн 2. Функции с переменным количеством аргументов
Урок 10. Паттерн 2. Функции с переменным количеством аргументовУрок 10. Паттерн 2. Функции с переменным количеством аргументов
Урок 10. Паттерн 2. Функции с переменным количеством аргументовTatyanazaxarova
 
Урок 9. Паттерн 1. Магические числа
Урок 9. Паттерн 1. Магические числаУрок 9. Паттерн 1. Магические числа
Урок 9. Паттерн 1. Магические числаTatyanazaxarova
 
Урок 8. Статический анализ для выявления 64-битных ошибок
Урок 8. Статический анализ для выявления 64-битных ошибокУрок 8. Статический анализ для выявления 64-битных ошибок
Урок 8. Статический анализ для выявления 64-битных ошибокTatyanazaxarova
 
Урок 6. Ошибки в 64-битном коде
Урок 6. Ошибки в 64-битном кодеУрок 6. Ошибки в 64-битном коде
Урок 6. Ошибки в 64-битном кодеTatyanazaxarova
 
Урок 5. Сборка 64-битного приложения
Урок 5. Сборка 64-битного приложенияУрок 5. Сборка 64-битного приложения
Урок 5. Сборка 64-битного приложенияTatyanazaxarova
 
Урок 4. Создание 64-битной конфигурации
Урок 4. Создание 64-битной конфигурацииУрок 4. Создание 64-битной конфигурации
Урок 4. Создание 64-битной конфигурацииTatyanazaxarova
 
PVS-Studio, решение для разработки современных ресурсоемких приложений
PVS-Studio, решение для разработки современных ресурсоемких приложенийPVS-Studio, решение для разработки современных ресурсоемких приложений
PVS-Studio, решение для разработки современных ресурсоемких приложенийTatyanazaxarova
 

More from Tatyanazaxarova (20)

Урок 27. Особенности создания инсталляторов для 64-битного окружения
Урок 27. Особенности создания инсталляторов для 64-битного окруженияУрок 27. Особенности создания инсталляторов для 64-битного окружения
Урок 27. Особенности создания инсталляторов для 64-битного окружения
 
Урок 26. Оптимизация 64-битных программ
Урок 26. Оптимизация 64-битных программУрок 26. Оптимизация 64-битных программ
Урок 26. Оптимизация 64-битных программ
 
Урок 25. Практическое знакомство с паттернами 64-битных ошибок
Урок 25. Практическое знакомство с паттернами 64-битных ошибокУрок 25. Практическое знакомство с паттернами 64-битных ошибок
Урок 25. Практическое знакомство с паттернами 64-битных ошибок
 
Урок 24. Фантомные ошибки
Урок 24. Фантомные ошибкиУрок 24. Фантомные ошибки
Урок 24. Фантомные ошибки
 
Урок 23. Паттерн 15. Рост размеров структур
Урок 23. Паттерн 15. Рост размеров структурУрок 23. Паттерн 15. Рост размеров структур
Урок 23. Паттерн 15. Рост размеров структур
 
Урок 21. Паттерн 13. Выравнивание данных
Урок 21. Паттерн 13. Выравнивание данныхУрок 21. Паттерн 13. Выравнивание данных
Урок 21. Паттерн 13. Выравнивание данных
 
Урок 20. Паттерн 12. Исключения
Урок 20. Паттерн 12. ИсключенияУрок 20. Паттерн 12. Исключения
Урок 20. Паттерн 12. Исключения
 
Урок 19. Паттерн 11. Сериализация и обмен данными
Урок 19. Паттерн 11. Сериализация и обмен даннымиУрок 19. Паттерн 11. Сериализация и обмен данными
Урок 19. Паттерн 11. Сериализация и обмен данными
 
Урок 17. Паттерн 9. Смешанная арифметика
Урок 17. Паттерн 9. Смешанная арифметикаУрок 17. Паттерн 9. Смешанная арифметика
Урок 17. Паттерн 9. Смешанная арифметика
 
Урок 16. Паттерн 8. Memsize-типы в объединениях
Урок 16. Паттерн 8. Memsize-типы в объединенияхУрок 16. Паттерн 8. Memsize-типы в объединениях
Урок 16. Паттерн 8. Memsize-типы в объединениях
 
Урок 15. Паттерн 7. Упаковка указателей
Урок 15. Паттерн 7. Упаковка указателейУрок 15. Паттерн 7. Упаковка указателей
Урок 15. Паттерн 7. Упаковка указателей
 
Урок 13. Паттерн 5. Адресная арифметика
Урок 13. Паттерн 5. Адресная арифметикаУрок 13. Паттерн 5. Адресная арифметика
Урок 13. Паттерн 5. Адресная арифметика
 
Урок 11. Паттерн 3. Операции сдвига
Урок 11. Паттерн 3. Операции сдвигаУрок 11. Паттерн 3. Операции сдвига
Урок 11. Паттерн 3. Операции сдвига
 
Урок 10. Паттерн 2. Функции с переменным количеством аргументов
Урок 10. Паттерн 2. Функции с переменным количеством аргументовУрок 10. Паттерн 2. Функции с переменным количеством аргументов
Урок 10. Паттерн 2. Функции с переменным количеством аргументов
 
Урок 9. Паттерн 1. Магические числа
Урок 9. Паттерн 1. Магические числаУрок 9. Паттерн 1. Магические числа
Урок 9. Паттерн 1. Магические числа
 
Урок 8. Статический анализ для выявления 64-битных ошибок
Урок 8. Статический анализ для выявления 64-битных ошибокУрок 8. Статический анализ для выявления 64-битных ошибок
Урок 8. Статический анализ для выявления 64-битных ошибок
 
Урок 6. Ошибки в 64-битном коде
Урок 6. Ошибки в 64-битном кодеУрок 6. Ошибки в 64-битном коде
Урок 6. Ошибки в 64-битном коде
 
Урок 5. Сборка 64-битного приложения
Урок 5. Сборка 64-битного приложенияУрок 5. Сборка 64-битного приложения
Урок 5. Сборка 64-битного приложения
 
Урок 4. Создание 64-битной конфигурации
Урок 4. Создание 64-битной конфигурацииУрок 4. Создание 64-битной конфигурации
Урок 4. Создание 64-битной конфигурации
 
PVS-Studio, решение для разработки современных ресурсоемких приложений
PVS-Studio, решение для разработки современных ресурсоемких приложенийPVS-Studio, решение для разработки современных ресурсоемких приложений
PVS-Studio, решение для разработки современных ресурсоемких приложений
 

Неудачная попытка сравнить PVS-Studio (VivaMP) и Intel C/C++ ("Parallel Lint")

  • 1. Неудачная попытка сравнить PVS- Studio (VivaMP) и Intel C/C++ ("Parallel Lint") Автор: Андрей Карпов Дата: 07.10.2009 Аннотация Изначально статья должна была носить название "Сравнение диагностических возможностей PVS- Studio (VivaMP) и Intel C/C++ Compiler ("Parallel Lint"). Отсутствие на данный момент достаточного количества информации о "Parallel Lint" ограничило возможности автора, и статья представляет собой предварительный вариант сравнения. Полный вариант статьи с корректным сравнением будет доступен читателю в будущем. Обратите внимание, что содержание статьи актуально на момент публикации. В дальнейшем возможно изменение диагностических возможностей обоих продуктов. Краткая справочная информация PVS-Studio (VivaMP) - статический анализатор параллельного Си/Си++ кода, в котором используется технология параллельного программирования OpenMP. Анализатор позволяет диагностировать различные виды ошибок, приводящих к некорректной или неэффективной работе OpenMP программ. Например, анализатор способен выявить некоторые ошибки синхронизации, обнаружить выход исключений за границы параллельных регионов и так далее. VivaMP входит в состав программного продукта PVS-Studio и представляет собой модуль расширения к среде Visual Studio 2005/2008 [1]. Intel C/C++ Compiler ("Parallel Lint") - подсистема статического анализа параллельного OpenMP кода, встроенная в компилятор Intel C/C++ начиная с версии 11.1. Для использования Intel C/C++ "Parallel Lint" необходимо создать специальную конфигурацию проекта, но в остальном работа с Intel C/C++ "Parallel Lint" мало чем отличается от обычной работы с компилятором Intel C/C++. Компилятор встраивается в среду Visual Studio 2005/2008. Более подробно с подсистемой "Parallel Lint" можно познакомиться, посмотрев вебинар Дмитрия Петунина "Static Analysis and Intel C/C++ Compiler ("Parallel Lint" overview)". Введение Мне как разработчику анализатора VivaMP было крайне интересно сравнить его диагностические возможности с возможностями подсистемы "Parallel Lint", реализованной в Intel C/C++. Дождавшись, когда Intel C/C++ версии 11.1 стал доступен для скачивания, и найдя время для начала исследований, я приступил к сравнению инструментов. К сожалению, это изучение и сравнение не увенчалось успехом, о чем свидетельствует название статьи. Но я уверен, что разработчики найдут в этой статье много интересной информации.
  • 2. Для начала исследований я решил испробовать Intel C/C++ на демонстрационном примере ParallelSample, входящем в состав PVS-Studio и содержащем ошибки связанные с использованием OpenMP. Программа ParallelSample содержит 23 паттерна ошибок, которые обнаруживаются анализатором VivaMP. Анализ этого проекта должен подтвердить, что анализаторы осуществляют схожую диагностику и их сравнение корректно. Подготовка к проверке ParallelSample с помощью Intel C++ "Parallel Lint" Скачивание и установка пробной версии Intel C/C++ Compiler версии 11.1.038 прошли легко и не вызвали никаких затруднений. Немного удивил размер дистрибутива, равный почти гигабайту. Но это понятно, учитывая, что я выбрал для скачивания самый полный вариант, включающий в себя MKL, TBB, поддержку IA64 и так далее. После завершения установки я решил, прежде чем приступить к анализу, собрать и запустить программу ParallelSample. Но с компиляцией демонстрационного примера ParallelSample возникли сложности. Компилятор выдал сообщение "Catastrophic error: unable to obtain mapped memory (see pch_diag.txt)", как показано на рисунке N1. Рисунок N1. Сообщение компилятора, выданное при попытке собрать приложение ParallelSample. При этом файл pch_diag.txt я у себя не обнаружил и обратился за помощью к Google. И почти сразу обнаружил ветку на форуме Intel, посвященную этой проблеме. Ошибка компиляции связана с использованием предкомпилируемых pch-файлов. В чем именно состоит проблема и как аккуратно изменить настройки, чтобы оставить действующей подсистему предкомпилируемых заголовочных файлов, я разбираться не стал. Для такого маленького проекта как ParallelSample это не имеет значения, и я просто отключил в проекте использование предкомпилируемых заголовков (см. рисунок N2).
  • 3. Рисунок N2. Отключение предкомпилируемых заголовков в настройках проекта. После этого проект был успешно собран, хотя сразу запустить его не удалось (см. рисунок N3).
  • 4. Рисунок N3. Ошибка при первом запуске ParallelSample. Это произошло потому, что я забыл прописать в окружение пути до необходимых DLL. Для этого следует воспользоваться пунктами "C++ Build Environment for applications running on IA-32" и "C++ Build Environment for applications running on Intel(R) 64", доступными через меню "Пуск", как показано на рисунке N4.
  • 5. Рисунок N4. Установка окружения для работоспособности приложений, собранных с использованием Intel C++. Модификация окружения стала последним шагом, после которого приложение ParallelSample стало успешно компилироваться, запускаться и работать. Теперь можно приступить к самому интересному - к запуску "Parallel Lint". Наиболее удобным вариантом использования Intel C++ "Parallel Lint" является создание отдельной конфигурации проекта. Дело в том, что нельзя одновременно получить исполняемый файл и диагностируемые сообщения от Intel C++ "Parallel Lint". Можно получить что-то одно. Постоянно менять настройки, чтобы собирать EXE-файл или чтобы получить диагностику от "Parallel Lint" - весьма неудобно. Следует пояснить такое поведение. В "классическом" режиме компилятор создает объектные файлы с кодом, которые затем объединяются с помощью редактора связей (компоновщик). В результате процесс компиляции выглядит как показано на рисунке N5.
  • 6. Рисунок N5. Стандартный режим работы компилятора и компоновщика. Для глубокого анализа параллельного кода необходимо владеть информацией обо всех модулях программы. Это позволяет понять, используется ли какая-то функция из одного модуля (cpp- файла) в параллельном режиме из другого модуля (cpp-файла), как показано на рисунке N6. Рисунок N6. Межмодульное взаимодействие.
  • 7. Чтобы собрать всю необходимую информацию, Intel C++ использует следующую стратегию. Компилятор по-прежнему создает *.obj файлы, но это не объектные файлы, а файлы, содержащие данные, необходимые для последующего анализа. Именно поэтому сборка исполняемого EXE- файла невозможна. Когда компилятор обработает все *.cpp файлы, вместо редактора связей (компоновщика) начинает работать анализатор кода. Опираясь на данные, содержащиеся в *.obj файлах, он диагностирует потенциальные проблемы и выводит соответствующие сообщения об ошибках. В целом процесс анализа можно представить, как показано на рисунке 7.. Рисунок N7. Сбор данных и последующая их обработка анализатором "Parallel Lint". Создание новой конфигурации осуществляется с помощью "Configuration Manager", как показано на рисунках N8-N10:
  • 8.
  • 9. Рисунок N8. Запускаем Configuration Manager.
  • 10. Рисунок N9. Создаем новую конфигурацию с именем "Intel C++ Parallel Lint", на основе существующей.
  • 11. Рисунок N10. Делаем вновь созданную конфигурацию активной. Следующий шаг - непосредственная активация анализатора "Parallel Lint". Для этого необходимо перейти во вкладку "Diagnostics" в настройках проекта (см. рисунок N11).
  • 12.
  • 13. Рисунок N11. Вкладка "Diagnostics" в настройках проекта, где можно активировать "Parallel Lint". Данная страница настроек содержит пункт "Level of Source Code Parallelization Analysis", задающий уровень анализа параллельного кода. Всего имеется три варианта, как показано на рисунке N12.
  • 14. Рисунок N12. Выбор уровня диагностики параллельных ошибок. Я выставил максимальный третий уровень диагностики (см. рисунок N13). Включать анализ заголовочных файлов (Analyze Include Files) я не стал, так как проект ParallelSample содержит ошибки только в *.cpp файлах. Рисунок N13. Выбран максимальный уровень диагностики. Анализ проектов и сравнение анализаторов Подготовка к началу анализа завершена, теперь можно запустить компиляцию проекта, чтобы получить диагностические сообщения. Именно это и было осуществлено, в результате чего был
  • 15. получен список сообщений. Приводить его здесь нет смысла, тем более что он дополнительно содержит предупреждения, не относящиеся к OpenMP. Из 23 трех примеров, содержащих ошибки, Intel C++ обнаружил 5 из них. Также он обнаружил одну дополнительную ошибку, возникающую в некорректной функции и не диагностируемую VivaMP. Количество ложных срабатываний - 2. Также я попробовал VivaMP на примере parallel_lint, входящем в состав дистрибутива Intel C++. В этом примере содержатся 6 ошибок. Здесь совпадение было полным. И VivaMP, и Intel C++ обнаружили эти 6 ошибок. Подведем промежуточный итог. Полное совпадение диагностики на примере parallel_lint (из дистрибутива Intel C++) и совпадение диагностики 5 из 23 ошибок на примере ParallelSample (из дистрибутива PVS-Studio) говорит о том, что выбран верный путь. Данные анализаторы подобны, и их сравнение в сфере анализа параллельного OpenMP кода корректно. Я приготовился к тому, чтобы начать сравнивать анализатор PVS-Studio (VivaMP) и Intel C++ ("Parallel Lint"). Сравнение я планировал осуществить на основании следующих паттернов ошибок: 1. Все паттерны ошибок, которые диагностируются VivaMP и примеры которых представлены в проекте ParallelSample. 2. Паттерны ошибок, которые мне известны, но пока не реализованы в анализаторе VivaMP. О подобных ошибках можно узнать из статей: "32 подводных камня OpenMP при программировании на Си++" [2] и OpenMP и статический анализ кода [3]. 3. Все паттерны ошибок, которые диагностируются Intel C++ ("Parallel Lint") и описание которых должно содержаться в документации к компилятору Intel C++. Хороший план сравнения. Но меня ждало большое разочарование. Третий пункт оказался невозможным для исследования. Я не смог найти в документации Intel C++ описание тех проверок, которые осуществляет Parallel Lint. Поиск в интернете также не дал результатов. Мне удалось обнаружить только разрозненные примеры, демонстрирующие некоторые возможности Parallel Lint. Тогда я обратился за помощью в сообщество разработчиков Intel, задав вопрос в форуме, где можно подробнее ознакомиться с Parallel Lint. Ответ был неутешителен: Presently, there's no such list in our current product release, other than what's presented in the compiler documentation under Parallel Lint section. But, we are developing description and examples for all messages, planned as of now for our next major release. Will keep you updated as soon as the release containing such a list is out. Appreciate your patience till then. Резюмируя - описание возможностей "Parallel Lint" и примеров нет, и в ближайшее время не будет. Я оказался перед выбором, оставить сравнение и продолжить его, когда появится документация на возможности "Parallel Lint" или провести неполное сравнение, а в дальнейшем пополнить его. Поскольку была проделана уже существенная часть работы, я решил все-таки продолжить написание статьи. В ней я проведу некорректное сравнение анализаторов. И подчеркиваю это. В том числе и названием самой статьи. Сравнение будет осуществляться на доступных мне на данный момент данных. В будущем, когда данных будет больше, я создам вторую версию этой статьи, где возможности Intel C++ будут
  • 16. учтены более полно. Сейчас сравнение будет осуществлено на основании следующих паттернов ошибок: 1. Все паттерны ошибок, которые диагностируются VivaMP и примеры которых представлены в проекте ParallelSample. 2. Паттерны ошибок, которые мне известны, но пока не реализованы в анализаторе VivaMP. О подобных ошибках можно узнать из статей: "32 подводных камня OpenMP при программировании на Си++" и "OpenMP и статический анализ кода". 3. Разрозненные примеры паттернов ошибок, которые входят в состав демонстрационного примера parallel_lint, а также те, которые мне удалось собрать из статей о "Parallel Lint", найденных мной в интернете. В таблице N1 представлены результаты некорректного сравнения анализаторов VivaMP и "Parallel Lint". После таблицы будут даны пояснения к каждому из пунктов сравнения. Еще раз подчеркну, что результаты сравнения некорректны. Данные в таблице представлены однобоко. Рассмотрены все ошибки, обнаруживаемые VivaMP и только часть ошибок, обнаруживаемых Intel C++ ("Parallel Lint"). Возможно, Intel C++ ("Parallel Lint") обнаруживает еще 500 неизвестных мне паттернов OpenMP ошибок, и тем самым на порядок превосходит возможности VivaMP.
  • 17.
  • 18.
  • 19. Таблица N1. Результаты некорректного (неполного) сравнения анализаторов PVS-Studio (VivaMP) и Intel C++ ("Parallel Lint"). Пояснение к пунктам сравнения, представленным в таблице N1 Пункт 1. Забытая директива parallel Ошибка возникает, когда забыта директива parallel. Эта ошибка относится к классу ошибок допускаемых по невнимательности и приводит к неожиданному поведению кода. Пример, где цикл не будет распараллелен: #pragma omp for for(int i = 0; i < 100; i++) { ... } При этом само по себе отсутствие ключевого слова "parallel" в паре, например со словом "for", вовсе не означает ошибку. Если директива "for" или "sections" находятся внутри параллельной секции, заданной директивой "parallel", то такой код не является подозрительным: #pragma omp parallel { #pragma omp for for(int i = 0; i < 100; i++) ... } Диагностика: PVS-Studio (VivaMP): диагностируется как ошибка V1001. Intel C++ (Parallel Lint): не диагностируется. Пункт 2. Забытая директива omp Ошибка возникает, когда явно забыта директива omp. Ошибка выявляется даже просто при компиляции кода (без использования Parallel Lint), но все-таки также учтем ее в нашем сравнении. Ошибка относится к классу ошибок по невнимательности и приводит к неожиданному поведению кода. Пример: #pragma single Диагностика: PVS-Studio (VivaMP): диагностируется как ошибка V1002. Intel C++ (Parallel Lint): диагностируется как warning #161.
  • 20. Пункт 3. Забытая директива for Иногда программист может забыть вписать директиву "for", что повлечет к выполнению двух циклов, а не их распараллеливанию. Разумно предупредить программиста, что код содержит потенциальную ошибку. Пример подозрительного кода: #pragma omp parallel num_threads(2) for(int i = 0; i < 2; i++) ... Диагностика: PVS-Studio (VivaMP): диагностируется как ошибка V1003. Intel C++ (Parallel Lint): не диагностируется. Пункт 4. Множественное распараллеливание циклов Код, содержащий множественное распараллеливание, потенциально может содержать ошибку или приводить к неэффективному использованию вычислительных ресурсов. Если внутри параллельной секции используется параллельный цикл, то, скорее всего, это избыточно и только снизит производительность. Пример кода, где возможно логично сделать внутренний цикл не параллельным: #pragma omp parallel for for(int i = 0; i < 100; i++) { #pragma omp parallel for for(int j = 0; j < 100; j++) ... } Диагностика: PVS-Studio (VivaMP): диагностируется как ошибка V1004. Intel C++ (Parallel Lint): не диагностируется. Пункт 5. Забытые order директивы Опасным следует считать совместное использование директив "for" и "ordered", если затем внутри цикла заданного оператором for не используется директива "ordered". Пример: #pragma omp parallel for ordered for(int i = 0; i < 4; i++) {
  • 21. foo(i); } Диагностика: PVS-Studio (VivaMP): диагностируется как ошибка V1005. Intel C++ (Parallel Lint): не диагностируется. Пункт 6. Не подключен заголовочный файл <omp.h> Отсутствие включения заголовочного файла <omp.h> в файле, где используются директивы OpenMP, например такие как "#pragma omp parallel for" теоретически может привести к ошибке и является плохим стилем. Если программа использует OpenMP, то она должна импортировать vcomp.lib/vcompd.lib. В противном случае произойдет ошибка на этапе выполенния, если вы используете компилятор Visual C++. Импорт этой библиотеки осуществляется в файле omp.h. Поэтому, если в проекте явно не указан импорт нужных библиотек, то #include <omp.h> должно присутствовать хотя бы в одном из файлов проекта. Или импорт библиотек должен быть явно задан в настройках проекта. В PVS-Studio данное диагностическое сообщение имеет низкий приоритет и активно только в "Pedantic Mode". Поэтому не будет зря вас беспокоить. Но несмотря на всю свою редкость, это настоящая ошибка, и именно поэтому она попала в сравнение. Приведу практический пример. Открываем проект parallel_lint, входящий в состав Intel C++. Компилируем его с использованием Visual C++. Запускаем и получаем ошибку при запуске исполняемого файла, как показано на рисунке N14.
  • 22. Рисунок N14. Результат запуска проекта Primes_omp1, собранного компилятором Visual C++ 2005. Причина - забытый #include <omp.h>. Замечу, что если собрать проект с использованием Intel C++, ошибок не возникает. Диагностика: PVS-Studio (VivaMP): диагностируется как ошибка V1006. Intel C++ (Parallel Lint): не диагностируется. Пункт 7. omp_set_num_threads внутри параллельной секции Некорректным является вызов функции omp_set_num_threads внутри параллельной секции, заданного директивой "parallel". В Си++ это приводит к ошибкам во время выполнения программы и ее аварийному завершению. Пример: #pragma omp parallel { omp_set_num_threads(2); } Диагностика: PVS-Studio (VivaMP): диагностируется как ошибка V1101. Intel C++ (Parallel Lint): не диагностируется. Пункт 8. Нечетность количества блокировок и разблокировок Опасным следует считать нечетное использование функций omp_set_lock, omp_set_nest_lock, omp_unset_lock и omp_unset_nest_lock внутри параллельной секции. Пример ошибочного кода: #pragma omp parallel sections {
  • 23. #pragma omp section { omp_set_lock(&myLock); } } Диагностика: PVS-Studio (VivaMP): диагностируется как ошибка V1102. Intel C++ (Parallel Lint): не диагностируется. Пункт 9. omp_get_num_threads в арифметических операциях Крайне опасным может являться использование omp_get_num_threads в некоторых арифметических выражениях. Пример некорректного кода: int lettersPerThread = 26 / omp_get_num_threads(); Ошибка заключается в том, что если omp_get_num_threads вернет, например, значение 4, то произойдет деление с остатком. В результате часть объектов не будет обработано. С более подробным примером можно ознакомиться в статье "32 подводных камня OpenMP при программировании на Си++" или в демонстрационном примере Parallel Sample, входящий в состав дистрибутива PVS-Studio. Другие выражения, использующие omp_get_num_threads, могут быть вполне корректны. Пример, не вызывающий вывод диагностических сообщений в PVS-Studio: bool b = omp_get_num_threads() == 2; Диагностика: PVS-Studio (VivaMP): диагностируется как ошибка V1103. Intel C++ (Parallel Lint): не диагностируется. Пункт 10. omp_set_nested внутри параллеьной секции Некорректным является вызов функции omp_set_nested внутри параллельной секции, заданной директивой "parallel". Пример: #pragma omp parallel { omp_set_nested(2); } Но если функция omp_set_nested находится во вложенном блоке, созданном директивой "master" или "single", то такой код вполне корректен:
  • 24. #pragma omp parallel { #pragma omp master { omp_set_nested(2); } } Диагностика: PVS-Studio (VivaMP): диагностируется как ошибка V1104. Intel C++ (Parallel Lint): не диагностируется. Пункт 11. Параллельное использование общих ресурсов Опасным следует считать незащищенное использование функций в параллельных секциях, если эти функции используют общие ресурсы. Примеры функций: printf, функции OpenGL. Пример опасного кода: #pragma omp parallel { printf("abcd"); } Пример безопасного кода, когда вызов функции защищен: #pragma omp parallel { #pragma omp critical { printf("abcd"); } } Диагностика: PVS-Studio (VivaMP): частично диагностируется как ошибка V1201. Intel C++ (Parallel Lint): частично диагностируется как error #12158.
  • 25. Диангоностика описана как частичная, по следующим причинам: В VivaMP список опасных функций далеко не полон и требует расширения. К сожалению, анализатор Parallel Lint выдает ложное предупреждение, когда вызов функции защищен. По поводу полноты списка функций, к сожалению, ничего не известно. Пункт 12. flush для указателя Директива flush служит для того, чтобы потоки обновили значения общих переменных. При использовании flush для указателя высока вероятность того, что программист ошибся, считая, что произойдет обновление данных по адресу, на которое ссылается указатель. Обновится значение именно переменной, содержащей указатель. Более того, в стандарте OpenMP явно сказано, что переменная-аргумент директивы flush не должна быть указателем. Пример: int *t; ... #pragma omp flush(t) Диагностика: PVS-Studio (VivaMP): диагностируется как ошибка V1202. Intel C++ (Parallel Lint): не диагностируется. Пункт 13. Использование threadprivate Директива threadprivate является крайне опасной директивой и ее использование рационально только в крайних случаях. По возможности, стоит избегать использования данной директивы. Более подробно с опасностью threadprivate можно познакомится в статье "32 подводных камня OpenMP при программировании на Си++" [2]. Пример опасного кода: #pragma omp threadprivate(var) Диагностика: PVS-Studio (VivaMP): диагностируется как ошибка V1203. Intel C++ (Parallel Lint): не диагностируется. Пункт 14. Состояния гонки. Статические переменные Одна из ошибок состояния гонки. Недопустима инициализация статической переменной в параллельной секции без специальной защиты. Пример: #pragma omp parallel num_threads(2) {
  • 26. static int cachedResult = ComputeSomethingSlowly(); ... } Корректный пример с использованием защиты: #pragma omp parallel num_threads(2) { #pragma omp critical { static int cachedResult = ComputeSomethingSlowly(); ... } } Диагностика: PVS-Studio (VivaMP): диагностируется как ошибка V1204. Intel C++ (Parallel Lint): диагностируется как warning #12246. Пункт 15. Состояние гонки. Общая переменная Одна из ошибок состояния гонки. Два или более потока модифицируют одну общую переменную, которая специально не защищена. Пример: int a = 0; #pragma omp parallel for num_threads(4) for (int i = 0; i < 100000; i++) { a++; } Корректный пример с защитой: int a = 0; #pragma omp parallel for num_threads(4) for (int i = 0; i < 100000; i++) { #pragma omp atomic
  • 27. a++; } Диагностика: PVS-Studio (VivaMP): диагностируется как ошибка V1204. Intel C++ (Parallel Lint): диагностируется как warning #12246, warning #12247, warning #12248. Пункт 16. Состояние гонки. Доступ к массиву Одна из ошибок состояния гонки. Два или более потока пытаются работать с одними и теми же элементами массива, используя для вычисления индексов различные выражения. Пример: int i; int factorial[10]; factorial[0]=1; #pragma omp parallel for for (i=1; i < 10; i++) { factorial[i] = i * factorial[i-1]; } Диагностика: PVS-Studio (VivaMP): не диагностируется. Intel C++ (Parallel Lint): диагностируется как warning #12246. Пункт 17. Состояние гонки. Опасная функция Одна из ошибок состояния гонки. Два или более потока вызывают функцию, принимающую в качестве формального аргумента значение по неконстантной ссылке или неконстантному указателю. При вызове такой функции в параллельной секции в нее передается общая переменная. Никакой защиты при этом не используется. Пример потенциально опасного кода: void dangerousFunction(int& param); void dangerousFunction2(int* param); int a = 0; #pragma omp parallel num_threads(4) { #pragma omp for
  • 28. for (int i = 0; i < 100000; i++) { dangerousFunction(a); dangerousFunction2(&a); } } Диагностика: PVS-Studio (VivaMP): диагностируется как ошибка V1206. Intel C++ (Parallel Lint): не диагностируется. Пункт 18. Состояние гонки. Модификация объекта Одна из ошибок состояния гонки. Два или более потока вызывают неконстантную функцию класса у общего объекта. Никакой защиты при этом не используется. Пример потенциально опасного кода: MyClass obj; #pragma omp parallel for num_threads(2) for (int i = 0; i < 100000; i++) { obj.nonConstMethod(); } Диагностика: PVS-Studio (VivaMP): диагностируется как ошибка V1207. Intel C++ (Parallel Lint): не диагностируется. Пункт 19. Приватные указатели Опасным следует считать применение директив "private", "firstprivate" и "threadprivate" к указателям (не массивам). Пример опасного кода: int *arr; #pragma omp parallel for private(arr)
  • 29. Если переменная будет указателем, каждый поток получит по локальной копии этого указателя, и в результате все потоки будут работать через него с общей памятью. Высока вероятность, что в коде имеется ошибка. Пример безопасного кода, где каждый поток работает со своим собственным массивом: int arr[4]; #pragma omp parallel for private(arr) Диагностика: PVS-Studio (VivaMP): диагностируется как ошибка V1209. Intel C++ (Parallel Lint): не диагностируется. Пункт 20. Неосторожное применение lastprivate Директива lastprivate после параллельной секции присваивает переменной значение из лексически последней секции, либо из последней итерации цикла. Код, в котором в последней секции не модифицируется помеченная переменная, по всей видимости, ошибочен. Пример: #pragma omp sections lastprivate(a) { #pragma omp section { a = 10; } #pragma omp section { } } Диагностика: PVS-Studio (VivaMP): диагностируется как ошибка V1210. Intel C++ (Parallel Lint): не диагностируется. Пункт 21. Бессмысленный flush. Локальные переменные Бессмысленным следует считать использование директивы flush для локальных переменных (объявленных в параллельной секции), а также переменных помеченных как threadprivate, private, lastprivate, firstprivate.
  • 30. Директива flush не имеет для перечисленных переменных смысла, так как эти переменные всегда содержат актуальные значения. И дополнительно снижает производительность кода. Пример: int a = 1; #pragma omp parallel for private(a) for (int i = 10; i < 100; ++i) { #pragma omp flush(a); ... } Диагностика: PVS-Studio (VivaMP): диагностируется как ошибка V1211. Intel C++ (Parallel Lint): не диагностируется. Пункт 22. Бессмысленный flush. Присутсвует неявный flush Неэффективным следует считать использование директивы "flush" там, где оно выполняется неявно. Случаи, в которых директива "flush" присутствует неявно и в ее использовании нет смысла: • в директиве barrier; • при входе и при выходе из секции директивы critical; • при входе и при выходе из секции директивы ordered; • при входе и при выходе из секции директивы parallel; • при выходе из секции директивы for; • при выходе из секции директивы sections; • при выходе из секции директивы single; • при входе и при выходе из секции директивы parallel for; • при входе и при выходе из секции директивы parallel sections; Диагностика: PVS-Studio (VivaMP): не диагностируется. Intel C++ (Parallel Lint): не диагностируется. Пункт 23. Исключения. Явный throw Согласно спецификации OpenMP, все исключения должны быть обработаны внутри параллельной секции. Выход исключения за пределы параллельной секции приведет к сбою в программе и, скорее всего, к ее аварийному останову. Пример недопустимого кода:
  • 31. try { #pragma omp parallel for num_threads(4) for(int i = 0; i < 4; i++) { throw 1; } } catch (...) { } Корректный код: size_t errCount = 0; #pragma omp parallel for num_threads(4) reduction(+: errCount) for(int i = 0; i < 4; i++) { try { throw 1; } catch (...) { ++errCount; } } Диагностика: PVS-Studio (VivaMP): диагностируется как ошибка V1301. Intel C++ (Parallel Lint): не диагностируется. Пункт 24. Исключения. Оператор new Согласно спецификации OpenMP, все исключения должны быть обработаны внутри параллельной секции. Оператор "new" в случае ошибки выделения памяти генерирует исключение, и это необходимо учесть при его использовании в параллельных секциях.
  • 32. Пример ошибочного кода: try { #pragma omp parallel for num_threads(4) for(int i = 0; i < 4; i++) { float *ptr = new (MyPlacement) float[1000]; delete [] ptr; } } catch (std::bad_alloc &) { } Более подробную информацию на эту тему можно найти в следующих записях блога разработчиков PVS-Studio: 1. OpenMP и исключения (exceptions) 2. Обработка исключений внутри параллельных секций Диагностика: PVS-Studio (VivaMP): диагностируется как ошибка V1302. Intel C++ (Parallel Lint): не диагностируется. Пункт 25. Исключения. Функции Согласно спецификации OpenMP, все исключения должны быть обработаны внутри параллельной секции. Если в параллельной секции используется функция, помеченная как явно бросающая исключения, то исключение должно быть обработано внутри параллельной секции. Пример ошибочного кода: void MyThrowFoo() throw(...) { throw 1; } try { #pragma omp parallel for num_threads(4) for(int i = 0; i < 4; i++)
  • 33. { MyThrowFoo(); } } catch (...) { } Диагностика: PVS-Studio (VivaMP): диагностируется как ошибка V1303. Intel C++ (Parallel Lint): не диагностируется. Пункт 26. Забытая инициализация. omp_init_lock Ошибочным следует считать использование переменных типа omp_lock_t / omp_nest_lock_t без их предварительной инициализации в функциях omp_init_lock / omp_init_nest_lock. Под использованием понимается вызов функции omp_set_lock и так далее. Пример ошибочного кода: omp_lock_t myLock; #pragma omp parallel num_threads(2) { omp_set_lock(&myLock); omp_unset_lock(&myLock); } Диагностика: PVS-Studio (VivaMP): не диагностируется. Intel C++ (Parallel Lint): не диагностируется. Пункт 27. Забытая инициализация. Локальные переменные Ошибочным следует считать использование переменных, объявленных в параллельном регионе локальными с использованием директив "private" и "lastprivate" без их предварительной инициализации. Пример: int a = 0; #pragma omp parallel private(a)
  • 34. { a++; } Диагностика: PVS-Studio (VivaMP): не диагностируется. Intel C++ (Parallel Lint): диагностируется как error #12361. Пункт 28. Забытая инициализация после параллельного региона Ошибочным следует считать использование переменных после параллельного участка кода, к которым применялась директива "private", "threadprivate" или "firstprivate". Перед дальнейшим использованием они должны быть вновь инициализированы. Пример некорректного кода: #pragma omp parallel private(a) { ... } b = a; Диагностика: PVS-Studio (VivaMP): не диагностируется. Intel C++ (Parallel Lint): диагностируется как error #12352, error #12358. Пункт 29. Отсутствие конструктора копирования Опасным следует считать применение директив "firstprivate" и "lastprivate" к экземплярам классов, в которых отсутствует конструктор копирования. Диагностика: PVS-Studio (VivaMP): не диагностируется. Intel C++ (Parallel Lint): не диагностируется. Пункт 30. Неэффективное использование критических секций Неэффективным следует считать использование критических секций или функций класса omp_set_lock, там где достаточно директивы "atomic". Директива atomic работает быстрее, чем критические секции, поскольку некоторые атомарные операции могут быть напрямую заменены командами процессора. Следовательно, эту директиву желательно применять везде, где
  • 35. требуется защита общей памяти при элементарных операциях. К таким операциям, согласно спецификации OpenMP, относятся операции следующего вида: • x binop= expr • x++ • ++x • x-- • --x Здесь х - скалярная переменная, expr - выражение со скалярными типами, в котором не присутствует переменная х, binop - не перегруженный оператор +, *, -, /, &, ^, |, <<, или >>. Во всех остальных случаях применять директиву atomic нельзя (это проверяется компилятором). Вообще, с точки зрения убывания быстродействия, средства защиты общих данных от одновременной записи располагаются так: atomic, critical, omp_set_lock. Пример неэффективного кода: #pragma omp critical { e++; } Диагностика: PVS-Studio (VivaMP): не диагностируется. Intel C++ (Parallel Lint): не диагностируется. Пункт 31. Бессмысленная защита локальных переменных Любая защита памяти от одновременной записи замедляет выполнение программы, будь то атомарная операция, критическая секция или блокировка. Следовательно, в тех случаях, когда она не нужна, эту защиту лучше не использовать. Переменную не нужно защищать от одновременной записи в следующих случаях: если переменная является локальной для потока (а также если она участвует в выражении threadprivate, firstprivate, private или lastprivate); если обращение к переменной производится в коде, который гарантированно выполняется только одним потоком (в параллельной секции директивы master или директивы single). Пример, где защита записи в переменную "p" не имеет смысла: #pragma omp parallel for private(p) for (i=1; i<10; i++) {
  • 36. #pragma omp critical p = 0; ... } Диагностика: PVS-Studio (VivaMP): не диагностируется. Intel C++ (Parallel Lint): не диагностируется. Пункт 32. Лишний reduction Иногда в коде может использоваться директива reduction, хотя указанные в ней переменные не меняются в параллельном регионе. Это может свидетельствовать как об ошибке, так и просто о том, что какая-то директива или переменная была забыта и не удалена в процессе рефакторинга кода. Пример, где не используется переменная abcde: #pragma omp parallel for reduction (+:sum, abcde) for (i=1; i<999; i++) { sum = sum + a[i]; } Диагностика: PVS-Studio (VivaMP): не диагностируется. Intel C++ (Parallel Lint): диагностируется как error #12283. Заключение Хотя сравнение двух анализаторов пока нельзя назвать законченным, эта статья позволяет читателю узнать о многих паттернах параллельных OpenMP-ошибок и о методах их выявления на самых ранних этапах разработки. Возможность выявить ошибку на этапе кодирования является сильным преимуществом методики статического анализа, так как эти ошибки могут с большим трудом выявляться на этапе тестирования или вовсе не выявляться весьма продолжительное время. Рассмотренные инструменты PVS-Studio (VivaMP) и Intel C/C++ ("Parallel Lint") будут крайне полезны разработчикам параллельных программ. Какой инструмент выбрать - зависит от разработчика. К сожалению, эта статья не дает ответ на этот вопрос. Пока можно сформулировать преимущества каждого из анализаторов следующим образом: Преимущества анализатора Intel C/C++ ("Parallel Lint"):
  • 37. анализатор входит в состав компилятора, что позволяет разработчикам, использующим Intel C/C++, без каких либо дополнительных вложений или усилий производить верификацию параллельного кода; • анализатор собирает вместе данные по всем модулям программы и затем целостно их обрабатывает, что позволяет осуществлять некоторые сложные алгоритмы диагностики; • авторитет и опыт разработчиков Intel C++ в области параллельных решений не вызывает сомнений. Преимущества анализатора PVS-Studio (VivaMP): • возможность проверить проект, разрабатываемый в Visual Studio 2005/2008 без необходимости его предварительной адаптации для сборки компилятором Intel C++; • разработчики, в том числе и автор статьи, доступны для оперативного общения и могут быстро реализовать в анализаторе те возможности, которые необходимы пользователям; • в состав анализатора входит подробное описание всех паттернов ошибок с примерами, интегрирующимися в справочную систему MSDN (доступной в online режиме здесь: http://www.viva64.com/content/PVS-Studio-help-ru/contents-ru.html); • интерфейс PVS-Studio позволяет интерактивно фильтровать диагностические сообщения несколькими методами, сохранять и загружать список предупреждений; • VivaMP является узкоспециализированным, активно развивающимся анализатором, с постоянно растущей базой паттернов параллельных ошибок. Библиографический список 1. Евгений Рыжков. Учебное пособие по PVS-Studio. http://www.viva64.com/art-4-1- 1796251700.html 2. Алексей Колосов, Евгений Рыжков, Андрей Карпов. 32 подводных камня OpenMP при программировании на Си++. http://www.viva64.com/art-3-1-464379766.html 3. Андрей Карпов, Евгений Рыжков. OpenMP и статический анализ кода. http://www.viva64.com/art-3-1-1177384583.html