28.16 다차원 배열(Multidimensional Array)의 메모리 레이아웃: 행 우선과 열 우선

28.16 다차원 배열(Multidimensional Array)의 메모리 레이아웃: 행 우선과 열 우선

텐서는 수학적으로는 다중 지표를 갖는 객체이지만, 실제 컴퓨터 메모리는 선형적(일차원) 주소 공간으로 구성되어 있다. 따라서 다차원 배열을 메모리에 저장하기 위해서는 다차원 지표를 일차원 오프셋(offset)으로 선형화(linearization)하는 규약이 필요하다. 이 규약을 메모리 레이아웃(memory layout)이라 하며, 대표적으로 행 우선(row-major, C-order)과 열 우선(column-major, Fortran-order)의 두 가지 방식이 널리 사용된다. 레이아웃의 선택은 연산의 성능, 캐시 효율, 그리고 라이브러리 간 상호 운용성에 직접적인 영향을 미친다.

1. 선형화의 기본 원리

형상이 (d_1, d_2, \ldots, d_n)n차 텐서 T의 원소 T[i_1, i_2, \ldots, i_n] (0 \leq i_k < d_k)는 메모리 상의 선형 주소 \mathrm{addr}(i_1, \ldots, i_n)에 저장된다. 이 선형 주소는 기준 주소(base address) b_0에 오프셋 함수 f(i_1, \ldots, i_n)을 더한 값으로 결정된다.

\mathrm{addr}(i_1, \ldots, i_n) = b_0 + s \cdot f(i_1, \ldots, i_n)

여기서 s는 원소 한 개가 차지하는 바이트 수이다. 오프셋 함수 f의 구체적 형태는 레이아웃 규약에 따라 달라진다.

2. 행 우선 레이아웃 (Row-major)

행 우선 방식은 가장 오른쪽(가장 마지막) 지표가 메모리 상에서 가장 빠르게 변화하도록 원소를 배열한다. 즉, 동일한 행의 연속된 열 원소들이 인접한 메모리 주소에 저장된다. 이 방식은 C, C++, Python의 NumPy(기본값), PyTorch, TensorFlow 등에서 표준으로 채택된다. 오프셋 함수는 다음과 같이 정의된다.

f_{\text{row}}(i_1, i_2, \ldots, i_n) = \sum_{k=1}^{n} i_k \prod_{j=k+1}^{n} d_j

2차원 배열 T \in \mathbb{R}^{M \times N}의 경우 구체적 형태는 다음과 같다.

f_{\text{row}}(i, j) = i \cdot N + j

예를 들어 3 \times 4 행렬에서 T[1,2]의 오프셋은 1 \cdot 4 + 2 = 6이다.

3. 열 우선 레이아웃 (Column-major)

열 우선 방식은 가장 왼쪽(가장 첫 번째) 지표가 메모리 상에서 가장 빠르게 변화하도록 원소를 배열한다. 즉, 동일한 열의 연속된 행 원소들이 인접한 메모리 주소에 저장된다. 이 방식은 Fortran, MATLAB, Julia, R, 그리고 BLAS/LAPACK과 같은 전통적 수치 선형대수 라이브러리에서 채택된다. 오프셋 함수는 다음과 같다.

f_{\text{col}}(i_1, i_2, \ldots, i_n) = \sum_{k=1}^{n} i_k \prod_{j=1}^{k-1} d_j

2차원 배열 T \in \mathbb{R}^{M \times N}의 경우 다음과 같이 표현된다.

f_{\text{col}}(i, j) = i + j \cdot M

동일한 3 \times 4 행렬에서 T[1,2]의 오프셋은 1 + 2 \cdot 3 = 7로, 행 우선 방식과 다른 위치를 나타낸다.

4. 두 레이아웃의 비교

항목행 우선 (Row-major)열 우선 (Column-major)
가장 빠르게 변하는 지표마지막 지표 i_n첫 번째 지표 i_1
연속 저장 단위행 (row)열 (column)
대표 언어/라이브러리C, C++, NumPy, PyTorchFortran, MATLAB, Julia, BLAS
2차 텐서 오프셋 공식i \cdot N + ji + j \cdot M
효율적인 접근 패턴행 방향 순회열 방향 순회
전치 관계T_{\text{row}} = (T_{\text{col}})^\topT_{\text{col}} = (T_{\text{row}})^\top

두 레이아웃 간의 수학적 관계는 단순한 전치로 기술된다. 즉, 동일한 메모리 블록을 행 우선으로 해석하면 M \times N 행렬이, 열 우선으로 해석하면 N \times M 행렬이 얻어진다.

5. 캐시 효율성과 성능 영향

현대 CPU의 메모리 계층은 캐시 라인(cache line)이라는 단위로 데이터를 적재하며, 일반적으로 64바이트 단위이다. 레이아웃과 순회 방향이 일치할 때 공간 지역성(spatial locality)이 극대화되어 캐시 히트율이 상승한다. 행 우선 레이아웃의 배열을 다음과 같은 이중 루프로 순회한다고 하자.

\text{for } i = 0, \ldots, M-1:\ \text{for } j = 0, \ldots, N-1:\ \text{access } T[i, j]

이 경우 j가 빠르게 변화하므로 연속된 메모리 접근이 발생하고 캐시 효율이 높다. 반대로 순회 순서를 뒤바꾸면 매 접근마다 캐시 미스(cache miss)가 발생할 가능성이 높아져 성능이 현저히 저하된다. 이러한 이유로 대규모 행렬 연산을 수행하는 라이브러리는 블로킹(blocking), 타일링(tiling), 전치 최적화 등 레이아웃을 고려한 알고리즘 기법을 적용한다.

6. 고차 텐서로의 일반화

n차 텐서에서도 동일한 원리가 적용된다. 형상 (d_1, d_2, d_3)인 3차 텐서의 경우, 행 우선 오프셋은 f = i_1 d_2 d_3 + i_2 d_3 + i_3, 열 우선 오프셋은 f = i_1 + i_2 d_1 + i_3 d_1 d_2이다. 딥러닝 프레임워크에서는 이러한 레이아웃 정보를 스트라이드(stride) 개념으로 일반화하여, 연속적이지 않은 텐서 뷰(non-contiguous view)를 효율적으로 지원한다. 레이아웃의 명시적 이해는 브로드캐스팅, 전치, 재형성(reshape), 그리고 GPU 커널 최적화를 설계하는 데 필수적인 전제 지식이다.