http://sulfurzona.ru/
News
Service
Magazine
Software (Battle City Game, Wallpaper manager, Superpad, VG-NOW, Puzzle Game, Netler Internet Browser, ..)
Wing-Thunder Game (fly simulator)
Dune Game (Dune III, Dune IV, Cheats, Forum, ..)
Games free
Turbo Pascal (Assembler, Docs, Sources, Debbugers, ..)
Books (Docs for developers)
Guest book
Компьютерная диагностика двигателя автомобиля (адаптер К-линии)Компьютерная диагностика двигателя автомобиля (адаптер К-линии)
 
 
Скачать игру Крыло-Гром (Wing-Thunder) бесплатно
 
 

Паскаль для новичков (часть 10)

Спрашивали? Отвечаю…

 
Автор: Владислав Демьянишин
 
Паскаль для новичковСначала хочу поблагодарить читателей за их письма. Есть письма разные: с благодарностью, с критикой, а есть письма читателей, которым не терпится попробовать свои знания на практике и они торопят меня, задают интересные вопросы и не дают расслабиться в такую весеннюю погоду, когда ласково пригревает солнце и щебечут воробьи. Что-то на лирику потянуло
 
 
Спрашивает читатель Александр: "Почему, если есть переменные W:word, L:longint и есть присваивание W:=65535, то конструкция writeln(w*32535) или L:=w*32535 дает неверный результат 33001, а конструкция writeln(65535*32535) или L:=65535*32535 возвращает верное значение 2132181225?"
 
Все дело в том, что в первом случае оба операнда (переменная W и константа) имеют один и тот же тип word и компилятор формирует соответствующий код арифметической операции над двумя операндами типа word. И даже конструкция L:=w*32535 все равно предполагает сперва выполнение выражения, а потом уже присваивание полученного результата переменной типа longint.
 
Во втором случае оба операнда (непосредственные значения, т.е. константы 65535 и 32535) удовлетворяют типу word. Но так как в данном случае компилятор сначала вычисляет результат такого константного выражения, а потом уже формирует код по присваиванию полученного результата переменой, то тип полученного результата приводится к типу переменной-получателю.
 
Т.е. в данном случае формируется не код вычисления результата выражения, а код присваивания, так как результат можно вычислить еще на этапе компиляции.
Выходом из создавшейся ситуации может служить конструкция предварительного присваивания L:=w и выражение L:=L*32535. Вот тогда и получим заветное значение типа longint без риска потерять старшие разряды числа.
 
Таким образом, читатель столкнулся с ситуацией скрытого переполнения, что также является ошибкой, но не может быть обнаружено на этапе компиляции, и при выполнении программы не приведет к возникновению ошибки "Error 201: Range check error" (ошибка при проверке границ), а значит, не даст о себе знать на этапе разработки и отладки.
Рассмотрим еще один пример:
 
{$R+}
var W : Word;
      L : LongInt;
 
begin
{ первый случай }
L:= $10000*$10000; { явное переполнение }
writeln(L); {на экране 0}
W:=L; { W=0 }
{ второй случай }
L:= $1000*$10000; {значение в пределах longint}
writeln(L); {на экране 268435456 }
W:=L; { место возникновения ошибки }
end.
 
В первом случае результат выражения $10000*$10000 выходит за пределы типа longint таким образом, что оставляет в переменной L значение 0 и не вызывает ошибки. А нулевое значение, как известно, не противоречит типу word переменной W.
Во втором случае результат от $1000*$10000 дает значение в пределах типа longint, и естественно превышает диапазон типа word, и возникает ошибка "Error 201: Range check error" (ошибка при проверке границ).
 

Теперь попрактикуемся

 

Снятие временных характеристик программ

 
Бывает чрезвычайно полезно провести оценку сравнительного быстродействия частей программы. Это может иметь большое значение для достижения приемлемой производительности программы и выявления неоптимальных участков кода.
Таким образом, программисту необходим инструмент для измерения интервала времени, затрачиваемого на выполнение некоторой задачи определенным участком кода составляемой программы.
 
Первый метод измерения интервалов времени основан на чтении счетчика системного таймера, находящегося в области данных BIOS по адресу $0040:$006C и занимающего 4 байта памяти. Этот счетчик изменяет свои показания каждую 1/18.2 секунды, увеличиваясь на единицу.
Необходимо описать переменную, расположенную в памяти по известному адресу
 
var SystemTimer : longint absolute $40:$6c;
 
Чтобы не сбить показания системных часов, не следует записывать в эту переменную что-либо. Нам лишь следует из нее читать показания системного таймера, и для этого опишем процедуру чтения этой переменной.
 
function ReadTimer : longint;
begin
ReadTimer := SystemTimer;
end;
 
А вот пример использования этой процедуры:
 
var start, finish, j : longint;
begin
start := ReadTimer;
{процесс, исследуемый на производительность}
for j := 0 to do begin
end;
finish := ReadTimer;
writeln('Время : ',(finish-start)/18.2:5:2,' сек.');
end.
 
Главное достоинство такого способа замера времени есть его простота, а его недостаток состоит в том, что точность замера интервалов времени ограничена 1/18.2 секунды, т.е. около 55 мсек.
 
Таким образом, если исследуемый процесс выполнится быстрее, чем за 55 мсек, то получим нулевой интервал времени. Чтобы избежать этого, можно задать цикл for с небольшим количеством повторений исследуемого процесса, чтобы выявить ничтожно малую величину времени, затраченного на выполнение данного процесса. При использовании этого способа переменной типа longint может хватить на измерение интервала продолжительностью до 32776 часов, а это около 4 лет. У кого хватит терпения ;O)
 
Если возникает необходимость осуществить паузу, то первое, что приходит на ум, это функция delay модуля CRT.PAS, которая осуществляет задержку выполнения программы на заданное количество миллисекунд. Но каково было мое удивление, когда я смог добиться полусекундной задержки строкой delay(50000), хотя с таким параметром должна была получиться пауза в 50 секунд. А все потому, что на процессорах, начиная с Celeron, код этой функции работает не так, как на старых процессорах.
Поэтому, хочу предложить универсальный способ осуществления задержки, который будет работать независимо от процессора x86. Вот код необходимой процедуры задержки:
 
procedure Pause( p : longint );
var T : longint;
begin
T := ReadTimer + p;
repeat until T <= ReadTimer;
end;
 
При ее применении паузу следует задавать в 18-х долях секунды, т.е. Pause(1) - 55 мсек, Pause(18) - одна секунда, Pause(1092) - одна минута.
 
Второй метод заключается в чтении счетчика канала щ0 микросхемы системного таймера, который изменяется с частотой 1193180 Гц (т.е. 1193180 раз в секунду) и позволяет добиться точности в 0.838 мксек. Это реализуется простой функцией:
 
function ReadTimerChipCount : word;
var frec : word;
begin
frec := port[$40];
frec := frec or (port[$40] shl 8);
ReadTimerChipCount := frec;
end;
 
т.е. читаем из порта $40 сначала младший байт, а затем старший байт двухбайтного счетчика.
Но полученное таким образом значение непригодно для использования без дополнительной обработки так, как этот счетчик непрерывно уменьшается на единицу, и варьируется в пределах 0..65535 из-за того, что BIOS при загрузке компьютера устанавливает коэффициент пересчета счетчика (регистр задвижки) данного канала в 65535. А нам необходимо нарастающее число. Следовательно, чтобы получить нарастающее число, нужно использовать выражение 65535-ReadTimerChipCount. Помимо этого необходимо еще к полученному значению добавить количество 1/18.2 долей секунды, умноженных на коэффициент пересчета, чтобы получить правильное значение времени. Вот функция, обеспечивающая все необходимое:
 
function ReadOscelator : longint;
begin
ReadOscelator := ((ReadTimer and $7fff)*$10000) or (65535-ReadTimerChipCount);
end;
 
Бесспорным достоинством этого метода является его высокая точность. А недостаток заключается в том, что переменной типа longint для хранения измеренного интервала времени может хватить на 30 минут. Хотя на практике приходится замерять интервалы времени, исчисляемые несколькими секундами.
 
Хочу снова вернуться к проблеме, связанной со стандартной функцией delay. А что, если попытаться создать аналог этой функции. Такую процедуру можно назвать этим же именем, но чтобы при использовании модуля CRT.PAS не возникало проблем, назовем ее так:
 
procedure NewDelay( ms : word );
const k = 1193180/1000;
var T : longint;
begin
T := ReadOscelator + trunc( ms*k );
repeat until T <= ReadOscelator;
end;
 
Константа k содержит число тактов микросхемы системного таймера, проходящих за одну миллисекунду. Затем этот коэффициент умножаем на количество заданных миллисекунд, и добавляем результат к общему числу прошедших тактов микросхемы системного таймера, с тем, чтобы потом ожидать нужного нам такта. Таким образом, формируется задержка в заданное количество миллисекунд.
 

Определение частоты центрального процессора

 
Иногда возникает необходимость определить частоту процессора. В числе машинных команд имеется команда RDTSC, которая возвращает в 32-разрядных регистрах EDX:EAX количество тактов процессора, произошедших с момента его сброса. Счетчик тактов процессора является 64-разрядным и его может хватить на 585 лет при частоте CPU 1 ГГц. При включении (сбросе) процессора счетчик тактов обнуляется. Чтобы из этого счетчика вычислить частоту процессора в МГц надо измерить несколько интервалов времени, например по системному таймеру (длительностью 1/18.2 c) и получить среднюю длительность в тактах процессора. Затем умножить эту величину на 18.2 (лучше умножить на 1193180 - частота таймера в Гц и разделить на 65536 - коэффициент пересчета микросхемы таймера и тогда получим более точное умножение на 18.2). Результат нужно разделить на 1000000, чтобы из Гц получить МГц.
 
Доступ к команде RDTSC контролируется флагом TSD в управляющем регистре CR4 процессора (если флаг сброшен, команда выполняется при любом уровне привилегий выполняемой программы, а если установлен - то только при уровне привилегий 0). Как показала практика, в задаче MS-DOS под Windows 98 такой метод работает нормально. Он так же работает и в реальном режиме центрального процессора, т.е если загрузить машину не Windows, а обычным MS-DOS (command prompt only).
 
Чтобы получить значение счетчика тактов процессора придется повозиться, так как компилятор Turbo Pascal не знает о существовании машинной команды RDTSC. Мало того, компилятор не в состоянии компилировать простые машинные команды, использующие 32-разрядные регистры. Поэтому, в моем представлении необходимая функция может выглядеть так:
 
function GetCPUClock : longint; assembler;
asm
db 0fh,31h {команда RDTSC, теперь значение счетчика в
EDX:EAX}
mov bx,00fh
db 66h,0c1h,0e3h,10h {shl ebx,16}
mov bx,4240h {в EBX загружено число 1000000}
db 66h,0f7h,0f3h {div ebx ;делим счетчик на миллион}
db 66h,8bh,0d8h {mov ebx,eax}
db 66h,0c1h,0e8h,10h {shr eax,16}
db 66h,33h,0d2h {xor edx,edx}
mov dx,ax
db 66h,8bh,0c3h {mov eax,ebx}
end;
 
она возвращает количество миллионов тактов процессора, произошедших со времени включения компьютера.
Ну вот, теперь осталось составить функцию окончательного определения частоты процессора
 
function GetCPUFrec : word;
var Start, Finish, T : longint;
begin
T := ReadTimer + 1;
repeat until T <= ReadTimer;
{ждем момента обновления системного счетчика, чтобы свести погрешность к минимуму}
Start := GetCPUClock;
T := ReadTimer + 18;
repeat until T <= ReadTimer;
{ждем в течении одной секунды}
Finish := GetCPUClock;
GetCPUFrec := Finish - Start;
end;
 
которая возвращает количество миллионов тактов процессора, произошедших за одну секунду, что и является искомой частотой процессора в МГц. Эту функцию можно применять для машин, включенных менее 24 часов подряд и для процессоров ниже 35 ГГц.
Ну а до этого еще далеко, так что можно быть спокойным.
 
Хочу добавить, что машинная команда RDTSC доступна начиная с процессоров Pentium (5x86), во всяком случае, в руководствах по процессорам i386, i486 такая команда не упоминается.
 
Все эти функции для удобства можно собрать в единый модуль и назвать его, например, profiler, как это сделал я.
 
Литература
1. Р. Джордейн. Справочник программиста персональных компьютеров типа IBM PC, XT и AT. - М.: Финансы и статистика, 1992. - 543 с.
2. Диалоговая справочная система Norton Guide.
 
Продолжение следует…
 
© Владислав Демьянишин
 
На нашем сайте можно не только бесплатно скачать игры, но и документацию и книги по программированию на MIDLetPascal, Turbo Pascal 6, Turbo Pascal 7, Borland Pascal, по программированию устройств Sound Blaster, Adlib, VESA BIOS, справочник Norton Guide и много другой полезной информации для программистов, включая примеры решения реальных задач по созданию резидентных программ.
 

Журнал > Программирование > Паскаль для новичков (Turbo Pascal, Assembler) > Паскаль для новичков (часть 10): Спрашивали? Отвечаю...
 
 
 
 
 
 
На главную страницу На предыдущую страницу На начало страницы