Вы находитесь на странице: 1из 5

Вернуться назад

КОМПЬЮТЕР ДОМА

íÓ˜ÌÓ ËÁÏÂÂÌË ‚ÂÏÂÌË


в программах
Эта статья завершает цикл материалов, начатый в разными ОС, они имеют общие недостатки, в первую
№ 7/01 и адресованный студентам и школьникам, очередь низкую точность и большую погрешность
измерения. Так, в DOS время отсчитывается кванта-
интересующимся программированием. Были рас- ми по 55 мс, в Windows 95 — по 13,7 мс (видимо, эта
смотрены практически все аспекты интерфейса величина зависит от конфигурации ПК), а в Windows
пользователя для спрайтовой игры (за исключени- 98/NT — по 5 мс. Таким образом, получается интер-
вал, который существенно больше принятого для от-
ем звука): перемещение по экрану одного или не- счета измеряемой величины: для DOS — 0,01 с, для
скольких спрайтов, работа с мышью и клавиатурой, Windows — 1 мс. С периодичностью вызова сообще-
вывод текста и чтение файла формата BMP, отсчет ния таймера дело обстоит еще хуже. Их частота не
превышает 18,2 в секунду, а минимальный интервал
времени и некоторые вопросы оптимизации. составляет все те же 55 мс. Кроме того, опыты пока-
Редакции хотелось бы знать мнение читателей, зывают, что приращение времени — величина непо-
стоянная. Попробуйте запустить следующий фраг-
стоит ли в дальнейшем продолжать циклы статей,
мент программы:
адресованные начинающему программисту: ис-
пользование OpenGL для отображения динамич-
t0 := GetTickCount;
ных 3D-сцен; внутренняя логика игр; элементы ис- for i := 0 to 10 do begin
t1 := t0;
кусственного интеллекта, применяемые в компью-
repeat
терных играх, и т. п. t0 := GetTickCount;
until t0 <> t1;
c[i] := t0-t1;
end;
азвание статьи не следует понимать как «опре-

Н деление истинного времени», поскольку речь


здесь пойдет о таком измерении малых интер-
валов времени, которое невозможно выполнить стан-
for i := 1 to 10 do
writeln(i:3,c[i]:3);

дартными средствами ОС. Если это приложение не будет единственным запу-


Программисту приходится довольно часто отслежи- щенным, то вы легко убедитесь в неритмичности сис-
вать время, например в игровых и мультимедийных темных часов.
приложениях, при разработке программ, работающих с Наряду с использованием GetTime и GetTickCount
аппаратной частью ПК, а также при проведении всевоз- можно применить и более радикальные меры: в DOS —
можных тестов. Кроме того, нередко требуется отла- переопределение прерывания таймера, в Windows —
дить критичные ко времени исполнения фрагменты ко- употребление QueryPerformanceCounter. Однако и
да, для чего нужны «часы» с высокой разрешающей здесь есть свои недостатки.
способностью. Тем не менее уже начиная с Pentium средства, при-
Один из методов измерения времени уже был годные для измерения времени, находятся в централь-
описан в журнале , но он возможен только в одной
1
ном процессоре. К ним можно отнести 64-разрядный
ОС (DOS и совместимые с ней). Другие операцион- счетчик циклов тактовой частоты, показания которого
ные системы обладают собственными инструмента- считываются программно. Очевидно, значение такто-
ми измерения времени, например Windows включает вой частоты достаточно велико, чтобы обеспечить лю-
сообщение таймера или функцию GetTickCount. К бую разумную точность. Беда только в том, что частота
сожалению, хотя такие средства и предоставляются работы у всех процессоров разная и колеблется от 60
МГц до 3 ГГц. Поэтому счетчик следует отградуировать
1
См. «Мир ПК», № 1/02, с.117. с помощью стандартных функций измерения времени

120 Мир ПК, март 2003


КОМПЬЮТЕР ДОМА

Листинг 1

unit timer_dw; j := j*2;


until j > n;
interface GetLessFF := i-1;
end;
function GetTimer_1:dword; {счетчик в 1 мкс (0,5 ч.)}
function GetTimer_50:dword; {счетчик в 50 мкс (1 сут.)} procedure InitTimer;
function GetTimer_1000:dword; {счетчик в 1 мс (20 сут.) } const
function GetCPUtick:int64; {счетчик в тактах проц. } n = 8; {количество измерений}
function GetCPUfreq:dword; {частота процессора в кГц} Const1 : extended = 0.0182; {част.стнд.таймера в кГц}
function GetDelta:dword; {погрешность частоты в кГц } var
procedure InitTimer; {реинициализация таймера} i,j : longint;
t : array[0..n]of extended; {результаты измерения
implementation тактов}
uses WinDos; d : array[0..n]of extended; {результаты измерения
времени, мс}
var t0,t1 : extended;
b1,b50,b1000,delta : longint; {коэффициенты деления} h_,m_,s_,d_,d1_ : word; {для определения времени}
C1,c50,c1000 : longint; {маски} sti,sta : extended; {сумма тиков; сумма тактов}
begin
function GetCPUfreq:dword; {частота процессора в кГц} t0 := getCPUtick;
begin sti := 0;
GetCPUfreq := b1000; for i := 0 to n do begin {формируем массив измерений}
end; GetTime(h_,m_,s_,d_);
repeat
function GetDelta:dword; {погрешность частоты в кГц } GetTime(h_,m_,s_,d1_);
begin until d1_ <> d_;
GetDelta := delta; t[i] := getCPUtick-t0;
end; if d1_ > d_ then
d[i] := (d1_-d_)*10
function GetTimer_1:dword; assembler; else
asm d[i] := ((d1_+100)-d_)*10;
db $0f,$31 {rdtsc} sti := sti + d[i];
and edx,c1 end;
div b1 sti := sti - d[0]; {время измерения в мс}
end; sta := t[n] - t[0]; {время измерения в тактах}
if ((n/sti*0.8) > Const1) or { если Const1 }
function GetTimer_50:dword; assembler; ((n/sti*1.2) < Const1) then { отличается }
asm Const1 := n/sti; { более 20% }
db $0f,$31 {rdtsc} for i := 0 to n-1 do begin {вычисляем за 1 тик}
and edx,c50 t[i] := (t[i+1]-t[i]);
div b50 end;
end;
for i := 1 to n-1 do {сортируем массив}
function GetTimer_1000:dword; assembler; for j := n-1 downto i do
asm if t[j] < t[j-1] then
db $0f,$31 {rdtsc} begin
and edx,c1000 t0 := t[j]; t[j] := t[j-1]; t[j-1] := t0;
div b1000 end;
end; j := n div 4; {отбрасываем ~ по 1/4 с каждой стороны}
t0 := 0;
function GetCPUtick:int64; for i := j to n-1-j do { берем середину }
var q : int64; t0 := t0 + t[i]; { отсортированного массива }
begin t0 := t0 / (n-j-j); {ср.кол-во тактов за тик}
asm t1 := t0 * Const1; {ср.кол-во тактов за мс}
db $0f,$31 {rdtsc} b1000 := round(t1); { вычисляем }
mov dword ptr [q],eax b50 := round(t1 / 20); { коэффициенты }
mov dword ptr [q+4],edx b1 := round(t1 / 1000); { деления }
end; c1 := GetLessFF( b1); { вычисляем }
getCPUtick := q; c50 := GetLessFF( b50); { значения }
end; c1000 := GetLessFF(b1000); { маски }
t1 := 0;
function GetLessFF(n:longint):longint;{возвращение for i := j to n-1-j do { оцениваем }
бинарное} t1 := t1 + sqr(t[i]-t0); {погрешность}
{число вида 00...0011...11 меньшее заданного} delta := round(sqrt(t1/(n-j-j))*Const1);{ измерения }
var i,j : longint; end;
begin
j := 1; begin
repeat InitTimer;
i := j; end.

Мир ПК, март 2003 121


КОМПЬЮТЕР ДОМА

ОС. Конечно, по абсолютной точности он будет усту- ксимальное число, не превосходящее делитель, все
пать стандартным средствам для измерения больших биты которого установлены в «1». Если маски не вы-
интервалов времени (например, погрешность может со- числять, а задавать константами, то на медленных
ставить несколько секунд за час — из-за неточности процессорах будет слишком маленький период обну-
градуировки), но при определении малых интервалов, а ления, а на тех, что ожидаются в будущем, вероятно,
также при вычислении отношения длительности двух произойдет аварийное завершение программы из-за
измерений его точности вполне достаточно. ошибки деления.
Измерение времени на основе внутреннего счет- Чтобы поточнее определить частоту ЦП, измере-
чика процессора реализовано в модуле, приведенном ния нужно проводить несколько раз, далее их массив
в листинге 1. Он содержит три функции для получе- сортируется и при вычислении берется только его
ния отсчетов времени, функцию для получения пол- центральная часть (наибольшие и наименьшие ре-
ного 64-разрядного значения счетчика (вдруг она ко- зультаты отбрасываются). Таким способом удается
му-нибудь понадобится), а также (в качестве побоч- отсечь отдельные измерения с большой погрешно-
ного продукта) функции, возвращающие измерен- стью, характерные для многозадачных ОС и вызван-
ную частоту процессора и предполагаемую погреш- ные разделением времени.
ность ее измерения. Модуль написан для TMT Pascal 4.0 и совместим с
Наличие трех функций измерения объясняется тем, любой из целевых платформ: DOS, Windows или OS/2,
2
что использовать 64-разрядные числа в большинстве причем в двух последних, как консольное приложение .
случаев неудобно (пока еще не нашли широкого при- Правда, при работе в стандартной оконной среде
менения 64-разрядные процессоры), а 32-разрядный Windows в приложениях, использующих кодовую стра-
счетчик, работающий на высокой частоте, довольно ча- ницу 866, возникают проблемы с кириллицей. Поэтому
сто обнуляется. Поэтому для измерения коротких ин- здесь я изменил своему правилу применять в програм-
тервалов времени следует предпочесть счетчик с макси- мах сообщения на русском языке. Другой способ реше-
мальным разрешением, равным 1 мкс, а в остальных ния проблемы описан во врезке.
случаях — с более грубым. Если необходимо переделать модуль для одно-
Градуировка модуля измерения времени выполня- платформного компилятора, не поддерживающего
ется в блоке инициализации, но эта процедура дос- совместимую с DOS процедуру GetTime, то послед-
тупна и извне. Она бывает нужна тогда, когда точ- нюю следует заменить стандартной операцией для
ность определения тактовой частоты процессора при выбранной ОС. Например, для Windows целесооб-
загрузке может показаться недостаточной. Пример разно выбрать GetTickCount, и тогда из цикла изме-
использования некоторых функций модуля приведен рения можно будет убрать дополнительную провер-
в листинге 2. ку на обнуление сотых долей секунды. Константы
В блоке инициализации модуля происходят под- модуля подобраны для оптимальной работы при ин-
счет частоты процессора, оценка погрешности вы- тервале приращения показаний системных часов,
числения, а также определение коэффициентов деле- равном 55 мс, но модуль сохраняет работоспособ-
ния для всех точек входа и масок для них. Последние
необходимы для того, чтобы предотвратить перепол-
нение при целочисленном делении 64-разрядного 2
Точнее, сам модуль сохраняет работоспособность и в GUI, но при-
числа на 32-разрядное. Маска представляет собой ма- мер программы осуществляет вывод в консольное окно или файл.

Как работать с текстом в кодировке 866 из оконной среды Windows


При разработке консольных приложений и DOS-программ с помо- дет отображаться на экране в соответствии с кодовой страницей 866. В
щью оконной среды (IDE) Windows возникает одна проблема, характер- среде ТМТ Паскаль для этого нужно внести изменения, выбрав
ная только для нашей страны, а именно, несоответствие кодировок ки- Options•Environment•Display•Font name.
риллицы: в DOS и консольном режиме используется кодовая страница С клавиатурой несколько сложнее. Необходимо установить кодовую
866, а в оконной среде — 1251. Проблема не является типично паска- таблицу 866 подобно тому, как устанавливаются любые языки, напри-
левской, с ней сталкиваются все программисты, которые, используя мер немецкий или итальянский. Для этого проще всего воспользовать-
оконную среду, пишут русскоязычные программы для DOS или консоли. ся программой RusLat227, которую можно переписать по адресу
Фактически задача разделяется на две части: достигнуть правильного http://www.netcity.ru/~sergb. Значительно удобнее поместить ссылку на
отображения на экране текста в кодировке 866 и добиться соответствия эту утилиту в папку «Автозагрузка», и тогда в Панели задач постоянно
странице 866 кодов, выдаваемых клавиатурой. будет присутствовать индикатор раскладки. Стандартный же индикатор
Первая часть решается просто: в качестве шрифта для отображения лучше убрать. Для этого необходимо выбрать опции «Пуск•Настрой-
текста программы следует указать Terminal. Именно его используют ка•Панель управления•Клавиатура•Язык» и выключить функцию «Отоб-
консольные приложения, и после его применения текст программы бу- ражать индикатор языка на панели задач».

122 Мир ПК, март 2003


КОМПЬЮТЕР ДОМА

Листинг 2 циентов деления. Здесь необходимо пойти на опре-


деленный компромисс: при росте числа измерений
uses timer_dw; (константа n) повышается точность последних, но в
этом случае увеличивается время, приходящееся на
const Nrep = 24; {количество измерений}
var них и, естественно, тот период, в течение которого
i,l : longint; пользователю придется простаивать в ожидании за-
f,d : array[0..Nrep-1]of longint;{частота и ее грузки программы. При уменьшении числа измере-
отклонение}
begin ний наблюдается обратная зависимость. Думаю, что
l := 0; время измерения не более 0,5 с можно считать впол-
for i := 1 to Nrep do begin не приемлемым. А если опираться на приращение
f[i] := GetCPUfreq;
d[i] := GetDelta; времени, равное 5 мс, то лучше задать следующие
writeln(‘CPU frequency:’,f[i]:8,’ +/-’,d[i]:6,’ kHz’); значения констант:
inc(l,f[i]);
InitTimer;
end;
l := l div Nrep; const
writeln(‘Average: ‘,l); n = 100;
writeln(‘Press Enter’); Const1 : extended = 0.2;
readln;
for i := 0 to Nrep-1 do
writeln(‘CPU frequency:’,f[i]:8,’ +/-’,d[i]:6,
kHz (real:’,f[i]-l:6,’)’); Можно изменять n таким образом, чтобы время из-
end. мерения не превышало фиксированной величины, на-
пример 500 мс. Однако для этого следует описать разме-
ры массивов не менее чем 501 — на случай, если вдруг
ность и при любых других, что, правда, порой ска- приращение времени составит 1 мс.
зывается на точности вычисления тактовой частоты В таблице приведены процедуры измерения време-
процессора и, следовательно, на точности коэффи- ни. Вот несколько рекомендаций по их применению.

Мир ПК, март 2003 123


КОМПЬЮТЕР ДОМА

Характеристики процедур измерения времени


Процедура Разрядность Единица Интервал Абсолютная Относительная Примерный Время
измерения обновления погрешность погрешность период измерения
обнуления
GetTime 32 10 мс 55 мс 5 мс Нет В начале суток 28,0* мкс
GetTickCount 32 1 мс 5 мс 2—4 мс Нет 49,7 суток 0,04* мкс
QueryPerformanceCounter 64 0,8 мкс 0,8 мкс 3—5 мкс Нет 500 000 лет 3—5** мкс
GetTimer_1 32 1 мкс 1 мкс 0,5 мкс ~0,003—0,3% 35 — 70 мин 0,2* мкс
GetTimer_50 32 50 мкс 50 мкс 25 мкс ~0,003—0,3% 1,3 — 2,5 суток 0,2* мкс
GetTimer_1000 32 1 мс 1 мс 0,5 мс ~0.003—0,3% 25 — 49,7 суток 0,2* мкс
GetCPUtick 64 1 такт 1 такт 32 такта Нет Более 200 лет ~100 тактов
* Для процессора Celeron-667. Для других процессоров может быть больше или меньше.
** Время не зависит от процессора, так как определяется шиной ISA или набором микросхем.

GetTime — предназначена для получения показа- измерений. Впрочем, хороший стиль программирова-
ний астрономического времени в часах, минутах и се- ния требует, чтобы подобные ситуации специально от-
кундах. С некоторой натяжкой может быть использо- слеживались. И тогда процедуру можно будет безболез-
вана для измерения временны́х интервалов от не- ненно использовать для измерения времени в интерва-
скольких секунд (при условии, что требуется точ- ле до получаса.
ность не ниже 1%). Данная процедура неудобна из-за GetTimer_50 — наверное, наилучший выбор для
того, что нужно отслеживать переходы на следую- программ, написанных для использования на ком-
щие секунду, минуту, час, а также сутки. Она работа- пьютере дома или в офисе. Счетчик переполнится
ет медленно из-за многочисленных промежуточных лишь спустя сутки с небольшим после последней
вызовов и преобразований. В 24 ч 00 мин 00 с показа- перезагрузки ПК, поэтому если компьютер ежеднев-
ния сбрасываются. но выключается или хотя бы перезапускается, то пе-
GetTickCount — подходит для измерения интерва- реполнение счетчика не грозит. В то же время про-
лов времени от 0,5 с в случае, если допустима погреш- цедура обеспечивает достаточную точность, напри-
ность в пределах 1%. При этом она предпочтительнее мер, при определении того, как распределяется вы-
всех остальных для измерения больших интервалов числительная нагрузка между разными блоками
времени (в пределах нескольких суток), так как в отли- при формировании одного кадра для игр в режиме
чие от GetTime не требует преобразований. Эта процеду- реального времени.
ра позволяет определить время в удобных единицах и GetTimer_1000 — несколько точнее, чем GetTick-
не несет в себе погрешности измерения частоты про- Count на небольших интервалах (до нескольких се-
цессора. Данная процедура — самая быстрая, она вы- кунд), и менее точна на больших вследствие погреш-
полняет наиболее простую работу, а именно читает зна- ности определения частоты. А если требуется знать
чения переменной из оперативной памяти или даже из не действительное значение времени, а отношение
кэш-памяти процессора. длительности двух измеренных интервалов, то всегда
QueryPerformanceCounter — опрашивает при ра- обеспечивает более высокую точность, чем
боте порты таймера, подключенного по шине ISA, GetTickCount. Кроме того, может использоваться в
или ее аналога в современных наборах микросхем. Ра- среде любой ОС.
ботает очень долго — несколько миллисекунд, что су- GetCPUtick — для измерения длительности выпол-
щественно искажает результаты измерения. Поэтому нения коротких фрагментов кода в тактах процессора, а
диапазон ее применения начинается с интервалов в также в качестве основы для разработки других систем
сотни микросекунд. Кроме того, сама единица измере- измерения времени.
ния довольно неудобна. Тем не менее процедура не InitTimer — применяется для реинициализации тай-
требует градуировки, возвращаемое ею значение ни- мера, т. е. для вычисления частоты процессора и всех
когда не переполняется и имеет достаточно высокое необходимых констант заново. Пример использования
разрешение, так что она может быть полезна тогда, дается в листинге 2.
когда нужно использовать лишь одну операцию в В заключение следует еще раз обратить внимание на
очень широком диапазоне: от долей миллисекунд до то, что функции GetTimer_XX нежелательно приме-
нескольких месяцев и даже лет. нять для определения времени суток, так как из-за по-
GetTimer_1 — применяется от десятков микросе- грешности измерения оно не будет совпадать с тем, ко-
кунд до десятков секунд, если, конечно, не хочется «на- торое показывают системные часы. ■
тыкаться» на переход через 0 чаще одного раза на 100 Сергей Андрианов

124 Мир ПК, март 2003