Предыдущие части:
1. Общие сведения. Legacy-таймеры PIT и RTC.
2. Шина PCI – таймер менеджера питания ACPI.
3. ACPI таблицы – таймер HPET.
4. Таймер контролёра Local-APIC.
5. Счётчик процессора TSC.
6. Win - профилирование кода.
---------------------------------------
Вводная часть
В своё время, первый 32-битный процессор i80386 наделал много шуму. Регистры в 2-раза большей разрядности и виртуальная память 4Gb казалась из мира фантастики. На его обкатку и реализацию более совершенных идей Интелу потребовалось без малого 10-лет, и в 1993-году мир увидел духовного преемника 486 – Pentium.
Ещё тогда инженеров заинтересовал вопрос, сколько инструкций за такт сможет выполнить процессор на скорости 200 MHz? Для решения этой задачи, они выделили один модельно-специфичный регистр под номером MSR.10h и с каждым тактом ядра стали увеличивать его на 1. В результате получили работающий непосредственно на частоте процессора, инкрементный счётчик. Вычисление скорости инструкций подразумевало двойное чтение этого счётчика, в промежутке которых вставляли тестируемый код.
Позже выяснилось, что при длительной работе CPU 32-битного значения
В своих доках Intel гарантирует, что 64-битный счётчик не переполнится в течении 10-ти лет непрерывной работы CPU (по команде Reset он сбрасывается в нуль), хотя на практике этот период намного больше и пропорционален текущей частоте. Допустим f процессора равна 3.0 GHz, а это в герцах 3.000.000.000. Значит за одну секунду он "простучит" именно столько раз, и на такую-же единицу увеличится TSC. Если перевести 3 млрд в hex, то получим всего
Если-бы TSC был 32-битный, то на этом процессоре он переполнился-бы буквально на 2-ой секунде. Однако вдвое большая разрядность позволяет сбрасывать в него уже астрономические значения, и даже по истечении 80-лет процессор 3 GHz будет продолжать зомбировать нас своим кадилом без переполнения счётчика. Но если учесть, что 3 GHz для Intel'a не предел, то такой запас вполне оправдан. К примеру анонсированный на январь этого года
На заднем дворе..
Техническая модель счётчика TSC далека от идеала и её нельзя воспринимать как эталон времени в системе. И речь здесь даже не о том, что если мы хотим использовать TSC в качестве программного таймера, то сначала должны откалибровать его.. т.е. узнать, сколько раз простучит CPU на текущей своей частоте в течении одной милли/секунды. Соответственно в любом случае на время калибровки нужно будет воспользоваться услугами дополнительного таймера, на роль которого как-нельзя лучше подходит анархист RTC со-своим независимым кварцем 32.768 кГц, или-же высокоточный таймер событий HPET.
Если реализацию TSC разложить на примитивы, то оказывается что в этом "академгородке" мир вращается не вокруг тактовой частоты процессора, а вокруг частоты системной шины чипсета. Ведь что такое частота процессора? Это произведение его множителя на частоту шины. Например если в биосе Bus-frequency =200 MHz, а CPU-ratio =12, то получим CPU-frequency =2400. Таким образом, на ход TSC влияют внешние факторы – во-первых частота шины, во-вторых температура ядра.
Рассмотрим случай, когда мы откалибровали один из счётчиков многоядерного процессора, после чего какой-то левый программный поток загрузил соседнее ядро на все 100 – это типичная ситуация на МР-системах. Тогда при достижении определённого в биос температурного значения, процессор выставит на шину сигнал PROCHOT#, что означает Processor Hot (горячий). В свою очередь чипсет примет этот сигнал и на какое-то время сбросит как питание, так и частоту ядра, чтобы предотвратить выход его из строя. Эти действа происходят в фоне и система никак даже не оповещает нас об этом. Соответственно частота процессора падает, и теперь нам нужно заново калибровать свой счётчик TSC. То-есть частота процессора жёстко не фиксирована и постоянно плавает в некотором диапазоне. Вот как демонстрирует это Intel в своих доках:
Здесь видно, что при достижении критической температуры процессора, логика по термодатчику сначала сбрасывает частоту, после чего с заданным шагом уменьшает и питание VID – Voltage Identificator. На всех материнских платах для этого предусмотрен т.н. VRM или
Для инженеров (которым нужен был TSC с фиксированной частотой под каждый процессор) это представляло проблему, и решить её кстати так и не удалось до сих-пор – счётчик как плавал, так и плавает в обе стороны. Однако какие-то попытки с их стороны всё-же были, на что Intel сделала акцент в своей документации – вот цитата из неё:
То-есть разраб утверждает, что на процессорах выше Pentium-4 счётчик тактов работает на постоянной частоте ядра и на него не влияют внешние факторы, хотя на самом деле температурный режим здесь не учитывается, т.к. он попадает под определение "форс-мажор". Однако и это уже большой плюс в их копилку! В графическом виде с грубыми штрихами, нововведение заключается в следующем..
Раньше, за основу бралась системная шина, частоту которой можно было править в биосе. Дальше к ней применялся множитель CPU (Ratio, Multiplier) и получали его рабочую частоту CPU-Clock. Соответственно чем выше частота на выходе из чипсета, тем активнее считал счётчик TSC. Здесь всё ясно..
А вот на новых чипсетах (справа), аппаратную модель логирования тактов вынесли в отдельный домен. Теперь частота шины BCLK и её овер в биосе на счётчик уже не влияют. Тут к клокеру сразу применяется множитель CPU, а все остальные механизмы для счётчика прозрачны. К примеру в иерархии выше может стоять технология "Speed-Step", которая использует несколько предопределённых шаблонов напряжения и частоты, и переключается между ними в зависимости от нагрузки на процессор. Поэтому в доках и говорится, что значения TSC могут несколько отличаться от заявленной производителем частоты процессора. Кстати помимо TSC, в этом-же домене живут встроенная графика PEG (PCI-Ex Graphics) и шина обмена-данными DMI. В спеках на PCI-Express можно найти такую схему:
Программный доступ
Одновременно с появлением в Pentium счётчика TSC, в набор инструкций процессора была включена инструкция ассемблера RDTSC (Read counter). Она возвращает значение 64-битного счётчика в регистровую пару EDX:EAX, причём в EDX сбрасываются старшие 32-бита, а в EAX – актуальные младшие. Запрет на использование юзером этой инструкции включается битом[2]
Начиная с микро/архитектуры процессоров Intel под кодовым названием "Nehalem", к механизму подсчёта тактов было введено интересное дополнение. Суть его заключается в том, чтобы была возможность не только прочитать значение TSC, но и узнать, какому именно ядру оно принадлежит. Если учесть, что у каждого ядра свои регистры и свой счётчик TSC, то в этом есть какой-то смысл.
Для реализации задуманного, прицепом к MSR.10h инженеры выделили ещё один регистр, ..на этот раз MSR.C0000103h и назвали его IA32_TSC_AUX. В этом AUX-регистре хранится уникальный идентификатор ядра. Так появилась родственная к предыдущей.. инструкция RDTSCP, которая так-же возвращает 64-битный счётчик в пару
Поддержка процессором расширенной версии определяется инструкцией CPUID с аргументом
Кстати среди Win32-API есть группа функций xxAffinityMask(). К примеру SetProcessAffinityMask() позволяет битовой маской жёстко прописать ядра, на которых должен исполняться наш процесс.. например 1,2,3 или только 2,4. В купе с этой функцией, от инструкции RDTSCP можно выжать ещё больше профита.
CPUID – идентификация процессора
Когда процессор только сходит с производственного конвейера, он не может даже элементарно сложить два числа. Чтобы наделить его интеллектом, производитель встраивает в тушку процессора постоянную память micro/code-ROM и заливает в неё набор поддерживаемых им инструкций. Теперь декодер процессора сможет распознать входной поток данных и понять, что именно хочет от него программа. Память эта доступна на запись, что позволяет обновлять только микрокоды, не прибегая к полной замене CPU на новый. Если ваш проц не поддерживает какие-то инструкции, имеет смысл заглянуть на сайт производителя и скачать обновления его микрокодов.
Помимо азбуки в виде инструкций, в этот-же ROM производитель зашивает и характеристики данной модели CPU. Это огромная база-данных, в которой в мельчайших деталях расписаны все свойства и возможности процессора. Информация закодирована битовой маской, для расшифровки которой имеются специальные таблицы. Одну из таких таблиц я прикрепил в скрепке, чтобы была возможность хотя-бы поверхностно ознакомится с ней.
Прочитать идентификатор CPU можно специальной инструкцией CPUID. В качестве аргумента она ждёт в регистре
Если мы хотим получить строку с именем процессора, то CPUID придётся в цикле вызывать сразу 3-раза, вскармливая ему следующий аргумент на каждой итерации. Дело в том, что под имя резервируется 48-байт, а инструкция CPUID не может работать с указателями на память – она возвращает инфу исключительно в регистры, которые нужно будет сбрасывать в приёмный буфер. В результате получим готовую строкуг типа: "Pentium(R) Dual-Core CPU E5200 @ 2.5GHz".
Не знаю, что там курят инженеры, но строку мы получаем в абсолютно неотформатированном виде, ..например в предложении выше между словами CPU и E5200 могут быть 5-пробелов, от которых нужно будет избавляться. Более того, зачем-то они расположили строку по-правому краю зарезервированных байт.. т.е. строка начинается с дополненными до 48-ми байт кучи пробелов - спрашивается зачем?:
В демонстрационном примере, я собрал всё выше/сказанное под один капот.
Изначально мы не знаем, сколько уровней запроса-информации поддерживает инструкция CPUID. Поэтому подсовываем ей аргумент
На сл.этапе, воспользовавшись функцией GetProcessAffinityMask() узнаём кол-во ядер процессора – эта fn. возвращает их в виде битовой маски, например 4-ядра будут представлены как 0000.1111b. Чтобы вычислить реальную (а не заявленную) частоту процессора, мы используя счётчик TSC посчитаем, сколько процессор сделает тактов за 1-сек – это и будет его частотой. Бонусом через CPUID.80000006h можно показать размер кэша L2.
На финишной прямой, при помощи того-же CPUID сбросим на консоль поддержку стандартной (CPUID.1h) и расширенной (CPUID.80000001h) версий RDTSC и RDTSCP соответственно. Напомню, что поддержка первой определяется битом[4] в регистре
Запустив этот код на своём стационаре Win7 я обнаружил, что реальная частота его процессора даже чуть выше заявленной 2.5GHz, зато отсутствует поддержка расширенной версии RDTSCP. А это и не удивительно, поскольку она появилась только в м/архитектуре процессоров "Nehalem", а у меня устаревший "Wolfdale".
Зато на буке с десяткой стоит более современный процессор и ему не чужды нововведения Intel, хотя реальная его частота просела на 5 kHz, и отличается от указанной вендором. Судя по подписи RDTSCP в регистре ECX, код исполнялся на нулевом ядре:
Под занавес..
На этом покончим с таймерами и в следующей части ознакомимся с тонкостями профилирования кода, для выявления т.н. "горячих точек" программы. Без понимания элементарных основ не возможно разобраться во-всём этом, т.к. операционная система в каждый момент времени ведёт себя далеко не стабильно. В этой области есть много моментов, на которые следует обратить внимание, если мы хотим получить приближённый к действительности результат. А пока, в таблице ниже я собрал основные свойства рассмотренных ранее таймеров, чтобы можно было сделать выводы:
1. Общие сведения. Legacy-таймеры PIT и RTC.
2. Шина PCI – таймер менеджера питания ACPI.
3. ACPI таблицы – таймер HPET.
4. Таймер контролёра Local-APIC.
5. Счётчик процессора TSC.
6. Win - профилирование кода.
---------------------------------------
Вводная часть
В своё время, первый 32-битный процессор i80386 наделал много шуму. Регистры в 2-раза большей разрядности и виртуальная память 4Gb казалась из мира фантастики. На его обкатку и реализацию более совершенных идей Интелу потребовалось без малого 10-лет, и в 1993-году мир увидел духовного преемника 486 – Pentium.
Ещё тогда инженеров заинтересовал вопрос, сколько инструкций за такт сможет выполнить процессор на скорости 200 MHz? Для решения этой задачи, они выделили один модельно-специфичный регистр под номером MSR.10h и с каждым тактом ядра стали увеличивать его на 1. В результате получили работающий непосредственно на частоте процессора, инкрементный счётчик. Вычисление скорости инструкций подразумевало двойное чтение этого счётчика, в промежутке которых вставляли тестируемый код.
Позже выяснилось, что при длительной работе CPU 32-битного значения
0xFFFFFFFF
= 4.294.967.295 для этих целей не хватает, т.к. переполнение регистра обратно в нуль влекло за собой ошибку в расчётах. Так MSR.10h разбух с 32 до 64-бит (поженили 2-регистра) и чтобы он не был безликим, окрестили его в IA32_Time_Stamp_Counter. Отметим, что TSC это не таймер – он не генерит никаких прерываний, а просто считает в себя такты процессора.В своих доках Intel гарантирует, что 64-битный счётчик не переполнится в течении 10-ти лет непрерывной работы CPU (по команде Reset он сбрасывается в нуль), хотя на практике этот период намного больше и пропорционален текущей частоте. Допустим f процессора равна 3.0 GHz, а это в герцах 3.000.000.000. Значит за одну секунду он "простучит" именно столько раз, и на такую-же единицу увеличится TSC. Если перевести 3 млрд в hex, то получим всего
0xB2D05E00
в сек, а дальше.. обратите внимание на столбец HEX ниже:Если-бы TSC был 32-битный, то на этом процессоре он переполнился-бы буквально на 2-ой секунде. Однако вдвое большая разрядность позволяет сбрасывать в него уже астрономические значения, и даже по истечении 80-лет процессор 3 GHz будет продолжать зомбировать нас своим кадилом без переполнения счётчика. Но если учесть, что 3 GHz для Intel'a не предел, то такой запас вполне оправдан. К примеру анонсированный на январь этого года
Ссылка скрыта от гостей
зарекается работать на частоте 5.3 GHz, соответственно и скорость переполнения его регистра TSC будет уже выше (с деталями этого процессора можно
Ссылка скрыта от гостей
).На заднем дворе..
Техническая модель счётчика TSC далека от идеала и её нельзя воспринимать как эталон времени в системе. И речь здесь даже не о том, что если мы хотим использовать TSC в качестве программного таймера, то сначала должны откалибровать его.. т.е. узнать, сколько раз простучит CPU на текущей своей частоте в течении одной милли/секунды. Соответственно в любом случае на время калибровки нужно будет воспользоваться услугами дополнительного таймера, на роль которого как-нельзя лучше подходит анархист RTC со-своим независимым кварцем 32.768 кГц, или-же высокоточный таймер событий HPET.
Если реализацию TSC разложить на примитивы, то оказывается что в этом "академгородке" мир вращается не вокруг тактовой частоты процессора, а вокруг частоты системной шины чипсета. Ведь что такое частота процессора? Это произведение его множителя на частоту шины. Например если в биосе Bus-frequency =200 MHz, а CPU-ratio =12, то получим CPU-frequency =2400. Таким образом, на ход TSC влияют внешние факторы – во-первых частота шины, во-вторых температура ядра.
Рассмотрим случай, когда мы откалибровали один из счётчиков многоядерного процессора, после чего какой-то левый программный поток загрузил соседнее ядро на все 100 – это типичная ситуация на МР-системах. Тогда при достижении определённого в биос температурного значения, процессор выставит на шину сигнал PROCHOT#, что означает Processor Hot (горячий). В свою очередь чипсет примет этот сигнал и на какое-то время сбросит как питание, так и частоту ядра, чтобы предотвратить выход его из строя. Эти действа происходят в фоне и система никак даже не оповещает нас об этом. Соответственно частота процессора падает, и теперь нам нужно заново калибровать свой счётчик TSC. То-есть частота процессора жёстко не фиксирована и постоянно плавает в некотором диапазоне. Вот как демонстрирует это Intel в своих доках:
Здесь видно, что при достижении критической температуры процессора, логика по термодатчику сначала сбрасывает частоту, после чего с заданным шагом уменьшает и питание VID – Voltage Identificator. На всех материнских платах для этого предусмотрен т.н. VRM или
Ссылка скрыта от гостей
Питание будет стремиться к нулю до тех пор, пока температура ядра не восстановится, после чего процессор снимет PROCHOT#, и всё вернётся на круги свои.Для инженеров (которым нужен был TSC с фиксированной частотой под каждый процессор) это представляло проблему, и решить её кстати так и не удалось до сих-пор – счётчик как плавал, так и плавает в обе стороны. Однако какие-то попытки с их стороны всё-же были, на что Intel сделала акцент в своей документации – вот цитата из неё:
17.15 TIME-STAMP COUNTER
Processor families increment the time-stamp counter differently:
• For Pentium M, Pentium-4, Intel Xeon and for P6 family processors: the time-stamp counter increments with every internal processor clock cycle. The internal processor clock cycle is determined by the current core-clock to bus-clock ratio. Intel SpeedStep® technology transitions may also impact the processor clock.
• For Intel Core Duo, Intel Xeon-5100, Core-2-Duo and Atom processors: the time-stamp counter increments at a constant rate. On certain processors, the TSC frequency may not be the same as the frequency in the brand string. Constant TSC behavior ensures that the duration of each clock tick is uniform and supports the use of the TSC as a wall clock timer even if the processor core changes frequency. This is the architectural behavior moving forward.
То-есть разраб утверждает, что на процессорах выше Pentium-4 счётчик тактов работает на постоянной частоте ядра и на него не влияют внешние факторы, хотя на самом деле температурный режим здесь не учитывается, т.к. он попадает под определение "форс-мажор". Однако и это уже большой плюс в их копилку! В графическом виде с грубыми штрихами, нововведение заключается в следующем..
Раньше, за основу бралась системная шина, частоту которой можно было править в биосе. Дальше к ней применялся множитель CPU (Ratio, Multiplier) и получали его рабочую частоту CPU-Clock. Соответственно чем выше частота на выходе из чипсета, тем активнее считал счётчик TSC. Здесь всё ясно..
А вот на новых чипсетах (справа), аппаратную модель логирования тактов вынесли в отдельный домен. Теперь частота шины BCLK и её овер в биосе на счётчик уже не влияют. Тут к клокеру сразу применяется множитель CPU, а все остальные механизмы для счётчика прозрачны. К примеру в иерархии выше может стоять технология "Speed-Step", которая использует несколько предопределённых шаблонов напряжения и частоты, и переключается между ними в зависимости от нагрузки на процессор. Поэтому в доках и говорится, что значения TSC могут несколько отличаться от заявленной производителем частоты процессора. Кстати помимо TSC, в этом-же домене живут встроенная графика PEG (PCI-Ex Graphics) и шина обмена-данными DMI. В спеках на PCI-Express можно найти такую схему:
Программный доступ
Одновременно с появлением в Pentium счётчика TSC, в набор инструкций процессора была включена инструкция ассемблера RDTSC (Read counter). Она возвращает значение 64-битного счётчика в регистровую пару EDX:EAX, причём в EDX сбрасываются старшие 32-бита, а в EAX – актуальные младшие. Запрет на использование юзером этой инструкции включается битом[2]
TSCD
в регистре конфигурации процессора CR4. Единичное состояние этого бита позволяет оперировать счётчиком только ядру, а нулевое – открывает доступ и юзеру. Прямым обращением к регистру MSR.10h счётчик доступен и на запись, но только в младшую его часть, при этом старшая часть обнуляется.
C-подобный:
;// прямое чтение/запись TSC из драйвера, или RMode
mov ecx,10h ;// номер регистра MSR
rdmsr ;// EDX:EAX = значение счётчика
mov ecx,10h
wrmsr ;// write MSR.10h
;// чтение TSC с любого уровня
rdtsc ;// EDX:EAX = значение счётчика
Начиная с микро/архитектуры процессоров Intel под кодовым названием "Nehalem", к механизму подсчёта тактов было введено интересное дополнение. Суть его заключается в том, чтобы была возможность не только прочитать значение TSC, но и узнать, какому именно ядру оно принадлежит. Если учесть, что у каждого ядра свои регистры и свой счётчик TSC, то в этом есть какой-то смысл.
Для реализации задуманного, прицепом к MSR.10h инженеры выделили ещё один регистр, ..на этот раз MSR.C0000103h и назвали его IA32_TSC_AUX. В этом AUX-регистре хранится уникальный идентификатор ядра. Так появилась родственная к предыдущей.. инструкция RDTSCP, которая так-же возвращает 64-битный счётчик в пару
EDX:EAX
, но бонусом в регистре ECX
ещё и подпись ядра. Итого её выхлоп получает уже в трёх регистрах ECX:EDX:EAX
.Поддержка процессором расширенной версии определяется инструкцией CPUID с аргументом
EAX=0x80000001
. Если она вернёт в EDX взведённый бит[27], значит процессор поддерживает RDTSCP. Польза от этой инструкции минимальна.. Поскольку она возвращает ID-ядра, то с её помощью можно отследить миграцию программных потоков с одного ядра процессора, на другое. Обычно если основной поток программы запущен на ядре#0, он на нём и исполняется. Однако это правило не действительно для дочерних потоков приложения, и системный планировщик Sheduler может тасовать их по свободным ядрам. Здесь и пригодится инструкция RDTSCP, которая при каждом вызове возвращает ID-ядра, на котором выполняется.Кстати среди Win32-API есть группа функций xxAffinityMask(). К примеру SetProcessAffinityMask() позволяет битовой маской жёстко прописать ядра, на которых должен исполняться наш процесс.. например 1,2,3 или только 2,4. В купе с этой функцией, от инструкции RDTSCP можно выжать ещё больше профита.
CPUID – идентификация процессора
Когда процессор только сходит с производственного конвейера, он не может даже элементарно сложить два числа. Чтобы наделить его интеллектом, производитель встраивает в тушку процессора постоянную память micro/code-ROM и заливает в неё набор поддерживаемых им инструкций. Теперь декодер процессора сможет распознать входной поток данных и понять, что именно хочет от него программа. Память эта доступна на запись, что позволяет обновлять только микрокоды, не прибегая к полной замене CPU на новый. Если ваш проц не поддерживает какие-то инструкции, имеет смысл заглянуть на сайт производителя и скачать обновления его микрокодов.
Помимо азбуки в виде инструкций, в этот-же ROM производитель зашивает и характеристики данной модели CPU. Это огромная база-данных, в которой в мельчайших деталях расписаны все свойства и возможности процессора. Информация закодирована битовой маской, для расшифровки которой имеются специальные таблицы. Одну из таких таблиц я прикрепил в скрепке, чтобы была возможность хотя-бы поверхностно ознакомится с ней.
Прочитать идентификатор CPU можно специальной инструкцией CPUID. В качестве аргумента она ждёт в регистре
EAX
код запрашиваемой информации. Имеется стандартный набор кодов с EAX=0x0000_xxxx
, и расширенный набор с EAX=0x8000_xxxx
. Приняв аргумент, на выходе CPUID заполняет информацией 4-регистра процессора EAX,EBX,ECX,EDX
, после чего нам остаётся только проверить нужные биты в них. Например чтобы получить строку вендора, достаточно вызвать CPUID с аргументом нуль:
Код:
Standard level: 0000_0000h
--------------------------
Input.: EAX=0000_0000h. Get maximum supported standard level and vendor ID string
Output: EAX=xxxx_xxxxh maximum supported standard level
EBX-EDX-ECX vendor ID string
------------------------------
GenuineIntel = Intel processor
UMC UMC UMC = UMC processor
AuthenticAMD = AMD processor
CyrixInstead = Cyrix processor
NexGenDriven = NexGen processor
CentaurHauls = Centaur processor
RiseRiseRise = Rise Technology processor
SiS SiS SiS = SiS processor
GenuineTMx86 = Transmeta processor
Geode by NSC = National Semiconductor processor
Если мы хотим получить строку с именем процессора, то CPUID придётся в цикле вызывать сразу 3-раза, вскармливая ему следующий аргумент на каждой итерации. Дело в том, что под имя резервируется 48-байт, а инструкция CPUID не может работать с указателями на память – она возвращает инфу исключительно в регистры, которые нужно будет сбрасывать в приёмный буфер. В результате получим готовую строкуг типа: "Pentium(R) Dual-Core CPU E5200 @ 2.5GHz".
Не знаю, что там курят инженеры, но строку мы получаем в абсолютно неотформатированном виде, ..например в предложении выше между словами CPU и E5200 могут быть 5-пробелов, от которых нужно будет избавляться. Более того, зачем-то они расположили строку по-правому краю зарезервированных байт.. т.е. строка начинается с дополненными до 48-ми байт кучи пробелов - спрашивается зачем?:
Код:
Extended levels: 8000_0002h, 8000_0003h, and 8000_0004h
-------------------------------------------------------
Input.: EAX=8000_0002h get processor name string (part 1)
EAX=8000_0003h get processor name string (part 2)
EAX=8000_0004h get processor name string (part 3)
Output: EAX-EBX-ECX-EDX = processor name string
В демонстрационном примере, я собрал всё выше/сказанное под один капот.
Изначально мы не знаем, сколько уровней запроса-информации поддерживает инструкция CPUID. Поэтому подсовываем ей аргумент
EAX=0
, и в этом-же регистре на выходе получаем макс.возможный код запроса. Эту-же операцию проводим и с расширенным набором EAX=80000000h
. Дальше получаем вендора, строку с именем процессора, и код различных его характеристик (см.доку в скрепке).На сл.этапе, воспользовавшись функцией GetProcessAffinityMask() узнаём кол-во ядер процессора – эта fn. возвращает их в виде битовой маски, например 4-ядра будут представлены как 0000.1111b. Чтобы вычислить реальную (а не заявленную) частоту процессора, мы используя счётчик TSC посчитаем, сколько процессор сделает тактов за 1-сек – это и будет его частотой. Бонусом через CPUID.80000006h можно показать размер кэша L2.
На финишной прямой, при помощи того-же CPUID сбросим на консоль поддержку стандартной (CPUID.1h) и расширенной (CPUID.80000001h) версий RDTSC и RDTSCP соответственно. Напомню, что поддержка первой определяется битом[4] в регистре
EDX
, а расширенной – битом[27]. Если код определит наличие усовершенствованной RDTSCP, то выводим его счётчик и ID-ядра на консоль, иначе – пропускаем этот шаг. Вот пример реализации:
C-подобный:
format pe console
include 'win32ax.inc'
entry start
;//---------
.data
caption db 13,10,' CPU info v0.1'
db 13,10,' ************************'
db 13,10,' CPUID max.Std,level.: 0x%08X'
db 13,10,' CPUID max.Ext.level.: 0x%08X',0
vend db 13,10
db 13,10,' Vendor..............: %s',0
name db 13,10,' Name................: %s',0
features db 13,10,' Features............: %X',0
cores db 13,10
db 13,10,' CPU count...........: %d',0
freq db 13,10,' CPU frequence.......: %d MHz',0
cache db 13,10,' L2 cache size.......: %d Kb',0
tscInfo db 13,10
db 13,10,' RDTSC support......: %d',0
tscP db 13,10,' RDTSCP support......: %d',0
tscStamp db 13,10,' RDTSC counter......: 0x%08X%08X',0
tscpStamp db 13,10,' RDTSCP counter......: 0x%08X%08X'
db 13,10,' RDTSCP core ID......: 0x%02X',0
pAffin dd 0
sAffin dd 0
cycle dd 0
tscpFlag db 0
cpu db 64 dup(0)
buff db 0
;//---------
.code
start:
;//(0)=== Макс.поддерживаемые уровни запросов CPUID
xor eax,eax ;// аргумент(0)
cpuid ;//
push eax ;//
mov eax,0x80000000 ;// аргумент
cpuid ;//
pop ebx ;//
cinvoke printf,caption,ebx,eax
;//(1)=== Получаем строку вендора
xor eax,eax ;// аргумент
cpuid ;//
mov dword[buff+0],ebx ;// кидаем его в буфер
mov dword[buff+4],edx ;//
mov dword[buff+8],ecx ;//
cinvoke printf,vend,buff ;//
;//(2)=== Строка с именем процессора
;// нужно вызывать в цикле аргументы 0x80000002,3,4
mov [cycle],3 ;// кол-во повторов
mov edi,buff ;// указатель на приёмник
push edi ;//
mov eax,0x80000002 ;// аргумент на старте
@@: push eax ;// запомнить его
cpuid ;// очередной вызов..
stosd ;// сбросить EAX по указателю EDI
mov eax,ebx ;//
stosd ;// сбросить EBX
mov eax,ecx ;//
stosd ;// сбросить ECX
mov eax,edx ;//
stosd ;// сбросить EDX
pop eax ;// восстановить аргумент
inc eax ;// увеличить его на 1
dec [cycle] ;// уменьшить счётчик повторов
jnz @b ;// повторить, пока он не нуль!
;//(3)=== Форматируем строку с именем процессора
;// (убираем дублируешиеся пробелы в буфере)
pop edi ;// указатель на него
mov esi,edi ;// он-же источник для стр.инструкций
mov ah,' ' ;// что искать..
@findDoubleSpace: ;//
lodsb ;// AL = очередной символ из ESI
or al,al ;// конец строки? (проверка на маркер 0)
je @prn ;// да..
cmp al,' ' ;// иначе: проверить на пробел
jne @fuck ;// нет..
cmp ax,' ' ;// иначе: проверить на дубль
je @f ;// пропустить, если пара пробелов
@fuck: stosb ;// иначе: перезаписать буф
@@: xchg ah,al ;// сделать текущий символ, предыдущим
jmp @findDoubleSpace ;// повторить, пока не встретим нуль..
@prn: mov byte[edi],0 ;// вставить маркер окончания строки
cinvoke printf,name,buff ;// вывести строку на консоль!
;//(4)=== Код с характеристиками процессора ============
mov eax,1 ;// аргумент
cpuid ;//
cinvoke printf,features,eax ;// лежит в EAX
;//(5)=== Кол-во ядер CPU ==============================
;// здесь -1 дескриптор текущего процесса
invoke GetProcessAffinityMask,-1,pAffin,sAffin
mov eax,[pAffin] ;// получили маску
xor ebx,ebx ;// здесь будет кол-во из маски
@@: shr eax,1 ;// вытолнуть мл.бит
inc ebx ;// кол-во ядер +1
or eax,eax ;// проверить оставшуюся маску на нуль
jne @b ;// повторить, если нет..
cinvoke printf,cores,ebx ;// в EBX лежат ядра!
;//(6)=== Реальная частота процессора ==================
;// читаем TSC за 0,5 сек с контрольным/вторым выстрелом
;// здесь CPUID выступает в качестве инструкции-сериализации,
;// чтобы очистить конвейер процессора от имеющихся в нём инструкций
mov [cycle],2 ;// повторов..
@@: xor eax,eax ;//
cpuid ;// теперь конвейер чистый!
rdtsc ;// берём такты в пару EDX:EAX
push eax ;// запомнить мл.часть
invoke Sleep,500 ;// накапливаем счётчик TSC..
rdtsc ;// повторный вызов
pop ebx ;// EBX = счётчик на входе
sub eax,ebx ;// TSC за 0,5 сек
mov ebx,500000 ;// перевести в Мбайты
xor edx,edx ;//
div ebx ;//
dec [cycle] ;//
jnz @b ;// повторить для точности..
cinvoke printf,freq,eax ;// EAX = реальная частота!
;//(7)=== Кэш L2 =======================================
mov eax,0x80000006
cpuid ;//
shr ecx,16 ;// лежит в битах[31:16]
cinvoke printf,cache,ecx
;//(8)=== Тест поддержки TSC и TSCP ====================
mov eax,1 ;//
cpuid ;//
mov eax,1 ;//
bt edx,4 ;// RDTSC
jc @f ;//
dec al ;//
@@: cinvoke printf,tscInfo,eax
mov eax,0x80000001
cpuid ;//
mov eax,0 ;//
bt edx,27 ;// RDTSCP
jnc @f ;//
inc al ;//
inc [tscpFlag] ;// взвести флаг, если имеется поддержка
@@: cinvoke printf,tscP,eax
;//(9)=== Читаем текущие счётчики TSC и TSCP ===========
rdtsc ;//
cinvoke printf,tscStamp,edx,eax
cmp [tscpFlag],0 ;// проверить флаг в переменной
je @f ;// пропустить, если он нуль
rdtscp ;// иначе: вызов!
cinvoke printf,tscpStamp,edx,eax,ecx ;// ECX = id-ядра
@@: cinvoke gets,buff ;// ждём клаву..
cinvoke exit,0 ;// на выход!
;//**********************************
section '.idata' import data readable
library msvcrt,'msvcrt.dll',kernel32,'kernel32.dll'
import msvcrt, printf,'printf',exit,'exit',gets,'gets';,gcvt,'_gcvt'
include 'api\kernel32.inc'
Запустив этот код на своём стационаре Win7 я обнаружил, что реальная частота его процессора даже чуть выше заявленной 2.5GHz, зато отсутствует поддержка расширенной версии RDTSCP. А это и не удивительно, поскольку она появилась только в м/архитектуре процессоров "Nehalem", а у меня устаревший "Wolfdale".
Зато на буке с десяткой стоит более современный процессор и ему не чужды нововведения Intel, хотя реальная его частота просела на 5 kHz, и отличается от указанной вендором. Судя по подписи RDTSCP в регистре ECX, код исполнялся на нулевом ядре:
Под занавес..
На этом покончим с таймерами и в следующей части ознакомимся с тонкостями профилирования кода, для выявления т.н. "горячих точек" программы. Без понимания элементарных основ не возможно разобраться во-всём этом, т.к. операционная система в каждый момент времени ведёт себя далеко не стабильно. В этой области есть много моментов, на которые следует обратить внимание, если мы хотим получить приближённый к действительности результат. А пока, в таблице ниже я собрал основные свойства рассмотренных ранее таймеров, чтобы можно было сделать выводы:
Вложения
Последнее редактирование: