30 de noviembre de 2011

Medir tiempos en Delphi

Varias veces he necesitado medir tiempos dentro del código de Delphi, sobre todo cuando queremos mejorar la performance de un programa, o saber si determinada funcionalidad incrementó el tiempo, o medir los tiempos de las consultas SQL.

No hay un “medidor” de tiempos, al menos que yo conozca, así que me puse a hacer uno y quizás les pueda servir a ustedes. Acá les dejo lo básico del código y luego un archivo con el código completo.

  TPerformanceTime = Class(TObject)
  private
    FElapsed: Int64;
    FStart, FFinish, FFrec:Int64;
  public
    Funcionando: Boolean;
    procedure Start;
    procedure Finish;
    function TiempoMS: Int64;
    function TiempoActual: Int64;    
    class function TiempoAStringFormateado(aTiempo: Int64): string;
    class function TiempoADateTime(Pasado: int64): TDateTime;
    class function TiempoAString(Formato: String; Tiempo: TDateTime): string;
  end;

La implementación de los dos métodos principales (Start y Finish) y el método que simplemente devuelve el tiempo pasado:

procedure TPerformanceTime.Start;
begin
  FElapsed := 0;
  QueryPerformanceFrequency(FFrec);
  QueryPerformanceCounter(FStart);
  Funcionando := True;
end;

procedure TPerformanceTime.Finish;
begin
  if (Funcionando) then
  begin
    QueryPerformanceCounter(FFinish);
    FElapsed := (FFinish - FStart) * MiliSegundos div FFrec;
    Funcionando := False;
  end;
end;

function TPerformanceTime.TiempoMS: Int64;
begin
  Result := FElapsed;
end;

Usarlo es muy facil, en el procedimiento donde queremos medir tiempos (por ejemplo, antes de hacer un Open de una TQuery), creamos el objeto:

  Counter := TPerformanceTime.Create;

Y en lo posible dentro de un try finally hacemos los siguientes pasos:

  try
    //Inicio el contador
    Counter.Start;
    //codigo que quiero medir su tiempo
    //por ejemplo un Query.Open;
    Counter.Finish;
    //Acá es donde vemos que hacemos con el tiempo obtenido Counter.TiempoMS
    //por ejemplo mostrarlo de forma "leible"
    ShowMessage('Duración -> ' + TPerformanceTime.TiempoAStringFormateado(Counter.TiempoMS));
  finally
    //Destrucción del contador
    Counter.Destroy;
  end;

Y básicamente eso es todo. Obviamente despues se puede ampliar su funcionamiento, por ejemplo el método de clase que llamé “TiempoAStringFormateado” devuelve el tiempo transcurrido usando un formato preestablecido, en mi caso como constantes en la cabecera de la unit:

CONST
  MiliSegundos = 1000;
  FORMATO_TIEMPO_LOG_DIAS = 'dd "Días "hh" Horas "nn" Minutos "ss" Segundos "zzz" Milisegundos"';
  FORMATO_TIEMPO_LOG_HORAS = 'hh" Horas "nn" Minutos "ss" Segundos "zzz" Milisegundos"';
  FORMATO_TIEMPO_LOG_MINUTOS = 'nn" Minutos "ss" Segundos "zzz" Milisegundos"';
  FORMATO_TIEMPO_LOG_SEGUNDOS = 'ss" Segundos "zzz" Milisegundos"';
  FORMATO_TIEMPO_LOG_MILISEGUNDOS = 'zzz" Milisegundos"';

Y el método en si:

class function TPerformanceTime.TiempoAStringFormateado(aTiempo: Int64): string;
var
  Hour, Min, Sec, MSec: Word;
  Tiempo: TDateTime;
begin
  //Pasa los milisegundos a TDateTime
  Tiempo := TiempoADateTime(aTiempo);
  //Extrae del tiempo las horas, minutos, segundos y milisegundos
  DecodeTime(Tiempo, Hour, Min, Sec, MSec);
  //Si al truncarlo hay algún valor es que al menos son días
  if Trunc(Tiempo) > 0 then
  begin
    Result := TiempoAString(FORMATO_TIEMPO_LOG_DIAS, Tiempo);
  end
  //Sino formatea con el valor más alto que encuentra.
  else if (Hour > 0) then
  begin
    Result := TiempoAString(FORMATO_TIEMPO_LOG_HORAS, Tiempo);
  end
  else if (Min > 0) then
  begin
    Result := TiempoAString(FORMATO_TIEMPO_LOG_MINUTOS, Tiempo);
  end
  else if (Sec > 0) then
  begin
    Result := TiempoAString(FORMATO_TIEMPO_LOG_SEGUNDOS, Tiempo);
  end
  else if (MSec > 0) then
  begin
    Result := TiempoAString(FORMATO_TIEMPO_LOG_MILISEGUNDOS, Tiempo);
  end;
end;

El archivo fuente pueden descargarlo desde acá (corregido):
UPerformanceTime.pas

9 comentarios:

  1. Interesante código. Sencillo y muy bien explicado.

    Gracias.

    ResponderEliminar
  2. Muchas gracias Lupin por este aporte.

    Lamentablemente el link de descarga no funciona (snif snif)

    ResponderEliminar
    Respuestas
    1. Ahora si, corregido: http://www.mediafire.com/?b3ewzkggmda803q

      Eliminar
    2. Lástima que para 13/08/2016 el link ya no sirve, llegué muy tarde snif snif

      Eliminar
  3. Muchas gracias de nuevo.

    me viene muy bien, no es que sea indispensable al funcionamiento de mi aplicación, pero a mis fines es perfecto.

    ResponderEliminar
  4. Muy bien Explicado, sencillo y funcional, me sirvio mucho, Gracias

    ResponderEliminar
  5. Por favor subelo nuevamente..... el link de descarga no funciona....

    ResponderEliminar
  6. Por favor subelo nuevamente..... el link de descarga no funciona....

    ResponderEliminar