Kinerja

Mengukur waktu dengan profiler, mengelola memori, dan meninjau ulang vektorisasi.

Daftar isi

Kode yang benar belum tentu cukup cepat, dan kode yang lambat biasanya hanya lambat pada satu bagian kecil. Topik ini membahas cara menemukan bagian itu secara terukur: pencatatan durasi dengan tic dan toc, pengukuran per fungsi dengan profiler, tinjauan ulang vektorisasi dengan pembandingan hasil yang ketat, serta pengelolaan memori lewat pemahaman salinan data dan pembersihan variabel. Angka waktu tidak ditampilkan pada keluaran contoh karena berubah antar eksekusi dan antar komputer, sehingga contoh menampilkan kuantitas yang tetap, seperti banyak elemen dan banyak pemanggilan fungsi.

Mengukur durasi dengan tic dan toc#

Pasangan tic dan toc adalah pewaktu sederhana: tic menandai awal pengukuran, toc melaporkan durasi sejak tanda itu. Bentuk keluarannya berupa angka detik yang dapat disimpan pada variabel, sehingga durasi beberapa bagian kode dapat dibandingkan dalam satu sesi. Pengukuran bertingkat didukung lewat bentuk tAwal = tic dan toc(tAwal), sehingga beberapa pewaktu dapat hidup bersamaan tanpa saling menimpa. Pewaktu ini cocok untuk pertanyaan sempit seperti berapa lama satu perhitungan tertentu, tetapi tidak menunjukkan bagian mana dari program yang paling memakan waktu.

matlab tic dan toc dengan keluaran non-waktu
banyakTitik = 100000;
suhuSimulasi = 20 + 2 * sin((1:banyakTitik) / 10000);
tic;
puncak = max(suhuSimulasi);
durasiCatat = toc;
fprintf('titik diproses : %d\n', banyakTitik);
fprintf('puncak tercatat: %.4f\n', puncak);
fprintf('durasi > 0     : %d\n', durasiCatat > 0);
text Keluaran
titik diproses : 100000
puncak tercatat: 22.0000
durasi > 0     : 1

Blok di atas sengaja tidak mencetak isi durasiCatat. Seratus ribu titik diproses dan puncak simulasi tercetak 22.0000, sedikit di bawah nilai batasnya karena titik sampel tidak jatuh persis pada puncak sinus. Nilai durasiCatat berbeda pada tiap eksekusi karena bergantung pada beban komputer saat itu. Saat pengukuran dilakukan langsung di Command Window, nilai toc dicetak apa adanya. Yang bermakna adalah perbandingan antar pengukuran, bukan angka tunggal.

Profiler sebagai alat pengukuran#

Profiler mengukur program per fungsi, bukan sebagai satu blok. Di editor MATLAB, tombol Run and Time menjalankan berkas sekaligus merekam pemanggilan fungsi. Jendela profiler kemudian menampilkan tabel fungsi dengan kolom nama, banyak pemanggilan, dan total waktu, diurutkan dari yang paling memakan waktu. Memilih satu baris tabel membuka berkas fungsi itu dengan penanda pada baris-baris termahal, sehingga perbaikan diarahkan ke baris yang benar, bukan ke tebakan. Tabelnya juga dipakai untuk memastikan bahwa sebuah perubahan memang mempercepat, dengan membandingkan rekaman sebelum dan sesudah.

matlab MATLAB: membuka jendela profiler
% tidak-dijalankan
% Membuka jendela profiler tanpa menjalankan berkas. Tombol Run and Time
% menjalankan berkas lebih dahulu, lalu membuka jendela yang sama berisi
% hasil rekamannya.
profile viewer

Perekaman yang sama dapat dilakukan tanpa jendela, yaitu dengan profile on sebelum kode yang diukur dan profile off sesudahnya. Fungsi profile dengan masukan 'info' mengembalikan struktur berisi tabel fungsi hasil rekaman. Profil berikut mengukur fungsi rerataBergerak yang dijalankan sekali.

matlab rerataBergerak.m
function hasil = rerataBergerak(data, lebar)
% RERATABERGERAK menghitung rerata tiap jendela berukuran lebar.
%   RERATABERGERAK(data, lebar) memakai perulangan dengan praalokasi hasil.
%   Tanpa argumen, contoh [21 23 22 25] dengan lebar 2 dipakai.

if nargin < 2
  data = [21 23 22 25];
  lebar = 2;
end
banyak = numel(data) - lebar + 1;
hasil = zeros(1, banyak);
for k = 1:banyak
  hasil(k) = sum(data(k:k+lebar-1)) / lebar;
end
end
matlab Merekam profil satu fungsi
suhuMentah = [21 23 22 25 24 26 28 27 29 31 30 32];
lebarJendela = 4;
profile on;
hasilProfil = rerataBergerak(suhuMentah, lebarJendela);
profile off;
T = profile('info');
fprintf('field struktur : %s\n', strjoin(fieldnames(T)', ', '));
fprintf('entri tabel    : %d\n', numel(T.FunctionTable));
nama = {T.FunctionTable.FunctionName};
milikKode = nama(ismember(nama, {'rerataBergerak'}));
fprintf('milik kode     : %s\n', strjoin(milikKode, ', '));
pemanggilan = [T.FunctionTable(ismember(nama, {'rerataBergerak'})).NumCalls];
fprintf('pemanggilan    : %d\n', pemanggilan);
fprintf('elemen hasil   : %d\n', numel(hasilProfil));
text Keluaran
field struktur : FunctionTable, Hierarchical
entri tabel    : 12
milik kode     : rerataBergerak
pemanggilan    : 1
elemen hasil   : 9

Struktur itu memuat bidang FunctionTable sebagai tabel fungsi dan Hierarchical sebagai pohon pemanggilan. Tabelnya berisi dua belas entri pada rekaman ini karena lingkungan eksekusi GNU Octave yang dipakai untuk memverifikasi contoh juga mencatat operasi internal seperti penjumlahan per elemen. Tabel profiler MATLAB menyajikan daftar fungsi yang lebih terkurasi. Di kedua implementasi, fungsi milik kode tetap dapat dikenali: rerataBergerak terpanggil satu kali dan menghasilkan sembilan elemen, tanpa satu angka waktu pun yang perlu dicetak.

Meninjau ulang vektorisasi#

Vektorisasi patut ditinjau ulang setelah praalokasi terpasang, karena keduanya menutup sumber kelambatan yang berbeda. Tinjauannya berlangsung dalam dua versi yang menghitung hal yang sama: versi perulangan pada fungsi rerataBergerak dan versi tervektorisasi yang memakai cumsum sehingga jumlah berjalan dihitung sekali lalu diselisihkan antar posisi. Pembandingan yang jujur menuntut kedua versi dijalankan pada data dan lebar jendela yang sama, lalu hasilnya dibandingkan ketepatannya, karena versi yang lebih cepat tetap tidak boleh mengubah hasil.

matlab Perulangan versus tervektorisasi pada data yang sama
suhuMentah = [21 23 22 25 24 26 28 27 29 31 30 32];
lebarJendela = 4;
rataPerulangan = rerataBergerak(suhuMentah, lebarJendela);
jumlahBerjalan = [0, cumsum(suhuMentah)];
banyakJendela = numel(suhuMentah) - lebarJendela + 1;
rataTervektorisasi = (jumlahBerjalan(lebarJendela+1:end) - jumlahBerjalan(1:end-lebarJendela)) / lebarJendela;
fprintf('banyak jendela : %d\n', banyakJendela);
fprintf('perulangan     : %.4f %.4f\n', rataPerulangan(1), rataPerulangan(end));
fprintf('tervektorisasi : %.4f %.4f\n', rataTervektorisasi(1), rataTervektorisasi(end));
fprintf('selisih maksimum: %g\n', max(abs(rataPerulangan - rataTervektorisasi)));
text Keluaran
banyak jendela : 9
perulangan     : 22.7500 30.5000
tervektorisasi : 22.7500 30.5000
selisih maksimum: 0

Kedua versi menghasilkan sembilan nilai rerata yang identik hingga selisih maksimum nol. Durasi kedua versi sengaja tidak dicetak karena berubah antar eksekusi, dan pada data sekecil ini keduanya selesai dalam waktu yang tidak berarti. Keputusan memilih bentuk akhirnya dikembalikan ke profiler pada data nyata. Bila rerataBergerak muncul sebagai fungsi termahal, versi cumsum menjadi kandidat pengganti. Bila tidak, keterbacaan versi perulangan lebih berharga daripada percepatan yang tidak terukur.

Memori dan pembersihan variabel#

Penugasan array tidak serta-merta menggandakan memori. Setelah pernyataan salinan = asal, kedua nama membaca data yang sama, dan salinan nyata baru dibuat ketika salah satu nama diubah. Mekanisme ini lazim disebut salinan-ditulis-saat-diubah (copy-on-write), dan akibatnya penugasan array besar menjadi murah selama datanya tidak diubah. Ukuran memori suatu variabel dapat diperiksa secara terarah. Bentuk whos yang dipanggil dengan satu nama mengembalikan struktur berisi ukuran serta jumlah byte variabel itu saja, tanpa menampilkan seluruh workspace.

matlab Memeriksa ukuran variabel lalu membersihkannya
matriksBesar = zeros(1000);
infoMemori = whos('matriksBesar');
fprintf('%s: %d x %d, %d byte\n', infoMemori.name, infoMemori.size(1), infoMemori.size(2), infoMemori.bytes);
clear matriksBesar
fprintf('setelah clear: %d\n', numel(whos('matriksBesar')));
text Keluaran
matriksBesar: 1000 x 1000, 8000000 byte
setelah clear: 0

Seribu kali seribu elemen tipe double memakai delapan byte per elemen, sehingga totalnya delapan juta byte. Ketika variabel sebesar itu sudah tidak terpakai, pernyataan clear melepas memorinya sehingga ruang itu dapat dipakai perhitungan berikutnya, dan pemeriksaan whos sesudahnya tidak lagi menemukan variabel tersebut. Kebiasaan yang sehat adalah membersihkan array besar segera setelah tahap pemrosesannya selesai, terutama pada skrip panjang yang menyiapkan banyak hasil antara.

Kesalahan umum#

  • Mengoptimalkan kode yang belum benar. Percepatan pada hasil yang salah hanya mempercepat kesalahan, dan refaktor vektorisasi menuntut pemahaman penuh atas logika yang diganti. Pastikan hasil kedua versi identik lebih dahulu, seperti pembandingan dengan selisih maksimum pada bagian sebelumnya.
  • Memperbaiki bagian yang tidak termahal. Tanpa profiler, tebakan tentang bagian lambat sering meleset karena durasi total didominasi fungsi yang tidak dicurigai. Baca tabel profil sebelum menyentuh satu baris pun.
  • Membandingkan vektorisasi dengan versi yang tidak dipraalokasi. Versi array-membesar membayar biaya penyalinan berulang, sehingga perbandingannya mengukur praalokasi, bukan vektorisasi. Pasang praalokasi pada kedua versi sebelum membandingkan bentuk perhitungannya.

Ringkasan#

  • tic dan toc mengukur durasi satu bagian kode, dengan angka yang wajar dicetak hanya saat diukur langsung karena berubah antar eksekusi.
  • Profiler mengukur per fungsi melalui profile on, profile off, dan profile('info'), dengan FunctionTable sebagai tabel fungsi berisi nama dan banyak pemanggilan.
  • Tinjauan ulang vektorisasi membandingkan versi perulangan dan tervektorisasi pada data yang sama, dan keputusannya dikembalikan ke profil pada data nyata.
  • Penugasan array berbagi memori sampai salah satu diubah, dan clear melepas memori variabel besar yang sudah tidak terpakai.

Referensi#