unit uRDTSC;

//使用目的
// ・この関数は全体の何割を消費してるか知りたい
// ・この処理は現在何クロックを消費してるか知りたい
// という趣味の領域が目的です
//
//使用方法
//  測定したい処理全体の先頭で startRDTSC;
//  測定したいユニットの先頭で RDTSCst(dtTSC[n]);
//  測定したいユニットの終了で RDTSCed(dtTSC[n]);
//    ....
// 測定したい処理全体の終了 endRDTSC;
// 　この時点で結果はクリップボードに出力されます
// 　ユニットには名前を付けておけます  dtTSC[n].name='func1'; なくても問題ない
// nは0～9の数字でユニットを識別
//  つまり最大10個のユニットを識別出来ます(増やすならTSCtimerSizeを修正)

// パフォーマンスチェックの都度、入れたり外したりが面倒なら
// 関数の先頭で
//   { $IFDEF enTSC}RDTSCst(dtTSC[0]);{$ENDIF}
// のように入れて 終了で
//   { $IFDEF enTSC}RDTSCed(dtTSC[0]);{$ENDIF}
// と入れます。enTSC をコンパイル時に入れなければこれらは外れます

// 関数の先頭は常に同じ場所を通りますが、exitやraiseがあると入れ忘れる場合もあるでしょう
// その場合は処理時間が少な目に出るので stcntとedcntのカウント値で確認出来ます
// また意図的に途中終了の比率を確認する事にも使えるでしょう

// 結果を自分で表示処理したい場合は
// endRDTSC;をendRDTSC(false);として、その後でdtTSC配列を確認して下さい

// 作者：裏目小僧
// 利用はご自由に。
// Lazarus2.2.4 32bit/64bit Delphi5である程度動作する事を確認しています
// Delphiで利用するにはUTF8からの文字コード変換が必要です
{$IFDEF FPC} {$mode Delphi}{$H+}   {$ENDif}

interface

uses
  Classes, SysUtils, Windows;

//RDTSC命令を利用して64bitのカウンタを読み出します
function RDTSC: int64;


type TrdTSCdt = record
    TSC, sum: int64;
    stcnt, edcnt: DWORD;
    Name: string;
  end;
const TSCtimerSize = 10;
var  dtTSC: array [0..TSCtimerSize] of TrdTSCdt;//最後は startRDTSC/endRDTSCの為に

procedure RDTSCst(var dt: TrdTSCdt); register;//dt.TSC:=RDTSC
procedure RDTSCed(var dt: TrdTSCdt); register;//dt.sum+=RDTSC-TSC
procedure startRDTSC();
procedure endRDTSC(clpBd: boolean = True); //clpBd=trueならクリップボードに結果を出力
var startRDTSCcnt, endRDTSCcnt: int64;
var startRDTSCtime, endRDTSCtime: TDateTime;
implementation
uses
  Clipbrd;
procedure startRDTSC();
var i: integer;
begin
  for i := 0 to High(dtTSC) do with  dtTSC[i] do
    begin
      TSC := 0;    // 0.. 7   //最後に RDTSCstが呼ばれた時のTSC
      sum := 0;    // 8..15   //RDTSCst～RDTSCed間の累計値
      stcnt := 0;  //16..19   //RDTSCstが呼ばれた回数
      edcnt := 0;  //20..23   //RDTSCedが呼ばれた回数
    end;
  startRDTSCtime := now;
  startRDTSCcnt := 0;
  endRDTSCcnt := 0;
  QueryPerformanceCounter(startRDTSCcnt);
  RDTSCst(dtTSC[High(dtTSC)]);
end;
function dtTSCprt1(n: integer): string;
var ms: double;
  timdiv: int64;
begin
  ms := (endRDTSCtime - startRDTSCtime) * 24 * 60 * 60 * 1000;
  if QueryPerformanceFrequency(timdiv) then
  begin //QueryPerformanceCounterが有効なら
    //現在のWin64だとNowでも時間分解能は十分なようだが
    ms := 1000.0 * (endRDTSCcnt - startRDTSCcnt) / timdiv;
  end;
  if n = -1 then
  begin
    //nn,1234567890, 99.99,123456,123456, 99.99,"name"
    Result := 'no,       cyc,全体の,st回数,ed回数,ed/st ,"name"';
    exit;
  end;
  if n = High(dtTSC) then
  begin
    with dtTSC[n] do
      Result := Format('  ,%10dcyc=%4.2fms このCPUは1msで%6.1fcyc実行', [sum, ms, sum / ms]);
    exit;
  end;

  with dtTSC[n] do                                   //d 10d  ,%6.2f                        ,%6d  ,%6d  ,%6.2f,"%s"
    Result := Format('%2d,%10d,%6.2f,%6d,%6d,%6.2f,"%s"', [n, sum, 100.0 * sum / dtTSC[High(dtTSC)].sum, stcnt, edcnt, 100.0 * edcnt / stcnt, Name]);

end;
function addS(s: array of ansistring): ansistring; //文字連結
var
  len, i, n: integer;
begin
  len := 0;
  for i := 0 to High(s) do len := len + length(s[i]);
  Setlength(Result, len);
  n := 1;
  for i := 0 to High(s) do
  begin
    move(s[i][1], Result[n], length(s[i]));
    Inc(n, length(s[i]));
  end;
end;
procedure endRDTSC(clpBd: boolean);
var s: string;
var i: integer;
begin
  RDTSCed(dtTSC[High(dtTSC)]);
  QueryPerformanceCounter(endRDTSCcnt);
  endRDTSCtime := Now;
  if not clpBD then exit;

  s := dtTSCprt1(-1);
  for i := 0 to High(dtTSC) do
    if dtTSC[i].TSC <> 0 then
      s := addS([s, #13#10, dtTSCprt1(i)]);
  Clipboard.AsText := s;
end;
//以下アセンブラ内で RDTSCをDB 0Fh, 031Hと記載してるのはDelphi5でRDTSCを使う為です
{$IFDEF FPC}{$ASMMODE intel}    {$ENDif}
{$IFDEF WIN64}
function RDTSC:Int64;{$IFDEF FPC} assembler;{$ENDif}
asm
// DB 048H   64bit版も結果は同じのようだ
DB 0Fh, 031H//  RDTSC //EDX:EAX
 SHL RDX,32
// MOV RAX,EAX   //RAX上位32bitは既に0になってる
 OR  RAX,RDX
end;

procedure RDTSCst(var dt:TrdTSCdt );register; {$IFDEF FPC} assembler;{$ENDif} //dt.TSC:=RDTSCl
asm    //        RCX=@dt
DB 0Fh, 031H//  RDTSC //EDX:EAX
 mov   DWORD ptr  [RCX],EAX;
 mov   DWORD ptr  [RCX+4],EDX;
 inc   DWORD ptr [RCX+16];
end;
procedure RDTSCed(var dt:TrdTSCdt ); register;{$IFDEF FPC} assembler;{$ENDif}//dt.sum+=RDTSCl-TSC
asm    //        RCX=@dt
DB 0Fh, 031H//  RDTSC //EDX:EAX  RAXの上位は0になるようだ
 SHL RDX,32
 OR  RAX,RDX
 sub RAX,[RCX];
 add [RCX+8],RAX
 inc  DWORD ptr [RCX+20];
end;
{$ELSE}//Win32 todo　以下はまだデバッグしていない
function RDTSC: int64;{$IFDEF FPC} assembler;{$ENDif}
asm
         DB      0Fh, 031H//    RDTSC //EDX:EAX
end;

procedure RDTSCst(var dt: TrdTSCdt); {$IFDEF FPC} assembler;{$ENDif} //dt.TSC:=RDTSCl
asm        //    EAX=@dt
         MOV     ECX,EAX
         DB      0Fh, 031H//         RDTSC //EDX:EAX
         MOV     [ECX  ],EAX;
         MOV     [ECX+4],EDX;
         INC     DWORD ptr   [ECX+16];
end;
procedure RDTSCed(var dt: TrdTSCdt); register;{$IFDEF FPC} assembler;{$ENDif}//dt.sum+=RDTSCl-TSC
asm       //     EAX=@dt
         MOV     ECX,EAX
         DB      0Fh, 031H//        RDTSC //EDX:EAX  RAXの上位は0になるようだ
         SUB     EAX,[ECX]
         SUB     EDX,[ECX+4]
         ADD     [ECX+ 8],EAX;
         ADC     [ECX+12],EDX;
         INC     DWORD ptr    [ECX+20];
end;
{$ENDif}
end.
