Python NumPy: How to Find the Memory Size of a NumPy Array in Python
Understanding memory consumption is essential when working with large datasets in scientific computing and machine learning. NumPy arrays are designed for memory efficiency, storing data in contiguous blocks rather than as scattered Python objects. Knowing exactly how much RAM your arrays consume helps you optimize data pipelines, prevent out-of-memory errors, and choose appropriate data types for your hardware constraints.
Using the nbytes Attribute (Recommended)
The .nbytes attribute provides the most direct way to get total memory used by array elements:
import numpy as np
# Create arrays of different sizes
small_array = np.array([1.0, 2.0, 3.0, 4.0, 5.0])
large_array = np.zeros((1000, 1000))
print(f"Small array: {small_array.nbytes} bytes")
print(f"Large array: {large_array.nbytes:,} bytes")
print(f"Large array: {large_array.nbytes / (1024**2):.2f} MB")
Output:
Small array: 40 bytes
Large array: 8,000,000 bytes
Large array: 7.63 MB
The .nbytes attribute returns the total bytes consumed by the array's data buffer only. It doesn't include Python object overhead (typically 96-112 bytes) or metadata. For large arrays, this overhead is negligible.
Understanding Array Memory Components
NumPy provides attributes to understand how memory is calculated:
import numpy as np
# Create a 2D array
matrix = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.int32)
print(f"Shape: {matrix.shape}") # Shape: (2, 3)
print(f"Total elements: {matrix.size}") # Total elements: 6
print(f"Bytes per element: {matrix.itemsize}") # Bytes per element: 4
print(f"Data type: {matrix.dtype}") # Data type: int32
print(f"Total bytes: {matrix.nbytes}") # Total bytes: 24
# Verify: size × itemsize = nbytes
assert matrix.size * matrix.itemsize == matrix.nbytes
The relationship is straightforward:
size: Total number of elementsitemsize: Bytes per elementnbytes: Total bytes = size × itemsize
Impact of Data Types on Memory
Choosing the right dtype dramatically affects memory consumption:
import numpy as np
# Same data, different types
n_elements = 1_000_000
arrays = {
'int8': np.zeros(n_elements, dtype=np.int8),
'int32': np.zeros(n_elements, dtype=np.int32),
'int64': np.zeros(n_elements, dtype=np.int64),
'float32': np.zeros(n_elements, dtype=np.float32),
'float64': np.zeros(n_elements, dtype=np.float64),
'complex128': np.zeros(n_elements, dtype=np.complex128),
}
print("Memory usage for 1 million elements:")
for dtype_name, arr in arrays.items():
mb = arr.nbytes / (1024 ** 2)
print(f" {dtype_name:12}: {mb:6.2f} MB ({arr.itemsize} bytes/element)")
Output:
Memory usage for 1 million elements:
int8 : 0.95 MB (1 bytes/element)
int32 : 3.81 MB (4 bytes/element)
int64 : 7.63 MB (8 bytes/element)
float32 : 3.81 MB (4 bytes/element)
float64 : 7.63 MB (8 bytes/element)
complex128 : 15.26 MB (16 bytes/element)
If your values fit within smaller ranges, use smaller dtypes:
- Ages (0-150):
uint8instead ofint64= 8× savings - Probabilities (0-1):
float32instead offloat64= 2× savings - Boolean flags:
bool(1 byte) instead of default integer
Data Type Reference
| dtype | Bytes | Value Range | Use Case |
|---|---|---|---|
bool | 1 | True/False | Flags, masks |
int8 | 1 | -128 to 127 | Small integers |
uint8 | 1 | 0 to 255 | Image pixels, ages |
int32 | 4 | ±2.1 billion | General integers |
int64 | 8 | ±9.2 quintillion | Large counts |
float32 | 4 | ±3.4×10³⁸ | ML models, graphics |
float64 | 8 | ±1.8×10³⁰⁸ | Scientific computing |
complex128 | 16 | Complex numbers | Signal processing |
NumPy vs Python List Memory
NumPy arrays are far more memory-efficient than Python lists for numeric data:
import numpy as np
import sys
n = 10000
# Python list of integers
py_list = list(range(n))
# NumPy array
np_array = np.arange(n, dtype=np.int64)
# Python list: stores pointers + integer objects
list_size = sys.getsizeof(py_list) # Just the list structure
# Each integer object adds ~28 bytes
# NumPy: contiguous memory block
array_size = np_array.nbytes
print(f"NumPy array data: {array_size:,} bytes")
print(f"Python list structure: {list_size:,} bytes")
print(f"Python list estimate (with objects): ~{n * 28 + list_size:,} bytes")
Output:
NumPy array data: 80,000 bytes
Python list structure: 80,056 bytes
Python list estimate (with objects): ~360,056 bytes
sys.getsizeof() on a Python list returns only the list structure size (pointers), not the objects it references. For NumPy arrays, getsizeof() includes object overhead but .nbytes gives the actual data size.
Memory Profiling Utility
Create a helper function for detailed memory analysis:
import numpy as np
def memory_report(arr, name="Array"):
"""Generate detailed memory report for a NumPy array."""
bytes_total = arr.nbytes
# Convert to appropriate unit
if bytes_total >= 1024**3:
size_str = f"{bytes_total / 1024**3:.2f} GB"
elif bytes_total >= 1024**2:
size_str = f"{bytes_total / 1024**2:.2f} MB"
elif bytes_total >= 1024:
size_str = f"{bytes_total / 1024:.2f} KB"
else:
size_str = f"{bytes_total} bytes"
print(f"=== {name} ===")
print(f" Shape: {arr.shape}")
print(f" Dtype: {arr.dtype}")
print(f" Elements: {arr.size:,}")
print(f" Bytes/element: {arr.itemsize}")
print(f" Total memory: {size_str}")
return bytes_total
# Usage
image = np.random.randint(0, 256, (1920, 1080, 3), dtype=np.uint8)
features = np.random.randn(50000, 768).astype(np.float32)
memory_report(image, "HD Image")
print()
memory_report(features, "ML Features")
Output:
=== HD Image ===
Shape: (1920, 1080, 3)
Dtype: uint8
Elements: 6,220,800
Bytes/element: 1
Total memory: 5.93 MB
=== ML Features ===
Shape: (50000, 768)
Dtype: float32
Elements: 38,400,000
Bytes/element: 4
Total memory: 146.48 MB
Estimating Memory Before Allocation
Calculate required memory before creating large arrays:
import numpy as np
def estimate_memory(shape, dtype=np.float64):
"""Estimate memory needed for an array without creating it."""
dtype = np.dtype(dtype)
elements = np.prod(shape)
bytes_needed = elements * dtype.itemsize
gb = bytes_needed / (1024**3)
return bytes_needed, f"{gb:.2f} GB" if gb >= 1 else f"{bytes_needed / 1024**2:.2f} MB"
# Check before allocating
shape = (100000, 10000)
for dtype in [np.float64, np.float32, np.float16]:
bytes_needed, readable = estimate_memory(shape, dtype)
print(f"{dtype}: {readable}")
Output:
<class 'numpy.float64'>: 7.45 GB
<class 'numpy.float32'>: 3.73 GB
<class 'numpy.float16'>: 1.86 GB
By understanding NumPy's memory model and choosing appropriate data types, you can build applications that efficiently handle datasets ranging from megabytes to terabytes.