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

535 views

Published on

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

Published in: Technology, Business
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
535
On SlideShare
0
From Embeds
0
Number of Embeds
2
Actions
Shares
0
Downloads
5
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

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

  1. 1. Неудачная попытка сравнить PVS-Studio (VivaMP) и Intel C/C++ ("ParallelLint")Автор: Андрей КарповДата: 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. Более подробно с подсистемой "ParallelLint" можно познакомиться, посмотрев вебинар Дмитрия Петунина "Static Analysis and Intel C/C++Compiler ("Parallel Lint" overview)".ВведениеМне как разработчику анализатора VivaMP было крайне интересно сравнить его диагностическиевозможности с возможностями подсистемы "Parallel Lint", реализованной в Intel C/C++.Дождавшись, когда Intel C/C++ версии 11.1 стал доступен для скачивания, и найдя время дляначала исследований, я приступил к сравнению инструментов. К сожалению, это изучение исравнение не увенчалось успехом, о чем свидетельствует название статьи. Но я уверен, чторазработчики найдут в этой статье много интересной информации.
  2. 2. Для начала исследований я решил испробовать Intel C/C++ на демонстрационном примереParallelSample, входящем в состав PVS-Studio и содержащем ошибки связанные с использованиемOpenMP. Программа ParallelSample содержит 23 паттерна ошибок, которые обнаруживаютсяанализатором VivaMP. Анализ этого проекта должен подтвердить, что анализаторы осуществляютсхожую диагностику и их сравнение корректно.Подготовка к проверке ParallelSample с помощью Intel C++ "ParallelLint"Скачивание и установка пробной версии 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. 3. Рисунок N2. Отключение предкомпилируемых заголовков в настройках проекта.После этого проект был успешно собран, хотя сразу запустить его не удалось (см. рисунок N3).
  4. 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. 5. Рисунок N4. Установка окружения для работоспособности приложений, собранных с использованием Intel C++.Модификация окружения стала последним шагом, после которого приложение ParallelSampleстало успешно компилироваться, запускаться и работать. Теперь можно приступить к самомуинтересному - к запуску "Parallel Lint".Наиболее удобным вариантом использования Intel C++ "Parallel Lint" является создание отдельнойконфигурации проекта. Дело в том, что нельзя одновременно получить исполняемый файл идиагностируемые сообщения от Intel C++ "Parallel Lint". Можно получить что-то одно. Постоянноменять настройки, чтобы собирать EXE-файл или чтобы получить диагностику от "Parallel Lint" -весьма неудобно. Следует пояснить такое поведение.В "классическом" режиме компилятор создает объектные файлы с кодом, которые затемобъединяются с помощью редактора связей (компоновщик). В результате процесс компиляциивыглядит как показано на рисунке N5.
  6. 6. Рисунок N5. Стандартный режим работы компилятора и компоновщика.Для глубокого анализа параллельного кода необходимо владеть информацией обо всех модуляхпрограммы. Это позволяет понять, используется ли какая-то функция из одного модуля (cpp-файла) в параллельном режиме из другого модуля (cpp-файла), как показано на рисунке N6. Рисунок N6. Межмодульное взаимодействие.
  7. 7. Чтобы собрать всю необходимую информацию, Intel C++ использует следующую стратегию.Компилятор по-прежнему создает *.obj файлы, но это не объектные файлы, а файлы, содержащиеданные, необходимые для последующего анализа. Именно поэтому сборка исполняемого EXE-файла невозможна. Когда компилятор обработает все *.cpp файлы, вместо редактора связей(компоновщика) начинает работать анализатор кода. Опираясь на данные, содержащиеся в *.objфайлах, он диагностирует потенциальные проблемы и выводит соответствующие сообщения обошибках. В целом процесс анализа можно представить, как показано на рисунке 7.. Рисунок N7. Сбор данных и последующая их обработка анализатором "Parallel Lint".Создание новой конфигурации осуществляется с помощью "Configuration Manager", как показанона рисунках N8-N10:
  8. 8. Рисунок N8. Запускаем Configuration Manager.
  9. 9. Рисунок N9. Создаем новую конфигурацию с именем "Intel C++ Parallel Lint", на основе существующей.
  10. 10. Рисунок N10. Делаем вновь созданную конфигурацию активной.Следующий шаг - непосредственная активация анализатора "Parallel Lint". Для этого необходимоперейти во вкладку "Diagnostics" в настройках проекта (см. рисунок N11).
  11. 11. Рисунок N11. Вкладка "Diagnostics" в настройках проекта, где можно активировать "Parallel Lint".Данная страница настроек содержит пункт "Level of Source Code Parallelization Analysis", задающийуровень анализа параллельного кода. Всего имеется три варианта, как показано на рисунке N12.
  12. 12. Рисунок N12. Выбор уровня диагностики параллельных ошибок.Я выставил максимальный третий уровень диагностики (см. рисунок N13). Включать анализзаголовочных файлов (Analyze Include Files) я не стал, так как проект ParallelSample содержитошибки только в *.cpp файлах. Рисунок N13. Выбран максимальный уровень диагностики.Анализ проектов и сравнение анализаторовПодготовка к началу анализа завершена, теперь можно запустить компиляцию проекта, чтобыполучить диагностические сообщения. Именно это и было осуществлено, в результате чего был
  13. 13. получен список сообщений. Приводить его здесь нет смысла, тем более что он дополнительносодержит предупреждения, не относящиеся к 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, theres no such list in our current product release, other than whats presented in the compilerdocumentation under Parallel Lint section. But, we are developing description and examples for allmessages, planned as of now for our next major release. Will keep you updated as soon as the releasecontaining such a list is out. Appreciate your patience till then.Резюмируя - описание возможностей "Parallel Lint" и примеров нет, и в ближайшее время небудет. Я оказался перед выбором, оставить сравнение и продолжить его, когда появитсядокументация на возможности "Parallel Lint" или провести неполное сравнение, а в дальнейшемпополнить его. Поскольку была проделана уже существенная часть работы, я решил все-такипродолжить написание статьи. В ней я проведу некорректное сравнение анализаторов. Иподчеркиваю это. В том числе и названием самой статьи.Сравнение будет осуществляться на доступных мне на данный момент данных. В будущем, когдаданных будет больше, я создам вторую версию этой статьи, где возможности Intel C++ будут
  14. 14. учтены более полно. Сейчас сравнение будет осуществлено на основании следующих паттерновошибок: 1. Все паттерны ошибок, которые диагностируются VivaMP и примеры которых представлены в проекте ParallelSample. 2. Паттерны ошибок, которые мне известны, но пока не реализованы в анализаторе VivaMP. О подобных ошибках можно узнать из статей: "32 подводных камня OpenMP при программировании на Си++" и "OpenMP и статический анализ кода". 3. Разрозненные примеры паттернов ошибок, которые входят в состав демонстрационного примера parallel_lint, а также те, которые мне удалось собрать из статей о "Parallel Lint", найденных мной в интернете.В таблице N1 представлены результаты некорректного сравнения анализаторов VivaMP и "ParallelLint". После таблицы будут даны пояснения к каждому из пунктов сравнения. Еще раз подчеркну,что результаты сравнения некорректны. Данные в таблице представлены однобоко. Рассмотренывсе ошибки, обнаруживаемые VivaMP и только часть ошибок, обнаруживаемых Intel C++ ("ParallelLint"). Возможно, Intel C++ ("Parallel Lint") обнаруживает еще 500 неизвестных мне паттерновOpenMP ошибок, и тем самым на порядок превосходит возможности VivaMP.
  15. 15. Таблица N1. Результаты некорректного (неполного) сравнения анализаторов PVS-Studio (VivaMP) и Intel C++ ("Parallel Lint").Пояснение к пунктам сравнения, представленным в таблице N1Пункт 1. Забытая директива parallelОшибка возникает, когда забыта директива parallel. Эта ошибка относится к классу ошибокдопускаемых по невнимательности и приводит к неожиданному поведению кода. Пример, гдецикл не будет распараллелен:#pragma omp forfor(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.
  16. 16. Пункт 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 forfor(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 orderedfor(int i = 0; i < 4; i++){
  17. 17. 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, входящий в состав IntelC++. Компилируем его с использованием Visual C++. Запускаем и получаем ошибку при запускеисполняемого файла, как показано на рисунке N14.
  18. 18. Рисунок 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{
  19. 19. #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", то такой код вполне корректен:
  20. 20. #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.
  21. 21. Диангоностика описана как частичная, по следующим причинам:В 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){
  22. 22. 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
  23. 23. 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
  24. 24. 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)
  25. 25. Если переменная будет указателем, каждый поток получит по локальной копии этого указателя, ив результате все потоки будут работать через него с общей памятью. Высока вероятность, что вкоде имеется ошибка.Пример безопасного кода, где каждый поток работает со своим собственным массивом: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.
  26. 26. Директива 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, все исключения должны быть обработаны внутри параллельнойсекции. Выход исключения за пределы параллельной секции приведет к сбою в программе и,скорее всего, к ее аварийному останову.Пример недопустимого кода:
  27. 27. 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" в случае ошибки выделения памяти генерирует исключение, и этонеобходимо учесть при его использовании в параллельных секциях.
  28. 28. Пример ошибочного кода: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++)
  29. 29. { 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)
  30. 30. { 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 работает быстрее, чемкритические секции, поскольку некоторые атомарные операции могут быть напрямую замененыкомандами процессора. Следовательно, эту директиву желательно применять везде, где
  31. 31. требуется защита общей памяти при элементарных операциях. К таким операциям, согласноспецификации 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++){
  32. 32. #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"):
  33. 33. • анализатор входит в состав компилятора, что позволяет разработчикам, использующим 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

×