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:

python
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:

text
nbytes int64 : 8000000
nbytes int32 : 4000000
hemat        : 4000000 byte

Cara membacanya:

  1. Baris 3 (np.arange(..., dtype=np.int64)): satu juta elemen dialokasikan 8 byte per elemen, total 8.000.000 byte.
  2. Baris 4 (astype(np.int32)): NumPy menyalin data ke dtype baru yang hanya menempati 4 byte per elemen.
  3. 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:

python
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:

text
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:

  1. Baris 4-6: b menunjuk objek yang sama dengan a; setelah a += 10, ketiga nama tetap merujuk blok yang diperbarui — tidak ada array baru.
  2. Baris 9-11: c = c + 10 dievaluasi dengan membuat array baru; nama c berpindah ke array baru, sedangkan d tetap memegang array lama yang isinya belum berubah.
  3. Output kedua: nilai c dan d kini 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:

python
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:

text
getsizeof: 8000112 byte
potongan berbagi blok: True
blok asli tetap hidup karena ditahan oleh potongan

Cara membacanya:

  1. Baris 5 (sys.getsizeof): ukuran yang dilaporkan mencakup 8.000.000 byte data ditambah ±112 byte header objek array.
  2. Baris 6-7 (potongan = besar[:100]): potongan seratus elemen hanyalah view, dan np.shares_memory membuktikan keduanya berbagi blok.
  3. Baris 8 (del besar): menghapus nama besar tidak melepas blok 8 MB, karena potongan masih 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:

python
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:

text
langsung : 499999500000.0
per chunk: 499999500000.0
sama     : True
  1. Baris 3: hitungan utuh sebagai pembanding.
  2. Baris 6-9: sepuluh chunk masing-masing 100.000 elemen dijumlahkan; pada setiap iterasi hanya satu potongan kecil yang aktif di memori.
  3. Baris 12: hasilnya identik untuk data ini. Untuk operasi yang sensitif urutan penjumlahan float, verifikasi dengan np.allclose seperti pada Chapter Perhitungan dengan Presisi Terbatas.
StrategiEfekCatatan
dtype lebih kecilnbytes turun 2-8xperiksa jangkauan dengan np.iinfo
operasi in-placetanpa array sementaradtype hasil harus sama dengan array asal
del + tanpa referensi tersisablok dibebaskan otomatisview sekecil apa pun menahan blok asli
chunkingpuncak memori tetap kecilpastikan cara penggabungan hasilnya benar