Seluruh bab di course ini bekerja pada array yang muat di memori — dan itulah batas terpenting NumPy: ndarray hidup di RAM, tidak menulis ke disk secara otomatis, dan tidak membagi pekerjaan ke beberapa mesin. Ketika data bergerak menuju ukuran memori komputer, hitung dulu, baru muat.

Bab ini menghitung kebutuhan memori secara eksak, menunjukkan dua strategi penghematan yang masih dalam wilayah NumPy (dtype dan pemrosesan per potongan), lalu menutup dengan panduan kapan waktunya pindah ke pustaka lain.

Menghitung Kebutuhan Memori Sebelum Memuat #

Rumusnya sederhana: jumlah elemen dikali ukuran satu elemen (itemsize). Untuk satu kolom 100 juta baris float64, hitungan berikut sebaiknya dilakukan sebelum loadtxt dipanggil:

python
import numpy as np

n = 100_000_000
byte_per_elemen = np.dtype(np.float64).itemsize
total_byte = n * byte_per_elemen
print(f"{n:,} baris x {byte_per_elemen} byte = {total_byte:,} byte")
print(f"= {total_byte / 1e6:,.1f} MB")
sampel = np.zeros(1_000_000)
print("array 1 juta elemen :", sampel.nbytes, "byte")

Output yang Dihasilkan:

text
100,000,000 baris x 8 byte = 800,000,000 byte
= 800.0 MB
array 1 juta elemen : 8000000 byte

Mari kita bedah:

  1. Baris 3-5 (n * byte_per_elemen): itemsize milik float64 adalah 8 byte, sehingga 100 juta elemen menuntut 800.000.000 byte — 800 MB untuk satu kolom saja.
  2. Baris 8 (sampel.nbytes): atribut nbytes menghitung kebutuhan array sungguhan; array 1 juta elemen memakan 8 MB dan berskala linear ke atas.
  3. Catatan praktis: hitungan ini belum termasuk array perantara — ekspresi seperti (data - mean) / std membuat beberapa salinan besar secara sementara, sehingga puncak memori bisa berlipat (dibahas pada Chapter Menghindari Copy Array).

Dengan pengali yang sama, kebutuhan memori tiap dtype untuk 100 juta baris:

dtypeByte per elemenTotal untuk 100 juta baris
float648800 MB
float324400 MB
int648800 MB
int324400 MB
uint81100 MB
bool1100 MB

Menekan Konsumsi Memori dengan dtype #

Langkah pertama yang selalu layak dicoba: pilih dtype sekecil yang masih memadai. Konversi satu kali menurunkan konsumsi sebesar rasio byte per elemennya:

python
import numpy as np

data64 = np.ones(5_000_000, dtype=np.float64)
data32 = data64.astype(np.float32)
data8 = data32.astype(np.uint8)
print("float64 :", data64.nbytes, "byte")
print("float32 :", data32.nbytes, "byte")
print("uint8   :", data8.nbytes, "byte")
print("hemat   :", round(100 * (1 - data8.nbytes / data64.nbytes)), "%")

Output yang Dihasilkan:

text
float64 : 40000000 byte
float32 : 20000000 byte
uint8   : 5000000 byte
hemat   : 88 %
  1. Baris 3-5: astype selalu menghasilkan salinan baru dengan dtype tujuan; array asal tidak berubah.
  2. Baris 6-8: lima juta elemen menempati 40 MB sebagai float64, 20 MB sebagai float32, dan 5 MB sebagai uint8.
  3. Baris 9: penghematan 87,5% — tetapi hanya bila rentang nilai memang muat 0-255. Kompromi presisi float32 dibahas pada Chapter Perhitungan dengan Presisi Terbatas, dan panduan memilih dtype pada Chapter Memilih dtype yang Efisien.

Memproses Data dengan Pola Chunk #

Strategi kedua: jangan proses seluruh data sekaligus. Bagi menjadi potongan, proses satu per satu, lalu akumulasi hasilnya:

python
import numpy as np

total_baris = 1_200_000
data = np.arange(total_baris, dtype=np.float64)
ukuran = 300_000
total = 0.0
for mulai in range(0, total_baris, ukuran):
    potongan = data[mulai:mulai + ukuran]
    total += potongan.sum()
    print(f"chunk {mulai:>9,} -> {potongan.shape}")
print("total per chunk :", total)
print("total langsung  :", data.sum())

Output yang Dihasilkan:

text
chunk         0 -> (300000,)
chunk   300,000 -> (300000,)
chunk   600,000 -> (300000,)
chunk   900,000 -> (300000,)
total per chunk : 719999400000.0
total langsung  : 719999400000.0
  1. Baris 7-10 (loop chunk): range(0, total, ukuran) melangkah 300 ribu; setiap potongan adalah view ke bagian array, jadi tidak ada salinan yang dibuat.
  2. Baris 9 (total += potongan.sum()): setiap potongan diringkas menjadi satu angka sebelum potongan berikutnya — memori kerja sebesar satu chunk, bukan seluruh data.
  3. Baris 11-12: hasil akumulasi per chunk sama persis dengan data.sum() langsung — untuk agregasi asosiatif, memotong tidak mengubah jawaban.

Demo di atas tetap memuat array penuh agar fokusnya pada polanya; pada data sungguhan, setiap potongan dibaca dari disk satu per satu. np.memmap memindahkan pembacaan itu ke sistem operasi: file dipetakan ke memori virtual, dan hanya halaman yang disentuh yang benar-benar dimuat:

python
import numpy as np

data = np.arange(1_000_000, dtype=np.float64)
data.tofile("transaksi.bin")
mmap = np.memmap("transaksi.bin", dtype=np.float64, mode="r")
print("bentuk :", mmap.shape)
print("elemen :", mmap[500_000])
total = 0.0
for mulai in range(0, mmap.size, 250_000):
    total += mmap[mulai:mulai + 250_000].sum()
print("total  :", total)

Output yang Dihasilkan:

text
bentuk : (1000000,)
elemen : 500000.0
total  : 499999500000.0
  1. Baris 4 (data.tofile(...)): array ditulis sebagai biner mentah tanpa header — format yang dibaca np.memmap (pola lengkapnya dibahas pada Chapter Memory-mapped Array).
  2. Baris 5 (np.memmap(..., mode="r")): array 8 MB tidak disalin ke RAM; mmap hanyalah jendela ke file di disk.
  3. Baris 8-10: indexing dan slicing bekerja persis seperti ndarray biasa, termasuk pola chunk di atas — kode perhitungan tidak berubah saat sumbernya berpindah dari RAM ke file besar.

Kapan NumPy Masih Cukup, dan Kapan Tidak #

Tabel berikut merangkum keputusan yang paling sering dihadapi:

GejalaPenyebabnyaYang layak dipertimbangkan
MemoryError saat memuatdata melebihi RAMdtype lebih kecil, chunk, np.memmap
Puncak memori berlipat saat menghitungarray perantara operasioperasi in-place, out= (Chapter Operasi In-place)
File jauh lebih besar dari RAM, perlu tabel berlabelkebutuhan ekosistem pandaspolars atau pyarrow/Parquet untuk data kolom
Perhitungan antar banyak mesinskala melampaui satu komputerDask untuk array paralel, PySpark untuk cluster
Agregasi berulang yang lambatoperasi inti NumPy berjalan satu utasDask, atau tulis ulang bagian kritisnya

Dua catatan untuk melengkapi tabel:

  1. Operasi inti NumPy tidak paralel otomatis. Penjumlahan dan fungsi per elemen berjalan satu utas; pengecualiannya perkalian matriks lewat BLAS yang boleh memakai beberapa utas. Paralelisme antar-potongan adalah pekerjaan alat seperti Dask, bukan NumPy.
  2. NumPy tidak punya mode out-of-core bawaan selain memmap. Format kolom seperti Parquet dan mesin query seperti polars menawarkan pembacaan selektif dan kompresi yang lebih canggih untuk tabel besar.

Di sisi lain, jangan terburu-buru pindah: data di bawah sekitar satu gigabyte pada RAM 8-16 GB biasanya masih nyaman untuk NumPy setelah dtype diturunkan dan pola chunk diterapkan (saran lainnya pada Chapter Mengurangi Penggunaan Memori dan Chapter Kapan NumPy Bukan Pilihan Terbaik). Tanda paling andal untuk pindah bukan ukuran file, melainkan MemoryError atau puncak memori yang melampaui RAM berulang kali.