You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

OpenMP+MPI混合梯形积分程序:1/2进程多线程无性能提升问题

问题:OpenMP+MPI混合并行积分程序的性能差异原因分析

问题描述

我在Linux环境下运行一款基于OpenMP+MPI混合并行的梯形法积分程序,计算区间[-1,1]上1亿个区间的积分值。测试发现:当进程数为1或2时,调整每个进程的线程数,程序运行时间几乎无明显变化;但进程数大于2时,增加线程数能显著缩短运行时间。请问该现象的原因是什么?

程序逻辑说明

主进程将1亿个积分区间分片分配给各MPI进程,每个进程通过OpenMP归约操作计算局部积分和,最终由主进程汇总所有局部和得到全局积分结果。

测试数据

进程数线程数单进程区间数测试1耗时[s]测试2耗时[s]测试3耗时[s]平均耗时[秒]
1110000000079.45820579.84933279.78467679.69740433
1210000000079.65929879.55554380.40097979.87194
1310000000079.55908279.53758579.53594279.544203
1410000000079.59400879.58301880.67396579.95033033
215000000042.89165842.37980442.37014942.54720367
225000000042.44790242.48796342.40599842.44728767
235000000042.43645742.45424342.43162642.44077533
245000000042.5822542.53683942.52666942.548586
313333333328.7867634.49012932.6658831.980923
323333333317.09971917.31480316.13247616.84899933
333333333311.87898811.47559211.67805811.677546
34333333339.271999.0914458.9583819.107272
412500000023.39559925.8025322.36548923.85453933
422500000013.04826212.9193312.89122912.95294033
43250000009.0642969.143099.3004319.169272
44250000007.1213197.0881557.0606057.090026333

编译运行命令(以4个MPI进程、8个OpenMP线程为例)

mpicc -fopenmp -o example example.c -lm
mpirun -np 4 ./example 8

程序代码

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <omp.h>
#include "mpi.h"

long double function(long double x) {
   return 0.1 * pow(x, 50) - 0.2 * pow(x, 48) + 0.3 * pow(x, 47) - 0.4 * pow(x, 45) +
           0.5 * pow(x, 43) - 0.1 * pow(x, 42) + 0.2 * pow(x, 40) - 0.3 * pow(x, 38) +
           0.4 * pow(x, 37) - 0.5 * pow(x, 35) + 0.3 * pow(x, 33) - 0.2 * pow(x, 32) +
           0.1 * pow(x, 30) - 0.2 * pow(x, 28) + 0.3 * pow(x, 27) - 0.1 * pow(x, 25) +
           0.2 * pow(x, 23) - 0.3 * pow(x, 22) + 0.1 * pow(x, 20) + log(pow(x, 2) + 1) + 1;
}

long double trapezoidal_method(long double local_a, long double local_b, int num_intervals, int num_threads) {
    
    omp_set_num_threads(num_threads);

    long double h = (local_b - local_a) / (double)num_intervals;
    long double total_sum = 0;

    long double global_b = local_b;

    #pragma omp parallel shared(total_sum)
    {
        #pragma omp for reduction(+:total_sum)
        
        for (int i = 1; i < num_intervals; i++) {
            long double a = local_a + (i - 1) * h;
            long double b = local_a + i * h;

            total_sum +=  0.5 * (function(a) + function(b)) * h;
        }
        
    }
    //printf("%Lf \n",total_sum);
    return total_sum;
}

int main(int argc, char *argv[]) {
    int rank, size;
    MPI_Status status;

    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    MPI_Comm_size(MPI_COMM_WORLD, &size);

    long double partial_sum, global_sum;

    long double parameters[3];

    double start_time, end_time;

    long double interval_start = -1;
    long double interval_end = 1;
    int num_intervals = 100000000;

    int num_threads = atoi(argv[1]);

    int intervals_per_process = (int) floor(num_intervals / size);

    long double step_per_process = (interval_end - interval_start) / size;

    start_time = omp_get_wtime();

    if (rank == 0) {
        partial_sum = 0;

        long double a = interval_start;
        long double b = interval_start + step_per_process;

        int intervals_left = num_intervals;

        for (int i = 1; i < size; i++) {
            long double parameters[3] = {a, b, intervals_per_process};

            MPI_Send(parameters, 3, MPI_LONG_DOUBLE , i, 0, MPI_COMM_WORLD);

            a = b;
            b += step_per_process;
            intervals_left -= intervals_per_process;
        }

        partial_sum = trapezoidal_method(a, interval_end, intervals_left, num_threads);
    } else {
        MPI_Recv(&parameters, 3, MPI_LONG_DOUBLE, 0, 0, MPI_COMM_WORLD, &status);
        partial_sum = trapezoidal_method(parameters[0], parameters[1], parameters[2], num_threads);
    }

    MPI_Reduce(&partial_sum, &global_sum, 1, MPI_LONG_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);

    end_time = omp_get_wtime();

    if (rank == 0) {   
        printf("Processes: %d | Threads: %d | Intervals per process: %d | Sum: %Lf | Execution time: %f s. \n", size, num_threads, intervals_per_process, global_sum, end_time - start_time);
    }

    MPI_Finalize();
    return 0;
}

原因分析

1. CPU核心资源的饱和状态

从测试数据的性能拐点推断,你的运行环境应该是一台配备4个物理计算核心的服务器:

  • 当进程数为1时,单个MPI进程会占用所有4个核心,此时开启多线程只会导致线程在核心间频繁切换,无法获得额外计算资源,因此耗时几乎不变。
  • 当进程数为2时,每个MPI进程分配到2个核心,1线程就已经占满对应核心的计算能力,增加线程同样会引发上下文切换,无法提升效率。
  • 当进程数大于2时,比如3个MPI进程,每个进程默认仅占用1个核心,此时给每个进程增加线程,能充分利用剩余的空闲核心(或超线程资源),让更多计算单元参与任务处理,因此耗时显著降低。

2. 任务粒度与并行资源的匹配度

  • 进程数1或2时,单个MPI进程的任务量分别为1亿、5000万区间,单线程就能把对应核心的计算能力拉满,多线程拆分任务后没有额外核心可以利用,自然无法缩短耗时。
  • 进程数大于2时,单个进程的任务量降至3333万或2500万区间,单线程无法占满核心资源,此时增加OpenMP线程能让更多核心参与计算,充分发挥并行优势。

3. MPI与OpenMP的资源调度逻辑

默认情况下,MPI会将进程绑定到不同的物理核心:

  • 进程数≤核心数时,每个MPI进程独占核心,OpenMP线程只能在该核心的超线程队列中运行,无法获得额外的计算资源,性能提升不明显。
  • 进程数>核心数时,MPI进程会共享核心资源,此时OpenMP多线程可以调度到空闲的核心上,真正实现多线程并行计算,因此耗时大幅下降。

内容的提问来源于stack exchange,提问作者mzkaoq

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 02:57:02