Home > Delphi, Did You Know > 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:

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

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

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

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

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.

Advertisements
  1. March 13, 2009 at 10:46 am

    waaaw … trnyata ada bedanya juga ya …
    aku pake metode 2 (karena terbiasa) …

    txs Mas Bayu (deLogic) atas ilmunya ..

  2. A Rahman
    May 22, 2010 at 2:57 pm

    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?

    • May 23, 2010 at 4:13 am

      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.

  3. May 25, 2011 at 5:08 pm

    artikel lama tapi ulasannya sangat hebat…. thx mas DeLogic

    • May 26, 2011 at 10:26 am

      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*) 🙂

  4. August 27, 2011 at 2:53 pm

    Untuk koneksi di atas pakai komponen apa kang ?

  5. August 27, 2011 at 2:54 pm

    Untuk koneksi di atas pakai komponen apa mas ?

    • August 30, 2011 at 6:50 pm

      CoreLab MySQL DAC, versinya saya gak ingat persis, mungkin versi 4.2 atau 5

  6. cahyo
    September 18, 2011 at 12:08 pm

    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?

    • September 18, 2011 at 4:11 pm

      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..

  7. cahyo
    September 25, 2011 at 10:34 pm

    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.

  8. February 4, 2012 at 12:02 am

    broo..kenapa gak ditambahi myquery1.disablecontrols??
    pantesan lama hehehe

    • February 4, 2012 at 4:29 am

      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.

      • February 4, 2012 at 11:16 am

        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..

  9. February 4, 2012 at 7:03 pm

    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?

  10. February 4, 2012 at 10:14 pm

    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

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: