Form Load Profiler: High Precision Timer
Pada beberapa kali kesempatan, saya menyajikan bagaimana mengukur waktu tayang (load-time) suatu form dari form tersebut di buat hingga benar – benar tampil dan siap digunakan. Beberapa variasi sudah saya sajikan, mulai dari pengukuran form tunggal hingga banyak form dengan menggunakan teknik interposer class maupun class helper. Ketiga variasi artikel tersebut menggunakan metode pengukuran waktu yang sama, yaitu menggunakan fungsi GetTickCount. Fungsi GetTickCount memiliki ketelitian hingga hitungan mili-detik (ms). Untuk kebutuhan secara umum, GetTickCount sudah mencukupi, namun adakalanya pengukuran yang dilakukan membutuhkan ketelitian yang lebih, misalnya dalam nano-detik. Disinilah GetTickCount tidak bisa memenuhinya.
Windows menyediakan 2 (dua) API yang berkaitan dengan pengukuran dengan ketelitian tinggi, yaitu QueryPerformanceCounter dan QueryPerformanceFrequency. Kedua fungsi ini digunakan saling melengkapi.
BOOL QueryPerformanceCounter(
LARGE_INTEGER *lpPerformanceCount // address of current counter value
);Parameters
lpPerformanceCount
Points to a variable that the function sets, in counts, to the current performance-counter value. If the installed hardware does not support a high-resolution performance counter, this parameter can be to zero.
Return Values
If the installed hardware supports a high-resolution performance counter, the return value is nonzero.
If the installed hardware does not support a high-resolution performance counter, the return value is zero.
BOOL QueryPerformanceFrequency(
LARGE_INTEGER *lpFrequency // address of current frequency
);Parameters
lpFrequency
Points to a variable that the function sets, in counts per second, to the current performance-counter frequency. If the installed hardware does not support a high-resolution performance counter, this parameter can be to zero.
Return Values
If the installed hardware supports a high-resolution performance counter, the return value is nonzero.
If the installed hardware does not support a high-resolution performance counter, the return value is zero.
Dari kutipan diatas, sudah jelas bahwa QueryPerformanceCounter digunakan untuk menampung nilai pencacah / penghitung pada saat API tersebut dipanggil, sedangkan QueryPerformanceFrequency digunakan untuk mendapatkan besaran frekuensi dari pencacah tersebut, berapa nilai cacah yang dihasilkan dalam 1 (satu) detik.
Sehingga untuk mendapatkan nilai pengukuran adalah dengan menghitung nilai cacah setelah pengukuran dikurangi dengan nilai cacah sebelum pengukuran. Nilai tersebut kemudian dibagi dengan frekuensi. Nilai ukur yang didapat adalah dalam satuan detik, untuk mengkonversi dalam satuan lain tinggal disesuaikan. Misalnya untuk dihitung dalam satuan mili-detik maka nilai tersebut dikalikan dengan 1000.
FElapsedTime := (FEnd - FStart) / FFrequency * 1000;
Integrasi ke Kode
Sekarang saatnya melakukan konversi dari penggunaan API GetTickCount ke QueryPerformanceCounter dan QueryPerformanceFrequency. Disini saya menggunakan kode sumber yang menggunakan teknik interposer class. Untuk penggunakan teknik class helper silahkan disesuaikan sendiri sebagai latihan.
Pertama, deklarasi variabel FStart dan FEnd harus diubah tipe datanya dari Cardinal menjadi Int64. Mengapa Int64? Karena nilai yang ditampung adalah sangat besar mengingat ketelitian presisi yang dihasilkan sangat tinggi, sehingga dibutuhkan tipe data yang lebih besar dari Cardinal (0..4294967295), yaitu Int64 atau LargeInt (-2^63..2^63-1). Kemudian tambahkan variabel untuk menyimpan nilai frekuensi dari pencacah tersebut, yaitu FFrequency, dengan tipe data Int64. Selanjutnya tambahkan variabel untuk menampung nilai hasil pengukuran, yaitu FElapsedTime, dengan tipe data Double.
protected FStart, FEnd : Int64; FFrequency : Int64; FElapsedTime : Double;
Perubahan kedua adalah pada constructor Create. Disini perlu dilakukan pemanggilan fungsi QueryPerformanceFrequency untuk mengetahui frekuensi nilai pencacah.
constructor TForm.Create(AOwner: TComponent); begin QueryPerformanceFrequency(FFrequency); QueryPerformanceCounter(FStart); inherited; end;
Ketiga, modifikasi method Activate, dan menambahkan perhitungan waktu ukur dalam 1 (satu) mili-detik.
procedure TForm.Activate; begin inherited; QueryPerformanceCounter(FEnd); FElapsedTime := (FEnd - FStart) / FFrequency * 1000; end;
Modifikasi keempat adalah modifikasi pada unit – unit lain yang menggunakan unit MultipleInterposerClassHP.pas. Bagian yang diubah adalah cara penyampaian pesan informasi besarnya waktu tayang. Jika sebelumnya menggunakan fungsi IntToStr karena waktu ukur berbasis pada tipe data Cardinal, maka sekarang menjadi FloatToStr karena waktu ukur, yaitu variabel FElapsedTime bertipe data Double. Sebagai contoh unit yang dimodifikasi adalah unit MultipleMainFormUnit.pas.
procedure TMainForm.btnShowResultClick(Sender: TObject); begin lblLoadTime.Caption := ' Form Load Time: ' + FloatToStr(FElapsedTime) + ' ms'; btnShowResult.Enabled := False; end;
Sehingga rangkaian kode MultipleInterposerClassHP menjadi sebagai berikut:
{----------------------------------------------------------------------------- The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/MPL-1.1.html Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for the specific language governing rights and limitations under the License. The Original Code is: MultipleInterposerClassHP.pas, released on 2008-09-30 The Initial Developer of the Original Code is Bayu Prasetio Portions created by Bayu Prasetio are Copyright (C) 2008 Bayu Prasetio. All Rights Reserved. -----------------------------------------------------------------------------} unit MultipleInterposerClassHP; interface uses Classes, Forms; type TForm = class(Forms.TForm) protected FStart, FEnd : Int64; FFrequency : Int64; FElapsedTime : Double; public { Public declarations } constructor Create(AOwner: TComponent); override; procedure Activate; override; end; implementation { TForm } uses Windows; procedure TForm.Activate; begin inherited; QueryPerformanceCounter(FEnd); FElapsedTime := (FEnd - FStart) / FFrequency * 1000; end; constructor TForm.Create(AOwner: TComponent); begin QueryPerformanceFrequency(FFrequency); QueryPerformanceCounter(FStart); inherited; end; end.
Dan sebagai contoh MultipleMainFormUnit.pas menjadi:
{----------------------------------------------------------------------------- The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/MPL-1.1.html Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for the specific language governing rights and limitations under the License. The Original Code is: MultipleMainFormUnit.pas, released on 2008-09-30 The Initial Developer of the Original Code is Bayu Prasetio Portions created by Bayu Prasetio are Copyright (C) 2008 Bayu Prasetio. All Rights Reserved. -----------------------------------------------------------------------------} unit MultipleMainFormUnit; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ComCtrls, MultipleInterposerClassHP; type TMainForm = class(TForm) btnShowResult: TButton; mmoLegend: TMemo; stbMain: TStatusBar; btnLoadSecondary: TButton; lblLoadTime: TLabel; btnLoadAnotherForm: TButton; procedure btnShowResultClick(Sender: TObject); procedure btnLoadSecondaryClick(Sender: TObject); procedure btnLoadAnotherFormClick(Sender: TObject); private { Private declarations } public { Public declarations } end; var MainForm: TMainForm; implementation {$R *.dfm} { TSingleForm } uses MultipleSecondaryFormUnit, MultipleAnotherFormUnit; procedure TMainForm.btnLoadAnotherFormClick(Sender: TObject); begin AnotherForm := TAnotherForm.Create(nil); AnotherForm.ShowModal; AnotherForm.Free; end; procedure TMainForm.btnLoadSecondaryClick(Sender: TObject); begin SecondaryForm := TSecondaryForm.Create(nil); SecondaryForm.ShowModal; SecondaryForm.Free; end; procedure TMainForm.btnShowResultClick(Sender: TObject); begin lblLoadTime.Caption := ' Form Load Time: ' + FloatToStr(FElapsedTime) + ' ms'; btnShowResult.Enabled := False; end; end.
Selengkapnya mengenai demo ini dapat di-unduh di sini: Source Code.