Di memori tidak ada baris dan kolom: array NumPy tersimpan sebagai satu blok byte yang memanjang, dan shape hanyalah cara membaca ulang blok tersebut. Konsekuensinya, urutan penyimpanan elemen — yang disebut memory layout — menentukan seberapa dekat data yang biasa diakses bersamaan, dan itu berpengaruh pada kecepatan.
Ada dua konvensi susunan yang dipakai NumPy: C-order (row-major, baris diisi lebih dulu) dan F-order (column-major, kolom diisi lebih dulu, mengikuti tradisi Fortran). Bab ini membahas cara mengenali keduanya, bagaimana satu data sebar bisa dibaca menjadi matriks berbeda, serta kapan perbedaan layout benar-benar terasa di pengukuran.
Dua Konvensi: C-order dan F-order #
Setiap array membawa bendera (flags) yang menyatakan layout-nya. Kita buat array 2x3 yang isinya sama, lalu bandingkan:
import numpy as np
c = np.array([[1, 2, 3], [4, 5, 6]], order="C")
f = np.array([[1, 2, 3], [4, 5, 6]], order="F")
print("order C :", c.flags["C_CONTIGUOUS"], c.flags["F_CONTIGUOUS"])
print("order F :", f.flags["C_CONTIGUOUS"], f.flags["F_CONTIGUOUS"])
print(c)
print(f)Output yang Dihasilkan:
order C : True False
order F : False True
[[1 2 3]
[4 5 6]]
[[1 2 3]
[4 5 6]]Bedah baris demi baris:
- Baris 3-4 (
order="C"danorder="F"): argumenordersaat pembuatan array menentukan urutan penulisan elemen ke memori, bukan tampilan di layar. - Baris 5-6 (
flags[...]): kedua array tampil identik di layar, tetapi benderanya berbeda —cberstatus C-contiguous,fberstatus F-contiguous. - Baris 7-8 (
print): karena NumPy menampilkan elemen berdasarkanshape, perbedaan layout tidak terlihat dari cetakan; hanyaflagsdanstridesyang membongkarnya.
| Aspek | C-order (row-major) | F-order (column-major) |
|---|---|---|
| Urutan isi memori | baris demi baris | kolom demi kolom |
| Akses berurutan paling cepat | menyapu sepanjang baris | menyapu sepanjang kolom |
| Tradisi bahasa | C, NumPy default | Fortran, MATLAB, R |
| Flag khas | C_CONTIGUOUS True | F_CONTIGUOUS True |
Membentuk Matriks dari Data Sebar #
Konvensi layout paling mudah diamati ketika satu data 1-D dibentuk menjadi matriks. reshape menerima argumen order yang memutuskan urutan pengisian:
import numpy as np
data = np.arange(6)
c = data.reshape(2, 3, order="C")
f = data.reshape(2, 3, order="F")
print("order C:")
print(c)
print("order F:")
print(f)Output yang Dihasilkan:
order C:
[[0 1 2]
[3 4 5]]
order F:
[[0 2 4]
[1 3 5]]Cara membacanya:
- Baris 3 (
data): enam elemen disimpan berurutan di memori:0 1 2 3 4 5— urutan fisik ini tidak pernah berubah. - Baris 4 (
order="C"): NumPy mengisi baris pertama lebih dulu (0 1 2), lalu baris kedua (3 4 5). - Baris 5 (
order="F"): NumPy mengisi kolom pertama lebih dulu (0 1), kolom kedua (2 3), dan kolom ketiga (4 5).
nbytes: Ukuran Fisik Data #
Jumlah byte yang benar-benar dipakai array dihitung langsung dari tipe datanya — rumusnya size x itemsize. Nilai ini menjadi dasar semua pembicaraan hemat memori pada bab-bab berikutnya:
import numpy as np
a = np.zeros((3, 4), dtype=np.int64)
print("size :", a.size)
print("itemsize:", a.itemsize)
print("nbytes :", a.nbytes)
print("cek :", a.nbytes == a.size * a.itemsize)Output yang Dihasilkan:
size : 12
itemsize: 8
nbytes : 96
cek : True- Baris 3 (
np.zeros(...)): array 3x4 berisi dua belas elemenint64dialokasikan. - Baris 4-6: setiap
int64menempati 8 byte (itemsize), sehingga dua belas elemen memakai 96 byte (nbytes). - Baris 7: persamaan
nbytes == size * itemsizeterpenuhi; rumus ini berlaku untuk layout apa pun.
Perbandingan cepat untuk satu juta elemen: float64 dan int64 memakai 8 MB, float32 dan int32 4 MB, sedangkan uint8 cukup 1 MB. Memilih tipe data yang lebih kecil adalah cara paling langsung menekan nbytes — dibahas pada Chapter Memilih dtype yang Efisien.
Dampak Layout terhadap Kecepatan #
Menyapu memori secara berurutan (searah layout) lebih cepat daripada melompat-lompat. Menyalin array hasil transpose adalah contoh yang mudah diukur: sumbernya tersusun melintang, sehingga penyalinannya harus melangkah tidak berurutan.
# notest
import numpy as np
import time
m = np.ones((2000, 2000))
mulai = time.perf_counter()
m.copy()
t_c = time.perf_counter() - mulai
mulai = time.perf_counter()
m.T.copy()
t_t = time.perf_counter() - mulai
print(f"copy C-order : {t_c * 1000:.2f} ms")
print(f"copy hasil transpose : {t_t * 1000:.2f} ms")Output yang Dihasilkan:
# (ilustrasi — angka waktu berbeda di tiap komputer)
copy C-order : 12.93 ms
copy hasil transpose : 38.47 msm.copy() menyalin 32 MB secara berurutan, sedangkan m.T.copy() menyalin jumlah byte yang sama tetapi harus meloncat antar baris. NumPy cukup pintar untuk mengatur ulang pekerjaannya sehingga selisihnya jarang sedramatis ini, tetapi pola “akses searah layout lebih cepat” selalu berlaku sebagai arah berpikir.
Layout juga memengaruhi kapan suatu operasi perlu menyalin data. ravel mengikuti aturan ini: untuk array F-order, meratakan dengan urutan memori menghasilkan view, sedangkan urutan C memaksa copy:
import numpy as np
f = np.arange(6).reshape(2, 3, order="F")
print("ravel order C:", np.shares_memory(f.ravel(), f))
print("ravel order K:", np.shares_memory(f.ravel(order="K"), f))Output yang Dihasilkan:
ravel order C: False
ravel order K: True- Baris 3 (
order="F"): array dibentuk dengan layout kolom. - Baris 4 (
ravel()): urutan bawaan adalah C, yang tidak searah layout, sehingga NumPy menyalin data (shares_memory False). - Baris 5 (
order="K"): urutan “mengikuti layout” (K) searah memori, sehingga hasilnya view tanpa salinan.
Perbedaan C-order dan F-order hanyalah permulaan; sebagian array bahkan tidak memenuhi keduanya. Kondisi itu bernama non-contiguous dan dibahas tuntas pada Chapter Contiguous vs Non-contiguous.