Частота системного таймера windows

Время на прочтение6 мин

Количество просмотров118K

Период таймера Windows по умолчанию составляет 15.6 мс – он тикает 64 раза в секунду. Когда программа увеличивает частоту таймера, растет потребление энергии, что сказывается на расходе батареи. При этом также расходуется вычислительная мощность компьютера, и даже больше, чем я думал – то есть компьютер начинает работать медленнее! Вот почему в течение многих лет Microsoft настоятельно не рекомендует разработчикам поднимать частоту таймера.
Почему же тогда почти каждый раз, когда я вижу разгон таймера, он вызван программой от Microsoft?

Узнать текущую частоту таймера Windows довольно просто с помощью утилиты clockres от sysinternals.

ClockRes v2.0 – View the system clock resolution
Copyright 2009 Mark Russinovich
SysInternals – www.sysinternals.com

Maximum timer interval: 15.600 ms
Minimum timer interval: 0.500 ms
Current timer interval: 1.000 ms

Для увеличения времени работы компьютера от батарей текущий период таймера (который может быть изменен функцией timeBeginPeriod) должен быть равен 15.6 мс; но, как вы видите выше, какая-то программа изменила его на 1 мс, что эквивалентно дополнительным 936 тикам в секунду.

Поиск виновного – WPF

Процесс поиска виновного в увеличении частоты не так очевиден, но все-таки довольно прост. В командной строке администратора наберем

powercfg -energy duration 5

и в текущем каталоге появится файл energy-report.html, в котором мы, в частности, прочитаем:

The stack of modules responsible for the lowest platform timer setting in this process.
Requested Period 10000
Requesting Process ID 3932
Requesting Process Path
C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\devenv.exe
Calling Module Stack
C:\Windows\SysWOW64\ntdll.dll
C:\Windows\SysWOW64\winmm.dll
C:\Windows\Microsoft.NET\Framework\v4.0.30319\WPF\wpfgfx_v0400.dll
C:\Windows\SysWOW64\kernel32.dll
C:\Windows\SysWOW64\ntdll.dll

Итак, Visual Studio 11, посредством использования WPF, запросила интервал в 1 мс, что и указано в отчете посредством несколько сбивающей с толку единицы измерения, равной 100 нс. Это известная проблема, связанная с WPF; все версии Visual Studio ведут себя так время от времени и, по-видимому, любое приложение, использующее WPF, может стать источником проблемы. Увеличение частоты может иметь смысл, когда программа пытается поддерживать постоянный фрейм рейт вывода, однако это не оправдывает WPF, поскольку она сохраняет высокую частоту таймера даже в том случае, когда никакой анимации не происходит.

Поиск виновного – SQL сервер

Другой процесс, часто виновный в увеличении частоты на моем компьютере – sqlservr.exe. Думаю, что он был установлен Visual Studio, но не уверен в этом, как не уверен, используется он или нет. В любом случае, SQL сервер не должен повышать частоту таймера; если таким образом предполагается повысить производительность приложения, то это больше похоже на костыль. И, как в случае с WPF, увеличение частоты нужно только тогда, когда сервер занят обработкой данных, а не постоянно.

Platform Timer Resolution:Outstanding Timer Request
A program or service has requested a timer resolution smaller than the platform maximum timer resolution.
Requested Period 10000
Requesting Process ID 2384
Requesting Process Path \Device\HarddiskVolume1\Program Files\Microsoft SQL Server\MSSQL10.MSSQLSERVER\MSSQL\Binn\sqlservr.exe

Поиск виновного – quartz.dll

У меня нет соответствующей записи из отчета powercfg, но C:\Windows\System32\quartz.dll является еще одним источником проблем с частотой таймера. Я даже толком не знаю, что такое этот Quarts (ну а мы-то знаем, что это не что иное, как Microsoft DirectShow – прим. пер.), но замечал, что иногда он расходует энергию зря.

Поиск виновного – Chrome

Обычно виновными становятся продукты Microsoft, однако к ним в компанию я добавляю еще Google Chrome. Когда я запускаю Chrome он постоянно увеличивает частоту таймера 1000 Гц, даже в том случае, когда компьютер работает от батареи, а я просматриваю простую HTML страницу. Привожу скриншот, фиксирующий преступление Chrome.

Поиск виновного – svchost.exe

Иногда svchost.exe увеличивает частоту таймера до 100 Гц. Это, конечно, не так страшно, как 1000 Гц, но все-таки раздражает. Особенно печально то, что я не могу определить, какой именно сервис это делает.

Общая трагедия – побеждает максимальная частота

Таймер Windows является глобальным ресурсом, он тикает с одинаковой частотой для всей системы целиком. Получается, что если какая-то программа увеличивает частоту таймера, то это сказывается на поведении всей системы.
Когда процесс вызывает функцию timeBeginPeriod, этот запрос частоты остается в силе до тех пор, пока не будет принудительно отменен с помощью timeEndPeriod или до конца работы приложения. Большинство программ (включая и мои тестовые, приведенные ниже) никогда не вызывают timeEndPeriod, полагаясь на системные средства очистки Windows. Это работает и вполне разумно для приложений, которым необходима повышенная частота таймера на всем протяжении исполнения. В противном случае хорошей идеей будет использование timeEndPeriod. По рекомендации Microsoft к приложениям второго вида относятся проигрыватели видео в режиме паузы и свернутые в трей игры. Сюда же можно включить веб-браузеры, которые в текущий момент не требуют высокой частоты таймера или при работе от батареи.

Так ли это важно?

Моим основным компьютером является ноутбук. Каждый день я использую его в автобусе и предпочитаю тратить батарею на что-то полезное, нежели чем на ненужные обращения к процессору 1000 раз в секунду.
Microsoft считает, что это важно. Они говорят: «нашей позицией остается последовательное улучшение энергоэффективности Windows ПК» и все-таки, даже 4 года спустя, сами, похоже, не исполняют свои собственные указания и не обращают внимания на свои же предупреждения: «некоторые приложения уменьшают период таймера до 1 мс, что приводит к сокращению времени работы мобильной системы на 25%».
Удобным способом измерения потребленной энергии является утилита Intel Power Gadget. На поддерживаемых процессорах Intel она покажет мощность, потребляемую упаковкой процессора в реальном времени с точностью до 0,01 Вт. На моем ноутбуке на платформе Sandy Bridge утилита показывает прирост в 0,3 Вт при повышении частоты таймера, что составляет почти 10% от стандартного потребления упаковкой процессора; применительно ко всей системе процент, конечно, будет меньше.
Увеличение на 0,3 Вт может выглядеть не таким большим, но есть пара моментов, заставляющих воспринимать его серьезно. Во-первых, если ваша программа работает, скажем, на 33 миллионах компьютеров (для Chrome это, наверное, даже заниженная оценка), увеличение частоты таймера приведет к потере примерно 10 МегаВатт энергии. Во-вторых, важность проблемы со временем будет только возрастать. Новые процессора с улучшенным объединенным таймером будут затрачивать на учащенные вызовы еще больше вычислительных мощностей.

Быстрые таймеры ухудшают производительность

Исполнение прерываний отнимает некоторую часть ресурсов компьютера, поэтому увеличение их количества в единицу времени должно несколько замедлить скорость его работы. Я проверил эту теорию, написав тестовую программу, крутящую циклы активности и докладывающую каждую секунду скорость своего исполнения. Во время работы программы я изменил частоту таймера, чтобы посмотреть его влияние на производительность.
Влияние было, причем существенное.
Я проделал быстрые тесты на двух компьютерах, так что точные цифры не стоит воспринимать слишком серьезно. Кроме того, они наверняка сильно зависят от аппаратной платформы, нагрузки и т.д. Однако результаты явно показали влияние ускорения таймера на производительность, оно составляет 2,5-5% — это больше, чем я предполагал. Степень замедления достаточно велика, чтобы заподозрить традиционный подход – повышение частоты таймера для увеличения производительности приложения – в контр-продуктивности.
Увеличение частоты таймера Windows не приводит ни к чему хорошему. При этом зря расходуется энергия и замедляется компьютер. Практика применения его во всех без разбора программах, висящих часами без активности, должна быть прекращена.
Вот результаты работы моей тестовой программы в графическом формате

20-секундный период по середине, где производительность неожиданно падает, совпадает с увеличением частоты таймера. Похожие результаты я получил во всех своих тестах, как при питании от батареи, так и от сети

Исходный код

Поскольку наука не делается без раскрытия исходного кода, привожу код своей тестовой программы.

#include “stdafx.h”

#include <stdio.h>
#include <Windows.h>

LARGE_INTEGER g_frequency;
const double kDelayTime = 1.0;

double GetTime()
{
    LARGE_INTEGER counter;
    QueryPerformanceCounter(&counter);
    return counter.QuadPart / double(g_frequency.QuadPart);
}

int g_array[1024];
int offset;
int g_sum;

void SpinABit()
{
    for (int i = 0; i < ARRAYSIZE(g_array); ++i)
    {
        g_sum += g_array[i + offset];
    }
}

void Stall()
{
    double start = GetTime();
    int iterations = 0;
    for (;;)
    {
        ++iterations;
        SpinABit();
        double elapsed = GetTime() – start;
        if (elapsed >= kDelayTime)
        {
            printf(“%1.5e iterations/s\n”, iterations / elapsed);
            return;
        }
    }
}

int main(int argc, char* argv[])
{
    QueryPerformanceFrequency(&g_frequency);
    for (;;)
        Stall();
    return 0;
}

А вот программа, повышающая частоту таймера на 20 сек.

#include <stdio.h>
#include <Windows.h>

#pragma comment(lib, “winmm.lib”)

int main(int argc, char* argv[])
{
    timeBeginPeriod(1);
    printf(“Frequency raised.\n”);
    Sleep(20000);
    printf(“Frequency lowered.\n”);
    // timeEndPeriod call is omitted because process
    // cleanup will do that.
    return 0;
}

Не забудьте проверить частоту системного таймера перед запуском теста, иначе вы можете не увидеть разницы.

И потом исправьте код своих программ. Все, как один.

Для работы проектов iXBT.com нужны файлы cookie и сервисы аналитики.
Продолжая посещать сайты проектов вы соглашаетесь с нашей
Политикой в отношении файлов cookie

Если вы увлекались киберспортом или оптимизацией Windows, то наверняка слышали о влияние высокоточного системного таймера HPET на общую производительность ПК. Но знаете ли вы, что, начиная с Windows 10 22H2 и Windows 11, компания Microsoft изменила классическую логику работы системного таймера, и прошлые команды, которые ранее позволяли существенно повысить производительность, более не работают. В этом небольшом материале вы узнаете, как исправить эту проблему и вернуть привычное поведение системного таймера HPET/TSC в новых версиях Windows, существенно ускорив работу своего компьютера.

Чтобы не нагружать вас лишней технической информацией, достаточно будет сказать, что ранее отключение или изменение значений системного таймера HPET глобально влияло на все процессы в Windows, существенно повышая отклик и плавность работы приложений и игр. Теперь же частота прерывания системного таймера HPET/TSC не функционирует на глобальном уровне ОС и не зависит от других процессов. Короче говоря, таймер теперь работает лишь для запущенных на переднем фоне приложений и не может опускаться ниже принудительно заданных значений, что существенно ломает многие старые игры и не позволяет пользователю добиться былой производительности. Подробнее почитать про всё это вы можете, перейдя по ссылке.

1. Нажимаем Win+R и вводим команду regedit. В появившемся окне переходим в раздел kernel и создаём новый параметр Dword32 с именем GlobalTimerResolutionRequests и значением 1 в шестнадцатеричной системе счисления. Или воспользуйтесь заранее уже готовым файлом с нужными настройками.

2. Нажимаем правым кликом мыши на «Пуск» и выбираем «Диспетчер устройств». Для отключения HPET и активации таймера TSC мы должны в разделе «Системные устройства» отключить «Высокоточный таймер событий» иногда он подписан как High Precision Even Timer. Правый клик мыши>>»Отключить устройство».

3. Теперь нам осталось только ввести команды для отключения встроенного программного таймера Windows и технологии экономии электроэнергии для ноутбуков. Вновь нажимаем Win+R и вводим команду CMD.

В появившемся окне вводим команды:

bcdedit /set disabledynamictick yes
bcdedit /set useplatformtick yes
bcdedit /set useplatformclock false
bcdedit /deletevalue useplatformclock

После перезагрузки ПК вы сразу почувствуете разницу в отклике Windows и плавности работы мыши. Однако, чтобы ещё сильнее ускорить работу компьютера, необходимо принудительно зафиксировать значения системного таймера TSC на минимальных значениях.

4. Выполним установку системной службы Windows, отвечающей за минимальное значение системного таймера.

  • Скачиваем и извлекаем папку архива в корень любого диска.
  • Переходим в папку установщика и запускаем не от имени администратора файл Installer.bat.
  • В появившемся окне выбираем желаемый таймер. Я советую 2 или 4.
  • Дожидаемся окончания установки. (После установки не удалять папку установщика).
  • Проверяем значение таймера, нажав 8.
  • Если всё прошло успешно, то значение системного таймера будет равняться 0.5 мс.

Если вы заметили отрицательный эффект от отключения системного таймера HPET, то удалите в реестре Windows запись GlobalTimerResolutionRequest и вновь включите «Высокоточный таймер событий» в «Диспетчере устройств». Не забыв удалить системную службу таймера и ввести в cmd следующие команды:

bcdedit /deletevalue disabledynamictick
bcdedit /deletevalue useplatformtick

Вот, собственно, и всё! Всего за пару не хитрых шагов мы исправили оплошность корпорации Microsoft, вернув себе классическое поведение системного таймера Windows, существенно ускорив работу приложений и игр. Не говоря уже о самой ОС. Уважайте свободу, используйте только проверенное ПО и ни в коем случае не позволяйте компаниям диктовать вам свои условия. С вами был Павел. Ещё увидимся!

Я провёл дополнительное тестирование в новых играх, используя Windows 10 версии 22H2 (1945.3636). И могу с уверенностью утверждать, что принудительное отключение высокоточного системного таймера HPET позволяет добиться лучшей производительности не только в старых, но и в новых играх. Например, дважды прогоняя встроенный бенчмарк Cyberpunk 2077, отчётливо видна разница в минимальном и среднем FPS. Не говоря уже про отсутствие эффекта «Желе». Миф подтверждён: HPET следует принудительно отключать для максимальной плавности в играх и рабочем ПО!

Сейчас на главной

Новости

Публикации

Среди суровых, будто вырезанных из скал пейзажей Фарерских островов есть место, способное обмануть даже самый рациональный ум. Это Сёрвогсватн — озеро с двойным именем, загадочной…

Представьте себе: где-то в провинции Сычуань, на юго-западе Китая, более 120 миллионов лет назад прогуливался себе динозавр. Ничего особенного, обычный день раннего мелового периода. Но эти…

Когда режиссер Джеймс Кэмерон создавал планету Пандору для своего знаменитого фильма «Аватар», он искал нечто по-настоящему внеземное. В конце концов, ему же надо было впечатлить зрителей…

В России существует остров, название которого как будто бы отсылается к какой-нибудь детской сказке и вымышленному населенному пункту из юмористического фэнтези. Называется он остров…

Сколько всего умеет ридер, если на нем установлен Android и
цветной экран? Проверяю на практике, что может Оникс Букс Фарадей 2: открываю
тяжелые книги, до 360 МБ, тестирую YouTube, смотрю, как…

Мир природы полон удивительных существ, но среди них есть такие, которые бросают вызов традиционным биологическим классификациям. К примеру, грибы, которые принадлежат к целому царству и имеют…


Исследование: системный таймер Windows

От:

Mr. None

Россия

http://mrnone.blogspot.com
Дата:  16.02.11 07:44
Оценка:

31 (4)

Навеяно спором

в ветке ниже

Автор: Mr. None
Дата: 15.02.11

. Поняв бесперспективность дальнейшей дискусси, я решил разложить всё по полочкам и провести более аккуратные измерения. Листинг программы привожу по тексту ниже ибо он весьма велик.

Какие есть таймеры внутри операционной системы: ждущие таймеры, мультимедийные таймеры, таймер часов и бла-бла-бла. Всё это верхушка айсберга. В основании лежит единый системный таймер. Он же аппаратный программируемый таймер, который мы помним из школьного курса программирования аппаратных средств в MS DOS. Напомню про него в кратце: это кварц с частотой 1.19318 МГц, который управляет генерацией аппаратного прерывания IRQ0 через определённые интервалы. Частота генерации этого прерывания варьируется от 18.206 Гц (так было в MS DOS), до собственно 1.19318 МГц (предельная частота кварца). Управлять частотой генерации IRQ0 можно программно, поэтому он и называется программируемым. В цифрах мог немного ошибиться (давно это всё было) и в реалии всё чуть-чуть сложнее (на самом деле таймер имеет несколько каналов, предназначенных для разных вещей), но сути дела это не меняет. Главное что нужно понять, так это то, что частота так называемого системного таймера Windows по сути и является этой самой частотой генерации прерывания IRQ0. Опять же детали могут немного отличаться, за сим остави эту часть аппаратчикам, а сами плавно перейдём к программной части.

Частота системного таймера по-умолчанию зависит от множества факторов (например на моей Windows 7 c 3-мя ядрами она составляла 1ms), в то время как на соседнем w2k8 сервере с одним ядром примерно 15.6ms. Опять же значение по умолчанию не важно, важен тот факт, что мы можем его изменять. Либо напрямую с помощью функции режима ядра NtSetTimerResolution:

NTSTATUS NtSetTimerResolution (
  IN ULONG RequestedResolution,
  IN BOOLEAN Set,
  OUT PULONG ActualResolution
);

Либо с помощью функции timeBeginPeriod из Multimedia SDK, котороая согласно исследованиям и статье Марка Руссиновича «Inside Windows NT High Resolution Timers» действительно перенаправляет вызов функции NtSetTimerResolution и влияет на точность (частоту срабатывания) системного таймера. Казалось бы, вуаля: вот прекрасный способ увеличить точность временных функций и избавиться от задержек ожидания и так далее. Но действительность гораздо суровее, чем это кажется.

Давайте разберёмся, на что влияет изменение точности системного таймера.
Ну естественно в первую очередь он влияет на точность временных измерений. Все остальные таймеры имеют свои показатели точности и гранулярности, которые в зависимости от критичности таймера либо близки к показателю системного, либо весьма далеки от него. Так например таймер часов на моей машине имеет точность примерно те же самый 15.6ms (получить это значение можно с помощью функции GetSystemTimeAdjustment). Принцип работы остальных таймеров примерно следующий: при срабатывании IRQ0 от некоего внутреннего счётчика этого таймера отнимается показатель времени, и когда счётчик становится меньше либо равен нулю, то таймер генерит событие. Судя по всему в случае некоторых таймеров счётчик уменьшается не на каждое срабатывание IRQ0 и уменьшается не на величину точности системного таймера, а на некоторое число, вычисляемое исходя из частоты срабатывания IRQ0 и точности этого таймера. Сразу признаюсь это лишь мои предположения, которые основаны на анализе значений системного таймера и показателей точности системных часов на моей машине.

Во-вторых, и что гораздо важнее, он влияет на точность отсчёта квантов процессорного времени, выделяемого планировщиком потокам. Если верить всё той же статье Марка Руссиновича («Windows NT High Resolution Timers»), квант отсчитывается по следующему алгоритму. При выделении потоку процессорного времени, определяется доступный ему квант в милисекундах — это значение сохраняется во внутреннюю переменную счётчик. При каждом срабатывании системного таймера, этот счётчик уменьшается на величину равную точности системного таймера. Таким образом, если квант равен 10ms, а точность системного таймера равна 1ms, то ровно через 10ms на 10-ом срабатывнии системного таймера поток будет вытеснен с процессора (если не случиться чего либо ужасного, вроде потока с Realtime приоритетом). Если же размер кванта равен тем же 10ms, а точность системного таймера 8ms, то вытеснение произойдёт через 18ms на 2-ом срабатывании таймера. В этом и только в это заключается влияение точности системного таймера на отсчёт кванта.

Теперь о том, на что не влияет точность системного таймера.
Она не влияет на размер кванта и на алгоритм работы планировщика задач. Размер кванта времени зависит от:

  • системной настройки «Processor scheduling» (My Computer / System Propertues / Advanced / Performance / Advanced), значение по-умолчанию которой зависит от типа системы — сервер или клиент;
  • факта принадлежности потока фоновому или активному процессу (в случае клиента);
    приоритета потока;
  • и прочее

Общее описание алгоритмы работы планировщика тоже предельно просто и описано даже в MSDN`е:

  • для каждого приоритета есть своя очередь (упрощённо говоря);
  • в первую очередь всегда выполняются потоки с наивысшем приоритетом;
  • потоки с меньшим приоритетом получают управление только если все потоки с приоритетом выше завершились или уснули;
  • когда поток отработал свой квант времени, он перемещается в конец очереди;
  • поток лишается остатка своего кванта времени, если запускает любую операцию синхронного обмена, wait- или sleep-функцию, вызывает функцию SwitchToThread или в системе появляется готовый к выполнению поток с более высоким приоритетом;
  • во всех остальных случаях поток отрабатывает свой квант до конца.

Из этого следует, что хоть изменение точности системного таймера и должны повлиять на точность отсчёта времени в функции Sleep, в общем случае они не должны повлиять на временные задержки, связанные с пробуждением спящего потока (пока процессор не освободится поток не сможет стать планируемым). И уж точно они не могут оказать практически никакого влияния на wait-функции, разве что на случай пробуждения по тайм-ауту.

Давайте проверять.
Простейшие тесты с вызовом timeBeginPeriod(1) и замерами для нескольких вызовов функции Sleep с параметром 1 (примеров коих множество на форуме), вроде показывают несостоятельность данного предположения: Sleep(1) действительно спит около 1ms. Я осмелился предположить иное, а именно: алгоритм работы планировщика работает именно так, как описано; а вот алгоритм функции Sleep может немного отличаться, например она может не впадать в коматозное состояние, если время сна меньше разрешения системного таймера или время сна успевает истечь до того, как процесс реально уснёт, поэтому происходит его мгновенное пробуждение. Короткие замеры показывают именно это самое побочное явление функции Sleep, суть которого если честно мне не интересна. Я поставил чебе цель проверить, действительно ли вызов Sleep(1) при точности системного таймера 1ms всегда будет спать ровно 1ms и никто не будет вмешиваться в этот процесс. Отдельный интерес представляет проверка поведения wait-функций в подобных же условиях. Для этих целей я написал простое приложение, которое может быть использовано со следующими аргументами (листинг ниже):
SleepTest.exe [-realtime] [-above-normal] [-loading-thread-below] [-adjust-timer] [-min-delay <integer>] [-test-wait]
-realtime — запустить потоки с максимальным приоритетом (по умолчанию false)
-above-normal — запустить потоки с повышенным приоритетом (по умолчанию false)
-loading-thread-below — поток, эмитирующий «полезную» нагрузку запускается с приоритетом ниже, чем ждущий поток (по умолчанию false)
-adjust-timer — прменить timeBeginPeriod перед засыпанием (по умолчанию false)
-min-delay — минимальное значение задержки которое нужно выводить на экран (единица соответствует в 0.001ms; по умолчанию 150, что соответствует 1.5ms)
-test-wait — запустить тест для wait-функций вместо sleep (по умолчанию false)

Итак тестирование.
Приложение скомпилировано на MS VC 2005 для 64-ёх битной платформы в release конфигурации по-умолчанию. Тестовая платформа: Windows 7 x64 с 3-ёх ядерным процессором (к сожалению все остальные платформы, которые есть под рукой — виртуальные и данный тест не имеет на них никакого смысла, потому что повлять на точность системного таймера для них мы не сможем — она всегда определяется хостовой системой). Машина имеет следующие показания точности системного таймера (по данным приложения CloclRes.exe).
D:\SleepTest\x64\release>clockres

ClockRes v2.0 — View the system clock resolution
Copyright (C) 2009 Mark Russinovich
SysInternals — www.sysinternals.com

Maximum timer interval: 15.600 ms
Minimum timer interval: 0.500 ms
Current timer interval: 1.000 ms

Приложение функционирует следующим образом. При старте создаёт несколько потоков: генерирующий нагрузку, ждущий и (для случая тестирования wait-функций) поток генерации события. Первые 2 потока всегда создаются с привязкой к одному процессору, чтобы эмулировать конкуренцию. 3-ий поток на многоядерной системе всегда привязывается к другому процессору, чтобы не оказывать влияния на вычисления. Поток генерирующий нагрузку просто загружает процессор, ждущий поток замеряет время ожидания (время нахождения внутри вызова Sleep(1) или время между генерацией события и выходом из wait-функции) и выводит его на экран, если оно больше указанного с помощью параметра -min-delay.

1) Запуск с нормальными приоритетами, без настройки таймера, тестируем Sleep (напоминаю, точность системного таймера по-умолчанию 1ms):
D:\SleepTest\x64\release>SleepTest.exe
399: 31.99ms
441: 1.85ms
1408: 2.02ms
3200: 1.55ms
3324: 1.77ms
3354: 1.79ms
4146: 1.67ms
8678: 1.81ms
10014: 3.1ms
11304: 1.63ms
13286: 9.58ms
15475: 1.72ms
16032: 32ms
19499: 2.8ms
20046: 1.68ms

2) Запуск с нормальными приоритетами, с настройкой таймера, тестируем Sleep:
D:\SleepTest\x64\release>SleepTest.exe -adjust-timer
5117: 1.67ms
5185: 2.01ms
5195: 3.15ms
6781: 27ms
8078: 2.02ms
9762: 2.14ms
9865: 2.01ms
11884: 6.96ms
11937: 3.98ms
12939: 3.04ms
13351: 7.99ms
18347: 4.04ms

Как и следовало ожидать, результат практически не отличается от предыдущего, потому что точность системного таймера и без того равна единице. Задержки становятся всё более очевидны и заметны, при переключении приложения на задний план

3) Убедимся, что на VMware эффекта от вызова timeBeginPeriod нет (запуск с нормальными приоритетами, с настройкой таймера, тестируем Sleep на Windows Server 2008 на VMware:
C:\Temp>SleepTest.exe -adjust-timer
1: 13.24ms
2: 14.37ms
3: 15.11ms
4: 14.24ms
5: 13.92ms
6: 14.85ms
7: 14.63ms
8: 23.27ms
9: 4.74ms
10: 14.58ms
11: 14.29ms
12: 15.04ms
13: 14.75ms

4) Запуск с приоритетом real-time, с настройкой таймера, тестируем Sleep:
d:\SleepTest\x64\release>SleepTest.exe -adjust-timer -realtime
1: 31.89ms
2: 31.98ms
3: 31.99ms
4: 31.99ms
5: 31.98ms
6: 32ms
7: 31.98ms
8: 31.98ms
9: 32ms
10: 31.98ms
11: 31.98ms

Этот результат наиболее красноречиво говорит о влиянии планировщика на функции ожидания.

5) Запуск с нормальным приоритетом, с настройкой таймера, тестируем WaitForSingleObject:
d:\Projects\Tests\SleepTest\x64\release>SleepTest.exe -adjust-timer -test-wait
6090: 1.61ms
26341: 31.83ms
31954: 2.3ms
33042: 2.01ms
39156: 34.76ms
52256: 27.89ms
91951: 2.47ms
105046: 31.78ms
118118: 31.51ms
131135: 31.13ms
184144: 31.21ms
197216: 30.87ms
210403: 30.59ms
236874: 1.64ms
250030: 31.98ms
263217: 1.58ms
289289: 3.19ms
302512: 1.53ms
342117: 3.15ms
355197: 40.36ms

Результат говорит сам за себя…

Итог.
Из тестов хорошо видно, что изменение точности системного таймера оказывает влияние на точность временных изменений, но совершенно не влияет на работу планировщика и его перенастройка никак не может гарантировать хоть какую-то величину задержки при выходе из функции ожидания.

Текст тестового приложения.

#include "stdafx.h"

#include <Windows.h>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <process.h>
#include <string>
#include <vector>

static const int MAX_BUFFER_SIZE = 10000;
std::vector<int> buffer;

struct SleepingData {
    bool adjustTimer;
    long minDelay;
};

struct WaitingData {
    bool adjustTimer;
    long minDelay;
    HANDLE waitEvent;
    HANDLE readyForWaitingEvent;
    LARGE_INTEGER setEventTime;
};

void
work()
{
    for (int i = MAX_BUFFER_SIZE - 1; i > 0; --i) {
        buffer.push_back(i);
    }

    std::sort(buffer.begin(), buffer.end());
}

unsigned
usefullWork(void *data)
{
    while (true) {
        work();
    }

    return 0;
}

unsigned
sleeping(void *data)
{
    SleepingData *sleepingData = reinterpret_cast<SleepingData*>(data);

    LARGE_INTEGER frequency;
    ::QueryPerformanceFrequency(&frequency);

    __int64 step = 0;
    while (true) {
        LARGE_INTEGER time1;
        LARGE_INTEGER time2;

        if (sleepingData->adjustTimer) {
            timeBeginPeriod(1);
        }

        ::QueryPerformanceCounter(&time1);
        Sleep(1);
        QueryPerformanceCounter(&time2);

        if (sleepingData->adjustTimer) {
            timeEndPeriod(1);
        }
        ++step;
        __int64 delay = ((time2.QuadPart - time1.QuadPart) * 100000) / frequency.QuadPart;

        if (delay > sleepingData->minDelay) {
            std::wcout << step << L": "<< delay / 100.0 << L"ms" << std::endl;
        }
    }

    return 0;
}

unsigned
eventGenerator(void *data)
{
    WaitingData *waitingData = reinterpret_cast<WaitingData*>(data);

    while (true) {
        ::WaitForSingleObject(waitingData->readyForWaitingEvent, INFINITE);
        QueryPerformanceCounter(&waitingData->setEventTime);
        ::SetEvent(waitingData->waitEvent);
    }

    return 0;
}

unsigned
waiting(void *data)
{
    LARGE_INTEGER frequency;
    ::QueryPerformanceFrequency(&frequency);

    __int64 step = 0;
    WaitingData *waitingData = reinterpret_cast<WaitingData*>(data);
    while (true) {
        if (waitingData->adjustTimer) {
            timeBeginPeriod(1);
        }

        LARGE_INTEGER completeWaitTime;

        ::SetEvent(waitingData->readyForWaitingEvent);
        ::WaitForSingleObject(waitingData->waitEvent, INFINITE);
        QueryPerformanceCounter(&completeWaitTime);

        if (waitingData->adjustTimer) {
            timeEndPeriod(1);
        }
        ++step;
        __int64 delay = ((completeWaitTime.QuadPart - waitingData->setEventTime.QuadPart) * 100000) / frequency.QuadPart;

        if (delay > waitingData->minDelay) {
            std::wcout << step << L": "<< delay / 100.0 << L"ms" << std::endl;
        }
    }

    return 0;
}

void usage()
{
    std::wcout << L"SleepTest.exe [-realtime] [-above-normal] [-loading-thread-below] [-adjust-timer] [-min-delay <integer>] [-test-wait]" << std::endl;
}

int _tmain(int argc, _TCHAR* argv[])
{
    bool procRealTime = false;
    bool testWait = false;
    long sleepingThreadPriority = THREAD_PRIORITY_NORMAL;
    long usefullWorkThreadPriority = THREAD_PRIORITY_NORMAL;

    buffer.reserve(MAX_BUFFER_SIZE);

    SleepingData sleepingData;
    sleepingData.adjustTimer = false;
    sleepingData.minDelay = 150;

    for (int idx = 1; idx < argc; ++idx) {
        std::wstring argument = argv[idx];
        if (L"-realtime" == argument) {
            procRealTime = true;
            sleepingThreadPriority = THREAD_PRIORITY_HIGHEST;
            usefullWorkThreadPriority = THREAD_PRIORITY_HIGHEST;
        } else if (L"-above-normal" == argument) {
            sleepingThreadPriority = THREAD_PRIORITY_HIGHEST;
            usefullWorkThreadPriority = THREAD_PRIORITY_HIGHEST;
        } else if (L"-loading-thread-below" == argument) {
            usefullWorkThreadPriority = THREAD_PRIORITY_LOWEST;
        } else if (L"-adjust-timer" == argument) {
            sleepingData.adjustTimer = true;
        } else if (L"-min-delay" == argument) {
            ++idx;
            if (idx < argc) {
                sleepingData.minDelay = _wtol(argv[idx]);
            } else {
                usage();
                return -1;
            }
        } else if (L"-test-wait" == argument) {
            testWait = true;
        } else {
            usage();
            return -1;
        }
    }

    WaitingData waitingData;
    waitingData.waitEvent = ::CreateEvent(0, FALSE, FALSE, 0);
    waitingData.adjustTimer = sleepingData.adjustTimer;
    waitingData.minDelay = sleepingData.minDelay;
    waitingData.readyForWaitingEvent = ::CreateEvent(0, FALSE, FALSE, 0);
    waitingData.setEventTime.QuadPart = 0;

    HANDLE usefullWorkThread = 0;
    HANDLE sleepingThread = 0;
    HANDLE eventGeneratorThread = 0;

    if (testWait) {
        usefullWorkThread = reinterpret_cast<HANDLE>(
            _beginthreadex(0, 0, &usefullWork, 0, CREATE_SUSPENDED, 0));

        sleepingThread = reinterpret_cast<HANDLE>(
            _beginthreadex(0, 0, &waiting, reinterpret_cast<void*>(&waitingData), CREATE_SUSPENDED, 0));

        eventGeneratorThread = reinterpret_cast<HANDLE>(
            _beginthreadex(0, 0, &eventGenerator, reinterpret_cast<void*>(&waitingData), CREATE_SUSPENDED, 0));
        ::SetThreadAffinityMask(usefullWorkThread, 0x02);
        if (THREAD_PRIORITY_NORMAL != sleepingThreadPriority) {
            ::SetThreadPriority(eventGeneratorThread, sleepingThreadPriority);
        }
        ::ResumeThread(eventGeneratorThread);

    } else {
        usefullWorkThread = reinterpret_cast<HANDLE>(
            _beginthreadex(0, 0, &usefullWork, 0, CREATE_SUSPENDED, 0));

        sleepingThread = reinterpret_cast<HANDLE>(
            _beginthreadex(0, 0, &sleeping, reinterpret_cast<void*>(&sleepingData), CREATE_SUSPENDED, 0));
    }

    ::SetThreadAffinityMask(usefullWorkThread, 0x01);
    ::SetThreadAffinityMask(sleepingThread, 0x01);

    if (procRealTime) {
        ::SetPriorityClass(::GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
    }

    if (THREAD_PRIORITY_NORMAL != usefullWorkThreadPriority) {
        ::SetThreadPriority(usefullWorkThread, usefullWorkThreadPriority);
    }
    if (THREAD_PRIORITY_NORMAL != sleepingThreadPriority) {
        ::SetThreadPriority(sleepingThread, sleepingThreadPriority);
    }

    ::ResumeThread(usefullWorkThread);
    ::ResumeThread(sleepingThread);

    wchar_t ch = std::wcin.get();

    ::TerminateThread(usefullWorkThread, 0);
    ::TerminateThread(sleepingThread, 0);
    ::CloseHandle(usefullWorkThread);
    ::CloseHandle(sleepingThread);
    ::CloseHandle(waitingData.waitEvent);
    return 0;
}

Компьютер сделает всё, что вы ему скажете, но это может сильно отличаться от того, что вы имели в виду.


Re: Исследование: системный таймер Windows

От:

Сергей Мухин

Россия

 
Дата:  16.02.11 07:47
Оценка:

+1

Здравствуйте, Mr. None, Вы писали:

MN>Навеяно спором в ветке ниже

Автор: Mr. None
Дата: 15.02.11

. Поняв бесперспективность дальнейшей дискусси, я решил разложить всё по полочкам и провести более аккуратные измерения. Листинг программы привожу по тексту ниже ибо он весьма велик.

надо статью сделать из этого поста


С уважением,
Сергей Мухин


Re[2]: Исследование: системный таймер Windows

От:

Mr. None

Россия

http://mrnone.blogspot.com
Дата:  16.02.11 07:56
Оценка:

Здравствуйте, Сергей Мухин, Вы писали:

СМ>Здравствуйте, Mr. None, Вы писали:


MN>>Навеяно спором в ветке ниже

Автор: Mr. None
Дата: 15.02.11

. Поняв бесперспективность дальнейшей дискусси, я решил разложить всё по полочкам и провести более аккуратные измерения. Листинг программы привожу по тексту ниже ибо он весьма велик.

СМ>надо статью сделать из этого поста

Можно. Но это именно исследования. Пусть сначала многоуважаемый ALL выскажется, вполне вероятно, что тут есть ошибки. А потом посмотрим.

Компьютер сделает всё, что вы ему скажете, но это может сильно отличаться от того, что вы имели в виду.


Re[3]: Исследование: системный таймер Windows

От:

Сергей Мухин

Россия

 
Дата:  16.02.11 08:01
Оценка:

Здравствуйте, Mr. None, Вы писали:

СМ>>надо статью сделать из этого поста


MN>Можно. Но это именно исследования. Пусть сначала многоуважаемый ALL выскажется, вполне вероятно, что тут есть ошибки. А потом посмотрим.

очепяток (10-ом срабатывнии). и наверно не 18 а 16 в одном месте (10ms, а точность системного таймера 8ms, то вытеснение произойдёт через 18ms на 2-ом)


С уважением,
Сергей Мухин


Re: Исследование: системный таймер Windows

От:

ononim

 
Дата:  16.02.11 08:22
Оценка:

Как много веселых ребят, и все делают велосипед…


Re: Исследование: системный таймер Windows

От:

Jolly Roger

 
Дата:  16.02.11 11:22
Оценка:

Здравствуйте, Mr. None, Вы писали:

MN>Из этого следует, что хоть изменение точности системного таймера и должны повлиять на точность отсчёта времени в функции Sleep, в общем случае они не должны повлиять на временные задержки, связанные с пробуждением спящего потока (пока процессор не освободится поток не сможет стать планируемым). И уж точно они не могут оказать практически никакого влияния на wait-функции, разве что на случай пробуждения по тайм-ауту.

Не заметил сразу Вывод не верный.

Так как изменение разрешения таймеру приводит к более частой активации планировщика, то планировщик получает возможность быстрее узнать об изменении статуса потока вследствии изменения состояния ожидаемого им объекта. Как следствие, планировщик получает возможность раньше переключить процессор на такой поток — например, в случае, когда данный поток имеет более высокий приоритет, чем поток, занимающий процессор в настоящий момент.

В этом легко убедиться с помощью теста, похожего на приведённый мной в предыдущей ветке, заменив в нём sleep на waitforsingleobject для EVENT’а, который бы взводил поток с пониженным приоритетом. Псевдокод примерно такой

HANDLE h = CreateEvent(...);

HANDLE thr = CreateThread(..., CREATE_SUSPENDED,...);
SetThreadPriority(thr, THREAD_PRIORITY_LOWEST);
Sleep(100);
ResumeThread(thr)
QueryPerformanceCounter(t1)
WaitForSingleObject(h, INFINITE)
QueryPerformanceCounter(t2);

С таким, примерно, телом потоковой функции

{
SetEvent(h);
for(;;);
}

Результаты будут так-же зависить от изменения настроек таймера, как и в случае sleep.

На что действительно таймер не влияет, так это на размер слайса потока, ну да это очевидно.

«Нормальные герои всегда идут в обход!»


Re: Исследование: системный таймер Windows

От:

Cyberax

Марс

 
Дата:  20.02.11 00:26
Оценка:

1 (1)

Здравствуйте, Mr. None, Вы писали:

MN>Какие есть таймеры внутри операционной системы: ждущие таймеры, мультимедийные таймеры, таймер часов и бла-бла-бла. Всё это верхушка айсберга. В основании лежит единый системный таймер. Он же аппаратный программируемый таймер, который мы помним из школьного курса программирования аппаратных средств в MS DOS.

Тема HPET не раскрыта. Насколько я понимаю, QueryPerformanceCounter на Vista+ использует именно его, а не RTC или RDTSC.

Sapienti sat!


Re: Исследование: системный таймер Windows

От:

netch80

Украина

http://netch80.dreamwidth.org/
Дата:  20.02.11 09:37
Оценка:

24 (3)

Здравствуйте, Mr. None, Вы писали:

MN>Какие есть таймеры внутри операционной системы: ждущие таймеры, мультимедийные таймеры, таймер часов и бла-бла-бла. Всё это верхушка айсберга. В основании лежит единый системный таймер. Он же аппаратный программируемый таймер, который мы помним из школьного курса программирования аппаратных средств в MS DOS. Напомню про него в кратце: это кварц с частотой 1.19318 МГц, который управляет генерацией аппаратного прерывания IRQ0 через определённые интервалы.

Сразу же дам несколько замечаний. Во-первых, там этих таймеров больше и они разнообразнее.
В «наследственной» (legacy) архитектуре, от которой Intel пытается избавиться уже давно (этак с момента окончательного изобретения HPET), это выглядит так. Есть кварц на 14318182 Гц (точнее, 315/22 МГц), эта частота родом из стандарта NTSC. В материнку эта частота попадает так:

* в HPET — неизменённой.
* в PIIX (aka ACPI-fast aka ACPI-safe timer) — делённый на 4, получаем 3579545.5 Гц.
* в бывший i8254 — делённый на 12, получаем 1193182 Гц.

Когда Intel таки избавится от старья, останется только первый из них и его догонят до 25 МГц или выше. То есть i2854 уже не будет. Окончательно это настанет где-то с переходом на 4KB блоки дисков на SATA и SAS шинах, то есть когда DOS, Windows 9x и прочее старьё просто не запустится.

Далее, надо логически разделять счётчики тактов несущей таймерной частоты и генераторы прерываний. i8254, по-современному, крив потому, что у него это совмещено, причём перенастройка означает потерю точности, а в окрестностях перехода через 0 надо учитывать возможность обгона. Такие же проблемы у HPET в части режимов (но не всех). PIIX сам даёт только счётчик, но не генератор; в паре к нему используется или local APIC, или i8254. Зато его счётчик — минимум 3 секунды полного цикла (на серверном железе при 32 битах в счётчике — десятки минут). HPET даёт оба, но с некоторыми ограничениями (перенастройка дороговата).

TSC — в процессоре — хороший счётчик, но не генератор прерываний. Устойчив с ограничениями (не включен EST, синхронная работа на всех ядрах). С приходом HPET исключается из применения как детальный счётчик, из-за этих ограничений.

Я ещё не вспомнил RTC, но у него крайне низкая точность, поэтому не учитываем.

Так вот — как уже заметил Cyberax — ориентация на названный Вами i8254 — не единственный вариант, мягко говоря. Начиная с ~2004 идёт переход на HPET как основной, если он есть. У него точность выше и, что хорошо, самый устойчивый счёт для прерываний в произвольные моменты (так называемый noHz стиль).

MN> Главное что нужно понять, так это то, что частота так называемого системного таймера Windows по сути и является этой самой частотой генерации прерывания IRQ0.

Как уже сказано выше, это давно стало неверным. Вероятно, c Vista, тут уже не знаю деталей. Но с семёрки — точно. В Unix мире рубежи другие, но времена — где-то такие же.

MN>Давайте разберёмся, на что влияет изменение точности системного таймера.

MN>Ну естественно в первую очередь он влияет на точность временных измерений. Все остальные таймеры имеют свои показатели точности и гранулярности, которые в зависимости от критичности таймера либо близки к показателю системного, либо весьма далеки от него.

Вот тут начинается вопрос стиля API. Например, во FreeBSD у функций снятия времени есть варианты fast (данные, установленные последним прерыванием), precise (обязательный запрос к аппаратному таймеру), по дефолту — precise. В Linux для gettimeofday() — кажется, только fast. В Windows это разделено на разные функции. GetSystemTimeAsFileTime() по крайней мере по начальную XP включительно читала R/O переменную через userland, не делая системный вызов, таким образом точность была как Вы описываете — до последнего прерывания. А вот QueryPerformanceCounter() могла и сисколл вызвать, зависело от версии и железа (хотя предпочтительным был TSC).

MN> Так например таймер часов на моей машине имеет точность примерно те же самый 15.6ms (получить это значение можно с помощью функции GetSystemTimeAdjustment).

Вы опять объединяете (я не говорю «путаете») точность измерения времени, точность таймеров отложенных запросов и частоту прерываний. Они в общем случае различны.
Вторая и третья совпадают только для вычислительной нагрузки, не вызывающей даже page fault. Иначе отложенный запрос может отработаться с большей точностью, чем таймерное прерывание.

MN>Во-вторых, и что гораздо важнее, он влияет на точность отсчёта квантов процессорного времени, выделяемого планировщиком потокам.

Да, при всё том же условии — нагрузка чисто вычислительная. Достаточно одного треда, ушедшего в спячку на ожидании чего-то внешнего (блок с диска, нажатие клавиатуры…), чтобы случился решедулинг. С другой стороны, обычно не делают решедулинг раньше нескольких квантов — он вреден для кэша. Поэтому таймерное прерывание может быть только поводом сменить активную задачу, но не обязанностью. Впрочем, это всё уже на уровне залезания в бутылку, в целом я не возражаю.

MN>Из этого следует, что хоть изменение точности системного таймера и должны повлиять на точность отсчёта времени в функции Sleep, в общем случае они не должны повлиять на временные задержки, связанные с пробуждением спящего потока (пока процессор не освободится поток не сможет стать планируемым). И уж точно они не могут оказать практически никакого влияния на wait-функции, разве что на случай пробуждения по тайм-ауту.

Это если переключение на пробуждённую задачу идёт сразу — что совсем не обязательно. Если у неё нет существенно более высокого диспетчерского приоритета, то выгоднее продолжить выполнение активной задачи, а отдать другой процессор, только когда «припечёт». Диспетчерский приоритет может строиться, например, по тому, сколько задача выполнялась до того, или по явной пометке интерактивного характера выполнения.

MN>Давайте проверять.

MN>Простейшие тесты с вызовом timeBeginPeriod(1) и замерами для нескольких вызовов функции Sleep с параметром 1 (примеров коих множество на форуме), вроде показывают несостоятельность данного предположения: Sleep(1) действительно спит около 1ms.

Вы тестировали на семёрке? У неё, как и у большинства OS этого поколения, для современного железа берётся темп прерываний 1000 или 2000 в секунду.

MN>Итог.

MN>Из тестов хорошо видно, что изменение точности системного таймера оказывает влияние на точность временных изменений, но совершенно не влияет на работу планировщика и его перенастройка никак не может гарантировать хоть какую-то величину задержки при выходе из функции ожидания.

У меня вывод, что надо пересмотреть тесты с учётом реального стиля работы системы. Хотя Ваши результаты от этого, конечно, не поменяются, потому что они чисто практические.

The God is real, unless declared integer.


Re[2]: Исследование: системный таймер Windows

От:

Mr. None

Россия

http://mrnone.blogspot.com
Дата:  21.02.11 05:11
Оценка:

Здравствуйте, Cyberax, Вы писали:

C>Здравствуйте, Mr. None, Вы писали:


MN>>Какие есть таймеры внутри операционной системы: ждущие таймеры, мультимедийные таймеры, таймер часов и бла-бла-бла. Всё это верхушка айсберга. В основании лежит единый системный таймер. Он же аппаратный программируемый таймер, который мы помним из школьного курса программирования аппаратных средств в MS DOS.

C>Тема HPET не раскрыта. Насколько я понимаю, QueryPerformanceCounter на Vista+ использует именно его, а не RTC или RDTSC.

Честно говоря и не пытался даже … Цель поста была в ином: проверить на что влияет, а на что не влияет системный таймер. И можно ли увеличить точность срабатывания wait-функций простым изменением точности системного таймера. Но за инфу спасибо.

Компьютер сделает всё, что вы ему скажете, но это может сильно отличаться от того, что вы имели в виду.


Re[2]: Исследование: системный таймер Windows

От:

Mr. None

Россия

http://mrnone.blogspot.com
Дата:  21.02.11 06:38
Оценка:

Здравствуйте, netch80, Вы писали:

N>Здравствуйте, Mr. None, Вы писали:


MN>>Какие есть таймеры внутри операционной системы: ждущие таймеры, мультимедийные таймеры, таймер часов и бла-бла-бла. Всё это верхушка айсберга. В основании лежит единый системный таймер. Он же аппаратный программируемый таймер, который мы помним из школьного курса программирования аппаратных средств в MS DOS. Напомню про него в кратце: это кварц с частотой 1.19318 МГц, который управляет генерацией аппаратного прерывания IRQ0 через определённые интервалы.


N>Сразу же дам несколько замечаний. Во-первых, там этих таймеров больше и они разнообразнее.

Да я знаю, потому и написал далее «и бла-бла-бла» . Я не ставил целью охватить все таймеры. Цель была проверить степень влияния системного таймера. По сути показать, что никакими шаманствами с точностью таймера вы не добьётесь от Windows превращения в real-time OS. Может быть заголовок не вполне удачный.

N>В «наследственной» (legacy) архитектуре, от которой Intel пытается избавиться уже давно (этак с момента окончательного изобретения HPET), это выглядит так. Есть кварц на 14318182 Гц (точнее, 315/22 МГц), эта частота родом из стандарта NTSC. В материнку эта частота попадает так:


N>Так вот — как уже заметил Cyberax — ориентация на названный Вами i8254 — не единственный вариант, мягко говоря. Начиная с ~2004 идёт переход на HPET как основной, если он есть. У него точность выше и, что хорошо, самый устойчивый счёт для прерываний в произвольные моменты (так называемый noHz стиль).

Спасибо, информация действительно интересная, у меня и вправду ориентация была на старый вариант и это я подозревал, потому упомянул: » Опять же детали могут немного отличаться, за сим остави эту часть аппаратчикам, а сами плавно перейдём к программной части.». Ибо аппаратная составляющая в таких тонкостях на суть вопроса не влияет. Объяснения на уровне IRQ и старого таймера достаточно для начального понимания. Кому интересно могут копать глубже. Но ещё раз благодарю за информацию — мне было интересно ваше пояснение.

MN>> Главное что нужно понять, так это то, что частота так называемого системного таймера Windows по сути и является этой самой частотой генерации прерывания IRQ0.


N>Как уже сказано выше, это давно стало неверным…

HPET тоже генерирует некое прерывание, только его значение настраивается опционально. Так что с точки зрения программной составляющей ничего практически не изменилось .

MN>> Так например таймер часов на моей машине имеет точность примерно те же самый 15.6ms (получить это значение можно с помощью функции GetSystemTimeAdjustment).


N>Вы опять объединяете (я не говорю «путаете») точность измерения времени, точность таймеров отложенных запросов и частоту прерываний. Они в общем случае различны.

N>Вторая и третья совпадают только для вычислительной нагрузки, не вызывающей даже page fault. Иначе отложенный запрос может отработаться с большей точностью, чем таймерное прерывание.

Вот тут как раз не путаю. Тут вопрос терминологии. Работа системных часов (system clock) основана, как и любые иные временные измерения, на некотором таймере. Точность этого таймера предельна низка, поэтому применяется так называемая коррекция времени (time adjustment). Параметры это коррекции можно получить с помощью метода GetSystemTimeAdjustment. Первое значение (lpTimeAdjustment) — это размер коррекции в 100-наносекундных интервалах, второе (lpTimeIncrement) — частота коррекции. То есть каждые (pTimeIncrement * 10) микросекунд значение system clock принудительно увеличивается на (pTimeAdjustment * 10) микросекунд. А дальше действительно всё зависит от аппаратуры: system clock может быть как отдельным аппаратным таймером (с частотой срабатывания pTimeIncrement ), так и висеть на любом ином из доступных. Поправьте, если я не прав.

MN>>Во-вторых, и что гораздо важнее, он влияет на точность отсчёта квантов процессорного времени, выделяемого планировщиком потокам.


N>Да, при всё том же условии — нагрузка чисто вычислительная. Достаточно одного треда, ушедшего в спячку на ожидании чего-то внешнего…

…Как на его место придёт другой процесс и совсем не обязательно ваш — это и есть реальная работа системы — жёская конкуренция за процессор. Именно поэтому в тесте я привязываю потоки к одному ядру и создаю вычислительную нагрузку без засыпаний — самая экстремальная ситуация для параллельной обработки.
Обработка нажатия клавиатуры, кстати, производится в ядре по аппаратному прерыванию, и оно прерывает все пользовательские потоки в системе и, ВНИМАНИЕ, по завершению возвращает управление тому потоку, который исполнялся в момент прерывания (с некоторым оговорками до и после платформы Win6.0 — по разному вычисляется остаток кванта, с учётом и без учёта времени, потраченного на обработку прерывания, но это тоже залезание в бутылку , так что оставим эту тему…

MN>>Из этого следует, что хоть изменение точности системного таймера и должны повлиять на точность отсчёта времени в функции Sleep, в общем случае они не должны повлиять на временные задержки, связанные с пробуждением спящего потока (пока процессор не освободится поток не сможет стать планируемым). И уж точно они не могут оказать практически никакого влияния на wait-функции, разве что на случай пробуждения по тайм-ауту.


N>Это если переключение на пробуждённую задачу идёт сразу — что совсем не обязательно. Если у неё нет существенно более высокого диспетчерского приоритета, то выгоднее продолжить выполнение активной задачи, а отдать другой процессор, только когда «припечёт». Диспетчерский приоритет может строиться, например, по тому, сколько задача выполнялась до того, или по явной пометке интерактивного характера выполнения.

Наличия подобной логике в поведения шедулера задач Windows мне не известно. Насколько я знаю из доступных мне источников, засыпание на любой sleep и wait-функции всегда окончательное без шансов на внезапное пробуждение. Согласно документации (тому же MSDN`у) при вызове любой из этих функций, у потока отбирается остаток кванта и она входит в ждущее состояние до наступление события ожидания (исключение составляет только Sleep(0), который отбирает остаток квант, но не переводит поток в ждущее состояние). Причём в случае одноядерных систем такое поведение для wait-функций более чем естественно: если сразу не отдать процессор другому, то точно никто не переведёт ожилаемый объект в сигнальное состояние . На многопроцессорных системах такое поведение может првести к проблемам связанным с падением производительности из-за частых переключений контекстов, поэтому в алгоритм работы критической секции была заложена возможность небольшого ожидания в активном состоянии до засыпания на мьютексе (функция InitializeCriticalSectionAndSpinCount). Но с другой стороны, тесты показывают, что вполне вероятно на многоядерных системах подобная логика может быть зашита в функцию Sleep, ибо при ожидании на малом интервале влияние переключения потоков на задержки возврата наблюдаются не на каждом срабатывании функции.

MN>>Давайте проверять.

MN>>Простейшие тесты с вызовом timeBeginPeriod(1) и замерами для нескольких вызовов функции Sleep с параметром 1 (примеров коих множество на форуме), вроде показывают несостоятельность данного предположения: Sleep(1) действительно спит около 1ms.

N>Вы тестировали на семёрке? У неё, как и у большинства OS этого поколения, для современного железа берётся темп прерываний 1000 или 2000 в секунду.

Вы не внимательно читали пост и описание тестов:

Тестовая платформа: Windows 7 x64 с 3-ёх ядерным процессором

Машина имеет следующие показания точности системного таймера (по данным приложения CloclRes.exe).
D:\SleepTest\x64\release>clockres

ClockRes v2.0 — View the system clock resolution
Copyright (C) 2009 Mark Russinovich
SysInternals — www.sysinternals.com

Maximum timer interval: 15.600 ms
Minimum timer interval: 0.500 ms
Current timer interval: 1.000 ms

Что называется новее только Windows Server 2008 R2 x64 . Но они все у меня виртуализированы и там нет возможности повлиять на точность таймеров по вполне понятным причинам .

MN>>Итог.


N>У меня вывод, что надо пересмотреть тесты с учётом реального стиля работы системы. Хотя Ваши результаты от этого, конечно, не поменяются, потому что они чисто практические.

А цель и была получить практический, а не теоретический результат .

Большое спасибо за комментарии. Они были интересны — с аппаратной составляющей я не возился уже очень давно, поэтому последние веяние в аппаратных таймерах мне были не известны. Но опять же подчёркиваю — на конечный результат они не могут повлиять, ибо с какой бы частотой не обновлялся таймер, некислое такое влияние на точность оказывает планировщик задач Windows .

Компьютер сделает всё, что вы ему скажете, но это может сильно отличаться от того, что вы имели в виду.


Re[3]: Исследование: системный таймер Windows

От:

netch80

Украина

http://netch80.dreamwidth.org/
Дата:  21.02.11 07:46
Оценка:

Здравствуйте, Mr. None, Вы писали:

MN>Да я знаю, потому и написал далее «и бла-бла-бла» . Я не ставил целью охватить все таймеры. Цель была проверить степень влияния системного таймера. По сути показать, что никакими шаманствами с точностью таймера вы не добьётесь от Windows превращения в real-time OS. Может быть заголовок не вполне удачный.

Ну вообще-то таки добиваются — real-time OS от Windows. По крайней мере я читал про такое, во времена NT4 и Win2000 было достаточно много таких разработок. Разумеется, «дьявол в мелочах» и, говоря про realtime, надо уточнять, какие именно численные характеристики имеются в виду. Я не думаю, что в случае Windows легко получится «hard realtime» со временами менее миллисекунды, но единицы миллисекунд получались достаточно легко.

MN>>> Главное что нужно понять, так это то, что частота так называемого системного таймера Windows по сути и является этой самой частотой генерации прерывания IRQ0.

N>>Как уже сказано выше, это давно стало неверным…
MN>HPET тоже генерирует некое прерывание, только его значение настраивается опционально. Так что с точки зрения программной составляющей ничего практически не изменилось .

Пожалуй, важнее то, что изменилась собственно частота прерываний. На большинстве ОС разлива не более трёхлетней давности держатся значений в 1000 и 2000 Гц, в то время как до этого было типичным 100 (в Unix), а во времена Windows 9x и MS-DOS — пресловутые 18.2. Это уж я не вспоминаю про noHz варианты, как современный ноутбучный Linux. Это даёт возможность срабатывать значительно точнее к нужному моменту. Ваш же тест в основном относится уже не к самому прерыванию, а к тому, когда шедулер попытается отдать управление. А это уже зависит от его алгоритма, а не от прочего. Кстати, Ваша цифра в 15.6 мс меня смущает — я не вижу ей обоснования. Это что, 64 Гц? Это очень странная цифра. Такую частоту может давать, например, RTC — его можно просить любой темп прерываний, который степень двойки от 1 до 15. Но тогда непонятно, почему идёт опора на RTC.

Собственно, при повторном чтении Вашего исходного сообщения этот интервал меня смущает больше всего: непонятно, откуда берётся и почему. Я бы рекомендовал уточнить свойства того железа, на котором Ваш W2k8server. Возможно, у него проблемы.

А если вопрос в шедулере — то надо поиграться с системными настройками. Любой универсальный шедулер может быть настроен на несколько стилей работы. Например, максимальная интерактивность и скорость реакции — но тогда не удивляйтесь, что только-только ставшую задачу отбросят и переведут на следующую. Или максимальная производительность — тогда шедулер будет откладывать переключение, пока больше не сможет (накопится очередь сердито рычащих задач)

MN>Вот тут как раз не путаю. Тут вопрос терминологии. Работа системных часов (system clock) основана, как и любые иные временные измерения, на некотором таймере. Точность этого таймера предельна низка, поэтому применяется так называемая коррекция времени (time adjustment). Параметры это коррекции можно получить с помощью метода GetSystemTimeAdjustment. Первое значение (lpTimeAdjustment) — это размер коррекции в 100-наносекундных интервалах, второе (lpTimeIncrement) — частота коррекции. То есть каждые (pTimeIncrement * 10) микросекунд значение system clock принудительно увеличивается на (pTimeAdjustment * 10) микросекунд. А дальше действительно всё зависит от аппаратуры: system clock может быть как отдельным аппаратным таймером (с частотой срабатывания pTimeIncrement ), так и висеть на любом ином из доступных. Поправьте, если я не прав.

В описании механизма Вы, похоже, правы, но я не могу понять, при чём тут он. Time adjustment — средство для плавной корректировки хода часов согласно данным внешнего источника. Внешним источником в типичном случае является NTP сервер. Средство time adjustment не используется в обычной работе без корректировки: внутреннего счёта достаточно.

(К слову, механизм плавной корректировки времени в таком виде выглядит ужасно. У него нет предельного значения корректировки, поэтому если её никто не остановит, время может уйти в противоположную сторону. Рывковая корректировка каждые N тактов условного темпа может давать локальные прыжки назад. Непонятно поведение средства, если кто-то захочет изменить темп системного таймера, который отражён в lpTimeIncrement. Unix аналоги adjtime[x], ntp_adjtime выглядят значительно разумнее спроектированными — они не требуют постоянного надзора и не дают хода счёта назад. Возможно, это результат кривого документирования подхода Windows, но буквальное чтение пугает.)

Поэтому давайте не будем привязываться к этому средству в описании обычного хода дел.

N>>Да, при всё том же условии — нагрузка чисто вычислительная. Достаточно одного треда, ушедшего в спячку на ожидании чего-то внешнего…

MN>…Как на его место придёт другой процесс и совсем не обязательно ваш — это и есть реальная работа системы — жёская конкуренция за процессор. Именно поэтому в тесте я привязываю потоки к одному ядру и создаю вычислительную нагрузку без засыпаний — самая экстремальная ситуация для параллельной обработки.

Хорошо. Теперь давайте предположим, что Ваш тест с W2k8 был на беспроблемной аппаратуре и включена генерация таймерных прерываний на, например, 1024 Гц (нетипичная цифра, но можно поверить). В таком случае получается, что несмотря на приход необходимости запустить другую задачу — текущий продолжает выполняться в течение 16 тактов, после чего шедулер таки решает, что надо сменить активную задачу и дать Вашей что-то сделать. Похоже на истину? jIMHO, очень — многие шедулеры именно так и делают.

MN>Обработка нажатия клавиатуры, кстати, производится в ядре по аппаратному прерыванию, и оно прерывает все пользовательские потоки в системе и, ВНИМАНИЕ, по завершению возвращает управление тому потоку, который исполнялся в момент прерывания (с некоторым оговорками до и после платформы Win6.0 — по разному вычисляется остаток кванта, с учётом и без учёта времени, потраченного на обработку прерывания, но это тоже залезание в бутылку , так что оставим эту тему…

Да, по умолчанию так везде, и это не специфика Windows. Но если это нажатие клавиатуры было передано в userland и процесс там ушёл в очередное ожидание — должен быть пересчёт активности, причём при условии, что не нужно (нельзя) сохранять текущую задачу активной. Вот тут-то и происходит перепланировка (скорее всего, без полного пересчёта диспетчерских приоритетов; этот пересчёт делается через фиксированные интервалы).

N>>Это если переключение на пробуждённую задачу идёт сразу — что совсем не обязательно. Если у неё нет существенно более высокого диспетчерского приоритета, то выгоднее продолжить выполнение активной задачи, а отдать другой процессор, только когда «припечёт». Диспетчерский приоритет может строиться, например, по тому, сколько задача выполнялась до того, или по явной пометке интерактивного характера выполнения.

MN>Наличия подобной логике в поведения шедулера задач Windows мне не известно. Насколько я знаю из доступных мне источников, засыпание на любой sleep и wait-функции всегда окончательное без шансов на внезапное пробуждение.

_Засыпание_ — практически безусловно (кроме случаев, когда случился обгон с другим процессором, который поднял событие именно в тот момент, когда задача уходила в спячку на этом событии). А вот _просыпание_ другой задачи — нет. Я говорю как раз о втором. Готовность задачи к запуску означает лишь переход её в категорию активных, но не немедленный запуск. Вы рассматриваете в своих тестах как раз этот случай: задача должна запуститься, но пока есть другая активная вычислительная задача, ей не дают немедленного запуска. Это должно решаться другими методами — например, заданием абсолютного приоритета. На практике сейчас таких абсолютных приоритетов нет (кроме отдельных частей ядра), а максимум что есть — рекомендация в виде realtime приоритета.

N>>Вы тестировали на семёрке? У неё, как и у большинства OS этого поколения, для современного железа берётся темп прерываний 1000 или 2000 в секунду.


MN>Вы не внимательно читали пост и описание тестов:

Я внимательно читал, но нужной мне цифры не увидел, потому что её там нет. Она должна была называться — частота прерываний таймера. В данных примерах Вы меряете лишь косвенный параметр.

MN>

MN>Тестовая платформа: Windows 7 x64 с 3-ёх ядерным процессором
MN>…
MN>Машина имеет следующие показания точности системного таймера (по данным приложения CloclRes.exe).
MN>D:\SleepTest\x64\release>clockres

MN>ClockRes v2.0 — View the system clock resolution
MN>Copyright (C) 2009 Mark Russinovich
MN>SysInternals — www.sysinternals.com

MN>Maximum timer interval: 15.600 ms
MN>Minimum timer interval: 0.500 ms
MN>Current timer interval: 1.000 ms


MN>Что называется новее только Windows Server 2008 R2 x64 . Но они все у меня виртуализированы и там нет возможности повлиять на точность таймеров по вполне понятным причинам .

Вот это вот «current timer interval» означает одно из двух:
1. Фиксированная частота — 1000Гц или кратная ей.
2. noHz режим с дискретом отсчёта 0.5мс. (Слабо верится, noHz ранее не был типичен для Windows.)

Но всё равно точных данных о частоте здесь не добыть.

N>>У меня вывод, что надо пересмотреть тесты с учётом реального стиля работы системы. Хотя Ваши результаты от этого, конечно, не поменяются, потому что они чисто практические.

MN>А цель и была получить практический, а не теоретический результат .

Верно. Но практика без теории мертва, потому что непонятно, что и как измерять. С неверной теорией Вы можете измерять вещи, которые не имеют никакого практического значения или, наоборот, не сообразить сделать важный тест. Фактически, где-то так и получилось: например, данные отчёта clockres.exe Вы считаете первичными, хотя это совсем не так. Тут есть ещё над чем подумать.

MN>Большое спасибо за комментарии. Они были интересны — с аппаратной составляющей я не возился уже очень давно, поэтому последние веяние в аппаратных таймерах мне были не известны. Но опять же подчёркиваю — на конечный результат они не могут повлиять, ибо с какой бы частотой не обновлялся таймер, некислое такое влияние на точность оказывает планировщик задач Windows .

Угу. И посему надо искать, какие ручки и как крутить в планировщике. Мне помнится, что они там таки были.

The God is real, unless declared integer.


Re[4]: Исследование: системный таймер Windows

От:

Mr. None

Россия

http://mrnone.blogspot.com
Дата:  21.02.11 11:58
Оценка:

Здравствуйте, netch80, Вы писали:

N>Здравствуйте, Mr. None, Вы писали:


MN>>Да я знаю, потому и написал далее «и бла-бла-бла» . Я не ставил целью охватить все таймеры. Цель была проверить степень влияния системного таймера. По сути показать, что никакими шаманствами с точностью таймера вы не добьётесь от Windows превращения в real-time OS. Может быть заголовок не вполне удачный.


N>Ну вообще-то таки добиваются — real-time OS от Windows. По крайней мере я читал про такое, во времена NT4 и Win2000 было достаточно много таких разработок. Разумеется, «дьявол в мелочах» и, говоря про realtime, надо уточнять, какие именно численные характеристики имеются в виду. Я не думаю, что в случае Windows легко получится «hard realtime» со временами менее миллисекунды, но единицы миллисекунд получались достаточно легко.

Нет. Это не возможно впринципе. Все эти разработки основывались на каком-нибудь допущении, например точность таймера не более 15 мс, единственный real-time поток, отсутствие драйверов, которые могут дать серьёздную задержку при обработке аппаратного исключения и так далее. Главный критерий операционной системы реального времени, не только минимальность задержки, но и гарантия величины временной задержки на отклик. А именно этого (гарантии) OS Windows предоставить и не может. Простой пример: на временную задержку может повлиять не только переключение контекста, но и банальное поступление любого аппаратного прерывания, которое прерывает исполнение любого пользовательского потока, опять же «почистить кэш» тоже никто не отменял . А если серьёздно, то вы можете проверить это даже с использованием моего тестового приложения: 1 поток с приоритетом real-time работает без задержек, но уже 2 потока с подобным приоритетом выдают рекордные задержки для всего теста — до 32 милисекунд!

MN>>>> Главное что нужно понять, так это то, что частота так называемого системного таймера Windows по сути и является этой самой частотой генерации прерывания IRQ0.

N>>>Как уже сказано выше, это давно стало неверным…
MN>>HPET тоже генерирует некое прерывание, только его значение настраивается опционально. Так что с точки зрения программной составляющей ничего практически не изменилось .

N>Пожалуй, важнее то, что изменилась собственно частота прерываний. На большинстве ОС разлива не более трёхлетней давности держатся значений в 1000 и 2000 Гц, в то время как до этого было типичным 100 (в Unix), а во времена Windows 9x и MS-DOS — пресловутые 18.2.

Ну это-то понятно, я сразу понимал, что скорее всего с тех пор всё стало гораздо лучше . Тут я и не спорю.
Кстати 18.2 на MS-DOS тоже не была пределом совершенства. Если я не ошибаюсь, то 18.2 — это при значении счётчика 65535, я добивался гораздо лучших результатов (кажется предельно минимальное значении счётчика на которое успевал реагировать Pentium 100 было около 10, меньше не работало из-за задержек BIOS`а кажется, точно уже не помню). Но это уже история.

N>…Ваш же тест в основном относится уже не к самому прерыванию, а к тому, когда шедулер попытается отдать управление. А это уже зависит от его алгоритма, а не от прочего.

О том и речь. Частота срабатывания прерывания влияет только на точность замеров времени, но не на величину задержки отклика — на это влияет алгоритм работы шедулера.

N>Кстати, Ваша цифра в 15.6 мс меня смущает — я не вижу ей обоснования. Это что, 64 Гц? Это очень странная цифра. Такую частоту может давать, например, RTC — его можно просить любой темп прерываний, который степень двойки от 1 до 15. Но тогда непонятно, почему идёт опора на RTC.



N>В описании механизма Вы, похоже, правы, но я не могу понять, при чём тут он. Time adjustment — средство для плавной корректировки хода часов… К слову, механизм плавной корректировки времени в таком виде выглядит ужасно … результат кривого документирования подхода Windows, но буквальное чтение пугает…


N>Поэтому давайте не будем привязываться к этому средству в описании обычного хода дел.

Это был всего лишь пример одного из таймеров системы, упомянутый между прочим, просто он лучше всего документирован. Да это и есть тот самый RTC, или его емуляция или что-то ещё. Именно так он и работает: один раз в 15.6 милисекунд происходит тик и значение времени увеличивается на 15.6 милисекунд. Для часов этого более чем достаточно, именно поэтому Microsoft и не рекомендует на них опираться в точных подсчётах. Согласен давайте забьём на это.

N>>>Да, при всё том же условии — нагрузка чисто вычислительная. Достаточно одного треда, ушедшего в спячку на ожидании чего-то внешнего…

MN>>…Как на его место придёт другой процесс и совсем не обязательно ваш — это и есть реальная работа системы — жёская конкуренция за процессор. Именно поэтому в тесте я привязываю потоки к одному ядру и создаю вычислительную нагрузку без засыпаний — самая экстремальная ситуация для параллельной обработки.

N>Хорошо. Теперь давайте предположим, что Ваш тест с W2k8 был на беспроблемной аппаратуре и включена генерация таймерных прерываний на, например, 1024 Гц (нетипичная цифра, но можно поверить). В таком случае получается, что несмотря на приход необходимости запустить другую задачу — текущий продолжает выполняться в течение 16 тактов, после чего шедулер таки решает, что надо сменить активную задачу и дать Вашей что-то сделать. Похоже на истину? jIMHO, очень — многие шедулеры именно так и делают.

Не W2k8, а именно Windows 7 и проблем с аппаратурой не наблюдается. Если верить показаниям ClockRes, то это скорее 1000 КГЦ: Current timer interval: 1.000 ms, то есть таймер срабатывает каждую милисекунду.

Я не понял насчёт 16 тактов — вы имели в виду количество операций процессора или количество срабатываний таймера? Если второе, то мне видится как раз иное: после засыпания поток тереяет свой квант. Второй поток посылает сообщение, что переводит 2-ой поток в состояние ready и помещает в конец очереди планировщика, далее второй поток честно отрабатывает свой квант до конца (как вы заметили там чисто вычислительная нагрузка без засыпания, поэтому ему нет резона отдавать процессор раньше времени). После истечения кванта, 2-ой поток выталкивается и первый поток получает управление. Вот и всё. Значения кванта могут сильно варьироваться, поэтому любые суждения о его величине — это гадания на кофейной гуще. В частности, если мне не изменяет память, для real-time потоков он всегда увеличен, по сравнению со всеми остальными. Отсюда и такие колосальные задержки в случае 5-ого теста.

N>>>Это если переключение на пробуждённую задачу идёт сразу — что совсем не обязательно. Если у неё нет существенно более высокого диспетчерского приоритета, то выгоднее продолжить выполнение активной задачи, а отдать другой процессор, только когда «припечёт». Диспетчерский приоритет может строиться, например, по тому, сколько задача выполнялась до того, или по явной пометке интерактивного характера выполнения.

MN>>Наличия подобной логике в поведения шедулера задач Windows мне не известно. Насколько я знаю из доступных мне источников, засыпание на любой sleep и wait-функции всегда окончательное без шансов на внезапное пробуждение.

N>А вот _просыпание_ другой задачи — нет. Я говорю как раз о втором. Готовность задачи к запуску означает лишь переход её в категорию активных, но не немедленный запуск. Вы рассматриваете в своих тестах как раз этот случай: задача должна запуститься, но пока есть другая активная вычислительная задача, ей не дают немедленного запуска.

Да. Именно этот случай я и рассматриваю. Потому что это наиболее типичная ситуация на нормально загруженной системе: конкуренция за процессорное время. Именно поэтому я эмулирую вычислительную нагрузку, именно поэтому я привязываю потоки к одному ядру. Потому что именно в таком окружении и начинают сбоить все методики повышения степени «real-time`енности» ОС Windows за счёт изменения частоты системного таймера. Именно это я и хотел показать: если будет конкуренция, даже малейшая, ничего не выйдет. Естественно, если у вас пара десятков ядер и натуральный параллелизм, то все эти тесты покажут, что нет никакой задержки — всё срабатывает максимально быстро и мгновенно (собственно так и у меня работет, если разнести нагружающий поток и ждущий по разным ядрам). Но в жизни так не бывает…

N>Это должно решаться другими методами — например, заданием абсолютного приоритета. На практике сейчас таких абсолютных приоритетов нет (кроме отдельных частей ядра), а максимум что есть — рекомендация в виде realtime приоритета.

И если появляется второй realtime поток, всё перестаёт работать (см. тест номер 5).

MN>>Вы не внимательно читали пост и описание тестов:


N>Я внимательно читал, но нужной мне цифры не увидел, потому что её там нет. Она должна была называться — частота прерываний таймера. В данных примерах Вы меряете лишь косвенный параметр.

Вы спросили тестировал ли я на 7-ке, я ответил, что именно на ней я и тестировал.

Частота прерываний таймера = 1 / Current timer interval = 1 / 1ms = 1 МГЦ. (точка — это отделение десятичной части, а не тысяч).

N>Вот это вот «current timer interval» означает одно из двух:

N>1. Фиксированная частота — 1000Гц или кратная ей.

1000 КГЦ

N>2. noHz режим с дискретом отсчёта 0.5мс. (Слабо верится, noHz ранее не был типичен для Windows.)

Вот тут ничего сказать не могу. Что есть то есть.

N>Верно. Но практика без теории мертва, потому что непонятно, что и как измерять. С неверной теорией Вы можете измерять вещи, которые не имеют никакого практического значения или, наоборот, не сообразить сделать важный тест. Фактически, где-то так и получилось: например, данные отчёта clockres.exe Вы считаете первичными, хотя это совсем не так. Тут есть ещё над чем подумать.

ClockRes возвращает значения, полученные функцией NtQueryTimerResolution, которая возвращает показания точности системного таймера как есть. Более того, в самой тестовой программе я использую явное изменение точности таймера с помощью функции timeBeginPeriod. Эта функция вызывает NtSetTimerResolution и устанавливает напрямую точность системного таймера — это чтобы отмести всякие вопросы вообще, но в моём случае смысла в этом нет, ибо точность и так 1ms. Как раз эта информация абсолютно точная и получена из весьма достоверного источника — статьи Марка Руссиновича «Inside Windows NT High Resolution Timers». Так что тут степень доверия наивысшая.

N>Угу. И посему надо искать, какие ручки и как крутить в планировщике. Мне помнится, что они там таки были.

Очень минимальне: максимум на что вы можете повлияеть — это на размер кванта (за абсолютность информации не ручаюсь, но иных ручек мне просто не известно). На алгоритм работы планировщика вы повлиять не можете. Основным постулатом тут является тот факт, что если ваш поток потерял свой квант (в следствии засыпания), то назад управление он получит не раньше чем свои кванты отработают (или потеряют в следствии засыпания) все потоки с таким же или большим приоритетом. И повлиять на это вы не способны. Даже real-time потоки конкурируют между собой, причём даже жёстче, чем обычные потоки — величина кванта у них гораздо выше.

Компьютер сделает всё, что вы ему скажете, но это может сильно отличаться от того, что вы имели в виду.


Re[4]: Исследование: системный таймер Windows

От:

Jolly Roger

 
Дата:  21.02.11 14:24
Оценка:

Здравствуйте, netch80, Вы писали:

N>Ну вообще-то таки добиваются — real-time OS от Windows. По крайней мере я читал про такое, во времена NT4 и Win2000 было достаточно много таких разработок.

Если я не путаю, там подменяли HAL, «врезая» в него собственно Real-time OS, а windows становилась одной из задач этой ОСи, т.е. как-бы превращалась в виртуальную.

N>Кстати, Ваша цифра в 15.6 мс меня смущает — я не вижу ей обоснования. Это что, 64 Гц? Это очень странная цифра. Такую частоту может давать, например, RTC — его можно просить любой темп прерываний, который степень двойки от 1 до 15. Но тогда непонятно, почему идёт опора на RTC.


N>Собственно, при повторном чтении Вашего исходного сообщения этот интервал меня смущает больше всего: непонятно, откуда берётся и почему. Я бы рекомендовал уточнить свойства того железа, на котором Ваш W2k8server. Возможно, у него проблемы.

Да нет, это нормально. Так и бывает — 64 или 100 Гц. Обе я видел сам, да и Руссинович говорит

Длительность интервала таймера зависит от аппаратной платформы и определяется HAL, а не ядром. Например, этот интервал на большинстве однопроцессорных x86-систем составляет 10 мс, а на большинстве многопроцессорных x86-систем — около 15 мс


N>А если вопрос в шедулере — то надо поиграться с системными настройками. Любой универсальный шедулер может быть настроен на несколько стилей работы. Например, максимальная интерактивность и скорость реакции — но тогда не удивляйтесь, что только-только ставшую задачу отбросят и переведут на следующую. Или максимальная производительность — тогда шедулер будет откладывать переключение, пока больше не сможет (накопится очередь сердито рычащих задач)

На Windows таких рычагов не много — длинный (12 * d) или короткий (2 * d) квант(где d — период таймера по умолчанию для данной системы), использовать или нет динамическое повышение приоритете, и сам приоритет. Нку и ещё интервал прерываний таймера через timebeginperiod. Как я понимаю, при переходе объекта ядра в сигнальное состояние, ожидащий его поток переводится в готовность и по возможности планируется на исполнение на подходящем процессоре, однако самого переключения не происходит, так как IRQL неподходящий. Далее должно сработать прерывание от таймера. По нему уменьшается счётчик кванта работающего потока и проверяктся появление более приоритетных готовых потоков. Если одно из условий выполняется, то в очередь добавляется DPC. Ну а оно уже будет обработано, когда до него дойдёт дело, и уже в его обработчики и выполняется само переключение контекстов. Причём даже если на процессоре крутится поток простоя, то оно всё равно работает так. Тесты вроде подтверждают.

N>(К слову, механизм плавной корректировки времени в таком виде выглядит ужасно. У него нет предельного значения корректировки, поэтому если её никто не остановит, время может уйти в противоположную сторону. Рывковая корректировка каждые N тактов условного темпа может давать локальные прыжки назад. Непонятно поведение средства, если кто-то захочет изменить темп системного таймера, который отражён в lpTimeIncrement. Unix аналоги adjtime[x], ntp_adjtime выглядят значительно разумнее спроектированными — они не требуют постоянного надзора и не дают хода счёта назад. Возможно, это результат кривого документирования подхода Windows, но буквальное чтение пугает.)

Да он не только выглядит, он и работает ужасно Без корректировки по какому-нибудь серверу точного времени пользоваться практически невозможно.

«Нормальные герои всегда идут в обход!»


Re[5]: Исследование: системный таймер Windows

От:

netch80

Украина

http://netch80.dreamwidth.org/
Дата:  23.02.11 06:13
Оценка:

Здравствуйте, Jolly Roger, Вы писали:

N>>Ну вообще-то таки добиваются — real-time OS от Windows. По крайней мере я читал про такое, во времена NT4 и Win2000 было достаточно много таких разработок.

JR>Если я не путаю, там подменяли HAL, «врезая» в него собственно Real-time OS, а windows становилась одной из задач этой ОСи, т.е. как-бы превращалась в виртуальную.

Вполне возможно. За давностью детали забылись. А сейчас вряд ли кто-то станет всерьёз этим заниматься — over 99% рынка занято линуксом…

JR>Да нет, это нормально. Так и бывает — 64 или 100 Гц. Обе я видел сам, да и Руссинович говорит

JR>

JR>Длительность интервала таймера зависит от аппаратной платформы и определяется HAL, а не ядром. Например, этот интервал на большинстве однопроцессорных x86-систем составляет 10 мс, а на большинстве многопроцессорных x86-систем — около 15 мс

OK. Непонятно, почему они выбрали такую цифру… может, потому, что доверяют RTC больше? Ладно, за давностью лет, наверно, уже не важно.

N>>А если вопрос в шедулере — то надо поиграться с системными настройками. Любой универсальный шедулер может быть настроен на несколько стилей работы. Например, максимальная интерактивность и скорость реакции — но тогда не удивляйтесь, что только-только ставшую задачу отбросят и переведут на следующую. Или максимальная производительность — тогда шедулер будет откладывать переключение, пока больше не сможет (накопится очередь сердито рычащих задач)

JR>На Windows таких рычагов не много — длинный (12 * d) или короткий (2 * d) квант(где d — период таймера по умолчанию для данной системы), использовать или нет динамическое повышение приоритете, и сам приоритет.

Это и для последних версий так?

JR> Нку и ещё интервал прерываний таймера через timebeginperiod. Как я понимаю, при переходе объекта ядра в сигнальное состояние, ожидащий его поток переводится в готовность и по возможности планируется на исполнение на подходящем процессоре, однако самого переключения не происходит, так как IRQL неподходящий. Далее должно сработать прерывание от таймера.

Я совсем не видел, как это сделано в Windows, но в большинстве Unix систем зависит от того, как был запущен тот источник, который сделал этот перевод в готовность. Если он в обработчике прерывания (включая softinterrupt), то на моменте перехода из такого обработчика в режим задачи производится контроль необходимой перепланировки. Если он в контексте задачи, но на другом процессоре, возможно посылание IPI на разбудить. И даже если он на том же процессоре, и выполняется задача, возможен быстрый контроль необходимости переключения (грубо говоря, if (current_process->prio + penalty < awaken->prio {switch;})) Я не удивлюсь, если что-то подобное будет добавлено в Windows.

N>>(К слову, механизм плавной корректировки времени в таком виде выглядит ужасно. У него нет предельного значения корректировки, поэтому если её никто не остановит, время может уйти в противоположную сторону. Рывковая корректировка каждые N тактов условного темпа может давать локальные прыжки назад. Непонятно поведение средства, если кто-то захочет изменить темп системного таймера, который отражён в lpTimeIncrement. Unix аналоги adjtime[x], ntp_adjtime выглядят значительно разумнее спроектированными — они не требуют постоянного надзора и не дают хода счёта назад. Возможно, это результат кривого документирования подхода Windows, но буквальное чтение пугает.)

JR>Да он не только выглядит, он и работает ужасно Без корректировки по какому-нибудь серверу точного времени пользоваться практически невозможно.

Гм, это уже другой вопрос — качество поддержания счёта локального времени. Оно может страдать или из-за кривого кварца (погрешность в 0.01% это 8 секунд на день, а на практике наблюдалось и 0.5%), или из-за пропуска прерываний. Первое лечится выбором нормального железа Второе — в общем тоже (настолько длительно блокировать может только аппаратный драйвер). Вот по таким причинам я считаю, что только с PIIX и HPET появились хоть сколько-то нормальные аппаратные таймеры в PC/Wintel. Стиль работы i8254 пригоден для ОС реального времени, но не разделения времени, как большинство современных. Для ОС разделения времени счётчик времени и генератор прерываний должны быть принципиально разделены и длительность полного периода счётчика времени должна измеряться минимум секундами, а лучше часами. А в случае SetSystemTimeAdjustment() проблемы ортогональные этому, но тоже существенные.

The God is real, unless declared integer.


Re[5]: Исследование: системный таймер Windows

От:

netch80

Украина

http://netch80.dreamwidth.org/
Дата:  24.02.11 07:03
Оценка:

Здравствуйте, Mr. None, Вы писали:

N>>Ну вообще-то таки добиваются — real-time OS от Windows. По крайней мере я читал про такое, во времена NT4 и Win2000 было достаточно много таких разработок. Разумеется, «дьявол в мелочах» и, говоря про realtime, надо уточнять, какие именно численные характеристики имеются в виду. Я не думаю, что в случае Windows легко получится «hard realtime» со временами менее миллисекунды, но единицы миллисекунд получались достаточно легко.

MN>Нет. Это не возможно впринципе.

Jolly Roger уже прокомментировал, как это делалось, по крайней мере в одном из вариантов исполнения. Метод в принципе не уникальный — в линуксе точно так же работает так называемый RTLinux — отдельный слой realtime и уже вне него основная часть (на разделении времени).

MN> Все эти разработки основывались на каком-нибудь допущении, например точность таймера не более 15 мс, единственный real-time поток, отсутствие драйверов, которые могут дать серьёздную задержку при обработке аппаратного исключения и так далее. Главный критерий операционной системы реального времени, не только минимальность задержки, но и гарантия величины временной задержки на отклик. А именно этого (гарантии) OS Windows предоставить и не может. Простой пример: на временную задержку может повлиять не только переключение контекста, но и банальное поступление любого аппаратного прерывания, которое прерывает исполнение любого пользовательского потока, опять же «почистить кэш» тоже никто не отменял . А если серьёздно, то вы можете проверить это даже с использованием моего тестового приложения: 1 поток с приоритетом real-time работает без задержек, но уже 2 потока с подобным приоритетом выдают рекордные задержки для всего теста — до 32 милисекунд!

Вы опять-таки говорите про исходное, не заточенное под RT ядро. Вполне возможно, что на ядре сборки формата «обычный десктоп» или «обычный сетевой сервер» это невозможно, но нигде не ограничивалось именно такой сборкой. Более того, именно в ядерной части NT, насколько я её знаю (только по книгам и чуть-чуть по исходникам), изначально (в отличие от Unix) был принят ряд мер, которые немного облегчают реализацию RT за счёт «отвязки» от логики жёсткого одноствольного исполнения. В них, правда, не было kernel preemption, оно было добавлено существенно позже и не для всех; но сам по себе перевод драйверов с линейной логики на событийно-управляемую существенно помогает этому.

N>>Пожалуй, важнее то, что изменилась собственно частота прерываний. На большинстве ОС разлива не более трёхлетней давности держатся значений в 1000 и 2000 Гц, в то время как до этого было типичным 100 (в Unix), а во времена Windows 9x и MS-DOS — пресловутые 18.2.


MN>Ну это-то понятно, я сразу понимал, что скорее всего с тех пор всё стало гораздо лучше . Тут я и не спорю.

MN>Кстати 18.2 на MS-DOS тоже не была пределом совершенства. Если я не ошибаюсь, то 18.2 — это при значении счётчика 65535, я добивался гораздо лучших результатов (кажется предельно минимальное значении счётчика на которое успевал реагировать Pentium 100 было около 10, меньше не работало из-за задержек BIOS`а кажется, точно уже не помню). Но это уже история.

Да. Собственно, юниксы тех времён строили таймер через i8254, но перепрограммировали его на 100Гц или сколько задано в конфигурации. Можно было и 1000, но на железе уровня P2 мы при таких уровнях уже получали потерю устойчивости системы. (Опять же, чем хорошо было бы noHz с самого начала — частоту срабатывания можно корректировать динамически в зависимости от скорости работы системы, не теряя её основных характеристик.) То, что Вы описываете, предполагает сверхкороткий обработчик прерывания, но он такой практически бесполезен.

N>>…Ваш же тест в основном относится уже не к самому прерыванию, а к тому, когда шедулер попытается отдать управление. А это уже зависит от его алгоритма, а не от прочего.

MN>О том и речь. Частота срабатывания прерывания влияет только на точность замеров времени, но не на величину задержки отклика — на это влияет алгоритм работы шедулера.

Ну почему же. Тут говорят, что у шедулера есть варианты квантов в 2 или 12 тиков таймера. Значит, корректируя эту частоту, можно получать и более мелкие кванты без изменения основного алгоритма.

N>>В описании механизма Вы, похоже, правы, но я не могу понять, при чём тут он. Time adjustment — средство для плавной корректировки хода часов… К слову, механизм плавной корректировки времени в таком виде выглядит ужасно … результат кривого документирования подхода Windows, но буквальное чтение пугает…

N>>Поэтому давайте не будем привязываться к этому средству в описании обычного хода дел.

MN>Это был всего лишь пример одного из таймеров системы, упомянутый между прочим, просто он лучше всего документирован. Да это и есть тот самый RTC, или его емуляция или что-то ещё. Именно так он и работает: один раз в 15.6 милисекунд происходит тик и значение времени увеличивается на 15.6 милисекунд.

Это он без корректировки так работает. Корректировкой его можно даже остановить. Но это таки другая тема.

MN> Для часов этого более чем достаточно, именно поэтому Microsoft и не рекомендует на них опираться в точных подсчётах. Согласен давайте забьём на это.

BTW, для часов это тоже уже может быть недостаточно. Например, для file time и сравнениях для make.

MN>Не W2k8, а именно Windows 7 и проблем с аппаратурой не наблюдается. Если верить показаниям ClockRes, то это скорее 1000 КГЦ: Current timer interval: 1.000 ms, то есть таймер срабатывает каждую милисекунду.

OK.

MN>Я не понял насчёт 16 тактов — вы имели в виду количество операций процессора или количество срабатываний таймера?

Таймера.

MN> Если второе, то мне видится как раз иное: после засыпания поток тереяет свой квант. Второй поток посылает сообщение, что переводит 2-ой поток в состояние ready и помещает в конец очереди планировщика, далее второй поток честно отрабатывает свой квант до конца (как вы заметили там чисто вычислительная нагрузка без засыпания, поэтому ему нет резона отдавать процессор раньше времени). После истечения кванта, 2-ой поток выталкивается и первый поток получает управление. Вот и всё. Значения кванта могут сильно варьироваться, поэтому любые суждения о его величине — это гадания на кофейной гуще. В частности, если мне не изменяет память, для real-time потоков он всегда увеличен, по сравнению со всеми остальными. Отсюда и такие колосальные задержки в случае 5-ого теста.

Да, это примерно совпадает с алгоритмом действия, как бы это сказать помягче, тупого планировщика «от сохи», в котором принципиально устранены все возможные средства ускоренного перепланирования. Он даже может удовлетворять >90% задач обычной среды разделения времени, если достаточно адекватно сделана схема мгновенных (диспетчерских) приоритетов.

N>>Вот это вот «current timer interval» означает одно из двух:

N>>1. Фиксированная частота — 1000Гц или кратная ей.

MN>1000 КГЦ

Один мегагерц — частота _прерываний_ таймера? Думаю, тут Вы таки ошибаетесь.

MN>ClockRes возвращает значения, полученные функцией NtQueryTimerResolution, которая возвращает показания точности системного таймера как есть. Более того, в самой тестовой программе я использую явное изменение точности таймера с помощью функции timeBeginPeriod. Эта функция вызывает NtSetTimerResolution и устанавливает напрямую точность системного таймера — это чтобы отмести всякие вопросы вообще, но в моём случае смысла в этом нет, ибо точность и так 1ms. Как раз эта информация абсолютно точная и получена из весьма достоверного источника — статьи Марка Руссиновича «Inside Windows NT High Resolution Timers». Так что тут степень доверия наивысшая.

OK.

N>>Угу. И посему надо искать, какие ручки и как крутить в планировщике. Мне помнится, что они там таки были.

MN>Очень минимальне: максимум на что вы можете повлияеть — это на размер кванта (за абсолютность информации не ручаюсь, но иных ручек мне просто не известно). На алгоритм работы планировщика вы повлиять не можете. Основным постулатом тут является тот факт, что если ваш поток потерял свой квант (в следствии засыпания), то назад управление он получит не раньше чем свои кванты отработают (или потеряют в следствии засыпания) все потоки с таким же или большим приоритетом. И повлиять на это вы не способны. Даже real-time потоки конкурируют между собой, причём даже жёстче, чем обычные потоки — величина кванта у них гораздо выше.

Я краем уха слышал, что в семёрке улучшили процессорный шедулинг. Или врут? Если не врут, то могли улучшить что-то именно для этой задачи?

The God is real, unless declared integer.


Re[6]: Исследование: системный таймер Windows

От:

Jolly Roger

 
Дата:  24.02.11 07:34
Оценка:

Здравствуйте, netch80, Вы писали:

JR>>На Windows таких рычагов не много — длинный (12 * d) или короткий (2 * d) квант(где d — период таймера по умолчанию для данной системы), использовать или нет динамическое повышение приоритете, и сам приоритет.


N>Это и для последних версий так?

Не стану утверждать наверняка, но мне о каких-то кардинальных изменениях не известно.

JR>> Нку и ещё интервал прерываний таймера через timebeginperiod. Как я понимаю, при переходе объекта ядра в сигнальное состояние, ожидащий его поток переводится в готовность и по возможности планируется на исполнение на подходящем процессоре, однако самого переключения не происходит, так как IRQL неподходящий. Далее должно сработать прерывание от таймера.


N>Я совсем не видел, как это сделано в Windows, но в большинстве Unix систем зависит от того, как был запущен тот источник, который сделал этот перевод в готовность. Если он в обработчике прерывания (включая softinterrupt), то на моменте перехода из такого обработчика в режим задачи производится контроль необходимой перепланировки. Если он в контексте задачи, но на другом процессоре, возможно посылание IPI на разбудить. И даже если он на том же процессоре, и выполняется задача, возможен быстрый контроль необходимости переключения (грубо говоря, if (current_process->prio + penalty < awaken->prio {switch;})) Я не удивлюсь, если что-то подобное будет добавлено в Windows.

Может быть в каких-то ситуациях оно так, может быть в новых версиях изменилось. MS не публикует алгоритмы планировщика, дабы не связывать себе руки гарантиями. По факту эти алгоритмы в той или иной степени меняются практически в каждой версии. Я опираюсь на имеющиеся публичные источники, свои тесты и логику. В принципе, вероятно нет препятствий, чтобы инициировать DPC непосредственно в момент перевода объекта в сигнальное состояние, однако по факту на W2k и WinXP этого не происходит, задержка между изменением состояния объекта и пробуждением ожидающего его потока на тех тестах, что я делал, варьируется от ~2 до ~16 мСек в зависимости от настроек таймера. На мой взгляд, это указывает, что инициируется переключение именно прерыванием таймера.

«Нормальные герои всегда идут в обход!»


Re[7]: Исследование: системный таймер Windows

От:

ononim

 
Дата:  24.02.11 14:55
Оценка:

8 (1)

JR>Может быть в каких-то ситуациях оно так, может быть в новых версиях изменилось. MS не публикует алгоритмы планировщика, дабы не связывать себе руки гарантиями. По факту эти алгоритмы в той или иной степени меняются практически в каждой версии. Я опираюсь на имеющиеся публичные источники, свои тесты и логику. В принципе, вероятно нет препятствий, чтобы инициировать DPC непосредственно в момент перевода объекта в сигнальное состояние, однако по факту на W2k и WinXP этого не происходит, задержка между изменением состояния объекта и пробуждением ожидающего его потока на тех тестах, что я делал, варьируется от ~2 до ~16 мСек в зависимости от настроек таймера. На мой взгляд, это указывает, что инициируется переключение именно прерыванием таймера.
Вы affinity поставьте на один и тот же процессор — и такие переключения станут намного быстрее. Винда часто ставит пробужденный поток на другой процессор, нежели тот на котором тред который просигналил объект, и исполнятся тот поток начинает ой как нескоро. Но разрешение таймера тут непричем — по крайней мере тесты корреляции не показали.

Как много веселых ребят, и все делают велосипед…


Re[8]: Исследование: системный таймер Windows

От:

Jolly Roger

 
Дата:  24.02.11 15:12
Оценка:

Здравствуйте, ononim, Вы писали:

O>Вы affinity поставьте на один и тот же процессор — и такие переключения станут намного быстрее. Винда часто ставит пробужденный поток на другой процессор, нежели тот на котором тред который просигналил объект, и исполнятся тот поток начинает ой как нескоро. Но разрешение таймера тут непричем — по крайней мере тесты корреляции не показали.

На однопроцессорной машине делалось

«Нормальные герои всегда идут в обход!»


Re[9]: Исследование: системный таймер Windows

От:

ononim

 
Дата:  24.02.11 15:41
Оценка:

8 (1)

O>>Вы affinity поставьте на один и тот же процессор — и такие переключения станут намного быстрее. Винда часто ставит пробужденный поток на другой процессор, нежели тот на котором тред который просигналил объект, и исполнятся тот поток начинает ой как нескоро. Но разрешение таймера тут непричем — по крайней мере тесты корреляции не показали.

JR>На однопроцессорной машине делалось

И без HT? Тогда чета очень большие у вас задержки были.
Вот такой вот нехитрый код:

#define SINGLE_SIGNAL_AND_WAIT
#define SINGLE_CPU

struct trdpar
{
    HANDLE wait;
    HANDLE set;
    volatile LONG *counter;
};


void __cdecl switch_perf_trd(void *p)
{
    trdpar *par = (trdpar *)p;
#ifndef SINGLE_SIGNAL_AND_WAIT
    for (;;)
    {
        ::WaitForSingleObject(par->wait, INFINITE);
        ::SetEvent(par->set);
        if (par->counter)InterlockedIncrement(par->counter);
    }
#else
    ::WaitForSingleObject(par->wait, INFINITE);
    for (;;)
    {
        
        ::SignalObjectAndWait(par->set, par->wait, INFINITE, FALSE);
        if (par->counter)InterlockedIncrement(par->counter);
    }
#endif

}


void switch_perf()
{
    volatile LONG counter = 0;
    trdpar foo1 = {::CreateEvent(0, 0, 1, 0), ::CreateEvent(0, 0, 0, 0), 0};
    trdpar foo2 = {foo1.set, foo1.wait, &counter};
    _beginthread(switch_perf_trd, 0, &foo1);
    _beginthread(switch_perf_trd, 0, &foo2);
    for (InterlockedExchange(&counter, 0);;)
    {
        Sleep(1000);
        printf("%u sw/sec              \r", InterlockedExchange(&counter, 0));
    }
}

int _tmain(int argc, wchar_t **argv)
 {

    ::SetPriorityClass(::GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
#ifdef SINGLE_CPU
    ::SetProcessAffinityMask(::GetCurrentProcess(), 1);
#endif
    switch_perf();
    return 0;
 }

будучи запущенным на P4 с HT под ХР Дает следующие результаты:

SINGLE_SIGNAL_AND_WAIT, SINGLE_CPU: 320000 sw/sec
!SINGLE_SIGNAL_AND_WAIT, SINGLE_CPU: 220000 sw/sec
SINGLE_SIGNAL_AND_WAIT, !SINGLE_CPU: 80000 sw/sec
!SINGLE_SIGNAL_AND_WAIT, !SINGLE_CPU: 90000 sw/sec

то есть минимальная задержка 3 микросекунды, а максимальная — 12 микросекунд, что, согласитесь, совсем на таймер не похоже.

Как много веселых ребят, и все делают велосипед…


Re[10]: Исследование: системный таймер Windows

От:

Jolly Roger

 
Дата:  24.02.11 17:50
Оценка:

Здравствуйте, ononim, Вы писали:

Чертовщина какая-то Ваш код показал у меня примерно 420000 и 270000. После этого попробовал вновь свой код, и он показал стабильно 1.94-1.97 вне зависимости от timeBeginPeriod, хотя буквально несколько дней назад этот-же тест показывал чёткую связь: 15 до timeBeginPeriod(1), и 1.95 — после. Перзагрузка ничего не меняет.

Пока не знаю, в чём дело Попробую завтра разобраться.

«Нормальные герои всегда идут в обход!»

  • Переместить
  • Удалить
  • Выделить ветку

Пока на собственное сообщение не было ответов, его можно удалить.

The default timer resolution on Windows is 15.6 ms – a timer interrupt 64 times a second. When programs increase the timer frequency they increase power consumption and harm battery life. They also waste more compute power than I would ever have expected – they make your computer run slower! Because of these problems Microsoft has been telling developers to not increase the timer frequency for years.

So how come almost every time I notice that my timer frequency has been raised it’s been done by a Microsoft program (or Chrome), that is not doing anything to justify requesting a high-frequency timer?

This article was updated July 13, 2013, based on feedback from readers. See the bottom for the new material.

Update July 15, 2014. Google has locked the Chrome bug to further editing. The last comment from Google says that Chrome doesn’t always raise the timer resolution, and besides, other programs also raise it.

Update March 2015: Chrome now avoids raising the timer frequency unnecessarily, and as of summer 2020 Chrome will not raise the timer frequency above 125 Hz when on battery power, making it a non-issue for battery life.

Update June 2015: UIforETW, my handy tool for recording ETW traces for performance analysis, now records the current timer frequency to the traces, in addition to the batch file for extremely detailed recording and analysis of timer frequency changes.

Update October 2020: Microsoft has updated the Windows Kernel (Windows 10 and above) so that if one process raises the timer interrupt frequency it has less effect on other processes. This should reduce both the power and CPU wastage concerns, although it does not completely eliminate them. Details here.

Update November 2020: Microsoft’s tools for measuring and investigating the timer interrupt frequency (clockres and powercfg) are insufficient and clunky. Among other things they just sample the timer interrupt frequency at a point in time, when many programs adjust the timer interrupt frequency many times per second. To get a better report run trace_timer_intervals.bat – it summarizes all changes by all processes over the time period recorded.

Seeing the current timer frequency is easy – just run the clockres tool by sysinternals.

ClockRes v2.0 – View the system clock resolution
Copyright (C) 2009 Mark Russinovich
SysInternals – http://www.sysinternals.com

Maximum timer interval: 15.600 ms
Minimum timer interval: 0.500 ms
Current timer interval: 1.000 ms

However this just gives you a snapshot, which is useless if Chrome or the Go runtime or other programs are constantly changing the timer interrupt frequency. This batch file gives a much better perspective over time.

Update: September 1, 2018. With the latest version of Windows 10 I see that clockres claims that the timer interrupt frequency goes up to 1 kHz when I unplug my laptop. TimerTool.exe says the same thing. ETW tracing and looking at the Microsoft-Windows-Kernel-Power provider shows that the OS itself is modifying the timer interrupt frequency (SystemTimeResolutionKernelChange events) about 30 times a second. I have been told that there are no power implications to kernel timer interrupt frequency changes, but even if that is true they make clockres and TimerTool useless. What a shame.

For maximum battery life the current timer interval (which can be changed with timeBeginPeriod, or NtSetTimerResolution) should be 15.6 ms. but as you can see above some program had set it to 1.0 ms. That means the timer interrupt is firing an extra 936 times per second, which should only be done if the benefits justify the costs.

Finding the culprit – WPF

Finding out who raised the timer frequency is non-obvious, but still fairly easy. Just open an administrator command prompt and run “powercfg -energy duration 5”. Part of the resulting HTML report will look like this:

The stack of modules responsible for the lowest platform timer setting in this process.
Requested Period 10000
Requesting Process ID 3932
Requesting Process Path
C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\devenv.exe
Calling Module Stack
C:\Windows\SysWOW64\ntdll.dll
C:\Windows\SysWOW64\winmm.dll
C:\Windows\Microsoft.NET\Framework\v4.0.30319\WPF\wpfgfx_v0400.dll
C:\Windows\SysWOW64\kernel32.dll
C:\Windows\SysWOW64\ntdll.dll

So, Visual Studio 11, through its use of WPF, requested a 1.0 ms timer interval, confusingly displayed as 10,000 with the units being 100 ns. This is a known problem with WPF. All versions of Visual Studio trigger this behavior sometimes, and presumably most WPF programs can also trigger it. While increasing the timer frequency might make sense for an application that is trying to maintain a steady frame rate it does not make sense for WPF to leave the timer frequency raised even when there is no animation going on (discussion on a WPF issue is here).

Finding the culprit – SQL Server

Another common culprit on my machine is sqlservr.exe. I think this was installed by Visual Studio but I’m not sure. I’m not sure if it is being used or not. Either way, SQL Server should not be raising the timer frequency. If doing so is needed to improve performance then that sounds like a design flaw. And, as with WPF, if raising the frequency is needed then it should only be done when SQL Server is busy, instead of leaving it permanently raised.

Platform Timer Resolution:Outstanding Timer Request
A program or service has requested a timer resolution smaller than the platform maximum timer resolution.
Requested Period 10000
Requesting Process ID 2384
Requesting Process Path \Device\HarddiskVolume1\Program Files\Microsoft SQL Server\MSSQL10.MSSQLSERVER\MSSQL\Binn\sqlservr.exe

Finding the culprit – quartz.dll

I don’t have the powercfg output for it but C:\Windows\System32\quartz.dll is another cause of an increased timer frequency. I’m not even sure what Quartz is (Expression Web Designer?) but I know it is sometimes wasting energy.

Finding the culprit – Chrome

Microsoft is the usual culprit on my machine, but Google’s Chrome is also an offender. If I run Chrome then it instantly raises the timer frequency to 1,000 Hz, even when I’m on battery power and just displaying a raw HTML page.

To the right we can see Chrome displaying a harsh indictment of Chrome.

Finding the culprit – svchost.exe

Sometimes svchost.exe raises the timer frequency to 100 Hz. That’s nowhere near as bad as 1,000 Hz, but still annoying. It’s particularly frustrating because I can’t tell which service is doing it.

Alternate techniques to find the culprit

If you record an ETW trace with Microsoft-Windows-Kernel-Power provider (UIforETW does this by default) then the Generic Events table will contain events with Task Names like SystemTimeResolutionChange (user-mode timer resolution changes?) and SystemTimeResolutionKernelChange (kernel-mode timer resolution changes?) and you can see what value they set the timer interval to (in typical inscrutable units, in hex just to make them more friendly). zero means reset to default. Unfortunately the SystemTimeResolutionKernelChange seem to get set in the context of whatever process is running so the Process field is useless, but at least the SystemTimeResolutionChange events will tell you what processes are changing the timer frequency, recorded over the tracing interval.

Tragedy of the commons – highest frequency wins

The Windows timer interrupt is a global resource and it ticks at one rate for the entire system. That means that a single program that raises the timer frequency affects the behavior of the entire system.

When a process calls timeBeginPeriod this frequency request is in force until it is explicitly cancelled with timeEndPeriod or until the process terminates. Most programs (including my own test program below) never call timeEndPeriod, relying instead on Windows process cleanup. This works, and is reasonable for any application that needs the timer frequency high for its entire lifetime, but for any process whose lifetime may outlast its need for a high frequency timer, it’s time to start calling timeEndPeriod. As Microsoft recommends, this includes movie players that are paused, and games that are minimized. It also includes web browsers that do not currently need high-resolution timers, or are running on battery power.

(see Sleep Variation Investigated for what the timer frequency affects)

(note that as of Windows 11 and beyond one process no longer affects other processes as much)

Does it matter?

My main home computer is a laptop. I use it on the bus every day and I like to save my battery power for useful things rather than having it wasted on waking up the CPU 1,000 times a second.

Microsoft says that it matters. In this article they say “We are committed to continuously improving the energy efficiency of Windows PCs” and yet, four years later, they don’t seem to be following their own guidelines or heading their own warnings which say “Some applications reduce this to 1 ms, which reduces the battery run time on mobile systems by as much as 25 percent.”

One handy way of estimating the power cost is to use the Intel Power Gadget tool. On supported Intel processors this shows you the power drawn by the CPU package in real-time with a claimed precision of 0.01 W. Power Gadget is handy because it works equally well whether on battery power or plugged in. On my Windows 7 Sandybridge laptop it consistently shows a .3 W increase in power draw from having the timer frequency increased. That’s almost 10% of the idle CPU package power draw, although a lower percentage of total system power draw.

An increase of 0.3 W may not seem like much but there are a couple of reasons to take it seriously. One is that if your software is on average running on 33 million machines (a conservative bet for something like Chrome) then increasing the timer frequency could be wasting about ten MW of power. A check-in that fixes such a bug gives you enough carbon-offset-karma to last a lifetime.

Update: probably the reason why power consumption increased by 0.3 W was that I had several processes on my laptop that were spinning in Sleep(1) loops but had not called timeBeginPeriod. On older versions of Windows they would start doing more work (consuming more CPU time and more electricity) when some other process called timeBeginPeriod(1). On recent versions of Windows this is no longer true so the power consumption increase is probably reduced, and the power consumption increase was always highly sensitive to what other processes were running, but unfortunately I didn’t investigate this enough at the time.

Another reason to take this issue seriously is that I have been told that the importance of this issue is only increasing over time. With newer CPUs and with better timer coalescing the frenetic interrupts are likely to consume a greater percentage of total compute power.

Fast timers waste performance

Executing interrupts also uses some execution resources so having more interrupts per second should make your computer run a little bit slower. I tested this theory by writing a program that spins in a busy loop and reports every second on how quickly it’s getting work done. While this program was running I would change the timer resolution and see whether its throughput was affected.

It was affected. A lot.

I just did some quick tests on two machines, so the exact values shouldn’t be taken too seriously, and results will certainly vary depending on machine type, load, etc. But the results clearly indicate a performance cost to having high-frequency interrupts enabled. The overhead that I measured varied from 2.5% to 5%. That’s about an order of magnitude more than I expected. This level of slowdown is significant enough that it makes the common practice of raising the timer frequency in high-performance animation software seem counter-productive.

Raising the Windows timer frequency is bad. It wastes power and makes your computer slower. Routinely doing this in all sorts of programs that end up sitting idle for hours really needs to stop.

Here are some raw results:

4.03904e+006 iterations/s
4.08690e+006 iterations/s
4.09211e+006 iterations/s
4.09437e+006 iterations/s
4.05934e+006 iterations/s
4.00926e+006 iterations/s
4.07723e+006 iterations/s
4.10709e+006 iterations/s
4.02196e+006 iterations/s
4.10028e+006 iterations/s
4.10170e+006 iterations/s
4.10272e+006 iterations/s
4.10708e+006 iterations/s
4.10137e+006 iterations/s
3.95200e+006 iterations/s
3.90879e+006 iterations/s
3.92327e+006 iterations/s
3.91697e+006 iterations/s
3.92326e+006 iterations/s
3.91740e+006 iterations/s
3.92221e+006 iterations/s
3.91711e+006 iterations/s
3.91795e+006 iterations/s
3.92029e+006 iterations/s
3.92204e+006 iterations/s
3.92487e+006 iterations/s
3.91863e+006 iterations/s
3.92451e+006 iterations/s
3.92307e+006 iterations/s
3.92017e+006 iterations/s
3.91865e+006 iterations/s
3.91699e+006 iterations/s
3.92120e+006 iterations/s
3.90531e+006 iterations/s
3.98594e+006 iterations/s
4.10586e+006 iterations/s
4.10674e+006 iterations/s
4.11726e+006 iterations/s
4.11836e+006 iterations/s
4.11177e+006 iterations/s
4.10970e+006 iterations/s

The 20 second period in the middle where performance suddenly drops is exactly when the timer resolution increase happened, and I got similar results every time I tried. I tested this both on my laptop on battery power and my workstation on wall power and the results were always similar.

Source code

It’s not science without disclosing the source code, so my performance measuring program is available on github.

And my program that raises the timer frequency for 20 s is also available on github.

Don’t forget to check the system timer resolution using clockres before running the test. Make sure the timer interval is at least 10 ms before doing the test or else you won’t see dramatic changes.

And fix your code. Everybody.

Update, July 13, 2013

I’ve added some clarifications based on reader confusion, and some new information that I learned from reader comments. Enjoy.

Some of the cost of raising the timer interrupt frequency has gone away on Windows 10 and above, as discussed here.

Tickless kernels can change some of this – see this ArsTechnica article about Windows 8 and this article about the tickless Linux kernel explains some of the issues and challenges. Note that even with a tickless kernel a regular interrupt is still required so that timeGetTime will have its increased precision.

There are two reasons for raising the timer frequency. One is that it improves the resolution of Sleep(n) and of timeouts on WaitForSingleObject. For instance, some games have a power saving mode that throttles the game to 30 fps and this can only be done accurately if Sleep(1) returns in one millisecond, rather than 15-16 milliseconds. By enabling a lower frame rate without requiring busy waiting the higher timer frequency actually saves energy, in this case. For details on Sleep(n) and timer frequency read Sleep Variation Investigated. Multi-media playback often raises the timer frequency for variants of this reason, but these programs should reset the frequency when they are no longer animating.

Another reason for raising the timer frequency is so that timeGetTime will be more accurate. This is used, for instance, by SQL Server to more accurately measure query times. This behavior can be controlled using trace flag T8038, and is discussed more in KB931279. For details on the difference between timeGetTime and GetTickCount see timeGetTime versus GetTickCount.

The Chrome developers realized years ago that raising the timer frequency on battery power was a bad idea, as documented in Chrome: Cranking Up The Clock. However their mitigation of not raising the frequency when on battery power regressed. It should work now, and Chrome on battery power should not raise the timer interrupt frequency above 125 Hz (8 ms interval).

Using QueryPerformanceCounter gives even more accurate time results, but QPC has a history of being buggy. More timing discussions can be found here and here.

The Windows timer frequency is set to the highest frequency requested by a running program. Timer frequency requests can be cancelled by calling timeEndPeriod, but this is rarely done. Timer frequency requests are automatically cancelled when a process ends. If powercfg -energy duration 5 shows that a process has raised the timer frequency you can solve this by killing that process.

Preventing the timer frequency from being raised on your machine is simple. All you have to do is inject code into every process which shims timeBeginPeriod before it is called so that calls to it are a NOP. However, despite this being an obviously trivial task that could be put together in mere seconds, nobody has yet offered up anything more than code snippets and links to references.

Timer Queues were suggested as being a better timer mechanism, but the advantages of this better timer mechanism were not described.

An unexpected side effect of this article is that many developers said “Cool – now I know how to increase the timer frequency!” That makes me nervous, but as long as those developers raise the timer frequency for good reasons, and reset it with timeEndPeriod when they no longer need it, then all will be well.

Reddit discussion is here.

OSNews discussion is here.

A comment on some random forum suggested that this article was misguided because on a busy server the wasted energy is swamped by the energy used for real work. That is true, but that hardly makes my claims irrelevant: On a busy computer the issue is the wasted performance. On an idle laptop the issue is the wasted electricity. I continue to believe that (on Windows 7 and below at least) a raised timer frequency is harmful. And if you don’t believe my test results, feel free to generate your own.

Raising the timer frequency isn’t (despite everything I’ve said) universally bad. It can be necessary. Many games (including those that I work on) raise the timer frequency in order to allow high frame rates (100+ fps). Having a high timer frequency means we can call Sleep(1) while waiting for the next frame, which means that we save power compared to busy waiting for the next frame! My complaint is with programs that raise the timer frequency and then leave it raised for days at a time, even when the program is just sitting idle in the background. That is what bothers me.

Профайлер это инструмент, при помощи которого можно определить время исполнения некоторого участка кода. Зачем это нужно? В первую очередь для оптимизации программ, и лишь потом любопытство и всё остальное. Поскольку в каждый момент времени многозадачная система далеко не стабильна, мы сталкиваемся здесь со-множеством проблем этического характера. Windows если и хочет сделать вид, что справляется со всеми задачами сразу, только у неё это плохо получается.. тем более, когда речь заходит о тесте производительности в пользовательском режиме. Профилирование требует к себе особого внимания, что для системы большая роскошь. Обсуждению этих проблем и посвящена данная статья.
———————————————

Аппаратные проблемы – конвейер CPU

Хотим мы того или нет, но профилируемый код рано-или-поздно попадёт в конвейер процессора. Это устройство уровня микроархитектуры позволяет процессору выполнять сразу несколько микроопераций (µOps) за такт. Кого интересуют детали его реализации, можете почитать статью на Хабре, а здесь я рассмотрю лишь связанные с ним проблемы.

В базовом варианте, исполнение процессором отдельных инструкций включает в себя 5-стадий. Поскольку в этих действиях просматривается строго определённая последовательность, её собрали в обобщённое понятие конвейер PipeLine:

1. Загрузка инструкции из кэша L1;

2. Разложение её на мелкие микрооперации – декодирование;

3. Загрузка привязанных к инструкции операндов (регистры/память);

4. Непосредственное исполнение микроопераций в блоках Execution ядра процессора;

5. Сброс результата обратно в регистры или память (отставка WriteBack).

Процессор принято разделять на два территориальных субъекта – исполнительное ядро BackEnd, и обеспечивающий работу ядра транспортный цех FrontEnd (см.рис.ниже). Эти два термина фломастером проводят черту так, что этапы 1,2,3 конвейера оказываются на территории фронта (на входе в процессор), а 4 и 5 лежат в исполнительном тылу BackEnd.

В ядре современного процессора могут находиться более 8-ми исполнительных блоков Execution-Unit: несколько целочисленных ALU, и отдельно для плавающей точки FPU. После декодирования инструкций на стадии[2], их микрокоды попадают в (жёлтый) блок-резервации, от куда в зависимости от типа инструкции подхватываются портами[0:2] исполнительных устройств:

Core.png

Cиним здесь выделены восемь мартеновских печей Execute, а зелёные блоки демонстрируют этап отставки WriteBack. Такому кол-ву блоков фронт должен поставлять непрерывный поток данных, иначе они будут по-просту голодать. Но построенные на базовых принципах конвейеры не справлялись с этой задачей, и пришлось к первой транспортной ленте добавить ещё 4 сдвинутых по фазе лент. Так конвейер стал скалярным и на продуктовую дотацию могут рассчитывать уже все исполнительные блоки ядра.

Позже выяснилось, что программисты редко используют сопроцессор FPU – в основном вся нагрузка ложится на целочисленные ALU. Это послужило толчком к технологии Hyper-Threading идея которой в том, чтобы создать ещё одну ксеро-копию фронта, не затрагивая при этом BackEnd. В результате ядро осталось прежним, зато транспортных жил получи две. Такая схема известна как «параллелизм на уровне потоков», хотя внутри ядра имеется ещё и параллелизм на уровне микро-инструкций (в данном случае 5 за такт). Основное требование к любого типа конвейерам – одинаковые его ширина и глубина, иначе в нём теряется смысл.

На рисунке ниже, стадии конвейера выделены цветом: [1]загрузка инструкции, [2]декодирование, [3]операнды, [4]исполнение, [5]запись результата. В сумме, процессор с уже прогретым 5-уровневым конвейером за один свой такт сможет выполнить 5 различных микроопераций в глубину

(не путать с инструкциями, которые стоят в иерархии выше и представлены здесь в виде одной строки):

Pipeline.png

Ещё у Pentium конвейер был 12-уровневым (я отобразил здесь только 4 в глубину), а на некоторых процессорах встречались даже 32-уровневые конвейеры. Создавая таких монстров инженеры преследовали всего одну цель – заставить процессор выполнить как-можно больше м/операций за единицу времени. Однако идея длинных конвейеров не прижилась из-за спекулятивного выполнения команд процессором. К примеру если переход был предсказан неверно, приходилось перезагружать его более длинной цепочкой. Поэтому в современном мире используются в среднем

Ссылка скрыта от гостей

, а на процессорах с поддержкой HT (т.е. все Intel и некоторые AMD) он ещё и гипер-скалярный.

Применительно к профайлерам, экосистема их разработки требует соблюдения определённых правил. В частности, обязательным условием является полная очистка исполнительного конвейера от всех инструкций, которые могут находится в нём до начала профилирования. Рассмотрим такой код..

C-подобный:

;// Не правильный вход в профайлер
;// ------------------------------
     rdtsc            ;// EAX = счётчик TSC на входе
     push    eax      ;// запомнить его

;// ..... ;// сюда помещаем профилируемый код

     rdtsc            ;// EAX = счётчик на выходе
     pop     ebx      ;// EBX = счётчик на входе
     sub     eax,ebx  ;// вычислить разницу!
                      ;// EAX = время профилируемого кода в тактах процессора.

На первый взгляд вроде всё ОК.. но мы не учитываем здесь особенности работы ООО-ядра. Процессоры выполняют инструкции абсолютно не в том порядке, в котором они следуют в нашем коде, т.е. беспорядочно, спекулятивно. Собственно об этом говорит и архитектура их ядер «Out-Of-Order», ООО. Таким образом профилируемый код может попасть в ядро и выполниться там задолго до того, как отработает первый RDTSC на входе. Ясно, что в этом случае вместо ток-шоу мы получим порно.

Сбросить конвейер в дефолт позволяет т.н. сериализация потока команд, и ассемблер имеет специальные инструкции для этого. Выбрав одну из них, мы должны будем предварять ею каждое чтение счётчика TSC, чтобы профилируемый код не бежал впереди паровоза, а входил в девственный конвейер уже после чтения счётчика. Вот их перечень:

C-подобный:

;// Привилегированные инструкции сериализации,
;// которые нужно вызывать из RMode, или драйвера Win:
      MOV    CR,xxxx    ;// запись в контрольные регистры CR0..CR4
      MOV    DR,xxxx    ;// запись в регистры отладки DR0..DR7
      WRMSR             ;// запись в любые регистры MSR
      LGDT              ;// обновление таблицы дескрипторов GDT/LDT
      WBINVD            ;// инвалидация кэшей (их очистка)

;// Непривилегированные инструкции сериализации,
;// которые можно вызывать из любого уровня:
      CPUID             ;// идентификация процессора
      LFENCE            ;// Load Fence
      RDTSCP            ;// чтение усовершенствованного TSCР

Здесь видно, что у нас (как юзеров без привилегий) вариантов всего три..
lfence ставит барьер на последующее чтение кода по-типу шлагбаума и гарантирует, что на момент её вызова все имеющиеся в конвейере инструкции уже сойдут с него.. т.е. просто ждёт, поэтому и латентность (задержка) у неё большая – порядка 30-ти тактов. Но по сравнению с ней cpuid вообще тяжеловес, и тусуется в весовой категории от 100 до 500 тактов, в зависимости от модели процессора и аргумента в EAX. Инструкцию rdtscp поддерживают только 64-битные процессоры, поэтому оставлю её на ваше усмотрение.

C-подобный:

;// Правильный вход в профайлер
;// ----------------------------
     lfence            ;// подождать исполнения уже загруженного кода
     rdtsc             ;// счётчик TSC на входе в чистый конвейер
     push    eax edx   ;// запомнить его

;// ..... ;// сюда помещаем профилируемый код

     lfence            ;// подождать очистки конвейра
     rdtsc             ;// счётчик на выходе
     pop     ecx ebx   ;// счётчик на входе
     sub     eax,ebx   ;// вычислить разницу!
     sbb     edx,ecx   ;// EDX:EAX = время профилируемого кода в тактах процессора.

Обратите внимание, что первый lfence на входе ничем нам не мешает, поскольку тест ещё не начат. Зато расход тактов на очистку конвейера после профилирования обязательно должен уйти в штрафбат в виде поправки, т.к. rdtsc на выходе посчитает его продолжением профилируемого кода.

Проблемы ОС – планировщик потоков

Чтобы создать иллюзию одновременного выполнения нескольких задач, Win использует основанную на приоритетах «вытесняющую многозадачность». В этой схеме, в первую очередь обслуживаются процессы с высоким приоритетом в диапазоне 31..16. Пользовательским приложениям система назначает нижнюю половину с номерами от 15 до 1 – с барского стола им может перепасть только тогда, когда насытятся буржуи. И даже в этом случае, если во-время исполнения нашего процесса вдруг проснётся процесс в более высоким приоритетом, он сразу-же отберёт наше время, хоть мы и не отработали его до конца. Именно поэтому схему назвали вытесняющей..

Приоритеты имеют свой класс, и назначается этот класс только процессам – это т.н. базовый приоритет. Теперь потоки процесса наследуют базовый приоритет своего родителя, и относительно него могут иметь ещё 7 приоритетов. Надеюсь таблица ниже прольёт свет на этот запутанный клубок:

BaseClass.png

По-умолчанию, пользовательским процессам система назначает класс Normal и соответственно потоки получают относительный приоритет [8]. Под этим флагом ходят почти все процессы в системе, и лишь некоторым из них она присваевает класс High. К примеру высокий приоритет имеет диспетчер-задач Taskmgr.exe, что позволяет ему вытеснять зависшие приложения. Если-бы у них был равный приоритет, это было-бы невозможно:

TaskMan.png

Система сажает на жёсткую диету все процессы с приоритетом Normal не оставляя им ни единого шанса получить квант до тех пор, пока работают процессы с более высоким приоритетом. Но тогда каким-же образом достигается компромисс, ведь наши потоки рискуют вообще никогда не попасть в поле зрения планировщика? Инженеры решили эту проблему просто.. Помимо приоритетов, с каждым потоком связывается понятие «состояние потока».

Рассмотрим ситуацию, когда мы запустили сразу несколько процессов типа браузер, текстовый редактор, плеер и т.д. Но ведь в каждый момент времени мы работаем только с одним из них, а остальные окна не активны. В системах с вытесняющей многозадачностью попытка активировать сразу два окна обречена на провал, и второе окно неизбежно уйдёт в ожидание какого-нибудь события, типа нажатие клавиши. Планировщик выделяет время только активным окнам, и это даёт возможность работать потокам с более низким приоритетом.

Потоки Thread могут находиться в одном из семи состояний, но только 4 из них заслуживают внимания. Обычной является ситуация, когда запущено два и более равноправных треда. К примеру сейчас у меня в фоне Total-Commander копирует файлы на флэшку, а в это время я печатаю текст. Тогда планировщик маятником выделяет квант мне, и по его истечению – квант тоталу, и так рекурсивно. Если-же в фоне имеется ещё один процесс но с более низким приоритетом (типа спуллер печати), он будет ждать, пока мы с тоталом не решим свои проблемы.

Sheduler.png

Процессорное время в виде квантов выделяется только тем потокам, которые находятся в состоянии «Running». В очередь-ожиданий поток может попасть или по нашему бездействию, или-же его могут вогнать туда функции типа Sleep() или WaitForХХObject(). В любом случае, пока не отработают все потоки из верхнего блока с приоритетом High, синему блоку с пулом Normal ничего не светит, но правда с небольшой поправкой. Не взирая на приоритеты, планировщик следит за всеми готовыми к исполнению потоками. Если в течении 4-сек готовый поток не получает управления, ему всё-же выделяется один квант, видимо из соображений жалости.

Исходя из вышесказанного можно сделать вывод, что если мы планируем написать свой профайлер для измерения скорости выполнения программного кода, то

обязательно должны присвоить его процессу приоритет как минимум выше-среднего, а лучше High (тогда поток получит относительный приоритет 13). В противном случае, в самый неподходящий момент планировщик может отобрать наш квант, и вернуть его по настроению, ..например через пару секунд.

Оперировать приоритетами потоков позволяет функция SetThreadPriority(), а задавать базовые приоритеты процессам SetPriorityClass(). Причём делать это можно динамически, в произвольный момент времени, а не только при старте процесса/потока. Вот их прототипы (обе функции при ошибке возвращают нуль):

C-подобный:

BOOL SetPriorityClass(    ;//<---- задаёт базовый приоритет процессу!
    hProcess,             ;// дескриптор процесса (-1 для своего)
    dwPriorityClass       ;// запрашиваемый приоритет
   );
;//===== Класс приоритета ===============
REALTIME_PRIORITY_CLASS   ;// 0x100
    HIGH_PRIORITY_CLASS   ;// 0x80
  NORMAL_PRIORITY_CLASS   ;// 0x20
    IDLE_PRIORITY_CLASS   ;// 0x40


BOOL SetThreadPriority(   ;//<---- задаёт приоритет потоку процесса!
    hThread,              ;// дескриптор потока, можно получить через GetCurrentThread()
    dwPriority            ;// запрашиваемый уровень приоритета
   );
;//===== Уровни приоритетов =============
THREAD_PRIORITY_TIME_CRITICAL   ;//  15  критический!
THREAD_PRIORITY_HIGHEST         ;//  2   высокий
THREAD_PRIORITY_ABOVE_NORMAL    ;//  1
THREAD_PRIORITY_NORMAL          ;// 0    средний
THREAD_PRIORITY_BELOW_NORMAL    ;// -1
THREAD_PRIORITY_LOWEST          ;// -2   низкий
THREAD_PRIORITY_IDLE            ;// -15  фоновый

Текущий приоритет в регистр EAX возвращают родственные им функции GetPriorityClass() и GetThreadPriority().

Сбор информации о таймере Windows

Рассмотрим легитимные функции, которые предлагает нам система для измерения интервалов времени.

1. GetSystemTimeAdjustment() – возвращает интервал программного таймера ОС.
Как-правило это значение равно 15.625 милли-сек, и соответственно получаем частоту: 1 / 0.015625 = 64 Гц.
Таймер с таким разрешением – это слон в посудной лавке, но что примечательно, почти все системные функции наоборот прослушивают именно его:

GetTickCount() – возвращает счётчик милли-секунд, прошедших с момента последнего старта системы.

GetSystemTime() – возвращает текущее время Windows.

timeGetTime() – точная ксерокопия GetTickCount().

Sleep() – усыпляет поток на указанное количество милли-секунд.

API из этого списка абсолютно не пригодны для использования в профайлерах, т.к. ни одна из них не сможет вернуть значение меньше 15.625 ms. То-есть получаем мёртвую зону во времени с шагом: 15.625 —> 31.250 —> 46.875, и т.д.. Единственная функция, от которой можно поиметь выгоду это Sleep(). С её помощью, в течении 1-сек обычно калибруют какой-нибудь счётчик, чтобы узнать его частоту. Но и в этом случае мили-секунды принудительно округляются до величины, кратной тику системного таймера.

=== Важно! ===============
Выше упоминалось, что время, выделяемое потоку на исполнение исчисляется в квантах.
Во-всех клиентских версиях Win длительность одного кванта равна двум тикам системного таймера 15.625 *2 = 31.250 ms. Если система серверная, то квант растягивается на 12-тиков, или 187.5 ms. Как-только поток израсходует свой квант, планировщик сохраняет контекст его регистров, и даёт возможность поработать такое-же время сл.потоку в очереди, и так по кругу
.

Теперь перейдём к функциям тяжёлой артиллерии..

2. QueryPerformanceFrequency() – возвращает частоту высокоточного таймера HPET (High-Precision-Event-Timer). Его использует заслуживающий доверие системный монитор производительности (Win+R—>perfmon). В отличии от предыдущего таймера 60 Hz, частота НРЕТ уже 14 MHz. Если HPET аппаратно не доступен, монитор садится на ACPI-таймер 3.579 MHz. Здесь мы имеем дело уже не с программным таймером Win, а с аппаратными девайсами чипсета.

На этой частоте работает счётчик QueryPerformanceCounter(). Функция возвращает кол-во тиков высокоточного таймера (HPET или ACPI). Используя пару QPC+QPF, можно контролировать отрезки времени с интервалом: 1 / 3579545 = 279 нано-секунд. Хороший выстрел..

3. CPU frequency – тактовая частота процессора. Если даже взять мин.частоту CPU=1.0 GHz, то получим разрешение в 1 нано-сек. Профайлер с таким разрешением позволит замерять не только блоки программного кода, но и время выполнения отдельных инструкций ассемблера. В качестве счётчика этой частоты используется специально предназначенный для этих целей TSC (Time-Stamp-Counter). Вот это наш клиент!

Формула времени

В компьютерной системе счётчик тиков служит для измерения длительности какого-либо события. Чтобы из этого счётчика получить время в секундах, мы должны знать частоту, на которой работает данный счётчик. Тогда разделив кол-во тиков на частоту, получим время: Time = Ticks / Frequency. Частота в герцах вычисляется просто.. достаточно включить счётчик и подождать ровно 1-сек.

Понятия «счётчик и его частота» жёстко привязаны друг-к-другу и нужно соблюдать между ними симметрию. К примеру, заведомо ложное время даст попытка деления счётчика TSC на частоту таймера HPET, поскольку TSC увеличивается с каждым тактом процессора, а не с тиком НРЕТ. Если мы считаем при помощи QueryPerformanceCounter(), то её показания должны делить строго на QueryPerformanceFrequency() и ни на что другое.

Судя по списку выше, использовать в своём профайлере мы можем всего три устройства:

1. Системный таймер с нарушенным вестибулярным аппаратом и частотой всего 64 Гц – GetTickCount();

2. Резидент внешней разведки – таймер HPET/ACPI со-своим счётчиком QueryPerformanceCounter();

3. Несомненный лидер этого списка – счётчик процессора TSC.

Математический сопроцессор FPU

Поскольку центр тяжести смещается здесь в сторону TSC, то условимся в профайлере использовать именно его. Проблему больших частот и мелких отрезков времени придётся решать при помощи сопроцессора FPU и чисел с плавающей точкой (1 нано-сек, это 0.000000001 сек). Из инструкций сопра понадобятся всего три:

FILD — загрузить тики профилируемого кода в регистр FPU;

FIDIV – разделить тики на частоту процессора;

FSTP – сохранить полученное время в переменной размером qword (8-байт).

FPU имеет 8-регистров ST0..ST7, которые организованы в виде стека. Если регистр не указывается явно, то подразумевается ST0. Каждая загрузка значений инструкцией FILD приводит к тому, что весь стек смещается вниз, а на место первого ST0 встаёт загружаемое значение. Инструкция FST просто сохраняет ST0 в переменной (store), а FSTP сохраняет и снимает его со-стека (при этом весь стек смещается вверх). Вот пример операций из моего демо-профайлера (результат деления лежит в регистре ST0):

fpu.png

Для вывода на консоль полученного времени, воспользуемся всё тем-же printf() со-спецификатором %.3f, где 3 – это кол-во знаков после микро-секундной точки, т.е нано-сек. Ничто не мешает указать и 6, тогда получим пико-секунды (см.кол-во разрядов в регистре ST0 на рисунке выше). Но не будем доводить ситуацию до абсурда, т.к. при каждом последующем запуске профайлера пико (и даже нано-сек) всё-равно не будут стабильны – это погрешность, которую нужно воспринимать как неотъемлемую часть жёстких сил природы.

Практическая часть

Результаты замеров во многом зависят от текущей занятости процессора, поэтому профилируемый участок кода нужно прогонять в цикле по нескольку раз. Важно понимать, что код не может исполняться быстрее, чем это позволяет процессор, а потому прогон с минимальным значением и будет самым достоверным.

Подглядывая за ходом работы процессора может возникнуть резонный вопрос – насколько правильные данные возвращает нам профайлер? ..т.е. не плохо-бы иметь под рукой эталонные значения. На эту роль претендует таблица растактовок Агнера Фога, которую можно

Ссылка скрыта от гостей

. Расход тактов на одну и туже инструкцию зависит от микро/архитектуры процессора и варьируется он в широких пределах. Агнер не поленился и собрал в одной доке информацию сразу обо всех актуальных процессорах. По этой причине, в демо-профайлере я предусмотрел вывод кодового имени CPU, прицепив к нему небольшую базу (пока только Intel, без AMD). Возвращает этот код CPUID с аргументом EAX=1:

C-подобный:

.data
table      dd   0x006d,dohan, 0x006e,yonah, 0x006f,conroe,0x00f2,netburst
           dd   0x1067,wolf,  0x106a,bloom, 0x106e,lynn,  0x206c,gulf
           dd   0x2065,arra,  0x206e,neh,   0x206f,west,  0x3067,silver
           dd   0x206a,sandy, 0x206d,sandy, 0x306a,ivy,   0x306e,ivy
           dd   0x306c,hass,  0x306f,hass,  0x4065,hass,  0x4066,hass
           dd   0x306d,broad, 0x4067,broad, 0x406f,broad, 0x5066,broad
           dd   0x406e,sky,   0x5065,sky,   0x506e,sky,   0x00f4,pres
           dd   0x506c,apollo,0x806e,kaby,  0x906e,coffe, 0x706a,gemini
           dd   0x406a,merri, 0x406c,charry,0x406d,avoton,0x5067,phi
           dd   0x506c,brox,  0x506a,moor,  0x506f,denver,0x606e,cougar
tableLen   =    ($ - table)/8

dohan      db   'Dothan',0
yonah      db   'Yonah',0
conroe     db   'Conroe',0
netburst   db   'Netburst',0
pres       db   'Prescott',0
wolf       db   'Wolfdale',0
bloom      db   'Bloomfield',0
lynn       db   'Lynnfield',0
gulf       db   'Gulftown',0
arra       db   'Arrandale',0
neh        db   'Nehalem',0
west       db   'Westmere',0
silver     db   'Silvermont',0
sandy      db   'Sandy-Bridge',0
ivy        db   'Ivy-Bridge',0
hass       db   'Haswell',0
broad      db   'Broadwell',0
sky        db   'Sky-Lake',0
apollo     db   'Apollo-Lake',0
kaby       db   'Kaby-Lake',0
coffe      db   'Coffee-Lake',0
gemini     db   'Gemini-Lake',0
merri      db   'Merrifield',0
charry     db   'Cherry View',0
avoton     db   'Avoton',0
phi        db   'Phi-Knights',0
brox       db   'Broxton',0
moor       db   'Moorefield',0
denver     db   'Denverton',0
cougar     db   'Cougar-Mountain',0
unk        db   'Unknown',0      
;//---------

Большинство штатных API определяют версию платформы (Win-7/8/10) из реестра. Однако интересней вытащить её без посторонней помощи из структуры РЕВ процесса. Это актуально например для шелл-кодов, которые должны выживать в природе без обращения к сервисам ОС. Нужные поля начинаются со-смещения 0хА4 среди которых есть и номер сборки Build. Привязав к этому Build текстовую строку, можно выводить её на экран:

Peb_sys.png

C-подобный:

buildTbl   dd  2600,wXp,3790,wXp64,6001,wVst,7600,w7,7601,w7
           dd  9200,w8, 9600,w81, 17763,w10,18363,w10
buildLen   =   ($ - buildTbl)/8

wXp        db  '  Win-XP Service Pack ',0
wXp64      db  '  Win-XP 64 Service Pack ',0
wVst       db  '  Win-Vista Service Pack ',0
w7         db  '  Win-7 Service Pack ',0
w8         db  '  Win-8 Service Pack ',0
w81        db  '  Win-8.1 Service Pack ',0
w10        db  '  Win-10 Service Pack ',0
unk        db  '  Unknown ',0

В демонстрационном примере ниже я вывел вход и выход из профайлера в отдельные макросы Start/StopProfile. Макросы удобны тем, что упрощают чтение кода и избавляют от повторяющихся участков, но только в исходнике, а не в уже скомпилированном коде (см.отладчик). Внешним инклудом я сделал и вспомогательные процедуры, которые просто собирают информацию о системе и не имеют непосредственного отношения к процессу профилирования – это позволит сконцентрировать внимание только на полезной нагрузке Payload. Исходники main и инклуд-прицепа можно найти в скрепке.

C-подобный:

format   pe console
include 'win32ax.inc'
include 'profile.inc'
entry    start
;//---------
.data
title      db  ' [ Code Profile ] ver.0.1',0
sysInfo    db  10,' System info..'
           db  10,'    OS Build...........:  %d.%d.%d',0
cpuArch    db  10,'    Processor..........:  %d MHz  ----> %s',0
perFreq    db  10,'    Perf-Mon frequency.:  %d.%d MHz',0
tmrRes     db  10,'    System Timer.......:  %d.%d ms',0

info       db  10,'    %12.0f Ticks = %.3f Micro/sec',0

str01      db  10,10,' RDTSC'
           db  10,   ' -------------------',0
str02      db  10,10,' LFENCE'
           db  10,   ' -------------------',0
str03      db  10,10,' Sleep 500'
           db  10,   ' -------------------',0

profTick   dq  0   ;// qword под тики TSC
profTime   dq  0   ;// qword под время FPU
cpuFreq    dd  0   ;// частота процессора
counter    dd  0   ;// счётчик повторов
fix        dd  0   ;// погрешность LFENCE

tAdjust    dd  0   ;// переменные,
tResolut   dd  0   ;//  ..для функции,
tDisable   dd  0   ;//    ..GetSystemTimeAdjustment()

align      8
qpf        dd  0,0  ;// для fn.QueryPerformanceFrequency()
buff       db  0
;//*****************************************
;// оформляем макросы
macro     StartProfile
{         lfence
          rdtsc
          push    eax edx }   ;// запомнить счётчик TSC

;// ===== Макрос на выходе
;// вычитает разницу тиков и выводит на консоль тики/время
macro     StopProfile
{         lfence
          rdtsc
          pop     ecx ebx     ;// счётчик TSC на входе
          sub     eax,ebx     ;//
          sbb     edx,ecx     ;// EDX:EAX = разница в тиках
          sub     eax,[fix]   ;// применить погрешность
          push    edx eax     ;// положить в стек для FPU

          finit                ;// ======== сопроцессор ==========
          fild    qword[esp]   ;// взять тики со-стека в регистр ST0
          fst     [profTick]   ;// сохранить их в переменной для вывода
          fidiv   [cpuFreq]    ;// получить время = тики / частоту CPU
          fstp    [profTime]   ;// сохранить время в переменной!
          add     esp,8        ;// восстановить стек от пушей..

         cinvoke  printf,info,dword[profTick],dword[profTick+4],\
                              dword[profTime],dword[profTime+4]
}
;//***** С Е К Ц И Я  К О Д А *********************
.code
start:  invoke  SetConsoleTitle,title        ;// обзовём консоль
        invoke  SetProcessAffinityMask,-1,1  ;// оставить только одно ядро CPU
        invoke  SetPriorityClass,-1,HIGH_PRIORITY_CLASS  ;// повысить приоритет процесса!!!

        call    Initialize      ;// проводим инициализацию из инклуда..
                                ;// выводит версию системы, частоты и т.п.

;//==== Профилируем саму инструкцию RDTSC ==================
       cinvoke  printf,str01
        mov     [counter],5     ;// количество прогонов
@@:     StartProfile            ;// зовём макрос

        rdtsc                   ;// код, время которого хотим узнать

        StopProfile             ;// выводим данные на консоль
        dec     [counter]       ;//
        jnz     @b              ;// повторить 5-раз..

;//==== Время инструкции LFENCE ===========================
       cinvoke  printf,str02
        mov     [counter],5
@@:     StartProfile

        lfence

        StopProfile
        dec     [counter]
        jnz     @b

;//==== Профилируем функцию Sleep(), ======================
;//==== с задержкой 500 милли-секунд, или 0,5 сек
       cinvoke  printf,str03
        mov     [counter],5
@@:     StartProfile

        invoke  Sleep,500

        StopProfile
        dec     [counter]
        jnz     @b

@exit: cinvoke  gets,buff     ;// ждём клаву..
       cinvoke  exit,0        ;// на выход!

;//*************************************************************
;// секция для базы-данных: версия системы и м/архитектура CPU
section '.info' data readable writable
build      dd  0
buildTbl   dd  2600,wXp,3790,wXp64,6001,wVst,7600,w7,7601,w7
           dd  9200,w8, 9600,w81, 17763,w10,18363,w10
buildLen   =   ($ - buildTbl)/8

wXp        db  '  Win-XP Service Pack ',0
wXp64      db  '  Win-XP 64 Service Pack ',0
wVst       db  '  Win-Vista Service Pack ',0
w7         db  '  Win-7 Service Pack ',0
w8         db  '  Win-8 Service Pack ',0
w81        db  '  Win-8.1 Service Pack ',0
w10        db  '  Win-10 Service Pack ',0
unk        db  '  Unknown ',0
;//----------------
;// база микроархитектуры процессора
table      dd   0x006d,dohan, 0x006e,yonah, 0x006f,conroe,0x00f2,netburst
           dd   0x1067,wolf,  0x106a,bloom, 0x106e,lynn,  0x206c,gulf
           dd   0x2065,arra,  0x206e,neh,   0x206f,west,  0x3067,silver
           dd   0x206a,sandy, 0x206d,sandy, 0x306a,ivy,   0x306e,ivy
           dd   0x306c,hass,  0x306f,hass,  0x4065,hass,  0x4066,hass
           dd   0x306d,broad, 0x4067,broad, 0x406f,broad, 0x5066,broad
           dd   0x406e,sky,   0x5065,sky,   0x506e,sky,   0x00f4,pres
           dd   0x506c,apollo,0x806e,kaby,  0x906e,coffe, 0x706a,gemini
           dd   0x406a,merri, 0x406c,charry,0x406d,avoton,0x5067,phi
           dd   0x506c,brox,  0x506a,moor,  0x506f,denver,0x606e,cougar
tableLen   =    ($ - table)/8

dohan      db   'Dothan',0
yonah      db   'Yonah',0
conroe     db   'Conroe',0
netburst   db   'Netburst',0
pres       db   'Prescott',0
wolf       db   'Wolfdale',0
bloom      db   'Bloomfield',0
lynn       db   'Lynnfield',0
gulf       db   'Gulftown',0
arra       db   'Arrandale',0
neh        db   'Nehalem',0
west       db   'Westmere',0
silver     db   'Silvermont',0
sandy      db   'Sandy-Bridge',0
ivy        db   'Ivy-Bridge',0
hass       db   'Haswell',0
broad      db   'Broadwell',0
sky        db   'Sky-Lake',0
apollo     db   'Apollo-Lake',0
kaby       db   'Kaby-Lake',0
coffe      db   'Coffee-Lake',0
gemini     db   'Gemini-Lake',0
merri      db   'Merrifield',0
charry     db   'Cherry View',0
avoton     db   'Avoton',0
phi        db   'Phi-Knights',0
brox       db   'Broxton',0
moor       db   'Moorefield',0
denver     db   'Denverton',0
cougar     db   'Cougar-Mountain',0

;//*******************************************************
section '.idata' import data readable
library  msvcrt,'msvcrt.dll',kernel32,'kernel32.dll'
import   msvcrt, printf,'printf',exit,'exit',gets,'gets'
include 'api\kernel32.inc'

Result.png

Обратите внимание, что время исполнения инструкций напрямую зависит от тактовой частоты процессора. На правом скрине частота ~1 GHz и потому время почти совпадает с тиками счётчика TSC, т.к. соотношение 1:1. Зато на левом.. с частотой процессора 2.5 GHz, потраченные на RDTSC такты почти такие-же, а вот расходное время уже в 2-раза меньше. Кроме того здесь видно, что микро/архитектура «Wolfdale» в тактах выполняет инструкцию LFENCE в 4-раза быстрее, чем это делает «Gemini-Lake», поэтому поправки на очистку конвейера в виде константы тут не преемлемы, и нужно вычислять её динамически, что собственно и предусмотрено в коде.

Посмотрите, сколько времени исполнялась функция Sleep() с 500 милли-сек задержкой, и сколько на неё тратят тактов CPU с различной тактовой частотой. Время в обоих случаях правильное и равно ~500'000 микро-сек (это говорит о том, что профайлер работает корректно). Но вот по тактам, за это время процессору с 2-раза большей частотой приходится и простучать в 2-раза больше – 1 млрд, против 500 млн.

Заслуживает внимания и частота, которую возвращает нам функция QueryPerformanceFrequency(). Мой бук с десяткой юзает НРЕТ, а десктопный чипсет G41 с семёркой вообще что-то среднее между ACPI-таймером (частота 3.5 MHz) и древним таймером PIT. В отличии от перфмона, системный таймер ОС никуда не спешит и в обоих случаях период его активности ограничивается одним разом в 15.6 милли-сек. Кстати именно поэтому, если вскормить профайлеру Sleep() с аргументом в диапазоне 1..14, он будет возвращать 15.6 ms. Это потому, что более мелкие интервалы вызывают помешательство функции, т.к. она физически не может отследить меньшее время, а только кратное 15. (правда при помощи timeBeginPeriod(1) можно уменьшить его до 1 ms, но это уже другая история).

Заключение

В наше время, заниматься профилированием кода ради оптимизации программ не имеет смысла – это было актуально на заре компьютерной индустрии. Процессоры (в том числе и компиляторы, особенно С++) сейчас намного умнее некоторых программистов, и на автомате отсеивают бесполезный код. Тестирование производительности скорее носит спортивный интерес, позволяя продвигаться всё глубже в дебри аппаратных механизмов. Есть поговорка: -«глухой считает, что те-кто танцуют – сумасшедшие». Так и здесь.. Некоторым это занятие может показаться бесполезным, но только не тем, кто увлечён низкоуровневым программированием. Раз-уж ассемблер сам предлагает нам такие возможности, так почему-же ими не воспользоваться?

Понравилась статья? Поделить с друзьями:
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
  • Какие основные характеристики имеет ос ms windows
  • Восстановить bcd windows 10 gpt
  • Как открыть список сертификатов windows
  • Как восстановить данные из папки windows old
  • Создание системной переменной в windows 10