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:
function TFields.FieldByName(const FieldName: WideString): TField; begin Result := FindField(FieldName); if Result = nil then DatabaseErrorFmt(SFieldNotFound, [FieldName], DataSet); end;
Jika ditelusuri, fungsi FindField adalah sebagai berikut:
function TFields.FindField(const FieldName: WideString): TField; var I: Integer; begin for I := 0 to FList.Count - 1 do begin Result := FList.Items[I]; if WideCompareText(Result.FFieldName, FieldName) = 0 then Exit; end; Result := nil; 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:
procedure TForm2.btnMethod1Click(Sender: TObject); var A, B : Double; FFreq : Int64; FStartCounter : Int64; FStopCounter : Int64; I : Integer; begin MyConnection1.Connected := True; MyQuery1.Active := True; A := 0; B := 0; QueryPerformanceFrequency(FFreq); QueryPerformanceCounter(FStartCounter); for I := 0 to 1000 do begin MyQuery1.First; while not MyQuery1.Eof do begin A := MyQuery1.FieldByName('jumlah').AsFloat; B := MyQuery1.FieldByName('jumlahnetto').AsFloat; MyQuery1.Next; end; end; QueryPerformanceCounter(FStopCounter); Label1.Caption := FloatToStr((FStopCounter - FStartCounter) / FFreq); MyQuery1.Active := False; MyConnection1.Connected := False; end;
Hasil dari eksekusi rutin tersebut adalah 117,7975 detik.

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:
procedure TForm2.btnMethod2Click(Sender: TObject); var A, B : Double; FFreq : Int64; FStartCounter : Int64; FStopCounter : Int64; I : Integer; begin MyConnection1.Connected := True; MyQuery1.Active := True; A := 0; B := 0; QueryPerformanceFrequency(FFreq); QueryPerformanceCounter(FStartCounter); for I := 0 to 1000 do begin MyQuery1.First; while not MyQuery1.Eof do begin A := MyQuery1.Fields[12].AsFloat; B := MyQuery1.Fields[13].AsFloat; MyQuery1.Next; end; end; QueryPerformanceCounter(FStopCounter); Label2.Caption := FloatToStr((FStopCounter - FStartCounter) / FFreq); MyQuery1.Active := False; MyConnection1.Connected := False; end;
Hasil dari eksekusi rutin tersebut adalah 27,1231 detik. Sangat manjur bukan ?

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:
procedure TForm2.btnMethod3Click(Sender: TObject); var A, B : Double; FFreq : Int64; FStartCounter : Int64; FStopCounter : Int64; I : Integer; AField : TField; AField2 : TField; begin MyConnection1.Connected := True; MyQuery1.Active := True; A := 0; B := 0; QueryPerformanceFrequency(FFreq); QueryPerformanceCounter(FStartCounter); AField := MyQuery1.FieldByName('jumlah'); AField2 := MyQuery1.FieldByName('jumlahnetto'); for I := 0 to 1000 do begin MyQuery1.First; while not MyQuery1.Eof do begin A := AField.AsFloat; B := AField2.AsFloat; MyQuery1.Next; end; end; QueryPerformanceCounter(FStopCounter); Label3.Caption := FloatToStr((FStopCounter - FStartCounter) / FFreq); MyQuery1.Active := False; MyConnection1.Connected := False; end;
Hasil dari eksekusi rutin tersebut adalah 26,9201 detik.

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.
procedure TForm2.btnMehtod4Click(Sender: TObject); var A, B : Double; FFreq : Int64; FStartCounter : Int64; FStopCounter : Int64; I : Integer; AField : array [1..2] of TField; begin MyConnection1.Connected := True; MyQuery1.Active := True; A := 0; B := 0; QueryPerformanceFrequency(FFreq); QueryPerformanceCounter(FStartCounter); AField[1] := MyQuery1.FieldByName('jumlah'); AField[2] := MyQuery1.FieldByName('jumlahnetto'); for I := 0 to 1000 do begin MyQuery1.First; while not MyQuery1.Eof do begin A := AField[1].AsFloat; B := AField[2].AsFloat; MyQuery1.Next; end; end; QueryPerformanceCounter(FStopCounter); Label4.Caption := FloatToStr((FStopCounter - FStartCounter) / FFreq); MyQuery1.Active := False; MyConnection1.Connected := False; 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
Sebagai latihan, silahkan mencoba dengan jumlah field yang lebih banyak dan bandingkan hasilnya. Adakah terjadi peningkatan kecepatan ? Silahkan cari tahu sendiri sebabnya.
Semoga bermanfaat.
waaaw … trnyata ada bedanya juga ya …
aku pake metode 2 (karena terbiasa) …
txs Mas Bayu (deLogic) atas ilmunya ..
Penggunaan metode 2 kayaknya agak susah, soalnya harus Hapal no urut fieldnya.
Mungkin bisa di atasi dengan membuat konstanta?
misalnya
const
NOMOR_REKENING 1
NAMA_DEBITUR 2
jadi kita bia bikin MyQuery1.Fields[NOMOR_REKENING].AsInteger;
Apakah juga bisa? tidak bikin lambat?
Bisa saja menggunakan konstanta sebagai representasi dari nomor urut / indeks field. Penggantian ini tidak mempunyai pengaruh pada saat proses / program dijalankan karena pada saat kompilasi kode, compiler mengubah konstanta yang dimaksud dengan nilai yang sesungguhnya.
Jadi misalkan pada baris kode tertulis MyQuery1.Fields[NOMOR_REKENING].AsInteger maka pada ssat kompilasi akan diubah menjadi MyQuery1.Fields[1].AsInteger.
artikel lama tapi ulasannya sangat hebat…. thx mas DeLogic
Salam Super untuk Pak Tulus dan terima kasih sudah sudi mampir dan berpartisipasi di blog yg sudah tidak pernah diupdate isi nya ini… (*sambil ngelus dada, beraneka macam rasa karena sudah gak sempat bikin tulisan lagi*) 🙂
Untuk koneksi di atas pakai komponen apa kang ?
Untuk koneksi di atas pakai komponen apa mas ?
CoreLab MySQL DAC, versinya saya gak ingat persis, mungkin versi 4.2 atau 5
Tx Bos, pencerahannya. Ini yang selama ini sy cari.
tapi kok sebagai deLogic di delphi.id g pernah di tampilin ulasan ini
Sy pake delphi +zeos+postgresql mengalami masalah performa ketika iterasi.
semoga ini bisa jadi jawaban yang selama ini aku cari.
Sekali lagi matur nuwun…
Eh jarang update mungkin kebanyakan order ya?
rekan cahyo,
semoga ulasan tersebut bermanfaat bagi Anda. Adapun mengapa ulasan ini tidak dibahas di delphi.id adalah karena memang tidak ada yg mengulasnya di sana, termasuk saya, 🙂
blognya jarang update ? iya saya belum sempat mengisi konten nya lagi karena berbagai hal.
*jadi malu, hehehhehe*
Terima kasih..
Oya, Pak, boleh request g? Harus boleh donk..he..he..maksa nih..
Saya ada kendala utk koneksi Client-server menggunakan WiFi kalo pas RTO
koneksi terputus tiba-tiba kemudian program tdk mau jalan.
Gimana caranya biar bisa biar bisa autoreconect?
sy menggunakan delphi+zeoslib+postgresql.
Tx sebelumnya.
broo..kenapa gak ditambahi myquery1.disablecontrols??
pantesan lama hehehe
Pada pengujian saya, saya tidak melakukan binding apapun control grid atau data aware terhadap object myquery. Fokus pengujian saya adalah bukan pada tayangan di data aware, tapi proses internal di dalamnya mengenai FieldByName. Jadi saya tidak perlu menggunakannya. Kalo anda melakukan binding ke kontrol visual / data aware, berarti, ya, Anda harus menambahkan DisableControls dan EnableControls.
oh gto, ya saya tampilkan ke grid jadi harus disablecontrols..
dah tak coba, dengan looping 10 sampai 100x, perbedaan dari fieldByName dan field hanya berkisar 1/3nya
perbedaan terbesar ketika jumlah looping besar, maka 1/3 dari fields[] tetapi tetap saja 1/3 dari fields[]
kesimpulan saya, terjadi perbedaan, tetapi untuk kasus tertentu misal harus membuat sebuah runtime dataset, terpaksa pakai fieldbyname.
DI sekarang sepi bro, ente jarang masuk kesono juga, lagi banyak job ya..
Pengaruh trik ini tentu saja tergantung dari frekuensi penggunaan dari FieldbyName tersebut. Ingat, saya menggarisbawahi pada penggunaan intensif, lihat paragraf pertama kalimat kedua. Jadi semakin intensif penggunaan FieldByName, pengaruh trik ini tentu akan sangat signifikan. 😉
Saya masih memantu DI dari jauh, saya masih berlangganan RSS Feed dari DI, tapi untuk ikut nimbrung sudah sangat jarang sekali. Ada beberapa thread, diskusi dan anggota yg menggunakan media FB untuk saling berinteraksi, Group Delphi Indonesia (DI) dan Pascal Indonesia (PI) ada lho di FB. Yah, itulah kemajuan teknologi yg tidak bisa kita pungkiri, semakin beragam media berinteraksi.
btw username Anda di DI apa ya?
ya, memang sudah disediakan dua fungsi by name dan field tergantung dari keperluan.
selama saya coba, tidak ada perbedaan perbandingannya, tetap fldbyname 1/3 lebih lambat.
oh, dah lama bgt Di sering error mulu jadinya juga males masuk neh.
heheh..siapa ya, ane dah lama masuk 2005.
wah boleh lah, nanti tak cari di fb klo lagi pada aktif, ane yg ada di fb masih maskofa yg di ym, bro syarif dan elvaefiana