OpenMP (for learning use only)

1. Definition

OpenMP (Open Multi-Processing) is an application programming interface (API) for parallel programming optimized for parallel computing on shared-memory multiprocessor systems. It is a portable, scalable parallel programming model that can run on multiple platforms, including computer clusters and large supercomputers.

OpenMP is an open standard consisting of a set of C, C++, and Fortran compiler directives that enable parallelization while writing serial code to achieve higher program performance. By breaking code up into multiple threads, OpenMP enables multiple processors to work on the problem simultaneously, reducing computation time.

OpenMP can add parallelization without modifying program code because it uses compiler directives to control thread creation and synchronization. This makes it ideal for applications that need to quickly parallelize existing code.

OpenMP provides a series of directives, including #pragma directives, used to tell the compiler which parts should be executed in parallel. Using these instructions in the code can achieve parallel computing and improve program performance.

2. The principle of its acceleration

The parallel acceleration principle of OpenMP is based on the parallel computing model of shared memory. In a shared memory computer system, multiple processors can simultaneously access shared main memory. In OpenMP, programmers can control how to distribute computing tasks to different processors by writing specific instructions to achieve parallel computing.

OpenMP uses a thread-based parallel computing model. Thread is the basic unit of program execution flow, and multiple threads can access shared main memory at the same time, thereby realizing parallel computing. Programmers can use OpenMP directives to create, synchronize, and manage threads. OpenMP also provides directives such as #pragma omp parallel and #pragma omp for to parallelize blocks of code. These instructions tell the compiler to create multiple threads at runtime to execute a specified block of code, and ensure proper coordination and data sharing between threads through synchronization mechanisms.

By assigning computing tasks to multiple threads for parallel execution, OpenMP can take advantage of multiple processors in a computer system, thus speeding up program execution. At runtime, OpenMP distributes computing tasks to different processors and uses synchronization mechanisms to ensure proper coordination and sharing of data between threads. In this way, OpenMP can achieve parallel acceleration and improve program performance.

3. Code application example

1.C++

Suppose we have a computationally intensive loop that needs to square each element in an array, the code is as follows:

#include <iostream>

const int N = 100000;

int main()
{
    double a[N];

    // 初始化数组
    for (int i = 0; i < N; i++) {
        a[i] = i;
    }

    // 计算数组每个元素的平方
    for (int i = 0; i < N; i++) {
        a[i] = a[i] * a[i];
    }

    return 0;
}

This code is executed serially, if the array size is large, then the calculation time may be relatively long. We can use OpenMP to parallelize the calculation tasks in the loop, thus speeding up the calculation process. Here is the code for parallel acceleration using OpenMP:

#include <iostream>
#include <omp.h>

const int N = 100000;

int main()
{
    double a[N];

    // 初始化数组
    for (int i = 0; i < N; i++) {
        a[i] = i;
    }

    // 计算数组每个元素的平方
    #pragma omp parallel for
    for (int i = 0; i < N; i++) {
        a[i] = a[i] * a[i];
    }

    return 0;
}

In this code, we have used the #pragma omp parallel for directive to parallelize the for loop. This directive tells the compiler to create multiple threads to perform computational tasks in a loop, and to use synchronization mechanisms to ensure proper coordination and data sharing between threads. In this way, we can take advantage of multiple processors in the computer system to speed up the calculation process.

2.Python

Here is a simple Python code example to calculate the nth term of the Fibonacci sequence:

def fib(n):
    if n <= 1:
        return n
    else:
        return fib(n-1) + fib(n-2)

print(fib(40))

This code is executed serially, and if n is large, the calculation time may be very long. We can use the PyOpenMP library to parallelize computing tasks and speed up the computing process. Here is the code to achieve parallel acceleration using PyOpenMP:

import pyopenmp

def fib(n):
    if n <= 1:
        return n
    else:
        with pyopenmp.Parallel() as p:
            if p.thread_num == 0:
                return fib(n-1) + fib(n-2)
            elif p.thread_num == 1:
                return fib(n-2)
            else:
                return fib(n-1)

print(fib(40))

In this code, we use the Parallel() context manager from the PyOpenMP library to create multiple threads to perform computational tasks. In the Parallel() context manager, programmers can use if statements to specify which threads perform which computation tasks. In this example, we use the if statement to determine the number of the current thread. If it is the first thread, calculate fib(n-1) + fib(n-2); if it is the second thread, calculate fib( n-2); other threads return 0. In this way, we can utilize multiple threads to perform calculation tasks simultaneously, thus speeding up the calculation process.

It should be noted that Python is an interpreted language, while OpenMP is designed for compiled languages, and Python's Global Interpretation Lock (GIL) may affect the execution efficiency of multi-threaded programs. Therefore, when using PyOpenMP, the number of threads and the thread allocation strategy need to be carefully considered to ensure the correctness and efficiency of the program.

3. Advanced example

shows how to compute the element-wise sum of a vector in parallel using the Numpy and PyOpenMP libraries:

import numpy as np
import pyopenmp as omp

def parallel_sum(arr):
    n = len(arr)
    sum = 0
    # 开始并行计算
    with omp.Parallel(num_threads=4):
        # 获取当前线程编号
        tid = omp.get_thread_num()
        # 计算当前线程需要处理的部分
        start = tid * (n // omp.get_num_threads())
        end = (tid + 1) * (n // omp.get_num_threads()) if tid != omp.get_num_threads() - 1 else n
        # 在每个线程中计算部分和
        local_sum = 0
        for i in range(start, end):
            local_sum += arr[i]
        # 合并每个线程的部分和
        with omp.Lock():
            sum += local_sum
    return sum

arr = np.arange(10000000)
sum = parallel_sum(arr)
print("sum = ", sum)

In the above code, we first use the Numpy library to create a one-dimensional array arr with a length of 10000000, and then define a function parallel_sum to calculate the element sum of the array. In the function, we use the with omp.Parallel(num_threads=4) statement to start the parallel calculation and assign the task to 4 threads to complete. In each thread, we first obtain the number of the current thread, then calculate the part that the current thread needs to process, and then use the for loop to calculate the element sum of this part. Finally, we use the with omp.Lock(): statement to add each thread's partial sum to the sum to ensure the correctness of the calculation.

It should be noted that parallelizing code using the PyOpenMP library requires careful handling of data sharing and synchronization to avoid problems such as race conditions and deadlocks. In addition, the choice of the number of threads also needs to consider the hardware resources of the computer and the effect of parallelization, and requires careful debugging and testing.

4. Practical case 1

Original code:


	pcl::console::print_highlight("计算法线\n");
	pcl::NormalEstimationOMP<pcl::PointNormal, pcl::PointNormal> ne;
	ne.setInputCloud(cloud_input);
	ne.setKSearch(50);
	ne.compute(*cloud_input);

Modified code:

#include <omp.h>

// ...

pcl::console::print_highlight("计算法线\n");
pcl::NormalEstimationOMP<pcl::PointNormal, pcl::PointNormal> ne;
ne.setInputCloud(cloud_input);
ne.setKSearch(50);

#pragma omp parallel for
for (int i = 0; i < cloud_input->size(); ++i)
{
    if (pcl::isFinite((*cloud_input)[i]))
    {
        pcl::PointNormal pn;
        ne.computePointNormal(*cloud_input, std::vector<int>{i}, pn.normal_x, pn.normal_y, pn.normal_z, pn.curvature);
        pn.x = (*cloud_input)[i].x;
        pn.y = (*cloud_input)[i].y;
        pn.z = (*cloud_input)[i].z;
        cloud_output->push_back(pn);
    }
}

In this code, #pragma omp parallel fora parallel loop is specified, which runs on multiple threads and distributes the work among the different threads. In the loop, we only compute normals for a finite number of points in the point cloud and store the result in the output point cloud. In this way, the advantages of multi-core CPUs can be utilized to improve computing efficiency.

5. Practical case 2

1. Point cloud downsampling

#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/point_types.h>
#include <pcl/console/time.h> //控制台时间库
#include <omp.h> // OpenMP库

int main (int argc, char** argv)
{
    if (argc != 3)
    {
        std::cerr << "请提供一个输入PCD文件和一个输出PCD文件作为参数!" << std::endl;
        return -1;
    }

    pcl::console::TicToc time; // 创建计时器
    time.tic();

    // 读取点云数据
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_input (new pcl::PointCloud<pcl::PointXYZ>);
    pcl::io::loadPCDFile (argv[1], *cloud_input);

    // 降采样
    pcl::VoxelGrid<pcl::PointXYZ> sor;
    sor.setInputCloud (cloud_input);
    sor.setLeafSize (0.01f, 0.01f, 0.01f);
    sor.setDownsampleAllData(true); // 防止数据集过大导致程序卡死
    sor.setSaveLeafLayout(true); // 为了能够精确测量程序执行时间
    sor.filter (*cloud_input);

    // 并行化降采样
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_output(new pcl::PointCloud<pcl::PointXYZ>);
    cloud_output->reserve(cloud_input->size()); // 预分配空间
    #pragma omp parallel for // 使用OpenMP并行化计算过程
    for(int i=0; i<cloud_input->size(); i++)
    {
        pcl::PointXYZ p = cloud_input->at(i);
        if(!std::isnan(p.x) && !std::isnan(p.y) && !std::isnan(p.z))
        {
            cloud_output->push_back(p);
        }
    }

    // 保存点云数据
    pcl::io::savePCDFileBinary (argv[2], *cloud_output);

    // 输出程序执行时间
    std::cout << "程序执行时间:" << time.toc() << "ms" << std::endl;

    return 0;
}

In the above code, we use VoxelGrid to downsample the input point cloud data, and then use OpenMP to parallelize the downsampling process. Specifically, we use #pragma omp parallel for to achieve parallelization, which allows the iteration operations in the loop to be executed in parallel. In this example, we assign the downsampled point cloud data to different threads for processing, so as to improve the execution efficiency of the program.

2. Point cloud downsampling and smoothing filtering

#include <pcl/point_types.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/filters/statistical_outlier_removal.h>
#include <pcl/console/time.h>
#include <pcl/point_cloud.h>
#include <pcl/point_representation.h>
#include <pcl/visualization/pcl_visualizer.h>

#include <iostream>
#include <omp.h>

using namespace std;
using namespace pcl;

typedef PointXYZ PointT;
typedef PointCloud<PointT> PointCloudT;

int main(int argc, char **argv)
{
    // Load point cloud data
    PointCloudT::Ptr cloud(new PointCloudT());
    if (io::loadPCDFile<PointT>("input.pcd", *cloud) == -1)
    {
        cerr << "Failed to load input point cloud!" << endl;
        return -1;
    }

    // Downsample the point cloud using voxel grid filter
    console::print_highlight("Downsampling the point cloud...\n");
    VoxelGrid<PointT> voxel_filter;
    voxel_filter.setInputCloud(cloud);
    voxel_filter.setLeafSize(0.01f, 0.01f, 0.01f);
    PointCloudT::Ptr cloud_downsampled(new PointCloudT());
    voxel_filter.filter(*cloud_downsampled);

    // Smooth the point cloud using statistical outlier removal filter
    console::print_highlight("Smoothing the point cloud...\n");
    StatisticalOutlierRemoval<PointT> outlier_filter;
    outlier_filter.setInputCloud(cloud_downsampled);
    outlier_filter.setMeanK(50);
    outlier_filter.setStddevMulThresh(1.0);
    PointCloudT::Ptr cloud_smoothed(new PointCloudT());
    outlier_filter.filter(*cloud_smoothed);

    // Visualize the original and processed point clouds
    visualization::PCLVisualizer viewer("Point Cloud Viewer");
    viewer.setBackgroundColor(0, 0, 0);
    viewer.addPointCloud<PointT>(cloud, "cloud");
    viewer.setPointCloudRenderingProperties(visualization::PCL_VISUALIZER_POINT_SIZE, 1, "cloud");
    viewer.addPointCloud<PointT>(cloud_smoothed, "cloud_smoothed");
    viewer.setPointCloudRenderingProperties(visualization::PCL_VISUALIZER_POINT_SIZE, 1, "cloud_smoothed");
    while (!viewer.wasStopped())
    {
        viewer.spinOnce(100);
    }

    return 0;
}

4. Is it possible to use OpenMP for parallelization only for codes containing for loops?

OpenMP is often used to parallelize iterations in loop structures, but not exclusively. Any code that can be broken down into multiple tasks that can be executed in parallel can be parallelized using OpenMP. For example, in fields such as matrix multiplication, image processing, and machine learning, OpenMP can be used for parallel operations to accelerate the calculation process.

However, when performing parallel operations, it should be noted that if there are dependencies between tasks, then synchronization is required to avoid problems such as race conditions and deadlocks. In addition, the choice of the number of threads also needs to consider the hardware resources of the computer and the effect of parallelization, and requires careful debugging and testing.

In short, OpenMP is not limited to the for loop structure, and can be used for any code that can be decomposed into multiple tasks that can be executed in parallel, but it needs to pay attention to the choice of synchronization and number of threads.

5. Existing problems and precautions

It should be noted that if there are NaN or infinity values ​​in the point cloud, it will cause the program to run incorrectly or the results will be inaccurate. Therefore, it is necessary to preprocess the point cloud before calculation to remove these illegal values.

In addition, when using OpenMP parallel processing, you need to pay attention to the number of threads and load balancing. If the number of threads is too many or too few, it will affect the efficiency of the program. In the code implementation, the number of threads can be controlled by setting the OMP_NUM_THREADS environment variable, and the number of threads and the way of assigning tasks can be adjusted according to the size of the point cloud and the complexity of processing tasks to achieve optimal efficiency and load balancing.

Guess you like

Origin blog.csdn.net/z377989129/article/details/129800139