[C++23 새기능 소개] std::mdspan

C++23에서는 다차원 배열을 효율적이고 유연하게 다룰 수 있는 새로운 도구인 std::mdspan이 도입되었습니다. 이번 글에서는 std::mdspan의 개념과 사용법, 그리고 이전 버전과 비교하여 어떻게 개선되었는지 알아보겠습니다.

std::mdspan이란 무엇인가요?

std::mdspan멀티디멘션 배열 스팬(Multidimensional Array Span)으로, 다차원 배열의 데이터를 복사 없이 효율적으로 접근할 수 있는 뷰(view)를 제공합니다. 이는 C++20에서 도입된 std::span을 다차원으로 확장한 개념으로, 고성능 계산과 과학 컴퓨팅에서 중요한 역할을 합니다.

이전 버전에서는 어떻게 했나요?

C++23 이전에는 다차원 배열을 다루기 위해 다음과 같은 방법을 사용했습니다.

1. 중첩 배열(Nested Arrays)

int arr[3][4]; // 2차원 배열

arr[1][2] = 5; // 요소 접근
  • 문제점:
    • 배열의 크기가 컴파일 타임에 고정되어야 합니다.
    • 동적 할당이 필요한 경우 포인터의 포인터를 사용해야 하며, 이는 복잡성과 오버헤드를 증가시킵니다.
    • 다차원 배열의 메모리 레이아웃을 직접 관리하기 어렵습니다.

2. 일차원 배열로 다차원 배열 시뮬레이션

int* arr = new int[rows * cols];

int get(int i, int j) {
    return arr[i * cols + j];
}

void set(int i, int j, int value) {
    arr[i * cols + j] = value;
}
  • 문제점:
    • 인덱싱을 수동으로 계산해야 하므로 오류 가능성이 높습니다.
    • 코드의 가독성이 떨어집니다.
    • 메모리 레이아웃을 변경하거나 다른 순서를 사용하려면 큰 수정이 필요합니다.

3. 벡터를 사용한 다차원 배열

#include <vector>

std::vector<std::vector<int>> arr(rows, std::vector<int>(cols));

arr[1][2] = 5;
  • 문제점:
    • 벡터의 벡터를 사용하면 연속적인 메모리 레이아웃이 보장되지 않습니다.
    • 메모리 할당이 중첩되어 오버헤드가 증가합니다.
    • 캐시 효율성이 떨어져 성능 저하가 발생할 수 있습니다.

C++23의 std::mdspan을 사용한 개선

std::mdspan을 사용하면 다차원 배열의 데이터를 효율적으로 참조하고, 메모리 레이아웃을 유연하게 관리할 수 있습니다.

예제: std::mdspan 사용

#include <mdspan>
#include <iostream>

int main() {
    constexpr int rows = 3;
    constexpr int cols = 4;

    int data[rows * cols] = {0}; // 일차원 배열로 메모리 할당

    std::mdspan<int, std::extents<rows, cols>> matrix(data);

    matrix(1, 2) = 5;

    std::cout << "matrix(1, 2) = " << matrix(1, 2) << '\n';

    return 0;
}
  • std::mdspan은 데이터를 소유하지 않고, 기존의 메모리를 참조합니다.
  • matrix(1, 2)와 같이 직관적인 인덱싱을 제공합니다.
  • 메모리 레이아웃과 접근 방식을 커스터마이즈할 수 있습니다.

어떻게 좋아졌나요?

  • 성능 향상: 연속적인 메모리 레이아웃을 유지하면서도 캐시 효율성을 극대화할 수 있습니다.
  • 유연성 증가: 메모리 레이아웃(행 우선, 열 우선 등)을 자유롭게 정의할 수 있습니다.
  • 가독성 개선: 다차원 인덱싱을 지원하여 코드가 직관적이고 이해하기 쉬워집니다.
  • 메모리 오버헤드 감소: 추가적인 메모리 할당 없이 데이터를 직접 참조하므로 오버헤드가 줄어듭니다.

상세한 예제와 비교

1. 메모리 레이아웃 변경

기존 방식

  • 메모리 레이아웃을 변경하려면 코드를 크게 수정해야 합니다.

C++23 방식

#include <mdspan>
#include <iostream>

int main() {
    constexpr int rows = 3;
    constexpr int cols = 4;

    int data[rows * cols] = {0};

    // 행 우선(Row-major) 메모리 레이아웃
    std::mdspan<int, std::extents<rows, cols>, std::layout_right> matrix_row(data);

    // 열 우선(Column-major) 메모리 레이아웃
    std::mdspan<int, std::extents<rows, cols>, std::layout_left> matrix_col(data);

    matrix_row(1, 2) = 5;
    std::cout << "matrix_row(1, 2) = " << matrix_row(1, 2) << '\n';
    std::cout << "matrix_col(1, 2) = " << matrix_col(1, 2) << '\n';

    return 0;
}
  • std::layout_right와 std::layout_left를 사용하여 메모리 레이아웃을 선택할 수 있습니다.
  • 동일한 데이터에 대해 다른 레이아웃의 뷰를 생성할 수 있습니다.

2. 동적 크기의 다차원 배열

#include <mdspan>
#include <vector>

int main() {
    int rows = 3;
    int cols = 4;

    std::vector<int> data(rows * cols);

    std::mdspan<int, std::dextents<size_t, 2>> matrix(data.data(), rows, cols);

    matrix(1, 2) = 5;

    std::cout << "matrix(1, 2) = " << matrix(1, 2) << '\n';

    return 0;
}
  • std::dextents를 사용하여 동적 크기의 다차원 배열을 지원합니다.
  • 런타임에 크기를 결정할 수 있습니다.

3. 사용자 정의 매핑 정책

#include <mdspan>
#include <iostream>

struct custom_mapping {
    size_t operator()(size_t i, size_t j) const {
        // 사용자 정의 인덱스 매핑
        return i * cols + j;
    }

    size_t rows;
    size_t cols;
};

int main() {
    size_t rows = 3;
    size_t cols = 4;

    int data[rows * cols] = {0};

    std::mdspan<int, std::dextents<size_t, 2>, custom_mapping> matrix(data, custom_mapping{rows, cols});

    matrix(1, 2) = 5;

    std::cout << "matrix(1, 2) = " << matrix(1, 2) << '\n';

    return 0;
}
  • 사용자 정의 매핑 정책을 통해 복잡한 인덱스 매핑을 구현할 수 있습니다.
  • 메모리 레이아웃을 완전히 커스터마이즈할 수 있습니다.

주의 사항

  • 데이터 소유권: std::mdspan은 데이터를 소유하지 않으므로, 데이터의 수명이 mdspan보다 길어야 합니다.
  • 헤더 파일 포함: std::mdspan을 사용하려면 <mdspan> 헤더를 포함해야 합니다.
  • 컴파일러 지원: C++23 기능이므로, 이를 지원하는 컴파일러와 표준 라이브러리가 필요합니다.

std::mdspan과 std::span의 차이점

  • std::span은 일차원 배열을 다루는 뷰이며, std::mdspan은 다차원 배열을 다룹니다.
  • std::mdspan은 메모리 레이아웃과 매핑 정책을 커스터마이즈할 수 있습니다.
  • 둘 다 데이터를 복사하지 않고 참조하므로 메모리 오버헤드가 없습니다.

요약

C++23의 std::mdspan은 다차원 배열을 효율적으로 다룰 수 있는 강력한 도구입니다. 이전에는 다차원 배열을 다루기 위해 복잡한 포인터 연산이나 비효율적인 자료 구조를 사용해야 했지만, 이제는 std::mdspan을 통해 직관적이고 유연한 인터페이스로 다차원 데이터를 처리할 수 있습니다. 이를 통해 코드의 가독성과 성능이 향상되며, 과학 계산, 머신 러닝, 그래픽스 등 고성능 컴퓨팅 분야에서 큰 이점을 제공합니다.

 

참고 자료:

 

반응형