На главную
ПРАВИЛА FAQ Помощь Участники Календарь Избранное DigiMania RSS
msm.ru
! Правила ЧаВо (FAQ) разделов Паскаля
В этом разделе разрешено создавать только темы, в которых описано РЕШЕНИЕ какой-либо общей проблемы, или описание какого-либо аспекта языка Паскаль.
Обсуждение уже созданных тем разрешено, но только конструктивное, например указание на ошибку или уточнение имеющегося текста.

Также читать Требования к оформлению статей
Модераторы: volvo877, Romtek
  
> Как замерять время выполнения кода?, Например сколько времени выполняется алгоритм
    Самым простым способом является одновременно самый очевидный:
    Узнаём и запоминаем время, потом делаем нужное нам действие, и сразу после него опять узнаём и запоминаем время.
    Разница между временем до действия и временем после действия и будет временем выполнения действия.

    В паскале есть стандартная процедура модуля 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 секунды. Но ведь это неверно %)
    Так получилось из-за того, что за время, пока выполнялась программа началась новая минута. Если же преобразовать всё время к количеству сотых частей секунды, то такой проблемы не возникнет.
    Вот пример действующего кода:
    ExpandedWrap disabled
      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:

    ExpandedWrap disabled
      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
    Сообщение отредактировано: volvo877 -
      Совсем забыл, что хотел запостить :(

      Для процессоров выше i486 существует способ очень точно сравнить время выполнения двух участков кода -- ето встроенный в процессор счетчик тактов. Ниже приведены два исходника -- процедура, считывающая значение етого счетчика, и пример ее использования на BP7.
      tsc.asm
      ExpandedWrap disabled
                .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
      ExpandedWrap disabled
        {$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. Обратите внимание и сделайте выводы, как много времени занимает вывод на екран. :)
      Прикреплённый файлПрикреплённый файлtsc.zip (0.93 Кбайт, скачиваний: 387)
      В соответсвтии с Первой Поправкой Марселя, "hello, world!" на древнем языке C следует произносить так:
      main(t,O){int _=main;char m[]=",!((+hd3+6( e";return O==_?((int(*)())O)(_,t+1),68:
      t==_?(*(char*)O?*(char*)O^=((int(*)())t)(O,t):17),O:printf(((int(*)())_)(_,m));}
      все здесь
        Если не хочется подключать модуль DOS, замерить время можно с помощью взятия значения напрямую из памяти по адресу $0046C (4 байта). Там хранится число "тиков" встроенных часов реального времени, которые тикают 18.54 раз в секунду. И именно оттуда тащат его функции модуля.
        Если у вас все еще нет процессора Pentium (или есть, но нужно измерять время на процессоре ниже него), можно разогнать встроенный таймер, при этом нужно будет использовать встроенный ассемблер. Вот этот код по-моему достаточно откомментирован:
        ExpandedWrap disabled
          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)), иначе процедура не будет работать :(
        Таймер программируется вот так:
        ExpandedWrap disabled
          {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 и нормальной частоте прерываний. Т.е. код для возврата:
        ExpandedWrap disabled
          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, то счет идет. Вне зависимости от нее, старый вектор прерывания вызывается с нужной частотой.
        Долог путь в бессмертие... я еще вернусь.
        Профильный скилл "Телепатия" 8%
        ТРОЛЛЬ - Троян Разрушительный Опасный, Лучше ЛинятЬ (с) Freezing Spell
        Прошу потестить игру.
          Не стал править предыдущий пост, он не расконвертился :(
          Насчет таймера в памяти ДОС:
          При каждом срабатывании прерывания 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с.
          Например, для тестовых задач оно определяется вот так:
          ExpandedWrap disabled
            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 Например, вам нужно узнать, сколько времени ваш ребенок (если есть, иначе ученик ;) ) решал задачку в уме. Делаем так:
          ExpandedWrap disabled
            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
          Долог путь в бессмертие... я еще вернусь.
          Профильный скилл "Телепатия" 8%
          ТРОЛЛЬ - Троян Разрушительный Опасный, Лучше ЛинятЬ (с) Freezing Spell
          Прошу потестить игру.
            ExpandedWrap disabled
              { Это только интерфейсная часть кода. Целый код модуля находится в архиве }
              Unit XTIMER;
               
              INTERFACE
              Var elapsed: Longint; { прошедшее время, в милисекундах. }
              Procedure ClockOn;    { включает счётчик времени }
              Procedure ClockOff;   { выключает его }
              Procedure PrintTime;  { выводит прошедшее время }
               
              IMPLEMENTATION
               
              END.

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

            Прикреплённый файлПрикреплённый файлxtimer.zip (1.32 Кбайт, скачиваний: 333)
              А на TMT можно использовать модуль ZenTimer.
              0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
              0 пользователей:


              [ Script Execution time: 0,1108 ]   [ 17 queries used ]   [ Generated: 24.05.17, 17:32 GMT ]