http://sulfurzona.com/
News
Service
Magazine
Software (Battle City Game, Wallpaper manager, Superpad, VG-NOW, Puzzle Game, Netler Internet Browser, ..)
Dune Game (Dune III, Dune IV, Cheats, Forum, ..)
Games free
Turbo Pascal (Assembler, Docs, Sources, Debbugers, ..)
Books (Docs for developers)
Forum
Guest book
Компьютерная диагностика двигателя автомобиля (адаптер К-линии)Компьютерная диагностика двигателя автомобиля (адаптер К-линии)
 
 
Продать часы
Клуб любителей Honda Civic. Подбор по производителю.
chasovshik.ru
Айроны
Клюшки айрон.Все виды клюшек для гольфа с доставкой
golf-store.ru

Паскаль для новичков (часть 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 и много другой полезной информации для программистов, включая примеры решения реальных задач по созданию резидентных программ. Предлагаю также посетить Марья искусница - сайт о рукоделии (http://mariya-iskusnica.ru).
 

Журнал > Программирование > Паскаль для новичков (Turbo Pascal, Assembler) > Паскаль для новичков (часть 10): Спрашивали? Отвечаю…
 
 
 
1173
 
ВКонтакте
Facebook
 
 
 
На главную страницу На предыдущую страницу На начало страницы
 
 
Украинский портАл Украина онлайн Рейтинг@Mail.ru Рейтинг Сайтов YandeG Rambler's Top100