Loop yang mengumpulkan hasil satu per satu sering menulis a = [a, x] — terlihat polos, tetapi diam-diam menyalin seluruh array pada setiap penambahan. Preallocation adalah obatnya: pesan seluruh memori di muka dengan zeros (atau cell), lalu isi per elemen. Chapter ini mengukur betapa mahalnya array yang tumbuh, lalu memetakan pola prealokasi untuk tipe, dimensi, dan kasus ketika ukuran akhir tidak diketahui. Cakupan materinya:

  • Biaya copy-on-append dan skalanya yang kuadratik O(n²)
  • zeros(1, n) sebelum loop, zeros(..., 'single') untuk tipe
  • cell(1, n) untuk cell array
  • Pola doubling bila ukuran akhir tidak diketahui

Masalah: Array yang Tumbuh di Dalam Loop #

Aturannya: jangan menumbuhkan array di dalam loop — alokasikan ukuran penuh sebelum loop dimulai. Mekanismenya: array MATLAB menempati satu blok memori berurutan. Saat a = [a, x] dieksekusi, MATLAB tidak bisa “menambah” satu elemen di akhir blok lama; MATLAB mengalokasikan blok baru yang lebih besar, menyalin seluruh isi lama, menulis x di ujungnya, lalu membebaskan blok lama. Penyalinan itu terjadi pada setiap iterasi:

Diagram di atas menunjukkan siklus alokasi-salin-bebaskan yang berulang; akumulasinya mencapai n(n+1)/2 elemen tersalin. Untuk n = 30000, totalnya ~4,5 × 10⁸ elemen — pekerjaan yang tidak diminta siapa pun.

zeros Sebelum Loop #

Aturannya: bila ukuran akhir diketahui, buat array penuh dengan zeros(1, n) sebelum loop, lalu isi per indeks. Mekanismenya: alokasi dan penyalinan hanya terjadi satu kali di awal; di dalam loop, MATLAB cukup menulis satu elemen ke alamat yang sudah tersedia. Berikut perbandingan terukurnya:

matlab
n = 30000;

tic;
a = [];                 % mulai kosong: tiap append menyalin ulang array
for k = 1:n
    a = [a, k];
end
tTumbuh = toc;

tic;
b = zeros(1, n);        % prealokasi: salin sekali di awal
for k = 1:n
    b(k) = k;
end
tPre = toc;

fprintf('n=%d\n', n);
fprintf('tumbuh  : %.4f s\n', tTumbuh);
fprintf('prealok : %.4f s\n', tPre);
fprintf('speedup : %.0fx, hasil identik: %d\n\n', tTumbuh / tPre, isequal(a, b));

Output yang Dihasilkan:

text
n=30000
tumbuh  : 0.5572 s
prealok : 0.0317 s
speedup : 18x, hasil identik: 1

Mari kita bedah baris-baris kode di atas:

  1. Baris 4 (a = [];): array kosong — titik awal pola tumbuh; setiap a = [a, k] berikutnya memicu salin ulang.
  2. Baris 11 (b = zeros(1, n);): memesan ruang untuk 30000 elemen sekali jalan; nilai awalnya nol.
  3. Baris 13 (b(k) = k;): menulis satu elemen ke slot yang sudah ada — tanpa alokasi baru.
  4. Baris 20 (isequal(a, b)): kedua pola menghasilkan array yang identik, jadi pergantian pola tidak mengubah hasil.

Bentuk a(end+1) = k juga menumbuhkan array dan mengikuti mekanisme mahal yang sama — prealokasi dengan zeros tetap menjadi pilihan yang andal.

Prealokasi dengan Tipe dan Dimensi #

zeros menerima tipe dan dimensi apa pun yang kita butuhkan: zeros(4, 2, 'single') memesan matriks single (4 byte per elemen — chapter Memory Usage membahasnya tuntas), dan zeros(2, 3, 4) memesan array 3D. Untuk mengisi atau memangkas, dua idiom ringkas sering menolong:

matlab
s = zeros(4, 2, 'single');
V = zeros(2, 3, 4);
fprintf('s: %s %dx%d, %d byte\n', class(s), size(s, 1), size(s, 2), whos('s').bytes);
fprintf('V: %dx%dx%d\n\n', size(V, 1), size(V, 2), size(V, 3));

isi = zeros(1, 5);
isi(:) = 7;             % isi seluruh elemen sekaligus
fprintf('isi massal : %d %d %d %d %d\n', isi);
isi(3:4) = [];          % buang dua elemen: ukuran mengecil
fprintf('setelah dipangkas: %d %d %d\n\n', isi);

Output yang Dihasilkan:

text
s: single 4x2, 32 byte
V: 2x3x4

isi massal : 7 7 7 7 7
setelah dipangkas: 7 7 7

Assignment isi(:) = 7 mengisi seluruh elemen dalam satu ekspresi, dan assignment ke [] memangkas ukuran array — dua operasi yang tetap murah karena array sudah prealokasi.

Cell array menimbulkan biaya serupa saat tumbuh, karena setiap sel menyimpan penunjuk data yang ikut disalin ulang. Prealokasinya memakai cell:

matlab
m = 100000;

tic;
k1 = {};
for k = 1:m
    k1{end + 1} = k;    % cell tumbuh: header cell disalin ulang
end
tCellTumbuh = toc;

tic;
k2 = cell(1, m);        % prealokasi cell
for k = 1:m
    k2{k} = k;
end
tCellPre = toc;

fprintf('cell n=%d: tumbuh %.4f s | prealok %.4f s | speedup %.1fx\n', ...
    m, tCellTumbuh, tCellPre, tCellTumbuh / tCellPre);
fprintf('hasil identik: %d\n\n', isequal(k1, k2));

Output yang Dihasilkan:

text
cell n=100000: tumbuh 0.4001 s | prealok 0.1816 s | speedup 2.2x
hasil identik: 1

Ukuran Tak Diketahui: Pola Doubling #

Aturannya: bila jumlah iterasi tidak diketahui di muka (misalnya membaca sampai kondisi berhenti), jangan menumbuhkan satu-satu — gandakan kapasitas setiap kali penuh, lalu pangkas sekali di akhir. Mekanismenya: dengan kapasitas yang berlipat dua, jumlah operasi salin hanya O(log n) kali, sehingga total penyalinan O(n), bukan O(n²):

matlab
tic;
d = zeros(1, 1024);     % kapasitas awal
jml = 0;
for k = 1:n
    if jml == numel(d)
        d = [d, zeros(1, numel(d))];   % kapasitas habis: gandakan
    end
    jml = jml + 1;
    d(jml) = k;
end
d = d(1:jml);           % pangkas ke ukuran sebenarnya
tDbl = toc;

fprintf('doubling : %.4f s (vs append %.4f s), speedup %.0fx\n', ...
    tDbl, tTumbuh, tTumbuh / tDbl);
fprintf('hasil identik: %d\n', isequal(d, b));

Output yang Dihasilkan:

text
doubling : 0.0879 s (vs append 0.5572 s), speedup 6x
hasil identik: 1

Fragmentasi Memori #

Efek samping pola tumbuh yang jarang terlihat: alokasi dan pembebasan berulang meninggalkan memory fragmentation — banyak blok kecil terpisah di heap. Meskipun total memori kosong cukup, blok berurutan yang besar untuk array berikutnya bisa gagal dialokasikan. Preallocation mengurangi fragmentasi karena MATLAB memesan sedikit blok besar, bukan ribuan blok kecil yang berganti-ganti. Pada program berumur panjang (sesi interaktif yang mengolah dataset bergantian), kebiasaan clear variabel besar dan pola prealokasi menjaga heap tetap padat.

Ringkasan #

Array yang tumbuh di dalam loop membayar penyalinan berulang dengan total O(n²); prealokasi zeros menurunkannya ke satu alokasi dengan hasil identik (isequal = 1). Keluarga polanya: zeros(1, n) untuk vektor, zeros(r, c, d) untuk dimensi lebih, zeros(n, 'single') untuk tipe hemat memori, cell(1, n) untuk cell array, dan pola doubling + pangkas bila ukuran akhir tak diketahui. Dengan array yang kini stabil ukurannya, sisa biaya loop tinggal pada isi badannya — anti-pola di dalam badan loop tersebut kita sapu bersih di chapter berikutnya.