MPI를 활용한 병렬 테이프로이드 적분 계산

병렬 처리 기법을 적용하여 정적분을 효율적으로 계산하는 예제를 소개합니다. 이 구현은 메시지 전달 인터페이스(MPI)를 사용해 여러 프로세스가 함께 작업을 나누어 수행하며, 결과를 통합하는 방식입니다.

함수 f(x) = x³에 대해 구간 [0, 3]에서의 정적분을 근사하는 알고리즘입니다. 각 프로세스는 전체 구간을 일정한 크기로 분할받아, 자체적인 테이프로이드 법칙을 통해 부분적분을 계산하고, 프로세스 0이 모든 결과를 수신하여 합산합니다.

다음은 수정된 코드입니다. 원본 소스에는 일부 논리 오류가 있었으며, 특히 마지막 프로세스의 구간 크기를 올바르게 조절하도록 수정했습니다.

#include <iostream>
#include <cmath>
#include "mpi.h"

// 적분할 함수: f(x) = x^3
double integrand(double x) {
    return std::pow(x, 3);
}

// 테이프로이드 규칙을 이용한 부분적분 계산
double trapezoidal_rule(double left, double right, int num_segments, double width) {
    double result = (integrand(left) + integrand(right)) * 0.5;
    double current_x = left;

    for (int i = 1; i < num_segments; ++i) {
        current_x += width;
        result += integrand(current_x);
    }
    return result * width;
}

int main() {
    int my_rank, process_count;
    const int total_intervals = 10000000;  // 총 구간 수
    double lower_bound = 0.0, upper_bound = 3.0;
    double interval_width, local_lower, local_upper;
    int local_interval_count;
    double local_integral = 0.0, global_integral = 0.0;
    int sender;

    MPI_Init(nullptr, nullptr);
    MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
    MPI_Comm_size(MPI_COMM_WORLD, &process_count);

    double start_time = MPI_Wtime();

    // 전체 구간을 프로세스 수로 나누고, 평균 구간 수 계산
    interval_width = (upper_bound - lower_bound) / total_intervals;
    local_interval_count = total_intervals / process_count;

    // 마지막 프로세스는 나머지를 포함하도록 조정
    if (my_rank == process_count - 1) {
        local_interval_count = total_intervals - (process_count - 1) * local_interval_count;
    }

    // 현재 프로세스의 구간 계산
    local_lower = lower_bound + my_rank * local_interval_count * interval_width;
    local_upper = local_lower + local_interval_count * interval_width;

    // 해당 구간에서의 적분 값 계산
    local_integral = trapezoidal_rule(local_lower, local_upper, local_interval_count, interval_width);

    // 프로세스 0은 다른 프로세스로부터 결과를 수신
    if (my_rank != 0) {
        MPI_Send(&local_integral, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);
    } else {
        global_integral = local_integral;

        for (sender = 1; sender < process_count; ++sender) {
            MPI_Recv(&local_integral, 1, MPI_DOUBLE, sender, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
            global_integral += local_integral;
        }

        // 최종 결과 출력
        std::cout << "총 구간 수: " << total_intervals << "\n";
        std::cout << "구간 [" << lower_bound << ", " << upper_bound << "] 에서의 적분 추정값: "
                  << std::scientific << global_integral << "\n";

        double end_time = MPI_Wtime();
        std::cout << "실행 시간: " << (end_time - start_time) << " 초\n";
    }

    MPI_Finalize();
    return 0;
}

컴파일 및 실행 명령어:

mpicxx trapezoidal_parallel.cpp -o integrate
mpirun -np 4 ./integrate

여기서 -np 뒤의 숫자는 사용할 프로세스 수를 의미합니다. 실험 환경은 24코어 시스템이며, 프로세스 수를 늘릴수록 실행 시간은 감소하지만, 특정 지점(약 14개) 이후부터는 개선이 거의 없음을 확인했습니다. 이는 통신 오버헤드와 부하 불균형이 병렬 성능을 제한하는 주요 요인임을 시사합니다.

또한, 단위 시간당 처리량(처리량 대비 시간)은 프로세스 수 증가에 따라 감소하는 경향을 보입니다. 즉, 10개 프로세스 사용 시 총 시간이 1/10로 줄어들지 않으며, 오히려 상대적 효율성이 낮아집니다.

결과적으로, 병렬화는 반드시 성능 향상이 되는 것은 아니며, 적절한 데이터 분할과 통신 설계가 중요합니다.

태그: mpi parallel computing numerical integration trapezoidal rule C++

7월 12일 20:30에 게시됨