Как замерить время выполнения программы?
   
Стандартный Паскаль Free Pascal Compiler
Использование процедуры GetTime <-- Этот метод тоже доступен
Работа с ячейкой памяти $0000:$046C  
Использование счетчика тактов процессора (RDTSC) <-- Немного по-другому, но смысл тот же
  Использование GetTickCount()
  Использование QueryPerformanceCounter()
   

Внимание !!!

При работе под Windows задача определения точного времени выполнения программы в общем случае не имеет решения... Дело в том, что в Windows существует планировщик заданий, который может в любой момент переключиться с Вашего приложения на другое. MSDN советует не делать никаких предположений о моменте переключения задач, следовательно, всегда существует вероятность того, что в определенный момент времени после запоминания начального этапа работы программы, но перед запоминанием конечного этапа произойдет вытеснение Вашей задачи планировщиком, и результат засекания времени окажется непредсказуемым...

Будем надеяться, что этого не произойдет...


Использование процедуры GetTime

Самый простой способ замеритьвремя выполнения кода - это запомнить системное время в начале работы алгоритма, затем (по окончании его работы) снова получить системное время, и вычесть из него то, что было запомнено при старте. Проще всего для этой цели воспользоваться стандартной процедурой GetTime модуля Dos (для удобства я написал свою функцию, возвращающую системное время в сотых долях секунды. Соответственно, время работы программы тоже вычисляется в сотых долях секунды).
Function GetTime: LongInt;
Var h, m, s, ms: Word;
begin
  Dos.GetTime(h, m, s, ms);
  GetTime := ms + 100 * (s + 60 * (m + 60 * h));
end;

...
{ Вызывать вот так: }
start := GetTime;
  ... { Здесь - алгоритм, время выполнения которого надо замерить }
WriteLn('Время выполнения = ', GetTime - start);
...
Естественно, срабатывает так называемое "золотое правило" - поскольку способ самый простой, у него много недостатков, в частности, результат будет неверным, если во время работы алгоритма (после первого вызова функции GetTime, но перед вторым) сменится дата... Тогда данный способ может показать и отрицательное время, но в любом случае оно будет неправильным (даже если будет правдоподобным)...

Кроме того, результат может зависеть от настроек среды программирования (или используемых директив компилятора)...


Работа с ячейкой памяти $0000:$046C

Если не хочется подключать модуль Dos, и пользоваться GetTime (кстати, вполне оправданное желание, т.к. сам вызов GetTime тоже занимает какое-то время, следовательно замер становится еще более неточным), можно взять значение числа тиков таймера напрямую из памяти по адресу $0000:$046C (4 байта), запомнить его, и после завершения работы основного алгоритма опять же вычесть из нового значения этой ячейки памяти ранее запомненное...

Здесь, точно так же, как и в случае с GetTime, возникает опасность смены даты во время работы программы... Если разность конечного и начального времени отрицательна, значит, была смена даты, и, чтобы получить правильное значение времени, к нему надо добавить $1800B0... (Я надеюсь, Вы не пишете программ, работающих более 48 часов?)

Полученное значение делим на 18.2 (таймер "тикает" каждые 1/18.2 секунды), и получаем время работы программы с точностью около 0.05 сек.
Var time: LongInt;
...

time := MemL[0:$046C];
  ... { Здесь - собственно программа }
time := MemL[0:$046C] - Time;

If time < 0 Then time := time + $1800B0;
WriteLn('Время выполнения = ', (time / 18.2):6:2);
...


Использование счетчика тактов процессора (RDTSC)

(C) Visitor, forum.sources.ru

Для процессоров выше i486 существует способ очень точно сравнить время выполнения двух участков кода - это встроенный в процессор счетчик тактов. Ниже приведены два исходника - процедура, считывающая значение этого счетчика, и пример ее использования на Паскале.

tsc.asm
        .MODEL LARGE
        .CODE
        .386c
PUBLIC  READTSC
READTSC PROC FAR
        enter 0, 0
        push ds
        db   0fh, 31h; RDTSC opcode
        mov  bx, ss:[bp+8]
        mov  ds, bx
        mov  bx, ss:[bp+6]
        mov  [bx], eax
        mov  [bx+4], edx
        pop ds
        leave
        retf 4
READTSC ENDP
        END
sample.pas
{$N+}

procedure ReadTSC(Var counter : Comp); far; external;
{$L tsc.obj}

Var a1, a2, a3 : Comp;
begin
   ReadTSC(a1);
   Writeln('Working :)'); { <--- Засекаем время выполнения этой строки }
   ReadTSC(a2);
   Writeln(a1:20:0, ', ', a2:20:0, ', ', (a2-a1):20:0)
end.
Примечание:

В 32-битном компиляторе FPC (да и в Дельфи тоже) этот же метод реализуется немного по-другому:
function rdtsc:Int64;
asm
  rdtsc
  mov    dword ptr [Result], eax
  mov    dword ptr [Result + 4], edx
end;

var
  Start, Finish, Duration:Int64;

begin
  Start := rdtsc;

  writeln('test'); // Посмотрим, за сколько тактов выполняется WriteLn

  Finish := rdtsc;
  Duration := (Finish - Start);
  WriteLn('Время выполнения (в тактах) = ', Duration);
end.


Использование GetTickCount() (32-битные компиляторы)

При использовании 32-битных компиляторов для определения времени выполнения можно использовать функцию GetTickCount(), возвращающую количество миллисекунд, прошедших с момента старта системы. Разумеется, в случае использования GetTickCount проблема с переменой даты не возникает, но тут нас подстерегает другая опасность: каждые 49,8 дней будет происходить целочисленное переполнение этого значения, так что есть небольшая опасность, что если вызвать функцию незадолго до этого события один раз, а затем второй раз - после, то получится неизвестно что, а именно: число, близкое к -2*MaxInt.

Использование:
uses Windows;
var Duration: Cardinal;

begin
  Duration := GetTickCount();
  
  ... // замеряемый блок
  
  Duration := GetTickCount() - Duration;
  WriteLn('Время выполнения = ', Duration);
end.
Этот метод также не отличается особой точностью (предельно малые интервалы времени при использовании GetTickCount() составляют 10 .. 16 мс), но зато он очень прост, и если очень высокая точность не нужна - очень даже имеет право на существование.


Использование QueryPerformanceCounter() (32-битные компиляторы)

Точность этого метода, естественно, гораздо выше, чем точность GetTime, и чем точность метода подсчета тиков таймера. Прежде всего - потому, что эта процедура была создана специально в качестве средства для точной работы со временем...

Используется QueryPerformanceCounter вот так:
Uses
  Windows, SysUtils;
Var
 t1, t2, Res: int64;
 bOk: BOOL;

Procedure StartTimer;
Begin
  t1 := 0; t2 := 0; Res := 0;
  bOK := QueryPerformanceFrequency(Res);
  If bOK Then QueryPerformanceCounter(t1);
 end;

Procedure StopTimer;
Begin
  If bOK Then QueryPerformanceCounter(t2);
End;

{ Время выполнения этой процедуры будет засекаться }
Procedure SomeProc;
Var
  i: Integer;
  a, b, T: Integer;
Begin
  For i := 1 To 10000 Do Begin
    a := 100; b := 250;
    T := a; a := b; b := T;
  End;
End;

Procedure CheckExecutionTime;
Begin
  StartTimer;
  SomeProc; // Запускаем тестируемую процедуру
  StopTimer;
  
  If bOK Then Writeln('Execution time: ' + Format('%g sec.',[(t2 - t1) / Res]));
End;

{ Изменяем приоритет потока на TIME_CRITICAL для увеличения точности }
Procedure Check_Time;
Var tp, pc: DWORD;
Begin
  tp := GetThreadPriority(GetCurrentThread());
  pc := GetPriorityClass(GetCurrentProcess());
  SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
  SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
  Try
    CheckExecutionTime()
  Finally
    SetThreadPriority(GetCurrentThread(), tp);
    SetPriorityClass(GetCurrentProcess(), pc);
  End
End;

{ Основная программа }
begin
  check_time();
end.
(С) Krid, forum.sources.ru



Free Web Hosting