C++ Attributes
2.2. C++ Attributes#
OpenMP directives for C++ can also be specified with the directive extension for the C++11 standard attributes .
The C++ example below shows two ways to parallelize a for loop using the #pragma syntax. The first pragma uses the combined parallel for directive, and the second applies the uncombined closely nested directives, parallel and for, directly to the same statement. These are labeled PRAG 1-3.
Using the attribute syntax, the same construct in PRAG 1 is applied two different ways in attribute form, as shown in the ATTR 1 and ATTR 2 sections. In ATTR 1 the attribute syntax is used with the omp :: namespace form. In ATTR 2 the attribute syntax is used with the using omp : namespace form.
Next, parallelization is attempted by applying directives using two different syntaxes. For ATTR 3 and PRAG 4, the loop parallelization will fail to compile because multiple directives that apply to the same statement must all use either the attribute syntax or the pragma syntax. The lines have been commented out and labeled INVALID.
While multiple attributes may be applied to the same statement, compilation may fail if the ordering of the directive matters. For the ATTR 4-5 loop parallelization, the parallel directive precedes the for directive, but the compiler may reorder consecutive attributes. If the directives are reversed, compilation will fail.
The attribute directive of the ATTR 6 section resolves the previous problem (in ATTR 4-5). Here, the sequence attribute is used to apply ordering to the directives of ATTR 4-5, using the omp :: namespace qualifier. (The using omp : namespace form is not available for the sequence attribute.) Note, for the sequence attribute a comma must separate the directive extensions.
The last 3 pairs of sections (PRAG DECL 1-2, 3-4, and 5-6) show cases where directive ordering does not matter for declare simd directives.
In section PRAG DECL 1-2, the two loops use different SIMD forms of the P function (one with simdlen(4) and the other with simdlen(8)), as prescribed by the two different declare simd directives applied to the P function definitions (at the beginning of the code). The directives use the pragma syntax, and order is not important. For the next set of loops (PRAG DECL 3-4) that use the Q function, the attribute syntax is used for the declare simd directives. The result is compliant code since directive order is irrelevant. Sections ATTR DECL 5-6 are included for completeness. Here, the attribute form of the simd directive is used for loops calling the Q function, in combination with the attribute form of the declare simd directives declaring the variants for Q .
//%compiler: clang
//%cflags: -fopenmp
/*
* name: directive_syntax_attribute.1
* type: C++
* version: omp_5.0
*/
#include <stdio.h>
#include <omp.h>
#define NT 4
#define thrd_no omp_get_thread_num
#pragma omp declare simd linear(i) simdlen(4)
#pragma omp declare simd linear(i) simdlen(8)
double P(int i){ return (double)i * (double)i; }
[[omp::directive(declare simd linear(i) simdlen(4))]]
[[omp::directive(declare simd linear(i) simdlen(8))]]
double Q(int i){ return (double)i * (double)i; }
int main() {
#pragma omp parallel for num_threads(NT) // PRAG 1
for(int i=0; i<NT; i++) printf("thrd no %d\n",thrd_no());
#pragma omp parallel num_threads(NT) // PRAG 2
#pragma omp for // PRAG 3
for(int i=0; i<NT; i++) printf("thrd no %d\n",thrd_no());
// ATTR 1
[[omp::directive( parallel for num_threads(NT))]]
for(int i=0; i<NT; i++) printf("thrd no %d\n",thrd_no());
// ATTR 2
[[using omp : directive( parallel for num_threads(NT))]]
for(int i=0; i<NT; i++) printf("thrd no %d\n",thrd_no());
// INVALID-- attribute and non-attribute on same statement
// [[ omp :: directive( parallel num_threads(NT) ) ]] ATTR 3
// #pragma omp for PRAG 4
// for(int i=0; i<NT; i++) printf("thrd no %d\n",thrd_no());
// INVALID-- directive order not guaranteed
// [[ omp :: directive( parallel num_threads(NT) ) ]] ATTR 4
// [[ omp :: directive( for ) ]] ATTR 5
// for(int i=0; i<NT; i++) printf("thrd no %d\n",thrd_no());
// ATTR 6
[[omp::sequence(directive(parallel num_threads(NT)),directive(for))]]
for(int i=0; i<NT; i++) printf("thrd no %d\n",thrd_no());
double tmp=0.0f;
#pragma omp simd reduction(+:tmp) simdlen(4)
for(int i=0;i<100;i++) tmp += P(i); // PRAG DECL 1
#pragma omp simd reduction(+:tmp) simdlen(8)
for(int i=0;i<100;i++) tmp += P(i); // PRAG DECL 2
printf("%f\n",tmp);
tmp=0.0f;
#pragma omp simd reduction(+:tmp) simdlen(4)
for(int i=0;i<100;i++) tmp += Q(i); // ATTR DECL 3
#pragma omp simd reduction(+:tmp) simdlen(8)
for(int i=0;i<100;i++) tmp += Q(i); // ATTR DECL 4
printf("%f\n",tmp);
tmp=0.0f;
[[ omp :: directive(simd reduction(+:tmp) simdlen(4))]]
for(int i=0;i<100;i++) tmp += Q(i); // ATTR DECL 5
[[ omp :: directive(simd reduction(+:tmp) simdlen(8))]]
for(int i=0;i<100;i++) tmp += Q(i); // ATTR DECL 6
printf("%f\n",tmp);
}
// repeated 5 times, any order:
// OUTPUT: thrd no 0
// OUTPUT: thrd no 1
// OUTPUT: thrd no 2
// OUTPUT: thrd no 3
// repeated 3 time:
// OUTPUT: 656700.000000