Arsip: tickers: pentium counter


by _aa_ in Articles more 13 years ago 2653
Ingin mengenal rdtsc, pentium high-resolution performance counter?
Silahkan mencermati informasi berikut:

by: aa A.BASIC cpu kelas pentium keatas mempunyai pencacah waktu internal selebar 64-bit (quadword) yang dapat dipanggil dengan instruksi rdtsc (Read TimeStamp Counter); berguna terutama untuk benchmarking dan pengukuran performansi dengan ketepatan tinggi (penggunaan umum lainnya adalah sebagai input-seed untuk random-generator, atau sebagai fungsi primitif perintah uptime); QueryPerformanceCounter-nya Windows API sebenarnya juga identik dengan rdtsc, bahkan tanpa overheadnya Windows, pemanggilan langsung bisa memberikan granularitas yang lebih halus, sekitar 1/10-nya. (nilai-nilai counter ini masih berupa clock-cycles, kita perlu menggunakan fungsi QueryPerformanceFrequency untuk konversi menjadi hitungan detik). contoh code:


const
  _rdtsc =  $310F; // compatibilitas dengan delphi 5 kebawah
{ menggunakan rdtsc sangat sederhana, karena hasil perintah rdtsc }
{ disimpan di edx:eax, di delphi langsung menjadi nilai int64     }
function ticks: int64; overload asm dw _rdtsc end;
// untuk delphi 6 keatas perintah rdtsc sudah dikenal oleh kompiler:
//   function ticks: int64; overload asm rdtsc end;
{ kecuali dalam perintah uptime, umumnya rdtsc dipanggil }
{ lebih dari sekali untuk dihitung selisihnya            }
function ticks(const oldtick: int64): int64; overload;
asm dw _rdtsc
  sub eax,[oldtick+0].dword
  sbb edx,[oldtick+4].dword
end;
fungsi ticks dengan parameter seperti di atas sebetulnya tidak terlalu berguna, disertakan di sini hanya sebagai kelengkapan dan contoh. dalam prakteknya, langsung saja gunakan variabel seperti berikut ini:

procedure something;
var
  tic: int64;
begin
  tic := ticks;
  { // bagian code yang akan diukur waktu prosesnya
    ...
  }
  tic := ticks - tic;
  showmessage('speed: ' + inttoStr(tic) + ' clocks');
end;
B. INTERMEDIATE1 seperti telah disebut diatas, hitungan rdtsc masih berupa clock-cycles, di Windows telah tersedia fungsi QueryPerformanceFrequency yang merupakan cacah hitungan per-detik dari cpu (di pc saya: 3010690000 cycles per-detik, sesuai dengan cpu-speed: 3.0GHz);

{ prototype dibawah ini telah dideklarasikan di unit Windows   }
{ tapi memasukkan seluruh unit Windows hanya untuk menggunakan }
{ sebuah fungsi saja, sangatlah tidak efisien.                 }
{ disini nama QueryPerformanceFrequency diganti menjadi GetTicksHz }
function GetTicksHz(var lpFrequency: int64): longbool; stdcall;
  external 'kernel32.dll' name 'QueryPerformanceFrequency';
juga perlu diingat bahwa rdtsc menghasilkan nilai unsigned int64 (tidak ada nilai negatif), kita harus menangani nilai negatif secara manual karena FPU (juga delphi) hanya mengakomodasi signed int64. nilai negatif int64 artinya output rdtsc >= $8000000000000000, atau dengan cpu-speed 3.0G seperti diatas, akan terjadi setelah kurang lebih... 97 tahun. (bandingkan dengan fungsi getTickCount yang berulang setiap 49.7 hari) dan karena nilai negatif akan saling meng-offset, kesalahan hitung hanya akan timbul jika batas negatif-positif (MaxInt64 dan 0) 'kebetulan' terlewati antara dua pemanggilan rdtsc. bagaimanapun juga, sebagai perfeksionis, kita akan tetap tangani hal itu :) (meski sangat kecil, kemungkinan rdtsc negatif tetaplah ada, karena nilai counter bisa diset/reset oleh sistem, baik sengaja atau tidak. supaya lengkap, disini disertakan juga pengetesan overflow tsb., dari hasil pengujian, lebih lambat sekitar 10-20% dibanding tanpa overflow-test). selanjutnya, analog dengan fungsi ticks berdasarkan clock-cycle diatas, kita buat juga fungsi tictac yang menghitung dalam satuan mili-detik. perhatian! meskipun param-nya bertipe sama (int64), jangan mencampur-adukkan hasil fungsi ticks dan tictac (terdapat perbedaan skala sebesar ticks_Hz).

const
  ticks_hz: double = 0;     // nilai frekuensi (clock-cycles per-detik)
  _1K: single = 1000;       // pembagi millisecond
  _40X = int64(1) shl 62;   // $4000000000000000 atau 2^62
  _80X = $8000000000000000; // jangan gunakan, nilai ini akan berarti NEGATIF
  _100e = _4X  4.0;        // positif 2^64 dalam format float
  Nfix: array[-1..0] of single = (_100e, 0); // penyesuaian negatif
function tictac1: int64; overload; 
asm dw _rdtsc;
  // rdtsc harus dipanggil pertama kali agar tidak mengganggu hitungan
  push edx; push eax;       // hasil rdtsc langsung disimpan
  mov eax,dword[ticks_hz];  // ambil nilai frekuensi
  mov ecx,dword[ticks_hz+4];
  sar edx,31                // jika negatif edx = -1; jika positif edx = 0
  or eax,ecx; jnz @@Count;  // sudah berisi atau masih kosong?
    push offset ticks_hz    // address ticks_hz sebagai var lpFrequency
                            // (stack otomatis dibersihkan oleh stdcall)
    call GetTicksHz;        // QueryPerformanceFrequency
    fild qword[ticks_hz]    // ambil sebagai integer
    fstp ticks_hz           // simpan lagi sebagai floating point
@@Count: // disini ticks_hz sudah berisi nilai    :{FPU Stack}
  fild [esp].qword;         // nilai rdtsc        :{empty} => {T}
  fadd dword[edx 4+Nfix+4]  // penyesuaian 2-s complement; jika negatif, +2^64
  fld ticks_hz;             // nilai frekuensi    :{T}     => {Hz,T}
  fdiv; // = fdivp st(1)    // T / Hz             :{Hz,T}  => {Hz/t} as {t}
  fmul _1K;                 // T / Hz  1000      :{t}     => {t 1000} as {t0}
  fistp qword ptr esp+0     // simpan ke stack    :{t0}    => {empty}
  pop eax; pop edx;         // Result
end;
function tictac1(const oldtick: int64): int64; overload;
asm dw _rdtsc;
  push edx; push eax;       // hasil rdtsc langsung disimpan
  mov eax,dword[ticks_hz];  // ambil nilai frekuensi
  mov ecx,dword[ticks_hz+4];
  sar edx,31                // jika negatif edx = -1; jika positif edx = 0
  or eax,ecx; jnz @@Count;  // sudah berisi atau masih kosong?
    push offset ticks_hz    // address ticks_hz sebagai var lpFrequency
                            // (stack otomatis dibersihkan oleh stdcall)
    call GetTicksHz;        // QueryPerformanceFrequency
    fild qword[ticks_hz]    // ambil sebagai integer
    fstp ticks_hz           // simpan lagi sebagai floating point
@@Count: // disini ticks_hz sudah berisi nilai    :{FPU Stack}
  fild oldtick;             // counter sebelumnya :{empty}   => {t0}
  fild [esp].qword;         // nilai rdtsc        :{t0}      => {T,t0}
  fadd dword[edx4+Nfix+4]  // penyesuaian 2-s complement; jika negatif, +2^64
  fld ticks_hz;             // nilai frekuensi    :{T,t0}    => {Hz,T,t0}
  fdiv;  // = fdivp st(1)   // T / Hz             :{Hz,T,t0} => {T/Hz,t0} as {t,t0}
  fmul _1K;                 // T / Hz   1000      :{t,t0}    => {t*1000,t0} as {t1,t0}
  fsubr; // = fsubrp st(1)  // t1 - t0            :{t1,t0}   => {t1-t0} as {new t0}
  fistp qword ptr esp+0     // simpan ke stack    :{t0}    => {empty}
  pop eax; pop edx;         // Result
end;
C. INTERMEDIATE2 Jika kita amati, code diatas bisa dioptimalkan dengan menyederhanakan pembagian dan perkalian dalam konversi, menyusun ulang instruksi, merotasi register fpu agar tidak terjadi bottleneck, serta menghilangkan overflow check yang terjadi setiap 97 tahun sekali (itupun muncul kalau pas kebetulan sedang sial saja).

function tictac2: int64; overload;
asm
  mov ecx,ticks_hz.dword;
  test ecx,ecx; jnz @@Count
    push offset ticks_hz; call GetTicksHz;
    fild qword[ticks_hz];
    fdivr _1K;              // _1K / ticks_hz
    fstp ticks_hz;          // simpan sebagai floating point
@@Count: dw _rdtsc;
  push edx; push eax;       // simpan ke stack
  fild [esp].qword;         // {empty} => {T}
  fmul ticks_hz             // {T} => {Tz}
  fistp qword ptr esp+0     // simpan ke stack => {empty}
  pop eax; pop edx          // Result
end;
function tictac2(const oldtick: int64): int64; overload;
asm
  mov ecx,ticks_hz.dword;
  test ecx,ecx; jnz @@Count
    push offset ticks_hz; call GetTicksHz;
    fild qword[ticks_hz];
    fdivr _1K;              // _1K / ticks_hz
    fstp ticks_hz;          // simpan sebagai floating point
@@Count: dw _rdtsc;
  push edx; push eax;       // simpan ke stack
  fild [esp].qword;         // {empty} => {T}
  fild oldtick;             // {T} => {t0,T}
  fxch; fmul ticks_hz;      // {t0,T} => {T,t0} => {Ts,t0}
  fxch; fsubr;              // {Ts,t0} => {t0,Ts} => {Ts-t0} as {new t0}
  fistp qword[esp]          // simpan ke stack => {empty}
  pop eax; pop edx;         // Result
end;
operasi pembagian dengan integer hampir selalu akan mengurangi ketelitian, sebagai alternatif, ataupun sekedar contoh, kita buat juga fungsi ticked, mirip dengan fungsi tictac diatas, bedanya disini param/resultnya bertipe double dan kita perhalus resolusinya menjadi micro-second.

const
  _1M: single = 1e6;        // pembagi microsecond
function ticked: double; overload;
asm
  mov ecx,ticks_hz.dword;
  test ecx,ecx; jnz @@Count
    push offset ticks_hz; call GetTicksHz;
    fild qword[ticks_hz];
    fdivr _1M;              // _1M / ticks_hz
    fstp ticks_hz;          // simpan sebagai floating point
@@Count: dw _rdtsc;
  push edx; push eax;       // simpan ke stack
  fild [esp].qword;         // {empty} => {T}
  add esp,8;                // kembalikan stack
  fmul ticks_hz             // {T} => {Tz}
  fstp Result               // simpan hasil
end;
{ perintah uptime seperti di unix, fungsi utamanya tidak lebih dari ini }
function uptime: double; overload;
begin
  ticked;
end;
function ticked(const oldtick: double): double; overload;
asm
  mov ecx,ticks_hz.dword;
  test ecx,ecx; jnz @@Count
    push offset ticks_hz; call GetTicksHz;
    fild qword[ticks_hz];
    fdivr _1M;              // _1M / ticks_hz
    fstp ticks_hz;          // simpan sebagai floating point
@@Count: dw _rdtsc;
  push edx; push eax;       // simpan ke stack
  fild [esp].qword;         // {empty} => {T}
  add esp,8;                // kembalikan stack
  fild oldtick;             // {T} => {t0,T}
  fxch; fmul ticks_hz;      // {t0,T} => {T,t0} => {Ts,t0}
  fxch; fsubr;              // {Ts,t0} => {t0,Ts} => {Ts-t0} as {new t0}
  fstp Result               // simpan hasil
end;
function ticked ini bisa diganti tipe param/result-nya dengan tipe extended tanpa mengubah sama sekali code program selanjutnya, tapi (hampir) tidak ada pengaruhnya, karena akurasi counter tidak akan lebih dari cpu-ticks, yang berarti masih dibawah tipe double. malah bikin lambat, karena akses extended (tbyte) 3x lebih lambat dari tipe double (qword); D. ADVANCED prosesor adakalanya menjalankan instruksi tidak persis sama urutannya seperti yang tertulis di program (fitur out-of-order exec di P2 keatas). untuk mempertajam akurasi pengukuran, instruksi serial perlu disisipkan diantara instruksi yang akan diukur (umumnya dipakai instruksi cpuid). masalahnya adalah, kebanyakan instruksi serial mempunyai clock-cycle atau latency relatif tinggi, untuk pengukuran yang sangat-sangat seksama (mis. mengukur sebuah instruksi cepat seperti SHL atau INC saja), sangat mungkin nilainya mempengaruhi pengukuran, sehingga perlu dihitung secara khusus dan dikurangkan dari total cycle (termasuk juga ekses clocks akibat CALL/RET pemanggilan fungsi). untuk pengukuran sebuah perintah high-level language seperti C/pascal (apalagi basic atau java) yang biasanya tersusun dari puluhan sampai ratusan instruksi asm, hal ini bisa diabaikan. cat. dalam multitasking os seperti windows, task-switch yang terjadi saat pemanggilan fungsi ticker akan merusak semuanya, oleh karena itu sebaiknya antara tick1 dan tick2 yang akan diukur, dibungkus dalam thread-priority real-time. juga perlu diketahui bahwa dalam multi-processor system, pemanggilan rdtsc adalah per-cpu (meski tidak terlalu masalah selama cacah counter tidak diset manual berbeda, karena semua cpu akan restart pada waktu yang (nyaris) bersamaan).

function __getticks:int64;
const
  _cpuidrdtsc = $310FA20F; // db 0fh,0a2h,0fh,031h
  cpuid_time: integer = 0;
  slack = 5; // approx. excess cycles at 1st-call
asm  
  push ebx; dd _cpuidrdtsc; // cpuid mengubah ebx!
  mov ebx,cpuid_time;
  test ebx,ebx; jz @@init;  // branch mispredict at 1st & 2nd call
  xor ecx,ecx; sub eax,ebx;
  setb cl; pop ebx;
  sub edx,ecx; ret;
@@init:
  ;//push offset ticks_hz; call GetTicksHz;
  ;//fild qword ptr ticks_hz;
  ;//fdivr _1M; fstp ticks_hz
  push esi; push 3; pop esi; jmp @@Loop
  @@rdtsc: xor eax,eax; dd _cpuidrdtsc; ret;
  @@Loop: call @@rdtsc; dec esi; jnl @@Loop;
  xor esi,eax; call @@rdtsc;
  add esi,eax; add eax,slack; 
  mov cpuid_time,esi; pop esi;
  adc edx,0; pop ebx;
end;
type r64 = packed record
  Lo, Hi: integer;
end;
{
  function tictacs: int64; overload;
  asm
    call __getticks; //push edx; push eax;
    ;//fild [esp].r64; fmul ticks_hz
    ;//fistp [esp].r64;
    ;//pop eax; pop edx;
  end;
}
function tictacs(const previoustick: int64): int64; overload;
asm
  call __getticks; //push edx; push eax;
  ;//fild previoustick_microseconds;
  ;//fild [esp].r64;
  ;//fmul ticks_hz; 
  ;//fsubr; fistp [esp].r64;
  ;//pop eax; pop edx;
  mov ecx,previoustick.r64.hi;
  sub eax,previoustick.r64.lo;
  sbb edx,ecx;
end;
sekian dulu, semoga bermanfaat. aa, http://www.google.com/search?q=aa+delphi
Local Business Directory, Search Engine Submission & SEO Tools FreeWebSubmission.com SonicRun.com