Delphi: как измерить точное время выполнения операции?

Источник: webdelphi

Точное время измерения выполнения операции в Delphi может пригодится во многих случаях, начиная от самого простого - показать пользователю время, затраченное на выполнение длительной операции (здесь, кстати, высокая точность нужна редко) и, заканчивая, ситуациями, когда в целях оптимизации программы нам необходимо выявить в программе наиболее "узкие" места в которых программа "застревает" на длительный промежуток времени. В основном, последняя ситуация характерна при разработке программ, использующих и обрабатывающих большие массивы данных, когда скорость выполнения операций выходит, если не на первое, то на одно из первых мест в требованиях к приложению.
Есть несколько способов узнать время выполнения операций в Delphi и все эти способы, в принципе, рассмотрены как в Сети, так и моем блоге. Поэтому представленная ниже статья - это лишь объединение всех возможных способов измерения времени в Delphi и опытный Delphi-программист здесь врядли встретит что-то новое для себя.

Способ №1 - самый простой. Используем функцию Now()

Самый простейший и наименее точный способ измерить время, затраченное на выполнение какой-либо операции в Delphi - воспользоваться функцией Now() из модуля System.SysUtils .

Исходный код может выглядеть, например, так:

uses System.DateUtils; .... var Start, Stop: TDateTime; Elapsed: int64; begin Start:=Now; //засекли начало выполнения операции DoSomething;//выполняем что-либо Stop:=Now; //засекли окончание выполнения операции Elapsed:=SecondsBetween(Start, Stop);//время в секундах end;

uses System.DateUtils; .... var Start, Stop: TDateTime; Elapsed: int64; begin Start:=Now; //засекли начало выполнения операции DoSomething;//выполняем что-либо Stop:=Now; //засекли окончание выполнения операции Elapsed:=SecondsBetween(Start, Stop);//время в секундах end;

Вполне вероятно, что у вас может возникнуть резонный вопрос: почему я использовал в примере SecondsBetween() , а не, например, MilliSecondsBetween() для большей точности? Сделал я это, опираясь исключительно, на описание функции Now() в официальной справке по Delphi, которая гласит следующее: "Несмотря на то, что в TDateTime могут передаваться миллисекунды, Now() имеет точность до ближайшей секунды". То есть, если использовать Now() , то определять интервал времени с точностью до миллисекунд - не имеет смысла.

Способ №2 - используем Windows API. Функция GetTickCount().

Функция GetTickCount() не имеет параметров и возвращает количество миллисекунд, прошедших с момента запуска системы. Судя по официальной справке Microsoft, разрешение функции GetTickCount() ограничено разрешением системного таймера, которое обычно находится в диапазоне от 10 до 16 миллисекунд. При этом, счётчик миллисекунд будет обнулен, если система запущена более 49,7 дней.
В принципе, пример использования этой функции похож на предыдущий:

var Start, Stop: cardinal; Elapsed: cardinal; begin Start:=GetTickCount; //засекли начало выполнения операции DoSomething;//выполняем что-либо Stop:=GetTickCount; //засекли окончание выполнения операции Elapsed:=Stop-Start;//время в миллисекундах end;

var Start, Stop: cardinal; Elapsed: cardinal; begin Start:=GetTickCount; //засекли начало выполнения операции DoSomething;//выполняем что-либо Stop:=GetTickCount; //засекли окончание выполнения операции Elapsed:=Stop-Start;//время в миллисекундах end;

Так, используя функцию GetTickCount() мы можем засечь время выполнения операции в Delphi с точностью до миллисекунды. Если и такая точность Вам не подходит и необходимо измерить интервал времени ещё точнее, то следующий способ - для вас.

Способ №3 - продолжаем использовать Windows API. Функции QueryPerformanceCounter и QueryPerformanceFrequency

QueryPerformanceCounter - извлекает текущее значение счетчика производительности, которое представляет собой метку времени с высоким разрешением (<1 мкс), которая может использоваться для измерения временных интервалов.
QueryPerformanceFrequency - извлекает частоту счетчика производительности. Частота счетчика производительности фиксируется при загрузке системы и согласована во всех процессорах,поэтому значение нужно запрашивать только при инициализации приложения, а результат может быть кэширован.

Для того, чтобы воспользоваться этими функциями для отсчёта интервала времени, затраченного на выполнение какой-либо операции в Delphi нам необходимо оформить исходный код, например, таким образом:

var iCounterPerSec: TLargeInteger; T1, T2: TLargeInteger; //значение счётчика ДО и ПОСЛЕ операции begin QueryPerformanceFrequency(iCounterPerSec);//определили частоту счётчика QueryPerformanceCounter(T1); //засекли время начала операции   DoSomething; //выполнили что-то   QueryPerformanceCounter(T2);//засекли время окончания ShowMessage(FormatFloat('0.0000', (T2 - T1)/iCounterPerSec) + ' сек.');//вывели количество секунд на выполнение операции end;

var iCounterPerSec: TLargeInteger; T1, T2: TLargeInteger; //значение счётчика ДО и ПОСЛЕ операции begin QueryPerformanceFrequency(iCounterPerSec);//определили частоту счётчика QueryPerformanceCounter(T1); //засекли время начала операции DoSomething; //выполнили что-то QueryPerformanceCounter(T2);//засекли время окончания ShowMessage(FormatFloat('0.0000', (T2 - T1)/iCounterPerSec) + ' сек.');//вывели количество секунд на выполнение операции end;

С полученными значениями T1 и T2 можно "играться" как угодно, например, выводить отдельно минуты/секунды/миллисекунды и т.д. тут всё зависит от ваших потребностей и желаний, я же показал наиболее простой пример использования счётчика с высоким разрешением в Delphi.

Способ №4 - используем возможности Delphi. Модуль System.Diagnostics

Модуль этот появился в Delphi уже достаточно давно (могу ошибаться, но, по-моему с Delphi XE-XE2). В модуле представлена всего одна запись (record) - TStopwatch , которая является ни чем иным, как удобной "обёрткой" для использования таймеров высокого разрешения из примера выше. Судя по достаточно скромной справке, TStopwatch использует функциональные возможности, зависящие от операционной системы, для получения доступа к таймерам с высоким разрешением, если они доступны. Если таймеры с высоким разрешением в ОС недоступны, то используются обычные таймеры.

Несмотря на то, что TStopwatch - это запись, для корректного использования всё же необходимо вызывать метод Create или StartNew .

Описание TStopwatch следующее:

TStopwatch = record strict private class var FFrequency: Int64; class var FIsHighResolution: Boolean; class var TickFrequency: Double; strict private FElapsed: Int64; FRunning: Boolean; FStartTimeStamp: Int64; function GetElapsed: TTimeSpan; function GetElapsedDateTimeTicks: Int64; function GetElapsedMilliseconds: Int64; function GetElapsedTicks: Int64; class procedure InitStopwatchType; static; public class function Create: TStopwatch; static; class function GetTimeStamp: Int64; static; procedure Reset; procedure Start; class function StartNew: TStopwatch; static; procedure Stop; property Elapsed: TTimeSpan read GetElapsed; property ElapsedMilliseconds: Int64 read GetElapsedMilliseconds; property ElapsedTicks: Int64 read GetElapsedTicks; class property Frequency: Int64 read FFrequency; class property IsHighResolution: Boolean read FIsHighResolution; property IsRunning: Boolean read FRunning; end;

TStopwatch = record strict private class var FFrequency: Int64; class var FIsHighResolution: Boolean; class var TickFrequency: Double; strict private FElapsed: Int64; FRunning: Boolean; FStartTimeStamp: Int64; function GetElapsed: TTimeSpan; function GetElapsedDateTimeTicks: Int64; function GetElapsedMilliseconds: Int64; function GetElapsedTicks: Int64; class procedure InitStopwatchType; static; public class function Create: TStopwatch; static; class function GetTimeStamp: Int64; static; procedure Reset; procedure Start; class function StartNew: TStopwatch; static; procedure Stop; property Elapsed: TTimeSpan read GetElapsed; property ElapsedMilliseconds: Int64 read GetElapsedMilliseconds; property ElapsedTicks: Int64 read GetElapsedTicks; class property Frequency: Int64 read FFrequency; class property IsHighResolution: Boolean read FIsHighResolution; property IsRunning: Boolean read FRunning; end;

  • Свойство IsHighResolution указывает, основан ли таймер на счетчике производительности с высоким разрешением.
  • Метод Start() начинает измерять прошедшее время.
  • Метод Stop() останавливает измерение прошедшего времени.
  • Свойство ElapsedMilliseconds получает общее истекшее время в миллисекундах.
  • Свойство Elapsed получает истекшее время в виде TTimeSpan .

Воспользоваться возможностями TStopwatch также достаточно просто, например, так:

uses System.Diagnostics; ... var SW: TStopwatch; begin SW:=TStopwatch.StartNew; SW.Start;   DoSomething;   SW.Stop; ShowMessage(SW.Elapsed.TotalMinutes.ToString); end;

uses System.Diagnostics; ... var SW: TStopwatch; begin SW:=TStopwatch.StartNew; SW.Start; DoSomething; SW.Stop; ShowMessage(SW.Elapsed.TotalMinutes.ToString); end;

В представленном выше примере мы воспользовались TStopwatch и вывели количество минут (с дробной частью), пошедших на выполнение какой-то операции. В целом же, используя возможность TStopwatch.Elapsed можно выводить любые значения, ограниченные лишь возможностями TTimeSpan (см. справку).

Подведем итог

Итак, чтобы измерить точное время выполнения операции в Delphi, необходимо, прежде всего определиться с тем какая точность Вас устроит? Если достаточно, чтобы интервал времени определялся с точностью до секунды, то достаточно воспользоваться обычной, давно известной функцией Now() . Да, точность самая низкая, но, зато - просто.

Если ваше требование к точности измерения времени ограничивается миллисекундой - используйте GetTickCount() : просто, достаточно надежно (только, если вы не планируете измерять интервал времени больше 49,7 дней).

Если же требуется использовать самые точные методы измерения времени на выполнение операции, то тут либо связка функций QueryPerformanceCounter() и QueryPerformanceFrequency() , либо более удобный способ - использовать TStopwatch из модуля System.Diagnostics .


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=39613