Счетчик меток реального времени TSC (Time Stamp Counter) —
регистр, содержимое которого инкрементируется с каждым тактом
процессорного ядра. Каждый раз при аппаратном сбросе (сигналом RESET)
отсчет в этом счетчике начинается с нуля. Разрядность регистра
обеспечивает счет без переполнения в течение сотен лет. Счетчик
продолжает счет как при исполнении инструкции HLT, так и при остановке
процессора по сигналу STPCLK# (для энергосбережения). Чтение счетчика
обеспечивает инструкция RDTSC, установкой бита CR4.TSD ее можно сделать
привилегированной (доступной лишь при CPL = 0). Чтение и запись TSC
возможны также по инструкциям обращения к MSR (при CPL ¦ 0), причем
запись может выполняться только в младшие 32 бита, а старшие биты при
операции записи обнуляются. Присутствие счетчика TSC определяется по
инструкции CPUID (ЕАХ = 1), Если в результате ее вызова бит 4 регистра
EDX равен 1, то процессор поддерживает счетчик меток реального времени
TSC.
Команда RDTSC (ReaD from Time Stamp Counter — чтение
64-разрядного счетчика меток реального времени TSC (Time Stamp Counter))
не имеет операндов. Машинный код этой команды — Of 31. Команда
проверяет состояние второго бита регистра CR4.TSD (Time Stamp Disable —
отключить счетчик меток реального времени):
- если CR4.TSD = 0, то выполнение команды RDTSC разрешается на любом уровне привилегий;
- если CR4. TSD = 1, то выполнение команды RDTSC разрешается только на нулевом уровне привилегий.
Если после данной проверки выполнение
команды разрешено на текущем уровне привилегий, то выполняется
сохранение значения 64-битного MSR-счетчика TSC в паре 32-битных
регистров EDX: ЕАХ. Если выполнение команды запрещено, то работа команды
заканчивается.
Разработаем две макрокоманды, использование которых позволит
нам получать количественную оценку работы кода, начиная от полной
программы и заканчивая отдельной командой. Эти макрокоманды не будут
отличаться компактностью, но этого от них и не требуется, так как они
являются лишь средством оценки эффективности, используемым на этапе
отладки и в конечном коде их наличие не требуется.
На взгляд автора, применение этих макрокоманд должно
подчиняться следующему алгоритму. Вызов первой макрокоманды, назовем ее
profileMn, должен зафиксировать момент, относительно которого будет
производиться отсчет тактов процессора, то есть в начале профилируемого
участка программы. Вызов второй макрокоманды prof i I er_out должен
зафиксировать момент окончания работы на этом участке программы.
Необходимо иметь в виду, что это «грязное» время работы программы, по
которому можно производить только приблизительную оценку ее скорости
работы. Для этого есть внутренняя и внешние причины. Внутренняя причина
заключается в том, что полученная величина включает время, затраченное
на работу некоторых команд, составляющих тело самой макрокоманды. Этот
недостаток исправить легко. Что касается внешних причин, то они
объективны по отношению к программе пользователя и мешают получению
истинного времени профилирования. В качестве таких внешних причин могут
быть программы операционной системы, которые могут приостанавливать на
время программу пользователя. Внешней причиной является и отладчик, так
как при работе в нем вообще нет смысла производить оценку скорости.
Ниже приведены макрокоманды profiler_in и profiler_out с
тестовым примером для проверки их работы. Данные макрокоманды производят
корректировку результата своей работы, с тем чтобы исключить
обсужденные выше внутренние причины «грязного» времени работы программы.
Заметим, что не всякий транслятор ассемблера «знает» о новых командах
микропроцессора, в том числе и о команде RDTSC. По этой причине мы ее
моделируем, инициализируя в сегменте кода 2 байта значениями машинного
кода этой команды db 0fh,31h.
:prg08_01.asm - программа демонстрации
использования макрокоманд profiler_in ;и profiler out для оценки
производительности фрагментов кода.
bin_dec_fpu macro string_bin_qword:REQ. string_pack:REQ.
adr_string_pack:REQ, len_ ~string_pack:REQ, ad^string^EQ. len_string:REQ
local cycl
макрокоманда вывода на консоль десятичного числа:
;на входе:
;string_bin_qword - адрес 64-битной ячейки (описанной dt) с преобразуемым
:двоичным целым числом
:string_pack - адрес 80-битной ячейки (описанной dt), в которую сохраняется
упакованное 10-е значение
:adr_string_pack - ячейка с адресом string_pack (описанной dd)
:len_string_pack - длина string_pack
:adr_string - ячейка с адресом string (описанной dd). В string помещаются
;символы десятичных цифр для вывода.
:len_string - размер string (18 байт)
:.........преобразуем bin->dec
finit
f 11d string_bin_qword :заносим в сопроцессор двоичное целое число fbstp string_pack извлекаем упакованное десятичное
¦-------------распакуем..........
Ids si.adr_string_pack
add si.len_string_pack-2 :на конец string_pack (18 упак. дес. цифр)
les di.adr_string
movcx.9 ;9 пар упакованных десятичных цифр
xor ax,ax cycl: xor ax,ax
std ;string_pack обрабатываем с конца
lodsb ;в al очередные 2 упакованные десятичные цифры
распаковываем - аИ=младшая, а1=старшая
shi ax.4
rol al .4
or ах.ЗОЗОп :преобразуем в символьное представление
xchg ah .al :ап-младшая. al-старшая
eld :в string записываем с начала
stosw
loop cycl
;.........выводим на консоль...............................
' mov bx.l стандартный дескриптор - экран
mov cx.len_string
Ids dx.adr_string -.формируем указатель на строку string
mov ah.40h :номер функции DOS
int 21h :выводим
jc exit ;переход в случае ошибки
endm
profileMn macro val_l:REQ
;val_l - ячейка памяти 64 бита (2x32) для сохранения момента :начала
профилирования ("грязного") pushad сохранение всех регистров общего
назначения в стеке
db Ofh.31h;RDTSC mov val_l+4.edx
mov va!_l.eax popad восстановление всех регистров общего назначения из стека
endm
profiler_out macro val_l:REQ. val_2:REQ
:val_l - ячейка памяти 64 бита (2x32). 8 которой при входе в макрос
сохранен :момент начала профилирования командой profilerjn. Далее в
макросе эта ячейка :содержит результат профилирования - число тактов
процессора :val_2 - ячейка памяти 64 бита (2x32). в которой сохраняется
момент ("грязный") окончания профилирования
pushad сохранение всех регистров общего назначения в стеке db 0fh,31h :RDTSC - окончание профилирования
mov val_2+4.edx
mov val_2,eax
профилируем pushad и popad с учетом двух shrd pushad popad
db 0fh.31h;RDTSC
:теперь необходимо получить чистое время профилирования, для чего
результат необходимо скорректировать (уменьшить) на количество тактов
процессора, :потребное для выполнения пар команд PUSHAONPOPAD и M0V
subeax, val_2
jnc $+4 :учет заема из старшего разряда
dec edx
subedx. val_2+4 ;в edx:eax кол-ва тактов для выполнения 2-х команд ;PUSHAD\POPAD и 2-х SHRD
mov eax,val_l
sub val_2.eax
mov eax.val_l+4
jnc $+7 :учет заема из старшего разряда при выполнении предыдущего вычитания
dec val_2+4
sub val_2+4,eax
;в val_2:val_2+4 - чистое количество тактов процессора для выполнения профилируемого участка
popad восстановление всех регистров общего назначения из стека
:выводим
bin_dec_fpu val_2_q. string_pack, adr_string_pack, len_string_pack. adr_string. len_string
endm .data
val_2 label dword val_2_q dq 0 val_l label dword
dq 0
:b string_pack исходное значение из val_2_q в упакованном десятичном формате string_pack dt О
len_string_pack=$-string_pack adr_stringpack dd string_pack
string db 18 dup (0) максимальный результат состоит из 18 десятичных цифр
len_string=$-string adr_string dd string
.code
профилируем выполнение команд работы со стеком profiIer_i n val_l
push eax
pop eax
profiler_out val_l. val_2 exit: :выход из программы
Составьте тестовые примеры и «поиграйтесь» с данной
программой. Обратите внимание, что при задании пустой последовательности
команд между парой макросов profilerjn и profi1er_out все равно
получается некоторая величина профилирования. Она постоянна, ее источник
— сами команды RDTSC, которые требуют тактов процессора для своего
исполнения. Эту величину можно скорректировать разными способами, но
можно и не трогать, а учитывать при подведении окончательных результатов
тестирования нужного вам фрагмента кода. На компьютере автора эта
величина равна 52.
В этой программе для визуализации результатов профилирования
разработана макрокоманда bi n_dec_fpu, производящая перевод значения из
двоичной системы в десятичную.