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:
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:
100,000,000 baris x 8 byte = 800,000,000 byte
= 800.0 MB
array 1 juta elemen : 8000000 byteMari kita bedah:
- Baris 3-5 (
n * byte_per_elemen):itemsizemilikfloat64adalah 8 byte, sehingga 100 juta elemen menuntut 800.000.000 byte — 800 MB untuk satu kolom saja. - Baris 8 (
sampel.nbytes): atributnbytesmenghitung kebutuhan array sungguhan; array 1 juta elemen memakan 8 MB dan berskala linear ke atas. - Catatan praktis: hitungan ini belum termasuk array perantara — ekspresi seperti
(data - mean) / stdmembuat 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:
| dtype | Byte per elemen | Total untuk 100 juta baris |
|---|---|---|
float64 | 8 | 800 MB |
float32 | 4 | 400 MB |
int64 | 8 | 800 MB |
int32 | 4 | 400 MB |
uint8 | 1 | 100 MB |
bool | 1 | 100 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:
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:
float64 : 40000000 byte
float32 : 20000000 byte
uint8 : 5000000 byte
hemat : 88 %- Baris 3-5:
astypeselalu menghasilkan salinan baru dengan dtype tujuan; array asal tidak berubah. - Baris 6-8: lima juta elemen menempati 40 MB sebagai
float64, 20 MB sebagaifloat32, dan 5 MB sebagaiuint8. - Baris 9: penghematan 87,5% — tetapi hanya bila rentang nilai memang muat 0-255. Kompromi presisi
float32dibahas 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:
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:
chunk 0 -> (300000,)
chunk 300,000 -> (300000,)
chunk 600,000 -> (300000,)
chunk 900,000 -> (300000,)
total per chunk : 719999400000.0
total langsung : 719999400000.0- Baris 7-10 (loop chunk):
range(0, total, ukuran)melangkah 300 ribu; setiappotonganadalah view ke bagian array, jadi tidak ada salinan yang dibuat. - Baris 9 (
total += potongan.sum()): setiap potongan diringkas menjadi satu angka sebelum potongan berikutnya — memori kerja sebesar satu chunk, bukan seluruh data. - 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:
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:
bentuk : (1000000,)
elemen : 500000.0
total : 499999500000.0- Baris 4 (
data.tofile(...)): array ditulis sebagai biner mentah tanpa header — format yang dibacanp.memmap(pola lengkapnya dibahas pada Chapter Memory-mapped Array). - Baris 5 (
np.memmap(..., mode="r")): array 8 MB tidak disalin ke RAM;mmaphanyalah jendela ke file di disk. - 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:
| Gejala | Penyebabnya | Yang layak dipertimbangkan |
|---|---|---|
MemoryError saat memuat | data melebihi RAM | dtype lebih kecil, chunk, np.memmap |
| Puncak memori berlipat saat menghitung | array perantara operasi | operasi in-place, out= (Chapter Operasi In-place) |
| File jauh lebih besar dari RAM, perlu tabel berlabel | kebutuhan ekosistem pandas | polars atau pyarrow/Parquet untuk data kolom |
| Perhitungan antar banyak mesin | skala melampaui satu komputer | Dask untuk array paralel, PySpark untuk cluster |
| Agregasi berulang yang lambat | operasi inti NumPy berjalan satu utas | Dask, atau tulis ulang bagian kritisnya |
Dua catatan untuk melengkapi tabel:
- 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.
- 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.