OpenMP и статический анализ кода
Upcoming SlideShare
Loading in...5
×

Like this? Share it with your network

Share

OpenMP и статический анализ кода

  • 503 views
Uploaded on

В статье рассмотрены принципы, положенные в основу реализации статического анализатора кода VivaMP. Приведенный в статье набор логических условий проверки позволяет диагностировать ряд ошибок в......

В статье рассмотрены принципы, положенные в основу реализации статического анализатора кода VivaMP. Приведенный в статье набор логических условий проверки позволяет диагностировать ряд ошибок в параллельных программах, созданных на основе технологии OpenMP.

More in: Technology
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
    Be the first to like this
No Downloads

Views

Total Views
503
On Slideshare
503
From Embeds
0
Number of Embeds
0

Actions

Shares
Downloads
3
Comments
0
Likes
0

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. OpenMP и статический анализ кодаАвторы: Андрей Карпов, Евгений РыжковДата: 10.11.2008АннотацияВ статье рассмотрены принципы, положенные в основу реализации статического анализаторакода VivaMP. Приведенный в статье набор логических условий проверки позволяетдиагностировать ряд ошибок в параллельных программах, созданных на основе технологииOpenMP.ВведениеМногие ошибки в программах, разработанных на основе технологии OpenMP, можнодиагностировать с помощью статического анализа кода [1]. В данной статье приведен набордиагностических правил, выявляющих потенциально опасные места в коде, которые с высокойвероятностью содержат ошибки. Описанные ниже правила ориентированы для проверки Си иСи++ кода. Но многие из правил после небольшой модификации могут использоватьсяприменительно и к программам на языке Fortran.Описанные в статье правила лежат в основе статического анализатора кода VivaMP,разработанного в компании ООО "Системы программной верификации" [2]. Анализатор VivaMPпредназначен для проверки кода приложений на языке Си/Си++. Анализатор интегрируется всреды разработки Visual Studio 2005/2008, а также добавляет раздел документации в справочнуюсистему MSDN. Анализатор VivaMP включен в состав продукта PVS-Studio.Данная статья пополняется и модифицируется вместе с развитием продукта PVS-Studio, На данныймомент это третий вариант статьи, который соответствует PVS-Studio 3.40. Если в статье указано,что какие-то диагностические возможности не реализованы, то это относится к PVS-Studio 3.40. Вдальнейших версиях анализатора данные возможности могут быть реализованы. Обратитесь кболее новому варианту этой статьи или к документации по PVS-Studio.Диагностические правилаЕсли не оговорено особо, то считается, что во всех правилах директивы используются совместно сдирективой "omp". То есть описание, что используется директива "omp" опускается, чтобысократить текст правил.Обобщенное исключение AДанное исключение применяется в нескольких правилах и для сокращения их текста вынесеноотдельно. Общий смысл состоит в том, что мы находимся вне параллельной секции или явноуказываем какой поток используется или экранируем код блокировками.Безопасными следует считать случаи, когда выполняются одно из следующих условий дляпроверяемого кода:
  • 2. 1. Нет параллельной секции (нет директивы "parallel"). 2. Внутри параллельной секции используется критическая секция, заданная директивой "critical". 3. Внутри параллельной секции имеется "master"-блок. 4. Внутри параллельной секции имеется "single"-блок. 5. Внутри параллельной секции имеется "ordered"-блок. 6. Внутри параллельной секции используются функции вида omp_set_lock и произведена блокировка.Правило N1Опасным следует считать использование директив "for" и "sections" без директивы "parallel".Исключения:Директивы "for" или "sections" находятся внутри параллельной секции, заданной директивой"parallel".Пример опасного кода:#pragma omp forfor(int i = 0; i < 100; i++) ...Пример безопасного кода:#pragma omp parallel{ #pragma omp for for(int i = 0; i < 100; i++) ...}Диагностические сообщения, выдача которых основана на данном правиле:V1001. Missing parallel keyword.Правило N2Опасным следует считать использование одной из директив, относящейся к OpenMP бездирективы "omp".Исключения:Использование директивы "warning".Пример опасного кода:#pragma single
  • 3. Пример безопасного кода:#pragma warning(disable : 4793)Диагностические сообщения, выдача которых основана на данном правиле:V1002. Missing omp keyword.Правило N3Опасным следует считать использование оператора for сразу после директивы "parallel" бездирективы "for".Пример опасного кода:#pragma omp parallel num_threads(2)for(int i = 0; i < 2; i++) ...Пример безопасного кода:#pragma omp parallel num_threads(2){ for(int i = 0; i < 2; i++) ...}Диагностические сообщения, выдача которых основана на данном правиле:V1003. Missing for keyword. Each thread will execute the entire loop.Правило N4Опасным следует считать создание параллельного цикла с использованием директив "parallel" и"for" внутри параллельной секции созданной директивой "parallel".Пример опасного кода:#pragma omp parallel{ #pragma omp parallel for for(int i = 0; i < 100; i++) ...}Пример безопасного кода:#pragma omp parallel
  • 4. { #pragma omp for for(int i = 0; i < 100; i++) ...}Диагностические сообщения, выдача которых основана на данном правиле:V1004. Nested parallelization of a for loop.Правило N5Опасным следует считать совместное использование директив "for" и "ordered", если затемвнутри цикла заданного оператором for не используется директива "ordered".Пример опасного кода:#pragma omp parallel for orderedfor(int i = 0; i < 4; i++){ foo(i);}Пример безопасного кода:#pragma omp parallel for orderedfor(int i = 0; i < 4; i++){ #pragma omp ordered { foo(i); }}Диагностические сообщения, выдача которых основана на данном правиле:V1005. The ordered directive is not present in an ordered loop.Правило N6Опасным следует считать вызов функции omp_set_num_threads внутри параллельной секции,заданной директивой "parallel".Пример опасного кода:
  • 5. #pragma omp parallel{ omp_set_num_threads(2);}Пример безопасного кода:omp_set_num_threads(2);#pragma omp parallel{ ...}Диагностические сообщения, выдача которых основана на данном правиле:V1101. Redefining number of threads in a parallel code.Правило N7Опасным следует считать нечетное использование функций 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); }}Пример безопасного кода:#pragma omp parallel sections{ #pragma omp section { omp_set_lock(&myLock); omp_unset_lock(&myLock);
  • 6. }}Диагностические сообщения, выдача которых основана на данном правиле:V1102. Non-symmetrical use of set/unset functions for the following lock variable(s): %1%.Правило N8Опасным следует считать использование функции omp_get_num_threads в арифметическихоперациях.Исключения:Возвращаемое функцией omp_get_num_threads значение используется для сравнения илиприравнивается переменной.Пример опасного кода:int lettersPerThread = 26 / omp_get_num_threads();Пример безопасного кода:bool b = omp_get_num_threads() == 2;switch(omp_get_num_threads()){ ...}Диагностические сообщения, выдача которых основана на данном правиле:V1103. Threads number dependent code. The omp_get_num_threads function is used in an arithmeticexpresion.Правило N9Опасным следует считать вызов функции omp_set_nested внутри параллельной секции, заданнойдирективой "parallel".Исключения:Функция находится во вложенном блоке, созданной директивой "master" или "single".Пример опасного кода:#pragma omp parallel{ omp_set_nested(2);
  • 7. }Пример безопасного кода:#pragma omp parallel{ #pragma omp master { omp_set_nested(2); }}Диагностические сообщения, выдача которых основана на данном правиле:V1104. Redefining nested parallelism in a parallel code.Правило N10Опасным следует считать использование функций, использующих общие ресурсы. Примерыфункций: printf.Исключения:Обобщенное исключение A.Пример опасного кода:#pragma omp parallel{ printf("abcd");}Пример безопасного кода:#pragma omp parallel{ #pragma omp critical { printf("abcd"); }}Диагностические сообщения, выдача которых основана на данном правиле:
  • 8. V1201. Concurrent usage of a shared resource via an unprotected call of the %1% function.Правило N11Опасным следует считать применение директивы flush к указателямПример опасного кода:int *t;...#pragma omp flush(t)Пример безопасного кода:int t;...#pragma omp flush(t)Диагностические сообщения, выдача которых основана на данном правиле:V1202. The flush directive should not be used for the %1% variable, because the variable has pointertype.Правило N12Опасным следует считать использование директивы "threadprivate".Пример опасного кода:#pragma omp threadprivate(var)Диагностические сообщения, выдача которых основана на данном правиле:V1203. Using the threadprivate directive is dangerous, because it affects the entire file. Use localvariables or specify access type for each parallel block explicitly instead.Правило N13Опасным следует считать инициализацию или модификацию объекта (переменной) впараллельной секции, если объект относительно этой секции является глобальным (общим дляпотоков).Пояснение правила:К глобальным объектам относительно параллельной секции относятся: 1. Статические переменные. 2. Статические члены класса (В текущей версии VivaMP данное правило не реализовано). 3. Переменные, объявленные вне параллельной секции. 4. При анализе кода функции, которая вызывается параллельно, глобальными объектами считаются глобальные переменные. Если анализируемая функция является членом класса, то считать члены класса глобальными или нет, зависит от того, как происходит вызов этой функции. Если вызов происходит из другой функции данного класса, то члены класса
  • 9. считаются глобальными. Если вызов происходит посредством оператора . или ->, то объекты также считаются глобальными.Последний пункт нуждается в пояснении. Приведем пример:class MyClass {public:int m_a;void IncFoo() { a++; }void Foo() {#pragma omp parallel forfor (int i = 0; i < 10; i++)IncFoo(); // Variant. A}};MyClass object_1;#pragma omp parallel forfor (int i = 0; i < 10; i++){object_1.IncFoo(); // Variant. BMyClass object_2;object_2.IncFoo(); // Variant. C}В случае варианта A мы будем считать, что члены класса общие, то есть глобальны по отношениюк функции IncFoo. В результате мы обнаружим ошибку состояния гонки внутри функции IncFoo.В случае варианта B мы будем считать, что члены класса локальны и ошибки в IncFoo нет. Но будетвыдано предупреждение, что параллельно вызывается не константный метод IncFoo из классаMyClass. Это поможет найти ошибку.В случае варианта C мы будем считать, что члены класса локальны и ошибки в IncFoo нет. Иошибок действительно нет.Объект может быть как простого типа тип, так и экземпляром класса. К операциям измененияобъекта относится: 1. Передача объекта в функцию по не константной ссылке.
  • 10. 2. Передача объекта в функцию по не константному указателю (В текущей версии VivaMP данное правило не реализовано). 3. Изменение объекта в ходе арифметических операций или операции присваивания. 4. Вызов у объекта не константного метода.Исключения: 1. Обобщенное исключение A. 2. К объекту применена директива "threadprivate", "private", "firstprivate", "lastprivate" или "reduction". Это исключение не касается статических (static) переменных и статических полей классов, которые всегда являются общими. 3. Модификация объекта защищена директивой "atomic". 4. Модификация объекта осуществляется внутри только одной секции, заданной директивой "section". 5. Инициализация или модификация объектов осуществляется внутри распараллеленного оператора for (внутри самого оператора, а не внутри тела цикла). Такие объекты согласно спецификации OpenMP автоматически считаются локальными (private). Пример: int i; ... #pragma omp parallel for for (i = 0; i < n; i++) {}. // i - is private.Пример опасного кода:#pragma omp parallel{ static int st = 1; // V1204}void foo(int &) {}...int value;MyObjectType obj;#pragma omp parallel forfor(int i = 0; i < 33; i++){ ++value; // V1205 foo(value); // V1206 obj.non_const_foo(); // V1207}Пример безопасного кода:
  • 11. #pragma omp parallel{ #pragma omp critical { static int st = 1; }}void foo(const int &) {}...int value;MyObjectType obj;#pragma omp parallel forfor(int i = 0; i < 33; i++){ #pragma omp atomic ++value; foo(value); obj.const_foo();}Диагностические сообщения, выдача которых основана на данном правиле:V1204. Data race risk. Unprotected static variable declaration in a parallel code.V1205. Data race risk. Unprotected concurrent operation with the %1% variable.V1206. Data race risk. The value of the %1% variable can be changed concurrently via the %2%function.V1207. Data race risk. The %1% object can be changed concurrently by a non-const function.Правило N14Опасным следует считать применение директив "private", "firstprivate" и "threadprivate" к ссылками указателям (не массивам).Пример опасного кода:int *arr;#pragma omp parallel for private(arr)
  • 12. Пример безопасного кода:int arr[4];#pragma omp parallel for private(arr)Диагностические сообщения, выдача которых основана на данном правиле:V1208. The %1% variable of reference type cannot be private.V1209. Warning: The %1% variable of pointer type should not be private.Правило N15Опасным следует считать отсутствие модификации переменной помеченной директивой"lastprivate" в последней секции ("section ").Исключения:Переменная также не модифицируется и во всех остальных секциях.Пример опасного кода:#pragma omp sections lastprivate(a){ #pragma omp section { a = 10; } #pragma omp section { }}Пример безопасного кода:#pragma omp sections lastprivate(a){ #pragma omp section { a = 10; } #pragma omp section
  • 13. { a = 20; }}Диагностические сообщения, выдача которых основана на данном правиле:V1210. The %1% variable is marked as lastprivate but is not changed in the last section.Правило N16Опасным следует считать использование переменной типа 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_lock_t myLock;omp_init_lock(&myLock);#pragma omp parallel num_threads(2){ ... omp_set_lock(&myLock);}Диагностические сообщения, выдача которых основана на данном правиле:В текущей версии VivaMP данное правило не реализовано.Правило N17Опасным следует считать использование переменных, объявленных в параллельной секциилокальными с использованием директив "private" и "lastprivate" без их предварительнойинициализации.
  • 14. Пример опасного кода:int a = 0;#pragma omp parallel private(a){ a++;}Пример безопасного кода:int a = 0;#pragma omp parallel private(a){ a = 0; a++;}Диагностические сообщения, выдача которых основана на данном правиле:В текущей версии VivaMP данное правило не реализовано.Правило N18Опасным следует считать использование после параллельной секции переменных, к которымприменялась директива "private", "threadprivate" или "firstprivate" без предварительнойинициализации.Пример опасного кода:#pragma omp parallel private(a){ ...}a++;Пример безопасного кода:#pragma omp parallel private(a){ ...}a = 10;
  • 15. Диагностические сообщения, выдача которых основана на данном правиле:В текущей версии VivaMP данное правило не реализовано.Правило N19Опасным следует считать применение директив "firstprivate" и "lastprivate" к экземплярамклассов, в которых отсутствует конструктор копирования.Диагностические сообщения, выдача которых основана на данном правиле:В текущей версии VivaMP данное правило не реализовано.Правило N20Неэффективным следует считать использование директивы "flush", там где оно выполняетсянеявно. Случаи, в которых директива "flush" присутствует неявно и в ее использовании нетсмысла: • В директиве barrier • При входе и при выходе из параллельной секции директивы critical • При входе и при выходе из параллельной секции директивы ordered • При входе и при выходе из параллельной секции директивы parallel • При выходе из параллельной секции директивы for • При выходе из параллельной секции директивы sections • При выходе из параллельной секции директивы single • При входе и при выходе из параллельной секции директивы parallel for • При входе и при выходе из параллельной секции директивы parallel sectionsДиагностические сообщения, выдача которых основана на данном правиле:В текущей версии VivaMP данное правило не реализовано.Правило N21Неэффективным следует считать использование директивы flush для локальных переменных(объявленных в параллельной секции), а также переменных помеченных как threadprivate, private,lastprivate, firstprivate.Пример:int a = 1;#pragma omp parallel for private(a)for (int i = 10; i < 100; ++i) { #pragma omp flush(a); ...}Диагностические сообщения, выдача которых основана на данном правиле:V1211. The use of flush directive has no sense for private NN variable, and can reduce performance.
  • 16. Правило N22Неэффективным следует считать использование критических секций или функций классаomp_set_lock, там где достаточно директивы "atomic".Диагностические сообщения, выдача которых основана на данном правиле:В текущей версии VivaMP данное правило не реализовано.Правило N23Неэффективным следует считать использование директивы 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); ...}Пример безопасного кода:int a = 1;#pragma omp parallel forfor (int i = 10; i < 100; ++i) { #pragma omp flush(a); ...}Диагностические сообщения, выдача которых основана на данном правиле:В текущей версии VivaMP данное правило не реализовано.Правило N24Согласно спецификации OpenMP все исключения должны быть обработаны внутри параллельнойсекции. Считается, что код генерирует исключения, если в нем:используется оператор throw;используется оператор new;
  • 17. вызывается функция, отмеченная как throw(...);Такой код должен быть обернут в блок try..catch внутри параллельнйо секции.Исключения:Используется оператор new, не бросающий исключения (new(std::nothrow) float[10000];).Пример опасного кода:void MyNotThrowFoo() throw() { }...#pragma omp parallel for num_threads(4)for(int i = 0; i < 4; i++){ ... throw 1; ... float *ptr = new float[10000]; ... MyThrowFoo();}Пример безопасного кода: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; }
  • 18. }if (errCount != 0) throw 1;Примечание. Конструкция nothrow new несколько обманчивая, так как возникает ощущение,что исключений здесь быть не может. Но следует учесть, что исключения могут бытьсгенерированы в конструкторе создаваемых объектов. То есть, если выделяется хотя бы одинstd::string или сам класс выделяет память по new (без nothrow), то исключения при вызовеnew(nothrow) всё равно могут быть сгенерированы. Диагностика данных ошибок заключаетсяв анализе тел конструкторов, (и тел конструкторов других объектов, содержащихся в классе)которые вызываются внутри параллельных секций. На данный момент даннаяфункциональность в VivaMP не реализована.Диагностические сообщения, выдача которых основана на данном правиле:V1301. The throw keyword cannot be used outside of a try..catch block in a parallel section.V1302. The new operator cannot be used outside of a try..catch block in a parallel section.V1303. The %1% function which throws an exception cannot be used in a parallel section outside of atry..catch block.Правило N25Опасным следует считать отсутствие включения заголовочного файла <omp.h> в файле, гдеиспользуются директивы OpenMP.Диагностические сообщения, выдача которых основана на данном правиле:V1006. Missing omp.h header file. Use #include <omp.h>.Правило N26Опасным следует считать наличие неиспользуемых переменных, отмеченных в директивеreduction. Это может свидетельствовать как об ошибке, так и просто о том, что какая-то директиваили переменная была забыта и не удалена в процессе рефакторинга кода.Пример, где не используется переменная abcde:#pragma omp parallel for reduction (+:sum, abcde)for (i=1; i<999; i++){ sum = sum + a[i];}Диагностика:В текущей версии VivaMP данное правило не реализовано.
  • 19. Правило N27Опасным следует считать незащищенный доступ в параллельной секции (образованнойдирективой for) к элементу массива с использованием индекса, отличного от используемого длячтения.Примечание. Ошибка может возникнуть и в случае защищенного доступа (single, critical, ...), носейчас для простоты считаем, что в этом случае доступ к элементам массива безопасен.Примечание.Исключения:1. Индекс является константой.Пример опасного кода:#pragma omp parallel for for (int i=2; i < 10; i++) array[i] = i * array[i-1]; //V1212ПРИМЕЧАНИЕПример безопасного кода:#pragma omp parallel for for (int i=2; i < 10; i++) { array[i] = array [i] / 2; array_2[i] = i * array[i-1]; }Диагностические сообщения, выдача которых основана на данном правиле:V1212. Data race risk. When accessing the array %1% in a parallel loop, different indexes are used forwriting and reading.ЗаключениеЕсли вы интересуетесь методологией проверки программного кода на основе статическогоанализа - напишите нам (support@viva64.com). Мы надеемся, что найдем общие интересы ивозможности для сотрудничества!Библиографический список1. Андрей Карпов. Тестирование параллельных программ.2. http://www.viva64.com/art-3-1-65331121.html3. Евгений Рыжков. VivaMP - инструмент для OpenMP.
  • 20. 4. http://www.viva64.com/art-3-1-1671511269.html