Satu juta elemen float64 memakai 8 MB — terasa kecil, tetapi data nyata tidak jarang berisi puluhan juta baris, dan mesin kerja kita hanya punya RAM terbatas. Menekan konsumsi memori bukan sekadar soal kenyamanan: operasi yang membuat banyak array sementara bisa menggandakan kebutuhan memori tanpa kita sadari.
Bab ini membahas empat strategi paling praktis: memperkecil dtype, memakai operasi in-place, menghapus referensi yang tidak terpakai dengan benar, dan memotong pekerjaan per chunk.
Memperkecil dtype #
Cara paling efektif menghemat memori adalah memilih dtype dengan itemsize lebih kecil. Untuk data bulat yang muat dalam 32 bit, penurunan dari int64 ke int32 memotong kebutuhan memori tepat menjadi separuhnya:
import numpy as np
a = np.arange(1_000_000, dtype=np.int64)
a32 = a.astype(np.int32)
print("nbytes int64 :", a.nbytes)
print("nbytes int32 :", a32.nbytes)
print("hemat :", a.nbytes - a32.nbytes, "byte")Output yang Dihasilkan:
nbytes int64 : 8000000
nbytes int32 : 4000000
hemat : 4000000 byteCara membacanya:
- Baris 3 (
np.arange(..., dtype=np.int64)): satu juta elemen dialokasikan 8 byte per elemen, total 8.000.000 byte. - Baris 4 (
astype(np.int32)): NumPy menyalin data ke dtype baru yang hanya menempati 4 byte per elemen. - Baris 7: selisihnya tepat 4.000.000 byte — hemat 50 persen hanya dengan mengubah tipe.
Operasi In-place: Memperbarui tanpa Array Baru #
Ekspresi a = a + 1 membuat array baru terlebih dahulu, baru kemudian nama a berpindah — sesaat memori terpakai dua kali ukuran data. Operator in-place seperti += memperbarui isi array lama di tempatnya:
import numpy as np
a = np.arange(5)
b = a
a += 10
print("in-place shares:", np.shares_memory(a, b))
print(a)
c = np.arange(5)
d = c
c = c + 10
print("rebind shares :", np.shares_memory(c, d))
print(c, d)Output yang Dihasilkan:
in-place shares: True
[10 11 12 13 14]
rebind shares : False
[10 11 12 13 14] [0 1 2 3 4]Bedah baris demi baris:
- Baris 4-6:
bmenunjuk objek yang sama dengana; setelaha += 10, ketiga nama tetap merujuk blok yang diperbarui — tidak ada array baru. - Baris 9-11:
c = c + 10dievaluasi dengan membuat array baru; namacberpindah ke array baru, sedangkandtetap memegang array lama yang isinya belum berubah. - Output kedua: nilai
cdandkini berbeda — bukti bahwa penulisan ulang nama (rebind) dan pembaruan isi (in-place) adalah dua hal yang berbeda.
Operator in-place yang tersedia mengikuti operator aritmetika: +=, -=, *=, /=, //=, %=, dan **=. Versi fungsinya (np.add(a, b, out=a)) dibahas pada Chapter Optimasi Operasi Array.
del dan Referensi yang Menahan Blok #
Memori array NumPy dibebaskan oleh reference counting Python: begitu referensi terakhir hilang, blok data langsung dilepas tanpa perlu memanggil pengumpul sampah. Yang perlu diwaspadai, satu referensi tersisa — termasuk view sekecil apa pun — cukup untuk menahan seluruh blok:
import numpy as np
import sys
besar = np.ones(1_000_000, dtype=np.float64)
print("getsizeof:", sys.getsizeof(besar), "byte")
potongan = besar[:100]
print("potongan berbagi blok:", np.shares_memory(potongan, besar))
del besar
print("blok asli tetap hidup karena ditahan oleh potongan")Output yang Dihasilkan:
getsizeof: 8000112 byte
potongan berbagi blok: True
blok asli tetap hidup karena ditahan oleh potonganCara membacanya:
- Baris 5 (
sys.getsizeof): ukuran yang dilaporkan mencakup 8.000.000 byte data ditambah ±112 byte header objek array. - Baris 6-7 (
potongan = besar[:100]): potongan seratus elemen hanyalah view, dannp.shares_memorymembuktikan keduanya berbagi blok. - Baris 8 (
del besar): menghapus namabesartidak melepas blok 8 MB, karenapotonganmasih menyimpan referensi ke array dasarnya.
Memotong Pekerjaan dengan Chunking #
Ketika data tidak muat atau tidak perlu diolah sekaligus, proseslah dalam potongan berukuran tetap. Pola for i in range(0, size, ukuran) menjaga puncak memori tetap kecil, dan hasil gabungannya harus kita pastikan setara dengan hitungan utuh:
import numpy as np
data = np.arange(1_000_000, dtype=np.float64)
total_langsung = data.sum()
total_chunk = 0.0
ukuran = 100_000
for i in range(0, data.size, ukuran):
total_chunk += data[i:i + ukuran].sum()
print("langsung :", total_langsung)
print("per chunk:", total_chunk)
print("sama :", total_langsung == total_chunk)Output yang Dihasilkan:
langsung : 499999500000.0
per chunk: 499999500000.0
sama : True- Baris 3: hitungan utuh sebagai pembanding.
- Baris 6-9: sepuluh chunk masing-masing 100.000 elemen dijumlahkan; pada setiap iterasi hanya satu potongan kecil yang aktif di memori.
- Baris 12: hasilnya identik untuk data ini. Untuk operasi yang sensitif urutan penjumlahan float, verifikasi dengan
np.allcloseseperti pada Chapter Perhitungan dengan Presisi Terbatas.
| Strategi | Efek | Catatan |
|---|---|---|
| dtype lebih kecil | nbytes turun 2-8x | periksa jangkauan dengan np.iinfo |
| operasi in-place | tanpa array sementara | dtype hasil harus sama dengan array asal |
del + tanpa referensi tersisa | blok dibebaskan otomatis | view sekecil apa pun menahan blok asli |
| chunking | puncak memori tetap kecil | pastikan cara penggabungan hasilnya benar |