ODE 函数中带有 ODEINT 的 OpenMP

OpenMP with ODEINT in ODE function

我正在尝试在内部并行化由 ODEINT 集成的 ODE 函数。

我做了下面的小例子

#include <iostream>
#include <chrono>
#include <Eigen/Dense>
#include <omp.h>
#include <boost/numeric/odeint.hpp>
#include <boost/numeric/odeint/external/openmp/openmp.hpp>
#include <boost/numeric/odeint/external/eigen/eigen.hpp>

using namespace boost::numeric::odeint;

class System {
private:
    Eigen::VectorXd _input_data;
public:
    System( Eigen::VectorXd &input_data ) { _input_data = input_data; };
    void operator() ( const Eigen::VectorXd &x , Eigen::VectorXd &dxdt , const double t ) {
        double _sum = 0.;
        #pragma omp parallel for reduction(+:_sum)
        for(int k = 0; k < _input_data.size(); ++k) {
            _sum += _input_data(k);
        };
        dxdt(0) = _sum;
    };
};

int main() {
    omp_set_num_threads(1);
    Eigen::VectorXd input_data = Eigen::VectorXd::Zero(100);
    System ode(input_data);
    runge_kutta_dopri5<Eigen::VectorXd> rk5_stepper;
    Eigen::VectorXd x = Eigen::VectorXd::Zero(1);
    auto start = std::chrono::high_resolution_clock::now();
    size_t steps = integrate_const(rk5_stepper, ode, x, 0., 1., 0.01);
    auto stop = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::microseconds>(stop - start);
    std::cout << "Execution time: " << duration.count() / 1000000. << " sec" << std::endl;
    return 0;
}

使用 CMakeLists.txt 文件

cmake_minimum_required(VERSION 3.13)

set(CMAKE_C_COMPILER /usr/local/bin/gcc-9)
set(CMAKE_CXX_COMPILER /usr/local/bin/g++-9)

project(ODEINT_OPENMP_TEST)

set(CMAKE_CXX_STANDARD 14)

include_directories("/usr/local/include")

find_package(OpenMP REQUIRED)

add_executable(ODEINT_OPENMP_TEST main.cpp)

target_link_libraries(ODEINT_OPENMP_TEST PRIVATE OpenMP::OpenMP_CXX)

当我尝试通过 omp_set_num_threads(N) 使用更多线程时,与仅使用单个线程 omp_set_num_threads(1) 相比,程序始终在变慢。选择 N=2,程序变得大约慢 x3(在我的机器上)。

直觉上,右边的函数应该 运行 并行更快?我做错了什么吗?

首先,您的循环太小,无法从在此特定示例中使用多线程中获益(在我的机器上按顺序大约 3.5 毫秒,在 6 个内核上使用 6 个线程大约 1.8 毫秒)。

此外,您的基准测试时间太短,您可能会测量意想不到的影响(缓存、页面错误、处理器频率缩放问题等)。考虑将其置于循环中以减轻大部分影响(如果这在现实世界条件下有意义)。

此外,某些 OpenMP 运行时会在执行并行部分时创建线程。这个操作相当慢。由于指令 #pragma omp parallel 包含在计时中,您还可以测量线程创建。

以下是我的 6 核机器的结果,尺寸大 1000 倍:

1 thread:  2.330 sec
2 threads: 1.212 sec
3 threads: 0.813 sec
4 threads: 0.649 sec
5 threads: 0.532 sec
6 threads: 0.459 sec

6线程加速5.1,不错

请注意,由于您的循环似乎受内存限制(内存吞吐量不随使用的内核数量而缩放),因此缩放比例可能会更差。