Setiap kali data berpindah antar pustaka, ada dua kemungkinan: array baru dialokasikan dan seluruh nilai disalin, atau kedua pustaka meminjam buffer memori yang sama. Pilihan ini menentukan kecepatan, pemakaian memori, dan — yang paling berbahaya — apakah mutasi pada satu objek diam-diam mengubah objek lain. Bab ini membekali kita dengan empat alat pembuktian: np.shares_memory, protokol __array__, buffer protocol Python, dan DLPack.
Bukti Berbagi Memori dengan np.shares_memory #
Fungsi np.shares_memory(a, b) menjawab satu pertanyaan presisi: apakah kedua array menunjuk ke byte yang sama di memori? Mari kita uji pada pola operasi yang paling sering dipakai:
import numpy as np
a = np.arange(6)
s = a[2:5]
r = a.reshape(2, 3)
f = a[[0, 1, 2]]
print("slice :", np.shares_memory(a, s))
print("reshape:", np.shares_memory(a, r))
print("fancy :", np.shares_memory(a, f))
print("copy :", np.shares_memory(a, a.copy()))Output yang Dihasilkan:
slice : True
reshape: True
fancy : False
copy : FalseBedah baris demi baris:
- Baris 3-4 (
a[2:5]): slicing menghasilkan view — metadata baru, buffer lama; laporanTrue. - Baris 5 (
a.reshape(2, 3)): mengubah bentuk tanpa mengubah urutan byte hanya mengatur ulang strides, sehingga tetap berbagi memori. - Baris 6-7 (
a[[0, 1, 2]]): fancy indexing selalu mengumpulkan elemen ke buffer baru —False. - Baris 8:
a.copy()memang sengaja menyalin —False.
Pola ini persis yang dibahas pada bab View vs Copy; di bab itu fokusnya mutasi array, di sini fokusnya pertukaran data antar pustaka, dan logikanya sama: hasil yang berbagi memori mutasinya menular, hasil yang menyalin tidak.
Protokol array dan array_interface #
Bagaimana NumPy mengenali objek dari pustaka lain? Lewat dua kontrak kelas. Atribut __array_interface__ mendeskripsikan array secara teknis, sementara method __array__ mengizinkan objek apa pun menyajikan dirinya sebagai ndarray:
import numpy as np
a = np.arange(3)
info = a.__array_interface__
print(sorted(info.keys()))
print(info["shape"], info["typestr"])
class SuhuLuar:
def __init__(self, nilai):
self.nilai = np.array(nilai)
def __array__(self, dtype=None):
if dtype is None:
return self.nilai
return self.nilai.astype(dtype)
s = SuhuLuar([27.5, 28.1])
b = np.asarray(s)
print(b, b.dtype)
print("berbagi memori ->", np.shares_memory(b, s.nilai))Output yang Dihasilkan:
['data', 'descr', 'shape', 'strides', 'typestr', 'version']
(3,) <i8
[27.5 28.1] float64
berbagi memori -> TruePenjelasannya:
- Baris 4-6 (
a.__array_interface__): dict berisi deskripsi array —shape,typestr(<i8berarti int64 little-endian),strides, dandata(alamat memori, sengaja tidak dicetak karena berubah tiap eksekusi). - Baris 8-14 (
class SuhuLuar): kelas murni Python yang menyediakan__array__; setiap fungsi NumPy boleh memintanya “menyerahkan diri” sebagai ndarray. - Baris 16-18 (
np.asarray(s)): NumPy memanggils.__array__(), dan hasil yang dikembalikan adalah array milik objek itu sendiri — karena itunp.shares_memorybernilaiTrue, tanpa salinan tambahan.
Kontrak inilah yang membuat duck array dari pustaka lain (CuPy, Dask, Sparse) dapat diterima hampir semua fungsi NumPy tanpa ubah kode.
Buffer Protocol Python #
Python punya kontrak pertukaran memori yang lebih tua dan lebih umum: buffer protocol. Objek yang mengekspos buffer mentah — bytes, bytearray, memoryview — dapat dibungkus NumPy tanpa menyalin:
import numpy as np
buf = bytearray([10, 20, 30, 40])
a = np.frombuffer(buf, dtype=np.uint8)
print(a)
a[0] = 99
print("bytearray ikut berubah:", bytes(buf))Output yang Dihasilkan:
[10 20 30 40]
bytearray ikut berubah: b'c\x14\x1e('Yang terjadi di balik layar:
- Baris 3-4 (
np.frombuffer(...)):bytearraymengekspos blok byte-nya, danfrombuffermembungkus blok itu sebagai arrayuint8— bukan menyalinnya. - Baris 6-7 (
a[0] = 99): penulisan pertama langsung menyentuh byte pertamabytearray;99dalam heksadesimal adalah0x63, terlihat di outputb'c\x14\x1e('.
DLPack: Standar Pertukaran Antar Pustaka #
Buffer protocol hanya mengenali memori CPU. Untuk pertukaran yang mencakup GPU, ekosistem menyepakati DLPack — standar deskriptor tensor lintas pustaka yang dipakai PyTorch, CuPy, JAX, dan NumPy. NumPy menyediakan np.from_dlpack untuk mengonsumsi objek DLPack:
import numpy as np
a = np.arange(5) * 10
b = np.from_dlpack(a)
print(b)
print("berbagi memori ->", np.shares_memory(a, b))
a[0] = 999
print("b ikut berubah:", b)
c = np.from_dlpack(a, copy=True)
print("copy=True ->", np.shares_memory(a, c))Output yang Dihasilkan:
[ 0 10 20 30 40]
berbagi memori -> True
b ikut berubah: [999 10 20 30 40]
copy=True -> FalseMari kita baca:
- Baris 4-5 (
np.from_dlpack(a)): ndarray itu sendiri adalah produsen DLPack yang sah; hasilbberbagi buffer dengana— zero-copy untuk array yang kontigu. - Baris 7-8: mutasi
a[0]terbawa keb, bukti bahwa kedua objek meminjam byte yang sama. - Baris 9-10 (
copy=True): kebutuhan salin bisa dinyatakan eksplisit;ckini menempati buffer sendiri.
Dengan DLPack, pola pertukaran menjadi seragam: pustaka GPU mana pun dapat menyerahkan tensornya ke NumPy tanpa memindahkan data dari kartu grafis, selama penerimaan yang kita minta mendukung perangkatnya.
Tabel Keputusan: Salin atau Berbagi Memori #
| Operasi | Salin atau berbagi memori? | Bukti di bab ini |
|---|---|---|
a[2:5] (slicing) | berbagi (view) | True |
a.reshape(2, 3) | berbagi | True |
a[[0, 1, 2]] (fancy) | salin | False |
a.copy() | salin | False |
np.asarray(ndarray) | berbagi | True (protokol __array__) |
np.frombuffer(bytearray) | berbagi | mutasi menular |
np.from_dlpack(a) | berbagi (bila kontigu) | True |
np.from_dlpack(a, copy=True) | salin | False |