{$MODESWITCH ADVANCEDRECORDS}
{$MODESWITCH RESULT}
unit dbench;

interface

uses
{$IFDEF WINDOWS}
  windows,
{$ENDIF}
  SysUtils,
  DateUtils;

var
  ITERATIONS: LongInt = 1000000; // number of atomic iterations
  TRIES: LongInt = 5;
  SEED: LongInt = 133; // random seed for all runs

// Declare my own fast random function
var
  RandSeed: Cardinal = 0;
function Random(Count: LongInt): LongInt; inline;

// ... And local random for multithreaded code
type
TRandomizer = record
  RandSeed: Cardinal;
  function Random(Count: LongInt): LongInt; inline;
end;

// Timer
type
TTimer = record
  DateTime: TDateTime;
  T: Int64;
end;

type
  TBenchProc = procedure;

var
  // may be: simple, markdown
  BenchOutputFormat: AnsiString = 'simple';
procedure RunBenchmark(const Name: AnsiString; Bench: TBenchProc);

// Measure how many time loop spends itself
procedure Bench_Empty;

function SystemTimer: Int64;
implementation

function Random(Count: LongInt): LongInt;
begin
  RandSeed := 214013 * RandSeed + 2531011;
  Result := RandSeed mod Count;
end;

function TRandomizer.Random(Count: LongInt): LongInt;
begin
  RandSeed := 214013 * RandSeed + 2531011;
  Result := RandSeed mod Count;
end;

function SystemTimer: Int64;
{$IFDEF WINDOWS}
var
  T, F: LARGE_INTEGER;
{$ENDIF}
begin
  Result := 0;
{$IFDEF WINDOWS}
  QueryPerformanceFrequency(@F);
  QueryPerformanceCounter(@T);
  Result:=Trunc(1000*T.QuadPart/F.QuadPart);
{$ENDIF}
end;

procedure RunTimer(var T: TTimer);
begin
  T.DateTime := SysUtils.Now;
  T.T := SystemTimer;
end;

function GetTimer(var T: TTimer): Int64;
begin
  if T.T = 0 then begin
    Result := DateUtils.MilliSecondsBetween(T.DateTime, SysUtils.Now);
  end else
    Result := SystemTimer - T.T;
end;

procedure RunBenchmark(const Name: AnsiString; Bench: TBenchProc);
var
  I: LongInt;
  Timer: TTimer;
  Time, MinTime, MaxTime: Int64;
begin
  RunTimer(Timer);
  // Warmup -- load caches
  Randseed := SEED;
  Bench;
  // Run tests
  MinTime := 0;
  MaxTime := 0;
  for I := 0 to TRIES - 1 do begin
    RandSeed := SEED;
    RunTimer(Timer);
    Bench;
    Time := GetTimer(Timer);
    if (MinTime = 0) or (Time < MinTime) then
      MinTime := Time;
    if Time > MaxTime then
      MaxTime := Time;
  end;
  if BenchOutputFormat = 'simple' then begin
    Writeln(Name, ': ', MinTime, 'ms (~', FormatFloat('##0.##', (MaxTime - MinTime)/(MinTime + 1)*100), '% precision)');
  end else if BenchOutputFormat = 'markdown' then begin
    Writeln('|', Name, ' | ', MinTime, ' ms (~', FormatFloat('##0.##', (MaxTime - MinTime)/(MinTime + 1)*100), '% deviation) |');
  end;
end;

procedure Bench_Empty;
var
  I: LongInt;
begin
  for I := 0 to ITERATIONS - 1 do begin
    Random(10000);
  end;
end;

end.
