Chapter sebelumnya mengoptimalkan data; chapter ini mengoptimalkan unit kerja yang mengolah data: function. Setiap pemanggilan fungsi membayar biaya tetap — penyiapan argumen, workspace baru, dan lompatan eksekusi — yang tidak terasa sekali tetapi terasa 100000 kali. Kita akan mengukur biaya itu, lalu membentuk fungsi yang bersahabat dengan optimasi MATLAB. Cakupan materinya:
- Overhead pemanggilan terukur: inline vs per elemen vs satu panggilan array
narginuntuk validasi dan default yang murah- Copy-on-write pada output dan pola
x = f(x) persistentsebagai cache untuk komputasi mahal berulang
Karena contoh ini membutuhkan fungsi pendamping (naik1, diskon, komputasiMahal), berkas contoh.m ditulis sebagai function file — bentuk yang berjalan identik di MATLAB dan Octave.
Overhead Pemanggilan: Inline vs Fungsi per Elemen #
Aturannya: jangan memanggil fungsi di dalam loop untuk pekerjaan yang bisa dihitung inline atau — lebih baik — dilakukan sekali untuk seluruh array. Mekanismenya: setiap naik1(v(k)) membuat MATLAB/Octave menyiapkan argumen, membuat stack frame dan workspace fungsi, menjalankan isi fungsi, lalu mengembalikan hasil — sementara v(k) + 1 hanya mengeksekusi satu operasi. Pengukurannya pada 100000 elemen:
n = 100000;
v = rand(1, n);
tic;
s1 = 0;
for k = 1:n
s1 = s1 + v(k) + 1; % dihitung inline, tanpa panggilan fungsi
end
tInline = toc;
tic;
s2 = 0;
for k = 1:n
s2 = s2 + naik1(v(k)); % satu panggilan fungsi per elemen
end
tCall = toc;
tic;
s3 = sum(naik1(v)); % satu panggilan untuk seluruh array
tArr = toc;
fprintf('panggilan: inline %.4f s | per-elemen %.4f s | array %.5f s\n', tInline, tCall, tArr);
fprintf('speedup : loop per-elemen vs satu panggilan array = %.0fx\n', tCall / tArr);
fprintf('hasil identik: %d\n\n', isequal(s1, s2) && isequal(s2, s3));Output yang Dihasilkan:
panggilan: inline 0.1283 s | per-elemen 0.4194 s | array 0.00063 s
speedup : loop per-elemen vs satu panggilan array = 666x
hasil identik: 1Mari kita bedah baris-baris kode di atas:
- Baris 6-10 (
s1 = s1 + v(k) + 1): baseline inline — loop membayar biaya interpreter per iterasi, tanpa biaya panggilan fungsi. - Baris 13-16 (
s2 = s2 + naik1(v(k))): pekerjaan sama, tetapi setiap iterasi membayar overhead satu panggilannaik1. - Baris 19 (
s3 = sum(naik1(v))):naik1dipanggil sekali untuk seluruh array (fungsi vectorizedy = x + 1bekerja pada array apa pun), lalusummenjumlahkan — overhead dibayar satu kali. - Baris 22-23 (
fprintf ...): rasiotCall / tArradalah speedup antara pola terburuk dan terbaik.
Bentuk Fungsi yang Ramah Optimasi #
MATLAB mengoptimalkan fungsi murni paling agresif: fungsi yang hasilnya hanya bergantung pada argumen, tanpa efek samping ke workspace luar. Dua kebiasaan merusak kemudahan optimasi tersebut:
globaldi dalam fungsi: nilai bisa berubah dari luar kapan saja, sehingga JIT sulit membuktikan hasil fungsi stabil — dan pembaca kode ikut menderita. Bawa data masuk lewat argumen, keluar lewat output.- Anonymous function berlapis di loop panas:
f = @(x) g(x) + cmeng-capture nilaicsaat dibuat; menaruh pembuatannya di dalam loop berulang-ulang menyalin konteks. Definisikan sekali di luar loop, dan ukur bila mencurigakan — di Octave, panggilan anonymous dan fungsi bernama pada contoh kita nyaris sama cepatnya.
nargin: Validasi dan Default di Awal #
Aturannya: letakkan pemeriksaan argumen dan nilai default di baris-baris pertama fungsi, lalu short-circuit — validasi selesai sebelum komputasi berat dimulai. Mekanismenya: nargin melaporkan jumlah argumen yang benar-benar dikirim pemanggil, sehingga cabang default adalah if murah sekali jalan, bukan validasi mahal per elemen. Polanya pada fungsi diskon:
function bayar = diskon(harga, persen)
% nargin < 2 berarti pemanggil tidak mengirim persen: pakai default
if nargin < 2
persen = 10;
end
bayar = harga - harga * persen / 100;
endPemanggilannya dan hasilnya:
fprintf('diskon(200000) = %d\n', diskon(200000));
fprintf('diskon(200000, 25) = %d\n\n', diskon(200000, 25));Output yang Dihasilkan:
diskon(200000) = 180000
diskon(200000, 25) = 150000Output, Copy-on-Write, dan Pola x = f(x) #
Fungsi menerima data lewat pass by value yang diimplementasikan sebagai copy-on-write (rekap dari chapter Memory Usage): argumen hanya benar-benar tersalin saat fungsi memodifikasi nilai tersebut. Konsekuensinya dua arah. Pertama, fungsi yang hanya membaca input besar gratis dari mekanisme ini. Kedua, fungsi yang mengubah input memicu salinan penuh pada modifikasi pertama — reuse buffer via in-place yang berlaku pada script tidak bisa kita andalkan di dalam fungsi secara eksplisit.
Pola yang tetap berguna adalah x = f(x): bila variabel yang sama menjadi input dan output, MATLAB berpeluang menghindari salinan karena pemanggil lama sudah tidak memakai nilai sebelumnya. Peluang ini adalah optimasi internal — bukan jaminan yang boleh diandalkan untuk kebenaran program — namun kebiasaan x = bersihkan(x) tidak pernah merugikan.
Persistent Cache untuk Komputasi Mahal Berulang #
Aturannya: bila sebuah fungsi menghitung ulang hasil yang identik dari panggilan ke panggilan, simpan hasilnya dengan persistent dan hitung hanya saat cache kosong. Mekanismenya: variabel persistent bertahan di antara pemanggilan fungsi yang sama (tidak ikut ter-reset per panggilan seperti variabel biasa), sehingga panggilan kedua dan seterusnya melompat langsung ke hasil tersimpan. Demonstrasinya pada komputasiMahal yang meringkas 200000 panggilan sqrt:
function hasil = komputasiMahal()
% komputasi mahal yang hasilnya tidak berubah antar panggilan
persistent tersimpan
if isempty(tersimpan)
total = 0;
for k = 1:200000
total = total + sqrt(k);
end
tersimpan = total; % simpan sekali untuk semua panggilan berikutnya
end
hasil = tersimpan;
endTiga pemanggilan berturut-turutnya:
tic; r1 = komputasiMahal(); t1 = toc;
tic; r2 = komputasiMahal(); t2 = toc;
tic; r3 = komputasiMahal(); t3 = toc;
fprintf('cache : panggilan-1 %.4f s | panggilan-2 %.6f s | panggilan-3 %.6f s\n', t1, t2, t3);
fprintf('hasil konsisten: %d\n', isequal(r1, r2) && isequal(r2, r3));Output yang Dihasilkan:
cache : panggilan-1 0.3507 s | panggilan-2 0.000042 s | panggilan-3 0.000009 s
hasil konsisten: 1Ringkasan #
Pengukuran kita menegaskan tiga angka: memanggil fungsi trivial per elemen 3x lebih lambat dari inline, satu panggilan array 666x lebih cepat dari loop per elemen, dan cache persistent memangkas panggilan berikutnya dari 0.3507 s ke sekitar 10 mikrodetik. Bentuk fungsinya ikut menentukan: fungsi murni tanpa global, validasi nargin di awal, API array-in array-out, dan pola x = f(x) yang bersahabat dengan copy-on-write. Setelah single-thread dioptimalkan maksimal, sumber daya berikutnya adalah banyak core — pintu masuknya di chapter Pengenalan Parallel Computing.