NumPy Memory Layout: C-Contiguous vs Fortran-Contiguous
NumPy stores multidimensional arrays as flat, one-dimensional blocks of computer memory, utilizing specific ordering conventions to map multi-axis indices to sequential memory addresses. The two primary conventions are C-contiguous (row-major) and Fortran-contiguous (column-major) order. This article explains the fundamental differences between these two layouts, illustrates how they store data linearly, examines their impact on computational performance, and demonstrates how to inspect and modify them in code.
Understanding Row-Major vs. Column-Major Order
Computer memory is inherently linear and one-dimensional. When NumPy creates a multidimensional array (such as a 2D matrix), it must flatten that data into a single continuous sequence of bytes.
- C-Contiguous (Row-Major): This is the default memory layout in NumPy and the standard used by the C programming language. In a C-contiguous array, the last axis changes the fastest. For a standard 2D array, this means items are stored row by row.
- Fortran-Contiguous (Column-Major): This layout is inherited from the Fortran programming language (and used by systems like MATLAB and R). In an F-contiguous array, the first axis changes the fastest. For a 2D array, items are stored column by column.
Memory Layout Example
Consider the following 2x3 matrix:
[[1, 2, 3],
[4, 5, 6]]The underlying linear memory storage depends entirely on the selected order:
- C-contiguous storage:
[1, 2, 3, 4, 5, 6]
The first row is written completely before moving to the second row. - Fortran-contiguous storage:
[1, 4, 2, 5, 3, 6]
The first column is written completely, followed by the second, and then the third.
Performance Implications
The memory layout directly affects cache utilization and execution speed due to CPU spatial locality. When a CPU reads a value from RAM, it also fetches adjacent values into its high-speed cache line.
- Row Operations: If you frequently iterate through
rows or perform reductions along columns (e.g.,
arr.sum(axis=1)), a C-contiguous layout is faster because adjacent row elements sit next to each other in memory. - Column Operations: If you frequently iterate down
columns or perform operations across rows (e.g.,
arr.sum(axis=0)), a Fortran-contiguous layout offers superior performance because adjacent column elements reside consecutively in memory. - External Libraries: Interfacing with external C or Cython libraries often requires C-contiguous memory, whereas interfacing with BLAS, LAPACK, or legacy Fortran routines may perform best with Fortran-contiguous inputs.
Accessing an array contrary to its memory layout causes frequent cache misses, which drastically reduces throughput for large datasets.
Checking and Changing Memory Layout in NumPy
You can inspect an array's layout by checking its .flags
attribute:
import numpy as np
# Create a default C-contiguous array
c_arr = np.array([[1, 2, 3], [4, 5, 6]], order='C')
print(c_arr.flags['C_CONTIGUOUS']) # True
print(c_arr.flags['F_CONTIGUOUS']) # False
# Create a Fortran-contiguous array
f_arr = np.array([[1, 2, 3], [4, 5, 6]], order='F')
print(f_arr.flags['C_CONTIGUOUS']) # False
print(f_arr.flags['F_CONTIGUOUS']) # TrueCommon array operations, such as transposition (arr.T),
do not copy data; instead, they alter the array's strides. Transposing a
C-contiguous array yields an array that behaves as
Fortran-contiguous.
To explicitly convert an array into a specific contiguous memory
layout, use np.ascontiguousarray() for C order or
np.asfortranarray() for Fortran order:
# Convert to C-contiguous memory
c_converted = np.ascontiguousarray(f_arr)
# Convert to Fortran-contiguous memory
f_converted = np.asfortranarray(c_arr)