Версия для печати
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум на Исходниках.RU > Pascal: Общие вопросы > Как замерять время выполнения кода?


Автор: Some1 09.12.03, 15:26
Самым простым способом является одновременно самый очевидный:
Узнаём и запоминаем время, потом делаем нужное нам действие, и сразу после него опять узнаём и запоминаем время.
Разница между временем до действия и временем после действия и будет временем выполнения действия.

В паскале есть стандартная процедура модуля Dos - GetTime.
Она узнаёт, сколько времени в момент её выполнения.
Вот её синтаксис:
procedure GetTime(var Hour, Minute, Second, Second100:word);

Её параметрамы должны быть обязательно переменные. После её выполнения в этих переменных будет содержаться время того момента, когда выполнится эта процедура:
  • Hour - Час (значение от 0 до 23)
  • Minute - Минута (значение от 0 до 59)
  • Second - Секунда (значение от 0 до 59)
  • Second100 - Сотая часть секунды (значение от 0 до 99)
Используя эту процедуру мы можем занести в переменные время до выполнения нужного нам действия, и в другие переменные время после выполнения нужного нам действия.
После этого можно посчитать разницу во времени, и эта разница будет длительностью выполнения замеряемого действия.

Чтобы посчитать разницу нужно перевести время до выполнения и время после выполнения к количеству сотых частей секунды. Нельзя напрямую сравнивать к примеру просто секунды. Почему?
Предположим в момент замера времени до выполнения нужного нам действия было время 10 минут 58 секунд. А в момент замера времени после выполнения было 11 минут 15 секунд. Если мы вычтем из секунд-после секунды-до, мы получим (15-58)=-43 секунды. Но ведь это неверно %)
Так получилось из-за того, что за время, пока выполнялась программа началась новая минута. Если же преобразовать всё время к количеству сотых частей секунды, то такой проблемы не возникнет.
Вот пример действующего кода:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    uses dos;
    var
      h1,m1,s1,t1:word;
      h2,m2,s2,t2:word;
      d:longint;
    begin
      gettime(h1,m1,s1,t1);
      ... {любое действие, продолжительность которого измеряем}
      gettime(h2,m2,s2,t2);
      {вычислим время выполнения d - результат будет в сотых долях секунды}
      d:=(longint(h2)*360000+longint(m2)*6000+s2*100+t2)- {количество сотых долей секунды после выполнения}
         (longint(h1)*360000+longint(m1)*6000+s1*100+t1); {их количество до выполнения действия}
      writeln('Действие выполнялось ',d/100:0:2,' секунды');
    end.

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

P.P.S:
Можно пойти чуть дальше, и написать функцию fGetTime:

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    function fGetTime: LongInt;
    var hr, min, sec, sec_100: word;
    begin
      GetTime(hr, min, sec, sec_100);
      fGetTime := longint(hr)*360000 + longint(min)*6000 + sec*100 + sec_100;
    end;
     
    { и работать с ней: }
    var before, after: longint;
    begin
      before := fGetTime;
      ...
      after := fGetTime;
      writeln('Действие выполнялось ', (after - before) / 100:0:2,' секунды')
    end;

V877

Автор: Visitor 11.12.03, 19:11
Совсем забыл, что хотел запостить :(

Для процессоров выше i486 существует способ очень точно сравнить время выполнения двух участков кода -- ето встроенный в процессор счетчик тактов. Ниже приведены два исходника -- процедура, считывающая значение етого счетчика, и пример ее использования на BP7.
tsc.asm
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
            .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
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    {$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.


В прикрепленном файле то же самое, tsc.asm оттранслирован.

P.S. Обратите внимание и сделайте выводы, как много времени занимает вывод на екран. :)

Автор: vesper1 12.12.03, 10:44
Если не хочется подключать модуль DOS, замерить время можно с помощью взятия значения напрямую из памяти по адресу $0046C (4 байта). Там хранится число "тиков" встроенных часов реального времени, которые тикают 18.54 раз в секунду. И именно оттуда тащат его функции модуля.
Если у вас все еще нет процессора Pentium (или есть, но нужно измерять время на процессоре ниже него), можно разогнать встроенный таймер, при этом нужно будет использовать встроенный ассемблер. Вот этот код по-моему достаточно откомментирован:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    procedure bogusprocedure;near; assembler;
    asm
    end;
    var int08save:pointer;
        int08handler_count:longint;
        int08handler_lessercount:word;
        int08handler_tickin:boolean;
    procedure int08handler;assembler;{no sense of far, it's int handler, so must have iret inside}
    asm
      sti
      push ax
      push dx
      push ds
      mov ax,seg @data
      mov ds,ax {loading DS, probably it's changed}
      mov al,int08handler_tickin
      or al,al
      je @hnd_l1 {if not tickin jump forward}
      db 66h
      inc word ptr int08handler_count
    @hnd_l1:
      dec int08handler_lessercount
      jne @hnd_l3
      mov int08handler_lessercount,0100h {256, must be 65536 div (speedup level)}
      {ok, let's call normal handler after all that}
      pushf
      call dword ptr int08save
      jmp @hnd_l2
    @hnd_l3:
      mov al,20h
      out 20h,al   {funny, BIOS caller uses call blablabla and ret instead of jmp short}
      db 0ebh,0
      call bogusprocedure
    @hnd_l2:
      pop ds
      pop dx
      pop ax
      iret
    end;

Обработчик прерывания устанавливается на прерывание 08, перед этим нужно сохранить старый вектор вызовом getintvec($08,addr(int08save)), иначе процедура не будет работать :(
Таймер программируется вот так:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    {programming timer channel 0, tickin 256, mode 3, hex counter, greater byte only}
    asm
        cli
        mov al,00100110b
        out 43h,al
        call bogusprocedure
        mov al,1
        out 40h,al
        call bogusprocedure
        sti
    end;
Вызов bogusprocedure стоит из-за того, что порт может не успеть принять записанный байт до того, как в него запишут следующий.
Значение AL в первом OUT флаговое, это байт управления таймером. Биты 7 и 6 содержат номер канала (для часов это 0), 5й и 4й управляют записью соответственно старшего и младшего байта счетчика таймера, 3,2,1 биты содержат режим работы (новый) счетчика, для часов используется режим 3 - периодические прямоугольные колебания выходного сигнала, каждый полупериод длится N/2 колебаний генератора (1193182 Гц), прерывание генерируется каждый переход с низкого на высокий уровень. 0й байт определяет формат записываемого счетчика (1 - двоично-десятичный, 0 - шестнадцатеричный). При записи обоих байтов счетчика сначала записывается младший байт. Для возврата в нормальное состояние в счетчик должен быть записан 0 в оба байта, что соответствует значению счетчика 65536 и нормальной частоте прерываний. Т.е. код для возврата:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    asm
        cli
        mov al,00110110b
        out 43h,al
        call bogusprocedure
        mov al,0
        out 40h,al
        call bogusprocedure {ok, delay also goes here}
        out 40h,al
        call bogusprocedure
        sti
    end;

Естественно, нужно еще и вернуть старый вектор, setintvec($08,addr(int08save));
Работой счетчика (на случай выключения из анализа процедуры или двух ;) ) можно управлять переменной int08handler_tickin, если она true, то счет идет. Вне зависимости от нее, старый вектор прерывания вызывается с нужной частотой.

Автор: vesper1 30.12.03, 13:55
Не стал править предыдущий пост, он не расконвертился :(
Насчет таймера в памяти ДОС:
При каждом срабатывании прерывания 08 происходит увеличение значения meml[0:$046C] на единицу, далее оно проверяется на достижение значения $1800B0 (в десятичной системе 1573040), которое соответствует переходу через полночь (24 часа). Если на часах полночь, ставится в единицу байт по адресу (вроде бы mem[0:$0467], точно не помню). Это значение меняется 18.2 раза в секунду (Some1 был прав) и доступно через "массив" meml любой программе, работающей под ДОС. Чтобы измерить время, нужно:
1) Определить кусок программы, для которого вам нужно узнать время его работы;
2) Сохранить значение этого участка памяти: starttime:=meml[0:$046C]; вставив этот оператор сразу перед началом куска;
3) Сразу после него считать значение еще раз: endtime:=meml[0:$046C]; Для иллюстрации я написал 2 переменные, можно обойтись и одной :)
4) Проверить, не было ли пОлночи в процессе работы: if endtime<starttime then inc(endtime,$1800B0);
5) Осталось взять разницу между значениями и разделить её на 18.2. Результат - время в секундах с точностью плюс-минус 0.05с.
Например, для тестовых задач оно определяется вот так:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    time:=meml[0:$046C];
    proces;
    time:=meml[0:$046C]-time;
    if time<0 then time:=time+$1800B0;
    writeln(time/18.2:6:2);

Использован кусок кода Кришкина, немного подправленный. proces - процедура, решающая задачу.

PS. Если в запрошенном куске будет запрос данных от пользователя, чистого времени вы уже не получите. Если, конечно, вам не нужно время реакции (или скорость набора) пользователя. :D Например, вам нужно узнать, сколько времени ваш ребенок (если есть, иначе ученик ;) ) решал задачку в уме. Делаем так:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    write('Задача №3: траляляляляляля. Введите ответ:');
    time:=meml[0:$046C];
    readln(x);
    time:=meml[0:$046C]-time;
    if time<0 then time:=time+$1800B0;
    writeln('Долговато пожалуй, целых ',time/18.2:6:2,' секунд');
    if x<>n then writeln('Кстати, ответ неверен.') else writeln('Правильно.');

Хотя вряд ли вы будете ребенка пытать арифметикой в полночь. :D :D

Автор: romtek 13.02.04, 22:49
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    { Это только интерфейсная часть кода. Целый код модуля находится в архиве }
    Unit XTIMER;
     
    INTERFACE
    Var elapsed: Longint; { прошедшее время, в милисекундах. }
    Procedure ClockOn;    { включает счётчик времени }
    Procedure ClockOff;   { выключает его }
    Procedure PrintTime;  { выводит прошедшее время }
     
    IMPLEMENTATION
     
    END.

тестировалось также на Пентиумах II и III, без использования модуля CRT.
Исходник модуля в архиве.

Автор: Александр. 05.04.04, 13:38
А на TMT можно использовать модуль ZenTimer.

Powered by Invision Power Board (https://www.invisionboard.com)
© Invision Power Services (https://www.invisionpower.com)