Skip to content

Array strides and memory layout

How NumPy strides map array indices to byte addresses, and when a transposed view needs a copy.

An array stride is the signed number of bytes the address moves when an index increases by one along an axis. A NumPy array has one stride per axis. In a risk pipeline, you can transpose a scenario-by-asset table without copying its values, but a later routine may still need to repack it. Shape tells you which indices are valid. Strides tell you how those indices reach memory. NumPy: ndarray.strides

Two ways to reach byte 40

Take a toy scenario table a with two rows and three columns, stored in C order. Each int64 element occupies 8 bytes. Advancing one column moves 8 bytes. Advancing one row at the same column moves past three elements, or 24 bytes. With zero-based indices (i,j)(i,j) and strides (s0,s1)(s_0,s_1), the byte offset from the start of this array is is0+js1i s_0+j s_1.
The transpose a.T swaps the axes. Its shape changes from (2, 3) to (3, 2) and its strides from (24, 8) to (8, 24). The starting address is shared, so a[1, 2] and a.T[2, 1] reach the same bytes through different index calculations. Writing through this transposed view changes the original value too. NumPy: transpose, NumPy: copies and views
1×24+2×8=2×8+1×24=40 bytes.1 \times 24 + 2 \times 8 = 2 \times 8 + 1 \times 24 = 40 \text{ bytes}.
The original has shape (2, 3) and strides (24, 8) bytes. The transpose has shape (3, 2) and strides (8, 24) bytes. Original index (1, 2) and transpose index (2, 1) both reach value 6 at offset 40 in the shared buffer.
The original has shape (2, 3) and strides (24, 8) bytes. The transpose has shape (3, 2) and strides (8, 24) bytes. Original index (1, 2) and transpose index (2, 1) both reach value 6 at offset 40 in the shared buffer.
The upper grids show logical index order. The lower strip shows the six values in physical memory order and the starting byte of each.

Contiguity and the next operation

Both arrays in the example use contiguous memory. The original is C-contiguous, so the last axis runs fastest in memory. The transpose is Fortran-contiguous, so the first axis runs fastest. Slicing can also yield a view that skips elements and has neither flag. Read flags.c_contiguous and flags.f_contiguous to check the layout. An axis of length one may have an arbitrary stride because its index never advances. NumPy: ndarray memory layout
A routine that accepts the transpose's strides can read the shared buffer directly. A routine that requires C order needs a C-order copy of this transpose, which np.ascontiguousarray can make explicitly. The copy preserves the transposed shape and values and allocates a separate buffer. NumPy: ascontiguousarray
import numpy as np a = np.array([[4, 7, 2], [3, 8, 6]], dtype=np.int64, order="C") t = a.T print(a.strides, t.strides) # (24, 8) (8, 24) print(t.flags.c_contiguous, t.flags.f_contiguous, np.shares_memory(a, t)) # False True True packed = np.ascontiguousarray(t) print(packed.strides, np.shares_memory(t, packed)) # (16, 8) False
For performance, follow the routine's actual inner loop. Reading adjacent elements can make better use of the cache than taking large jumps, but an iterator can choose memory order even when the logical rows run across it. NumPy's nditer does so by default for this transpose. Repacking incurs a copy up front, so its cost belongs in any timing comparison. NumPy: iterating over arrays

Sources

Last updated
  • numpy
  • arrays
  • memory layout
  • array views
← Back to the glossaryBack to top ↑