5.4.1Scientific Computing (Python)

NumPy — ndarray structure, dtype, shape, strides

1,910 words9 min readdifficulty · medium1 backlinks

WHAT is an ndarray, really?

WHY this design? Because storing a 2-D table as a real 2-D structure (list of lists / pointers) is slow and memory-scattered. Instead NumPy keeps everything in ONE cache-friendly block and uses math (strides) to fake dimensions. This is what makes NumPy fast.

Figure — NumPy — ndarray structure, dtype, shape, strides

dtype — WHAT each element is

Every element in an ndarray has the same dtype — that's why arrays are homogeneous (unlike Python lists). Homogeneity is exactly what lets NumPy step a fixed number of bytes each time.


shape — HOW the grid is sized


strides — HOW NumPy walks memory (the heart)

Derivation from first principles

Lay out a 2-D array row by row (C/"row-major" order). For shape (R, C) with itemsize = s bytes, the flat position of A[i, j] (counting elements) is:

flat_index(i,j)=iC+j\text{flat\_index}(i,j) = i\cdot C + j

Why? To skip to row i you must pass i full rows, each of length C; then move j within the row.

Convert to bytes by multiplying by s:

byte_offset(i,j)=(iC+j)s=i(Cs)+j(s)\text{byte\_offset}(i,j) = (i\cdot C + j)\cdot s = i\,(C\cdot s) + j\,(s)

Match this to the general stride formula

  byte_offset=kikstridek  \boxed{\;\text{byte\_offset} = \sum_k i_k \cdot \text{stride}_k\;}

By comparison, the strides are:

stride0=Cs,stride1=s\text{stride}_0 = C\cdot s, \qquad \text{stride}_1 = s


Views vs copies (the big payoff)


Worked example: predict the offset by hand


Common mistakes


Recall Feynman: explain to a 12-year-old

Imagine a long single shelf of numbered books — that's the memory. You pretend it's a bookcase with rows and columns. To find "row 2, column 1" you don't really walk a bookcase; you do quick maths: "skip 2 rows worth of books, then 1 more." The "skip-amount" for each direction is the stride. The shape says how many rows/columns you're pretending to have, and the dtype says how fat each book is. Flipping the bookcase sideways (transpose) doesn't move a single book — you just swap which skip-number means "row" and which means "column."


Flashcards

What three pieces of metadata interpret an ndarray's flat buffer?
dtype (element type/size), shape (size per axis), strides (bytes to step per axis).
What are the units of strides?
Bytes (not elements).
Formula for byte offset of element with indices i_k?
offset = Σ_k i_k · stride_k.
For a C-contiguous (3,4) int64 array, what are the strides?
(32, 8): last axis 8 = itemsize, first axis 4×8 = 32.
Does reshape usually copy data?
No — it returns a view, only changing metadata (unless the layout is incompatible).
What does .T (transpose) do to memory?
Nothing to the buffer; it swaps the strides (and shape). A C-array becomes F-contiguous.
Why are NumPy arrays homogeneous (all one dtype)?
So each element is a fixed byte size, letting strides step a constant amount → fast indexing.
How do you get a real independent array from a slice?
Use .copy(); check sharing with np.shares_memory.
C-contiguous stride of axis k formula?
stride_k = itemsize · product of shape sizes for axes after k.
Difference between .size and .nbytes?
size = number of elements (product of shape); nbytes = size × itemsize.

Connections

  • NumPy — broadcasting (uses 0-stride tricks to "stretch" axes)
  • NumPy — views vs copies & np.shares_memory
  • NumPy — vectorization & performance
  • CPU cache & memory locality
  • Row-major vs column-major (C vs Fortran order)
  • Python lists vs arrays

Concept Map

contains

contains

includes

includes

includes

fixes itemsize + enforces

enables fixed step

sizes axes, product = size

converts index to bytes

multiplied by strides

locates element in

one cache-friendly block

reshape changes metadata only

ndarray

Flat byte buffer

Metadata bookkeeping

dtype

shape

strides

Homogeneous elements

Index i,j

Byte offset

NumPy speed

Hinglish (regional understanding)

Intuition Hinglish mein samjho

Dekho, NumPy ka ndarray asal mein ek ek hi lambi line of bytes hoti hai memory mein — bas ek flat block. Uske upar NumPy teen chhote numbers rakhta hai: dtype (har element kitne bytes ka hai, jaise int64 = 8 bytes), shape (kitne rows-columns dikhane hain), aur strides (ek step lene ke liye kitne bytes aage jaana hai). Yahi teen cheezein decide karti hain ki flat bytes ko hum 2-D ya 3-D grid ki tarah kaise "padhein".

Magic ye hai ki jab tum reshape ya transpose karte ho, to data hilta hi nahi — sirf ye metadata badalta hai. Isliye ye operations bahut fast hote hain (O(1)). Element A[i,j] ka byte offset nikalne ka formula simple hai: offset = i*stride0 + j*stride1. C-order (row-major) mein last axis ka stride sabse chhota hota hai (= itemsize), kyunki row ke andar wale neighbours memory mein bilkul saath-saath hote hain.

Ek important trap: Python list ka slice copy banata hai, par NumPy ka slice ek view hai — same memory share karta hai. Agar tum view ko change karoge to original bhi change ho jaayega! Independent chahiye to .copy() use karo, aur np.shares_memory() se check kar lo. Aur yaad rakho — strides hamesha bytes mein hote hain, elements mein nahi; element step chahiye to itemsize se divide karo.

Ye chapter isliye important hai kyunki yahi NumPy ki speed ka raaz hai: ek cache-friendly flat block + thoda sa maths (strides). Jab tum ye samajh jaate ho, to broadcasting, vectorization, aur views-vs-copies sab apne aap clear ho jaate hain. Bas mantra yaad rakho: Data Size, Shape, Step.

Go deeper — visual, from zero

Test yourself — Scientific Computing (Python)

Connections