Here's a tiny interfaced class I wrote because I'm chasing performance issues in a big GUI app, so I spend a lot of time going "that takes a while, I wonder which bit takes the time". Which means some sort of logging, and after my coworkers decided that SynLog was too hard I came up with this trivial thing:

Here's a tiny interfaced class I wrote because I'm chasing performance issues in a big GUI app, so I spend a lot of time going "that takes a while, I wonder which bit takes the time". Which means some sort of logging, and after my coworkers decided that SynLog was too hard I came up with this trivial thing:

type
  ILogTimer = interface
  end;
  TLogTimer = class(TInterfacedObject, ILogTimer)
  private
    FStartTime: TDateTime;
    FCaption: string;
  public
    constructor Create(const Caption: string); reintroduce;
    destructor Destroy; override;
  end;

constructor TLogTimer.Create(const Caption: string);
begin
  inherited Create;
  FStartTime := Now;
  FCaption := Caption;
end;

destructor TLogTimer.Destroy;
begin
  OutputDebugString(PChar(Format('%s %0.1gms', [FCaption, (Now -
FStartTime) * SecsPerday * 1000])));
  inherited;
end;

Usage is two lines, the ILogTimer variable and a constructor call to assign it. The idea is that it's less typing every time I want to time something, and if I need to I can stop the whole lot in one place (by undefining something, say)

Comments

  1. Moz Le, I agree, but the Delphi TStopWatch is an advanced record, so no try/finally/free is needed. In Delphi-7 the class implementations is the only one that works, but I'm sure that one could be turned into an interface solution as well.

    ReplyDelete
  2. Nice idea, I was just thinking the other day that I should look into which parts of the code were waiting for queries, in order to improve responsiveness. This seems like a neat way.

    I think this can be slightly improved using the fact that interfaces aren't freed until the end of the procedure/function:

    Then you just have to do like this:

      procedure TForm1.Button1Click(Sender: TObject);
      begin
        LogTimer('Button1Click');
        Sleep(124);
      end;

    where

      function LogTimer(const Caption: string): ILogTimer;
      begin
        result := TLogTimer.Create(Caption);
      end;

    I'd also record the start and end time and subtract, but that's a detail.

    For reference here's the full code of my variant:

    type
      ILogTimer = interface
        ['{6A92AB8A-E144-4F9F-BD6A-50842401B539}']
      end;
      TLogTimer = class(TInterfacedObject, ILogTimer)
      private
        FStartTime: int64;
        FCaption: string;
      public
        constructor Create(const Caption: string);
        destructor Destroy; override;
      end;

    constructor TLogTimer.Create(const Caption: string);
    begin
      inherited Create;
      QueryPerformanceCounter(FStartTime);
      FCaption := Caption;
    end;

    destructor TLogTimer.Destroy;
    var
      finishTime, freq: int64;
    begin
      QueryPerformanceCounter(finishTime);
      QueryPerformanceFrequency(freq);
      OutputDebugString(PChar(Format('%s %0.1gms', [FCaption, (finishTime - FStartTime) * (1000.0 / freq)])));
      inherited;
    end;

    function LogTimer(const Caption: string): ILogTimer;
    begin
      result := TLogTimer.Create(Caption);
    end;

    ReplyDelete
  3. If you add a factory method returning an instance of the interface type you can use it without an ILogTimer variable. Call the factory method and don't assign the result to a variable. When the method exits it will release the instance then.

    This is probably a case of relying on an implementation detail. and it can be confusing to anyone who doesn't know why it works. Can be handy for simple things though, I have an ICursorChanger that sets the screen cursor to a wait cursor on creation and back to arrow on destruction, and a factory function BusyCursor. For methods that will block the UI thread for a moment I can just put a call to BusyCursor at the top of the method. When it scopes out at the end it resets the cursor.

    ReplyDelete

Post a Comment