Chapter Vectorization pada Section Operasi Matematika telah memperkenalkan pola-pola idiomatiknya; chapter ini membedah sisi yang belum kita ukur: performa. Kita akan membandingkan loop dan bentuk vektornya dengan tic/toc, memverifikasi hasilnya identik lewat isequal, lalu melihat pola mana yang paling berdampak. Cakupan materinya:

  • Timing terukur: akumulator, filter if, statistik per baris
  • accumarray untuk agregasi per kelompok
  • Tabel konversi before/after untuk refactor cepat
  • Kapan loop tidak bisa divektorkan, dan konteks jujur soal JIT

Akumulator: Loop vs sum #

Aturannya: loop yang hanya menjumlahkan (atau mengalikan) elemen satu per satu sebaiknya diganti sum (atau prod). Mekanismenya: sum menjumlahkan seluruh array dalam satu panggilan tingkat rendah yang dikompilasi, sementara loop pengguna membayar biaya interpreter pada tiap iterasi — penerjemahan baris, pengecekan batas, dan pengecekan tipe. Berikut pengukurannya pada 500000 elemen:

matlab
rng(42);
n = 500000;
v = rand(1, n);

tic;
sLoop = 0;
for k = 1:n
    sLoop = sLoop + v(k);
end
tLoop = toc;

tic;
sVec = sum(v);
tVec = toc;

fprintf('akumulasi : loop %.4f s | sum %.6f s | speedup %.0fx\n', tLoop, tVec, tLoop / tVec);
fprintf('hasil identik: %d\n\n', isequal(sLoop, sVec));

Output yang Dihasilkan:

text
akumulasi : loop 0.6367 s | sum 0.000648 s | speedup 983x
hasil identik: 1

Mari kita bedah baris-baris kode di atas:

  1. Baris 2-3 (n = 500000; v = rand(...)): menyiapkan data uji berukuran besar agar selisih waktu terukur, bukan sekadar noise.
  2. Baris 5-9 (for k = 1:n): akumulator klasik — sLoop dibaca dan ditulis ulang tiap iterasi.
  3. Baris 12 (sVec = sum(v);): penjumlahan seluruh array dalam satu panggilan fungsi.
  4. Baris 15 (fprintf(...)): rasio tLoop / tVec adalah speedup.
  5. Baris 16 (isequal(sLoop, sVec)): verifikasi bahwa kedua cara menghasilkan nilai yang persis sama — vectorization bukan pendekatan hampiran.

Filter: Mask Logical vs if dalam Loop #

Aturannya: seleksi “jika elemen memenuhi syarat, kumpulkan” diganti tiga langkah vektor: bangun mask (v > 0.5), pilih indeks (find) atau langsung pilih nilai (v(mask)). Mekanismenya: perbandingan mengevaluasi seluruh array sekaligus menghasilkan logical mask, lalu indeksasi logical menyaring elemen yang lolos tanpa percabangan per elemen. Agar perbandingannya adil, versi loop di bawah sudah memakai prealokasi — biaya pertumbuhan array tidak ikut terukur:

matlab
tic;
idx = zeros(1, n);       % prealokasi agar biaya growth tidak ikut terukur
jml = 0;
for k = 1:n
    if v(k) > 0.5
        jml = jml + 1;
        idx(jml) = k;
    end
end
idx = idx(1:jml);
tIf = toc;

tic;
idxVec = find(v > 0.5);
tMask = toc;

fprintf('filter    : loop-if %.4f s | mask %.6f s | speedup %.0fx\n', tIf, tMask, tIf / tMask);
fprintf('hasil identik: %d\n\n', isequal(idxVec, idx));

Output yang Dihasilkan:

text
filter    : loop-if 0.8494 s | mask 0.004036 s | speedup 210x
hasil identik: 1

Pola ini bekerja untuk transformasi sekaligus seleksi: v(v > 0.5) * 2 menggandakan hanya elemen yang lolos dalam satu ekspresi. Bila yang dibutuhkan hanya jumlah elemen yang lolos, sum(v > 0.5) adalah idiom frekuensi paling ringkas.

Statistik per Baris: mean(A, 2) #

Aturannya: agregasi per baris atau per kolom matriks memakai argumen dimensi — mean(A, 2) untuk rata-rata tiap baris, mean(A, 1) untuk tiap kolom — bukan loop yang memanggil mean seribu kali. Mekanismenya: mean(A, 2) meruntuhkan dimensi kedua dalam satu panggilan; loop pengguna memanggil mean(A(i,:)) per baris sehingga biaya panggilan fungsi dikalikan jumlah baris:

matlab
A = rand(1000, 300);

tic;
rLoop = zeros(1000, 1);
for i = 1:1000
    rLoop(i) = mean(A(i, :));
end
tBaris = toc;

tic;
rVec = mean(A, 2);
tKolom = toc;

fprintf('mean baris: loop %.4f s | mean(A,2) %.6f s | speedup %.0fx\n', ...
    tBaris, tKolom, tBaris / tKolom);
fprintf('selisih maksimum: %.2e\n\n', max(abs(rLoop - rVec)));

Output yang Dihasilkan:

text
mean baris: loop 0.0492 s | mean(A,2) 0.000356 s | speedup 138x
selisih maksimum: 0.00e+00

Pola dimensi ini berlaku seragam untuk sum, max, min, cumsum, dan std — pola yang telah kita pakai di Section Pengolahan Data untuk skor per observasi.

accumarray: Agregasi per Kelompok #

Aturannya: ringkasan per kelompok (total penjualan per pelanggan, jumlah kejadian per kategori) memakai accumarray — satu panggilan menggantikan loop dengan kondisi per kelompok. Mekanismenya: accumarray(sub, val, [jumlahKelompok 1]) menjumlahkan val ke keranjang yang ditunjuk sub; kita tidak perlu membangun if per kelompok secara manual:

matlab
pelanggan = [1 3 2 1 3 1 2];
tagihan   = [50 30 20 70 60 10 40];

total = accumarray(pelanggan', tagihan', [3 1]);

totalLoop = zeros(3, 1);
for p = 1:3
    totalLoop(p) = sum(tagihan(pelanggan == p));
end
fprintf('accumarray: total per pelanggan = %d %d %d\n', total);
fprintf('versi loop : total per pelanggan = %d %d %d\n', totalLoop);
fprintf('hasil identik: %d\n\n', isequal(total, totalLoop));

Output yang Dihasilkan:

text
accumarray: total per pelanggan = 130 60 90
versi loop : total per pelanggan = 130 60 90
hasil identik: 1

Pelanggan 1 menagih 50 + 70 + 10 = 130, dan kedua versi sepakat. Argumen ketiga [3 1] menentukan jumlah keranjang keluaran; tanpa argumen tersebut, accumarray menentukan jumlah keranjang dari nilai sub terbesar.

Tabel Konversi Before/After #

Pola loop (before)Bentuk vector (after)Fungsi kunci
s = 0; for k, s = s + v(k)s = sum(v)sum
for k, if v(k) > t, kumpulkanfind(v > t) / v(v > t)logical indexing
for i, r(i) = mean(A(i,:))r = mean(A, 2)sum, mean, dimensi
for k, s(k) = s(k-1) + v(k)s = cumsum(v)cumsum
for p, for k, if kelompok...accumarray(sub, val)accumarray
for i, for j, Z(i,j) = f(...)Z = f(X, Y) lewat meshgridmeshgrid

Kapan Loop Tidak Bisa Divektorkan #

Vectorization berhenti ketika iterasi saling bergantung: hasil iterasi k dibutuhkan untuk menghitung iterasi k+1, dan hubungan tersebut bukan akumulasi standar. Contoh klasiknya adalah Fibonacci — setiap suku adalah jumlah dua suku sebelumnya:

matlab
a = 0;
b = 1;
fib = zeros(1, 10);
for k = 1:10
    fib(k) = b;
    baru = a + b;   % iterasi berikutnya memakai hasil iterasi ini
    a = b;
    b = baru;
end
fprintf('fibonacci (10 suku): %d %d %d %d %d %d %d %d %d %d\n', fib);

Output yang Dihasilkan:

text
fibonacci (10 suku): 1 1 2 3 5 8 13 21 34 55

Keluarga kasus lain yang menolak vectorization: simulasi dinamis yang statusnya berubah tiap langkah waktu, dan algoritme dengan early exit. Untuk kasus-kasus ini, loop eksplisit adalah pilihan yang jujur dan benar — dipercepat dengan preallocation (chapter berikutnya), bukan dipaksakan menjadi array.

Ringkasan #

Pengukuran pada 500000 elemen menegaskan pola dari chapter sebelumnya: sum mengalahkan akumulator loop, logical indexing mengalahkan if per elemen, dan mean(A, 2) mengalahkan loop per baris — semuanya dengan hasil identik yang diverifikasi isequal. accumarray melengkapi keluarga ini untuk agregasi per kelompok. Batasnya juga jelas: iterasi yang saling bergantung seperti Fibonacci tetap milik loop. Satu sumber perlambatan yang kerap menempel pada loop yang tersisa — array yang tumbuh di dalamnya — menjadi topik chapter berikutnya: preallocation.