OpenMP is a widely adopted parallel programming model and API that enables developers to write efficeint, scalable applications for shared-memory systems. It simplifies the process of parallelizing code by providing compiler directives, runtime library functions, and environment variables. This framework is particularly effective on multi-core processors, where it allows fine-grained control over thread execution and synchronization.
The core execution model in OpenMP follows a fork-join pattern. Initially, only one master thread runs. When a parallel construct is encountered, the master spawns multiple worker threads to execute the designated block concurrently. Once the parallel section completes, all threads converge back into the master thread, resuming sequential execution.
To achieve optimal performance and correctness, developers must adhere to key practices such as avoiding race conditions, minimizing contention, ensuring balanced work distribution, and managing memory access patterns efficiently.
OpenMP supports C, C++, and Fortran, making it accessible across a broad range of scientific and engineering applications. Its simplicity and integration with standard compilers make it ideal for accelerating computationally intensive tasks through loop-level parallelism and data-parallel operations.
Installing OpenMP Support
On Ubuntu systems, OpenMP is not installed separately—it's included within GCC (GNU Compiler Collection). Most modern versions of GCC (4.2+) come with built-in OpenMP suppport. To verify availability:
gcc --version
Check if your compiler supports OpenMP by attempting to compile a test file:
gcc -fopenmp -o test test.c
If no errors occur during compilation, OpenMP is available.
Basic Usage in C/C++
Include the header:
#include <omp.h>
Compile with OpenMP flags:
For GCC or Clang:
gcc -fopenmp program.c -o program
Define Parallel Regions:
Use #pragma omp parallel to mark blocks for concurrent execution:
#pragma omp parallel
{
// Code executed by multiple threads
}
Distribute Loop Workload:
Apply #pragma omp for to enable automatic partitioning of loop iterations among available threads:
#pragma omp for
for (int idx = 0; idx < N; idx++) {
// Process each iteration in parallel
}
Synchronize Threads:
Ensure coordination using barriers or critical sections:
#pragma omp parallel
{
// Some parallel work
#pragma omp barrier
// All threads wait here
#pragma omp critical
{
// Only one thread executes this block at a time
}
}
Control Variable Scoping:
By default, variables are shared across threads. Use clauses like private, firstprivate, lastprivate, and reduction to manage data visibility:
#pragma omp for private(counter) reduction(+:total)
for (int i = 0; i < N; i++) {
int counter = i * 2;
total += counter;
}
Adjust Thread Count at Runtime:
Override the default number of threads via function calls or environment variables:
omp_set_num_threads(8);
#pragma omp parallel
{
// Runs with exactly 8 threads
}
Debugging and Monitoring:
Use built-in functions to inspect thread behavior:
int thread_id = omp_get_thread_num();
int total_threads = omp_get_num_threads();
Many compilers also offer debugging tools and profilers tailored for OpenMP code.
Note: OpenMP is best suited for shared-memory architectures and loop-based parallelization. For distributed-memory environments requiring complex inter-node communication, alternatives like MPI may be more apppropriate.
Example: Parallel Summation
This example demonstrates how to compute the sum of an array using OpenMP’s parallel for directive:
#include <stdio.h>
#include <omp.h>
int main() {
const int SIZE = 1000;
int total_sum = 0;
int data[SIZE];
// Initialize array values
for (int i = 0; i < SIZE; i++) {
data[i] = i;
}
// Compute sum in parallel
#pragma omp parallel for reduction(+:total_sum)
for (int i = 0; i < SIZE; i++) {
total_sum += data[i];
}
printf("Sum from 0 to %d: %d\n", SIZE - 1, total_sum);
return 0;
}
In this case, reduction(+:total_sum) ensures that partial sums from each thread are correctly combined into a final result. The parallel for directive automatically divides the loop iterations among active threads.
Compile and run:
gcc -fopenmp summation.c -o summation
./summation
Output will consistently reflect the correct total, even though individual thread execution order may vary between runs.