Topik ini membahas tata letak memori array NumPy, yaitu cara nilai-nilainya tersusun di dalam memori utama. Pembahasan mencakup atribut strides dan nbytes, perbedaan urutan C dan urutan F, konsekuensi kontiguitas terhadap kelancaran pembacaan data, serta pengembalian array ke urutan C dengan np.ascontiguousarray. Setelah membacanya, angka strides pada hasil inspeksi dapat dibaca dan ditafsirkan tanpa menebak.
Strides dan nbytes: peta byte#
Setiap ndarray menyimpan nilai numeriknya di dalam satu penyangga (buffer) byte, yaitu deretan alamat memori yang berdampingan. Atribut strides menyatakan berapa byte yang harus dilompati untuk berpindah satu elemen pada setiap sumbu, sedangkan nbytes menyatakan ukuran total penyangganya. Keduanya diturunkan dari itemsize, yaitu ukuran satu elemen dalam byte, sehingga nilainya deterministik dan selalu sama untuk array dengan tipe serta bentuk yang sama.
Inspeksi berikut memakai array suhu satu dimensi berisi lima nilai sebagai contoh datanya.
import numpy as np
suhu = np.array([27.5, 28.0, 26.5, 27.0, 28.5])
print(suhu.strides)
print(suhu.nbytes)
print(suhu.itemsize)(8,)
40
8Keluarannya konsisten dengan aritmetika sederhana: satu elemen float64 berukuran 8 byte, sehingga strides berisi (8,), dan lima elemen memakan 40 byte. Untuk array berdimensi dua, alamat sebuah elemen dihitung dari dua langkah yang sesuai dengan sumbunya, sebagaimana rumus berikut.
Notasi dan adalah strides untuk sumbu baris dan kolom. Rumus ini berlaku untuk semua array tanpa kecuali, dan perbedaan urutan memori pada bagian berikutnya hanya mengubah isi serta , bukan bentuk rumusnya.
Urutan C dan urutan F#
NumPy mengenal dua urutan penyimpanan kanonis. Urutan C (row-major) menyimpan baris demi baris, sehingga nilai awal satu baris mengikuti akhir baris sebelumnya secara langsung. Urutan F (column-major, mengikuti konvensi Fortran) menyimpan kolom demi kolom. Fungsi reshape dan sebagian besar fungsi pembuat array menghasilkan urutan C secara default, sedangkan np.asfortranarray membuat salinan baru dengan urutan F.
Contoh berikut menumpangkan keduanya pada matriks yang sama, lengkap dengan flag kontiguitasnya. Operasi .T dipakai untuk menunjukkan bahwa transpose hanyalah view.
import numpy as np
matriks = np.arange(1, 7, dtype=np.int64).reshape(2, 3)
transpose = matriks.T
print(matriks.strides)
print(transpose.strides)
print(matriks.flags["C_CONTIGUOUS"], matriks.flags["F_CONTIGUOUS"])
print(transpose.flags["C_CONTIGUOUS"], transpose.flags["F_CONTIGUOUS"])(24, 8)
(8, 24)
True False
False TruePada matriks berurutan C, pindah satu baris berarti melompat 24 byte, yaitu tiga elemen, sedangkan pindah satu kolom hanya 8 byte. Transpose tidak menyalin apa pun: penyangganya tetap milik matriks, dan yang berubah hanyalah bentuk serta susunan strides menjadi (8, 24). Karena susunannya tidak lagi memenuhi pola urutan C, flag C_CONTIGUOUS transpose bernilai False, meskipun penyangga dasarnya sama sekali tidak berubah.
Salinan berurutan F dapat dibuat dengan np.asfortranarray, dan urutan memori aslinya dapat dilihat melalui ravel(order="K"), yang membaca penyangga menurut susunan memorinya.
import numpy as np
matriks = np.arange(1, 7, dtype=np.int64).reshape(2, 3)
urutan_f = np.asfortranarray(matriks)
print(matriks.strides)
print(urutan_f.strides)
print(matriks.ravel(order="K"))
print(urutan_f.ravel(order="K"))(24, 8)
(8, 16)
[1 2 3 4 5 6]
[1 4 2 5 3 6]Diagram berikut menyandingkan kedua penyangga untuk matriks logis yang sama.
Kedua panel memuat matriks logis yang sama, tetapi urutan byte di dalam penyangganya berbeda. Penyangga urutan C berisi 1 sampai 6 secara berurutan, sedangkan penyangga urutan F berisi 1, 4, 2, 5, 3, 6 sesuai kolomnya. Nilai strides pada kedua panel saling bertukar karena rumus alamat harus tetap menghasilkan elemen yang benar dari penyangga yang tersusun berbeda.
Mengapa kontiguitas berpengaruh#
Kontiguitas menentukan seberapa jauh lompatan memori pada setiap langkah perulangan. Pada array berurutan C, membaca sepanjang baris mengunjungi alamat yang berurutan, sedangkan membaca sepanjang kolom melompat sejauh ukuran satu baris pada setiap langkah. Selisihnya sudah terlihat pada matriks kecil di atas, yaitu 24 byte berbanding 8 byte, dan membengkak seiring lebar matriksnya.
Ambil matriks persegi berisi sepuluh ribu kolom dengan tipe float64. Ukuran satu barisnya 80.000 byte, sehingga berpindah satu baris berarti melompat sejauh itu. Membaca satu kolom berarti mengunjungi alamat yang saling berjauhan pada setiap langkah, sedangkan membaca satu baris mengalir searah dari awal penyangga hingga akhirnya. Perangkat keras membaca memori dalam blok yang lebih besar daripada satu elemen, sehingga aliran berurutan umumnya dimanfaatkan lebih efisien daripada lompatan berjarak jauh. NumPy tetap menghasilkan jawaban yang benar untuk kedua arah, tetapi kelancaran pembacaan penyangganya berbeda.
Array hasil operasi tidak selalu kontigu. Slice kolom, transpose, dan sebagian operasi lain menghasilkan view yang strides-nya tidak lagi memenuhi pola urutan C, meskipun penyangga dasarnya tidak berubah. Apabila array semacam itu perlu kembali ke urutan C, misalnya sebelum diberikan kepada kode yang menuntut susunan tersebut, fungsi np.ascontiguousarray membuat salinan baru yang penyangganya disusun ulang.
import numpy as np
matriks = np.arange(1, 7, dtype=np.int64).reshape(2, 3)
transpose = matriks.T
salinan = np.ascontiguousarray(transpose)
print(transpose.strides)
print(salinan.strides)
print(np.shares_memory(transpose, salinan))
print(salinan.flags["C_CONTIGUOUS"])(8, 24)
(16, 8)
False
TrueKeluarannya menegaskan tiga hal: strides salinan kembali mengikuti pola urutan C, np.shares_memory melaporkan False karena penyangganya benar-benar baru, dan flag C_CONTIGUOUS kembali bernilai True. Salinan seperti ini berbiaya memori dan waktu sebanding ukuran array, sehingga dipanggil seperlunya, bukan di dalam setiap putaran perulangan.
Kesalahan umum#
Dua kesalahan berikut tidak memunculkan error apa pun, dan justru karena itulah keduanya sulit ditemukan. Keduanya melibatkan fungsi yang terlihat sepele, yaitu ravel dan slicing, pada array yang tata letaknya tidak lagi berurutan C. Keluaran yang tampak benar membuat polanya lolos dari pemeriksaan kasat mata.
Menganggap ravel selalu mengembalikan view#
Fungsi ravel() tanpa argumen membaca elemen menurut urutan C. Pada array berurutan C, urutan baca itu sama dengan urutan memori, sehingga hasilnya berupa view. Pada array berurutan F, urutan baca C tidak sama dengan urutan memori, sehingga ravel diam-diam membuat salinan. Apabila hasilnya dimutasi dengan harapan data aslinya ikut berubah, perubahan itu tidak pernah terjadi, sementara salinan tak terduga justru menambah pemakaian memori.
import numpy as np
urutan_f = np.asfortranarray(np.arange(1, 7, dtype=np.int64).reshape(2, 3))
rata_c = urutan_f.ravel()
rata_k = urutan_f.ravel(order="K")
print(np.shares_memory(rata_c, urutan_f))
print(np.shares_memory(rata_k, urutan_f))
print(rata_k)False
True
[1 4 2 5 3 6]Perbaikannya adalah memakai ravel(order="K") yang membaca penyangga menurut urutan memorinya, sehingga keduanya tetap berbagi memori. Apabila urutan baca tertentu memang diinginkan dan salinan diterima, flatten lebih jujur karena selalu menyalin.
Menganggap hasil slicing tetap kontigu#
Slice yang mengambil sebagian kolom menghasilkan view yang penyangganya tetap milik array asal. Strides-nya dipertahankan, sedangkan bentuknya menyusut, sehingga pola urutan C tidak lagi terpenuhi dan flag kontiguitasnya berubah.
import numpy as np
matriks = np.arange(12, dtype=np.int64).reshape(3, 4)
kolom = matriks[:, 1:3]
print(kolom.strides)
print(kolom.flags["C_CONTIGUOUS"])(32, 8)
FalseKeluaran tersebut wajar dan bukan tanda error: view mempertahankan strides array dasarnya. Apabila kontiguitas diperlukan, panggil np.ascontiguousarray atas view tersebut, dengan catatan pemanggilan itu membuat salinan baru sebagaimana dibahas pada bagian sebelumnya.
Ringkasan#
stridesmenyatakan lompatan byte per sumbu,nbytesmenyatakan ukuran total penyangga, dan keduanya bersifat deterministik untuk tipe serta bentuk yang sama.- Urutan C menyimpan baris demi baris dan urutan F menyimpan kolom demi kolom, sedangkan transpose hanyalah view yang menukar strides tanpa menyalin apa pun.
- Membaca sepanjang sumbu ber-strides kecil mengalir berurutan di memori, sedangkan sumbu ber-strides besar melompat jauh, sehingga kontiguitas memengaruhi kelancaran pembacaan penyangga.
np.ascontiguousarraymengembalikan array ke urutan C melalui salinan baru, danravelpada array non-C dapat diam-diam menyalin, sementararavel(order="K")mengikuti urutan memori.
Referensi#
- numpy.ndarray.strides — NumPy — Definisi resmi strides beserta contoh perhitungan alamatnya.
- The N-dimensional array (ndarray) — NumPy — Penjelasan penyangga, itemsize, dan tata letak memori internal ndarray.
- numpy.ndarray.flags — NumPy
— Daftar flag array, termasuk
C_CONTIGUOUSdanF_CONTIGUOUS. - numpy.ascontiguousarray — NumPy — Rujukan fungsi penyalin array ke urutan C.