Google
 

Thursday, July 30, 2009

Hindari FieldByName pada Penggunaan Intensif

Hindari FieldByName pada Penggunaan Intensif

Bagi Delphiers yang sering berinteraksi dengan database, tentu tidak asing dengan penggunaan FieldByName. Pada operasi akses field yang bersifat intensif, misalnya konversi data, penyalinan data dari satu database atau tabel ke database atau tabel lainnya, penggunaan FieldByName harus dihindari.

Untuk mencapai suatu field dengan menggunakan FieldByName, terlebih dahulu harus dilakukan pencarian alamat referensi field yang dimaksud dengan melakukan iterasi setiap field pada daftar field (FieldList) dari awal hingga akhir untuk mencocokkan namanya (FieldName). Proses iterasi akan berhenti ketika ditemukan nama field pada daftar field. Proses kemudian mengembalikan alamat referensi field yang dimaksud.

Sekilas, proses yang terjadi pada FieldByName ditunjukkan pada potongan kode berikut:

1.function TFields.FieldByName(const FieldName: WideString): TField;
2.begin
3. Result := FindField(FieldName);
4. if Result = nil then DatabaseErrorFmt(SFieldNotFound, [FieldName], DataSet);
5.end;

Jika ditelusuri, fungsi FindField adalah sebagai berikut:

01.function TFields.FindField(const FieldName: WideString): TField;
02.var
03. I: Integer;
04.begin
05. for I := 0 to FList.Count - 1 do
06. begin
07. Result := FList.Items[I];
08. if WideCompareText(Result.FFieldName, FieldName) = 0 then Exit;
09. end;
10. Result := nil;
11.end;

Oke, untuk lebih mantapnya, perlu kita buat sebuah rutin untuk mengukur bagaimana penggunaan FieldByName pada operasi yang intensif mengakses field. Perlu diingat bahwa kode yang dijadikan sebagai contoh dibuat sesederhana mungkin, hanya mengakses 2 field saja dan tidak ada operasi lain yang menyertainya seperti perhitungan, seleksi kondisi dan sebagainya. Pada keadaan sesungguhnya, bentuk operasi bisa jauh lebih kompleks dan bervariasi.

Berikut gambaran singkat mengenai data yang akan dijadikan bahan pengukuran
- Sistem Core2Duo 1.66GHz, 667MHz FSB, 2MB L2 Cache, 1GB RAM, dengan free ram > 256MB (ada program yg berjalan: Delphi2007, FF, EMS SQL Manager, Notepad++)
- Windows XP Professional SP3
- MySQL 5.1.21, akses lokal, konfigurasi standar
- Tabel dengan jenis InnoDB, jumlah record sekitar 29000-an
- Pengukuran menggunakan QueryPerformaceCounter & QueryPerformanceFrequency
- Sangat dimungkinkan terjadi proses caching pembacaan hasil proses sebelumnya. Untuk itu lakukan proses perhitungan dengan jeda waktu yang lama dan berkali - kali untuk meyakinkan nilainya.

Metode 1

Metode 1 menggunakan FieldByName seperti pada potongan kode berikut:

01.procedure TForm2.btnMethod1Click(Sender: TObject);
02.var
03. A, B : Double;
04. FFreq : Int64;
05. FStartCounter : Int64;
06. FStopCounter : Int64;
07. I : Integer;
08.begin
09. MyConnection1.Connected := True;
10. MyQuery1.Active := True;
11.
12. A := 0;
13. B := 0;
14. QueryPerformanceFrequency(FFreq);
15. QueryPerformanceCounter(FStartCounter);
16. for I := 0 to 1000 do
17. begin
18. MyQuery1.First;
19. while not MyQuery1.Eof do
20. begin
21. A := MyQuery1.FieldByName('jumlah').AsFloat;
22. B := MyQuery1.FieldByName('jumlahnetto').AsFloat;
23. MyQuery1.Next;
24. end;
25. end;
26. QueryPerformanceCounter(FStopCounter);
27.
28. Label1.Caption := FloatToStr((FStopCounter - FStartCounter) / FFreq);
29.
30. MyQuery1.Active := False;
31. MyConnection1.Connected := False;
32.end;

Hasil dari eksekusi rutin tersebut adalah 117,7975 detik.

Hasil Pengukuran Metode 1

Hasil Pengukuran Metode 1

Cukup lama bukan ? Lalu adakah teknik untuk mempersingkat operasi tersebut ? Tentu saja ada !

Metode 2

Teknik ini mungkin sudah banyak diketahui, namun mungkin jarang digunakan. Alih - alih menggunakan nama field untuk mencari field yang dikehendaki, teknik ini langsung mengakses alamat referensi dari field yang dikehendaki dengan memanfaatkan nomor indeks field tersebut dalam daftar field. Tentu saja untuk melakukannya harus diketahui terlebih dahulu nomor indeks yang tepat. Field pertama diakses melalui nomor indeks 0, field kedua diakses melalui nomor indeks 1, demikian seterusnya.

Salah satu sebab teknik ini jarang dilakukan adalah karena konsistensi nomor urut field. Jika misalnya terdapat perubahan susunan tabel atau perintah query, maka nomor urut field dimungkinkan tidak sesuai lagi. Dengan demikian harus dilakukan perubahan nomor urut pada kode sumber.

Disisi lain, teknik ini sangat sesuai apabila diterapkan pada aplikasi konversi / migrasi database / tabel dimana jumlah dan nama field sangat variatif. Disini, nama field tidak dapat dijadikan sebagai acuan. Yang perlu dilakukan hanyalah memberikan nomor indeks dari awal hingga akhir dan lakukan proses iterasi per record, dari field awal sampai akhir.

Berdasarkan kode metode 1, maka dilakukan perubahan seperti pada kode berikut:

01.procedure TForm2.btnMethod2Click(Sender: TObject);
02.var
03. A, B : Double;
04. FFreq : Int64;
05. FStartCounter : Int64;
06. FStopCounter : Int64;
07. I : Integer;
08.begin
09. MyConnection1.Connected := True;
10. MyQuery1.Active := True;
11.
12. A := 0;
13. B := 0;
14. QueryPerformanceFrequency(FFreq);
15. QueryPerformanceCounter(FStartCounter);
16. for I := 0 to 1000 do
17. begin
18. MyQuery1.First;
19. while not MyQuery1.Eof do
20. begin
21. A := MyQuery1.Fields[12].AsFloat;
22. B := MyQuery1.Fields[13].AsFloat;
23. MyQuery1.Next;
24. end;
25. end;
26. QueryPerformanceCounter(FStopCounter);
27.
28. Label2.Caption := FloatToStr((FStopCounter - FStartCounter) / FFreq);
29.
30. MyQuery1.Active := False;
31. MyConnection1.Connected := False;
32.end;

Hasil dari eksekusi rutin tersebut adalah 27,1231 detik. Sangat manjur bukan ?

Hasil Pengukuran Metode 2

Hasil Pengukuran Metode 2

Masih belum puas dengan hasil tersebut ? Simak teknik berikutnya.

Metode 3

Teknik ini lebih jarang lagi digunakan. Alih - alih melakukan penunjukan field yang dimaksud dengan memberikan nomor indeks yang sesuai pada setiap iterasi record dimana proses penunjukan field membutuhkan proses untuk mengakses kelas TField, teknik ini langsung mengakses kelas TField tersebut berada. Caranya adalah dengan terlebih dahulu mendapatkan alamat dimana kelas TField tersebut berada. Alamat tersebut kemudian di simpan pada variabel lokal yang akan digunakan untuk mengakses field tersebut secara langsung.

Berikut kode untuk metode 3:

01.procedure TForm2.btnMethod3Click(Sender: TObject);
02.var
03. A, B : Double;
04. FFreq : Int64;
05. FStartCounter : Int64;
06. FStopCounter : Int64;
07. I : Integer;
08. AField : TField;
09. AField2 : TField;
10.begin
11. MyConnection1.Connected := True;
12. MyQuery1.Active := True;
13.
14. A := 0;
15. B := 0;
16. QueryPerformanceFrequency(FFreq);
17. QueryPerformanceCounter(FStartCounter);
18. AField := MyQuery1.FieldByName('jumlah');
19. AField2 := MyQuery1.FieldByName('jumlahnetto');
20. for I := 0 to 1000 do
21. begin
22. MyQuery1.First;
23. while not MyQuery1.Eof do
24. begin
25. A := AField.AsFloat;
26. B := AField2.AsFloat;
27. MyQuery1.Next;
28. end;
29. end;
30. QueryPerformanceCounter(FStopCounter);
31.
32. Label3.Caption := FloatToStr((FStopCounter - FStartCounter) / FFreq);
33.
34. MyQuery1.Active := False;
35. MyConnection1.Connected := False;
36.end;

Hasil dari eksekusi rutin tersebut adalah 26,9201 detik.

Hasil Pengukuran Metode 3

Hasil Pengukuran Metode 3

Tentu saja masih ada celah perbaikan walaupun pengaruhnya mungkin tidak signifikan, tergantung dari kode Anda, yaitu dengan mengganti kode FieldByName menjadi Fields[x] dimana x adalah nomor indeks field yang dikehendaki.

Contoh kode diatas sangat intensif menggunakan variabel bertipe TField untuk menampung alamat referensi dari field yang dikehendaki. Lalu bagaimana apabila jumlah field yang diakses sangat banyak atau bahkan sangat bervariasi ? Bukankah sangat tidak efisien apabila mendeklarasikan semua variabel yang diperlukan ? Lalu adakah teknik untuk mensiasatinya ? Simak metode selanjutnya.

Metode 4

Salah satu teknik yang dapat digunakan adalah dengan menggunakan array dinamis. Namun untuk menyederhanakan pembahasan, pada contoh ini hanya akan mengunakan array statis.

01.procedure TForm2.btnMehtod4Click(Sender: TObject);
02.var
03. A, B : Double;
04. FFreq : Int64;
05. FStartCounter : Int64;
06. FStopCounter : Int64;
07. I : Integer;
08. AField : array [1..2] of TField;
09.begin
10. MyConnection1.Connected := True;
11. MyQuery1.Active := True;
12.
13. A := 0;
14. B := 0;
15. QueryPerformanceFrequency(FFreq);
16. QueryPerformanceCounter(FStartCounter);
17. AField[1] := MyQuery1.FieldByName('jumlah');
18. AField[2] := MyQuery1.FieldByName('jumlahnetto');
19. for I := 0 to 1000 do
20. begin
21. MyQuery1.First;
22. while not MyQuery1.Eof do
23. begin
24. A := AField[1].AsFloat;
25. B := AField[2].AsFloat;
26. MyQuery1.Next;
27. end;
28. end;
29. QueryPerformanceCounter(FStopCounter);
30.
31. Label4.Caption := FloatToStr((FStopCounter - FStartCounter) / FFreq);
32.
33. MyQuery1.Active := False;
34. MyConnection1.Connected := False;
35.end;

Sama seperti sebelumnya, tentu saja masih ada celah perbaikan walaupun pengaruhnya mungkin tidak signifikan, tergantung dari kode Anda, yaitu dengan mengganti kode FieldByName menjadi Fields[x] dimana x adalah nomor indeks field yang dikehendaki.

Hasil dari eksekusi rutin tersebut adalah 26,8862 detik.

Hasil Pengkuran Metode 4

Hasil Pengkuran Metode 4

Sebagai latihan, silahkan mencoba dengan jumlah field yang lebih banyak dan bandingkan hasilnya. Adakah terjadi peningkatan kecepatan ? Silahkan cari tahu sendiri sebabnya.

Semoga bermanfaat.

No comments: