본문 바로가기
-----ETC-----/C++ 성능 최적화 및 고급 테크닉 시리즈

[C++ 성능 최적화 및 고급 테크닉] Day 25: 프로젝트: 고성능 매트릭스 라이브러리 개발 (2)

by cogito21_cpp 2024. 8. 1.
반응형

프로젝트 목표

이 단계에서는 매트릭스 라이브러리의 성능을 더욱 향상시키기 위해 최적화 기법을 적용하고, 추가적인 기능을 구현합니다. 특히, 다음과 같은 부분을 다룹니다:

  1. 루프 언롤링 및 벡터화: 성능을 향상시키기 위한 루프 최적화.
  2. 캐시 친화적 접근: 메모리 접근 패턴을 최적화하여 캐시 효율성을 높입니다.
  3. 추가 기능 구현: 행렬의 역행렬 및 행렬식 계산 기능 추가.

 

Step 1: 루프 언롤링 및 벡터화

행렬 곱셈에서 루프 언롤링을 적용하여 성능을 향상시킵니다. 루프 언롤링은 반복문 내의 작업을 반복적으로 실행하지 않고, 여러 번의 작업을 한 번에 수행하도록 변경하는 기법입니다.

 

Matrix.cpp: 루프 언롤링 적용

#include "Matrix.h"

Matrix operator*(const Matrix& lhs, const Matrix& rhs) {
    if (lhs.getCols() != rhs.getRows()) {
        throw std::invalid_argument("Number of columns of the first matrix must be equal to the number of rows of the second matrix");
    }

    Matrix result(lhs.getRows(), rhs.getCols());
    size_t lhsCols = lhs.getCols();
    size_t rhsCols = rhs.getCols();
    size_t rows = lhs.getRows();

    for (size_t i = 0; i < rows; ++i) {
        for (size_t j = 0; j < rhsCols; ++j) {
            double sum = 0.0;
            for (size_t k = 0; k < lhsCols; k += 4) {
                sum += lhs(i, k) * rhs(k, j)
                     + lhs(i, k + 1) * rhs(k + 1, j)
                     + lhs(i, k + 2) * rhs(k + 2, j)
                     + lhs(i, k + 3) * rhs(k + 3, j);
            }
            result(i, j) = sum;
        }
    }

    return result;
}

 

Step 2: 캐시 친화적 접근

행렬의 전치 연산에서 캐시 친화적인 접근 방식을 적용하여 성능을 향상시킵니다. 데이터가 캐시 라인에 잘 맞도록 배치하는 것이 중요합니다.

 

Matrix.cpp: 캐시 친화적 전치 연산

#include "Matrix.h"

Matrix transpose(const Matrix& matrix) {
    Matrix result(matrix.getCols(), matrix.getRows());
    size_t blockSize = 16; // 캐시 친화적 접근을 위한 블록 크기 설정

    for (size_t i = 0; i < matrix.getRows(); i += blockSize) {
        for (size_t j = 0; j < matrix.getCols(); j += blockSize) {
            for (size_t k = i; k < i + blockSize && k < matrix.getRows(); ++k) {
                for (size_t l = j; l < j + blockSize && l < matrix.getCols(); ++l) {
                    result(l, k) = matrix(k, l);
                }
            }
        }
    }

    return result;
}

 

Step 3: 행렬의 역행렬 및 행렬식 계산

행렬의 역행렬과 행렬식을 계산하는 기능을 추가합니다. 역행렬을 계산하기 위해 가우스-조던 소거법을 사용하고, 행렬식은 재귀적으로 계산합니다.

 

Matrix.h: 역행렬 및 행렬식 함수 선언

Matrix inverse(const Matrix& matrix);
double determinant(const Matrix& matrix);

 

Matrix.cpp: 역행렬 및 행렬식 함수 구현

#include "Matrix.h"

Matrix inverse(const Matrix& matrix) {
    if (matrix.getRows() != matrix.getCols()) {
        throw std::invalid_argument("Matrix must be square to find its inverse");
    }

    size_t n = matrix.getRows();
    Matrix augmented(matrix.getRows(), matrix.getCols() * 2);

    // Augment the matrix with the identity matrix
    for (size_t i = 0; i < n; ++i) {
        for (size_t j = 0; j < n; ++j) {
            augmented(i, j) = matrix(i, j);
            augmented(i, j + n) = (i == j) ? 1.0 : 0.0;
        }
    }

    // Perform Gaussian elimination
    for (size_t i = 0; i < n; ++i) {
        if (augmented(i, i) == 0) {
            throw std::runtime_error("Matrix is singular and cannot be inverted");
        }

        for (size_t j = 0; j < 2 * n; ++j) {
            augmented(i, j) /= augmented(i, i);
        }

        for (size_t k = 0; k < n; ++k) {
            if (k != i) {
                double factor = augmented(k, i);
                for (size_t j = 0; j < 2 * n; ++j) {
                    augmented(k, j) -= factor * augmented(i, j);
                }
            }
        }
    }

    // Extract the inverse matrix
    Matrix result(n, n);
    for (size_t i = 0; i < n; ++i) {
        for (size_t j = 0; j < n; ++j) {
            result(i, j) = augmented(i, j + n);
        }
    }

    return result;
}

double determinant(const Matrix& matrix) {
    if (matrix.getRows() != matrix.getCols()) {
        throw std::invalid_argument("Matrix must be square to calculate its determinant");
    }

    size_t n = matrix.getRows();

    if (n == 1) {
        return matrix(0, 0);
    }

    if (n == 2) {
        return matrix(0, 0) * matrix(1, 1) - matrix(0, 1) * matrix(1, 0);
    }

    double det = 0.0;
    for (size_t i = 0; i < n; ++i) {
        Matrix subMatrix(n - 1, n - 1);
        for (size_t j = 1; j < n; ++j) {
            size_t colIndex = 0;
            for (size_t k = 0; k < n; ++k) {
                if (k == i) continue;
                subMatrix(j - 1, colIndex++) = matrix(j, k);
            }
        }
        det += ((i % 2 == 0) ? 1 : -1) * matrix(0, i) * determinant(subMatrix);
    }

    return det;
}

 

테스트 코드

main.cpp

#include <iostream>
#include "Matrix.h"

int main() {
    Matrix mat1(3, 3);

    mat1(0, 0) = 1; mat1(0, 1) = 2; mat1(0, 2) = 3;
    mat1(1, 0) = 0; mat1(1, 1) = 1; mat1(1, 2) = 4;
    mat1(2, 0) = 5; mat1(2, 1) = 6; mat1(2, 2) = 0;

    std::cout << "Matrix 1:" << std::endl;
    mat1.print();

    Matrix inv = inverse(mat1);
    std::cout << "Inverse of Matrix 1:" << std::endl;
    inv.print();

    double det = determinant(mat1);
    std::cout << "Determinant of Matrix 1: " << det << std::endl;

    Matrix transposed = transpose(mat1);
    std::cout << "Transposed Matrix 1:" << std::endl;
    transposed.print();

    return 0;
}

 

이제 스물다섯 번째 날의 학습을 마쳤습니다. 고성능 매트릭스 라이브러리 개발 프로젝트의 두 번째 단계를 통해 루프 언롤링 및 벡터화, 캐시 친화적 접근, 행렬의 역행렬 및 행렬식 계산 기능을 추가했습니다.

질문이나 피드백이 있으면 언제든지 댓글로 남겨 주세요. 내일은 "프로젝트: 고성능 매트릭스 라이브러리 개발 (3)"에 대해 학습하겠습니다.

반응형