Минимальная единица времени в ос windows

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

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

Вся суть

Вся суть

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

Вводные: нам нужен таймер, на Windows, с точностью порядка 1мс, драйвер при этом мы писать не хотим и решения при исполнении которых процессор попытается радикально ускорить глобальное потепление не приемлем.

Есть ли такое решение? Из коробки — нету, но при помощи нехитрых приспособлений наше досадное недоразумение превращается… в точный таймер, конечно же.

У нас есть некоторое количество

досадных недоразумений

системных API которые с каждой новой весией Windows всё сильнее ужимают с целью экономии батареи на ноутбуках, общий обзор можно посмотреть в статье по ссылке в самом начале, с графиками. В целом, можно сказать что сколько-нибудь удовлетворительный тайминг начинается примерно со 100мс, всё что ниже чем 15.6мс за гранью допустимого (по мнению ребят из Microsoft). Да и вообще, 640КБ ну точно хватит всем, правда?

Ну а временные промежутки меньше 1мс вообще немыслимы — большинство API даже не принимает таких значений, не говоря уже о корректной работе с ними.

Исходя из этого я буду строить своё решение вокруг трех недокументированных функций API Win32: NtQueryTimerResolution, NtSetTimerResolution, NtDelayExecution.
Связка из первых двух позволяет добиться разрешения системного таймера меньше 1мс, а третья — воспользоваться этим дополнительным разрешением для сна с точностью менее 1мс.

Итак, начнем: я пишу преимущественно на C#, но на любом многих ЯП можно написать всё точно то же самое.

Шаг 0: поднимем разрешение до максимального. Начиная с Win10 2004 это разрешение больше не является глобальным так что можно ни в чём себе не отказывать (с другой стороны — если процесс не поднял себе разрешение то оно будет 15.6мс вне зависимости от того что там в «глобальном» параметре).

[DllImport("ntdll.dll", SetLastError = true)]
static extern int NtQueryTimerResolution(out int MinimumResolution, out int MaximumResolution, out int CurrentResolution);
[DllImport("ntdll.dll", SetLastError = true)]
static extern int NtSetTimerResolution(int DesiredResolution, bool SetResolution, out int CurrentResolution);

private static void AdjustTimerResolution()
{
    var queryResult = NtQueryTimerResolution(out var min, out var max, out var current);

    if (queryResult != 0) return;

    _systemTimerResolution = TimeSpan.FromTicks(current);

    if (NtSetTimerResolution(max, true, out _) == 0)
    {
        _systemTimerResolution = TimeSpan.FromTicks(max);
    }
}

Шаг 1: создадим класс PreciseTimer. Полный код я привести, увы, не могу но общая структура такова: поток с максимальным приоритетом который крутится в while(true) цикле и следущие важные поля:

// Период срабатывания
private TimeSpan _period;
// Время прошедшее от последнего срабатывания
private readonly Stopwatch _sw = Stopwatch.StartNew();
// Время оставшееся до следующего срабатывания
public TimeSpan Remaining => _period - _sw.Elapsed;
// Таймер уничтожен и должен быть остановлен
private bool _disposed;

Приметка для людей которые не пишут на C#: Stopwatch это обертка над Win32 методамиQueryPerformanceFrequency и QueryPerformanceCounter, никакой дополнительной магии нету.

Шаг 2: выясним сколько же нам спать. И спим!

private static void TimerTick()
{
    // Реализацию выбора следующего таймера оставим пытливым читателям
    PreciseTimer nextTimer = GetNextTimer();

    while (!nextTimer._disposed)
    {
        var remaining = nextTimer.Remaining;

        if (remaining > _systemTimerResolution)
        {
            // Если разрешение системного таймера позволяет - спим
            SleepPrecise(remaining);
            continue;
        }
            
        // Когда разрешение уже не позволяет спать - спиним
        while (nextTimer.Remaining > TimeSpan.Zero)
        {
            // YieldProcessor(), для X86 это инструкция REP NOP
            Thread.SpinWait(1000);
        }
         
        // Дождались: тикаем!
        nextTimer.Tick();
        break;
    }
}

// Функция unsafe потому что автор кода - ленивая жопа
// Перед броском гнилым помидором подумайте: хотелось бы вам выделять память вручную?
[DllImport("ntdll.dll", SetLastError = true)]
static unsafe extern int NtDelayExecution(bool alertable, long* delayInterval);

private static unsafe void SleepPrecise(TimeSpan timeToSleep)
{
    // Посчитаем число целых периодов сна, округлим отбрасываем дробной части
    var periods = (int)(timeToSleep.TotalMilliseconds / _systemTimerResolution.TotalMilliseconds);
      
    if (periods == 0)
        return;
      
    // И спим!
    var ticks = -(_systemTimerResolution.Ticks * periods);
    NtDelayExecution(false, &ticks);
}

Шаг 3: посмотрим что из этого вышло: запустим таймер на 1 минуту и запишем полученные промежутки времени. Код обвязки был использован тоже из статьи по линку в начале, но, к сожалению, там нет кода чтобы построить те великолепные графики, поэтому… не стреляйте в программиста, он рисует как умеет.

Тесты запускались на Ryzen 9 5950X под управлением Win11 версии 22000.469
Среднее значение для таймера в 1мс: 1.022мс, stddev = 0.018

Распределение тиков, таймер 1мс

Распределение тиков, таймер 1мс

Для таймера в 10мс: 10.022мс, stddev = 0.017

Распределение тиков, таймер 10мс

Распределение тиков, таймер 10мс

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

С загрузкой процессора вопрос интереснее, в целом можно утверждать что обнаружению она не поддается: все утилиты радостно рапортируют о 0% загрузке. Установив вручную Affinity на конкретное ядро процессора ничего интересного тоже не обнаружено:

А вы угадаете на каком ядре сейчас вовсю жарит 1мс таймер?

А вы угадаете на каком ядре сейчас вовсю жарит 1мс таймер?

Должен признать что это best case когда циклы сна полностью совпали с таймером. Поскольку код оптимизирован под точность он иногда будет нагружать одно ядро примерно на 30-40%.
Тут стоит сразу же уточнить что эта нагрузка для процессоров у которых есть HyperThreading (или аналог) относительно безвредная: гарантируется что в это время второй поток сможет исполняться на том же ядре с минимальным ущербом для производительности. У процессоров без оного ситуация хуже, но тесты на ноутбуках показывают что в плане потребления энергии/тепла ситуация гораздо лучше чем у более грубых решений (while(true) цикл без rep nop, например).

Подводя итог: цель достигнута? Мне кажется что ответ «да».

Все сниппеты кода вдохновлены реальными событиями и использованы в продакшне.

UPD: По просьбам трудящихся произвел замер с осциллографом при помощи USB-Serial кабеля на базе чипа FTDI. Во время измерения использовался break для генерации сигнала. К сожалению, я наткнулся на ограничение частоты в 250Гц из-за чего сгенерировать 500Гц сигнал не удалось и в результате период таймера установлен в 2.48мс для генерации 200Гц сигнала (см результаты выше для понимания куда делись 0.02мс).

Рассчет статистики доверен осциллографу

Рассчет статистики доверен осциллографу

В целом, осциллограф согласен с полученными ранее результатами — видно что иногда всё-таки бывают выбросы, но, как для софтварного таймера на ОС без гарантий реального времени, результат вполне удовлетворительный.

Экзамен по предмету «Операционные
системы» для специальности 230105
«Программное обеспечение вычислительной
техники и автоматизированных систем»
проводиться в форме письменного теста,
содержащего от 40 до 50 вопросов на знание
теории строения операционных систем и
конкретных моментов управления
процессами, памятью и файловой системой
операционной системы. На экзамен суммарно
отводиться 50 баллов.

  • Какая информация не содержится в Блоке
    Управления Процессом?

    1. идентификатор процесса

    2. приоритет

    3. область восстановления/сохранения
      задачи (процессорные регистры)

    4. карта занятой памяти

    5. список ожидаемых ресурсов

    6. нет правильных ответов

  • Отметьте основные задачи операционных
    систем

    1. развлечение пользователя

    2. управление ресурсами компьютера

    3. обеспечение большего удобства работы
      с компьютером

    4. поддержка операторов ЭВМ

    5. решение математических задач

    6. взаимодействие с внешними запоминающими
      устройствами

  • У операционной системы OS/360
    выпущенной компанией IBM
    в конце
    60-х годов не было …

    1. виртуальной памяти

    2. многозадачности

    3. возможности работать в терминальном
      режиме

    4. графического интерфейса

  • Многозадачность это …

    1. способность ОС запускать различные
      программы

    2. способность работать на многопроцессорных
      ВС

    3. способность выполнять несколько
      программ одновременно

    4. синоним универсальности ОС

    5. ответы а) и б)

  • Может ли однопроцессорная ОС работать
    на вычислительной системе с несколькими
    однотипными процессорами?

    1. да

    2. нет

  • Сколько страниц виртуальной памяти
    может адресоваться алгоритмом страничного
    преобразования в современной
    Intel-архитектуре (32 бита)?

    1. 210

    2. 220

    3. 230

    4. 1000000

    5. это зависит от таблицы дескрипторов

    6. это зависит от операционной системы

  • Операционные системы семейства Windows
    NT/2000/XP/2003
    являются многопроцессорными ОС?

    1. да

    2. нет

  • Известно, что в настоящий момент в
    состоянии «выполнение» находиться два
    процесса. Выберите правильные в этом
    случае утверждения

    1. Это многозадачная ос

    2. В системе используется виртуальная память

    3. в ядре ОС реализован хороший алгоритм
      планирования

    4. это многопроцессорная ОС

    5. в системе отсутствует тупик

    6. запущено более одной пользовательской
      задачи

    7. при планировании в системе используются
      потоки

    8. нет верных утверждений

  • Что такое «куча» в системах Windows?

    1. синоним виртуальной памяти системы

    2. синоним виртуальной памяти процесса

    3. участки свободной памяти системы

    4. участок памяти с динамическим
      распределением внутри

    5. участок памяти процесса, содержащий
      данные

  • Информация о медианном размере файлов
    используется для

    1. увеличения быстродействия файловой
      системы (ФС)

    2. только теоретических разработок

    3. увеличения скорости поиска файлов в
      каталогах

    4. подсчета занимаемого пользователем
      пространства на томе

    5. увеличения количества хранимой на
      томе полезной информации

    6. нет правильного ответа

  • Операционные системы семейства
    UNIX/Linux
    являются многозадачными ОС?

    1. да

    2. да, но только при наличии нескольких
      процессоров

    3. нет

  • Системы пакетной обработки называются
    так, потому что они

    1. последовательно решают несколько
      задач, образующих один «пакет» и затем
      выдают результаты расчета всего
      «пакета»

    2. принимают пакет из нескольких задач
      и решают его параллельно, выдавая
      решения по мере получения

    3. реализуют взаимодействие друг с другом
      посредством пакетных сетевых протоколов

    4. являются синонимом «коробочных»
      программных продуктов

    5. являются разновидностью кластерных
      вычислительных систем

  • Носители информации большего объема
    обычно … чем носители меньшего объема.

    1. быстрее

    2. медленнее

    3. дороже (за 1 Мб хранения)

    4. Дешевле (за 1 Мб хранения)

    5. быстрее и дешевле (за 1 Мб хранения)

    6. медленнее и дешевле (за 1 Мб хранения)

  • Что такое «квант»?

    1. единица измерения объема оперативной
      памяти

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

    3. один такт генератора частоты процессора

    4. Минимальная единица времени выполнения процесса

    5. минимальная задержка при общении с
      внешними устройствами

    6. Нет правильных ответов

  • В чем различие между абсолютными и
    перемещаемыми программными модулями?

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

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

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

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

  • Какое поколение операционных систем
    представляют ОС для персональных
    компьютеров?

    1. 1

    2. 2

    3. 3

    4. 4

    5. 5

  • Какое действие выполняет функция shmat
    в UNIX?

    1. захват времени процессора в монопольное
      использование

    2. захват участка памяти и запрет на его
      выгрузку

    3. выделение участка памяти в стандартной
      куче процесса

    4. подключение к разделу страничной
      подкачки

    5. Нет правильных ответов

  • Отметьте пункты, которые гарантируют
    невозможность возникновения тупика

    1. однозадачная ОС

    2. наличие развитых средств синхронизации
      процессов

    3. наличие достаточного количества
      виртуальной памяти

    4. отсутствие монопольных ресурсов

    5. наличие механизма «контрольных точек»

    6. возможность процессов отдавать
      занятые ресурсы по требованию

    7. всё выше перечисленное

  • Для запуска перемещаемого модуля в
    произвольном фиксированном разделе
    памяти (многозадачность с фиксированными
    разделами) операционная система

    1. использовала особенности процессоров
      Intel при адресации памяти

    2. изменяла код модуля после загрузки
      его в память

    3. отключала виртуальную память

    4. использовала функции отображения
      файлов на память

    5. редактировала содержимое каталога
      таблиц

  • Реализация виртуальной памяти возможна
    только

    1. при наличии жесткого диска

    2. в многозадачных системах

    3. с помощью функции VirtualAlloc

    4. при поддержке со стороны аппаратуры
      ВС

    5. ответы a) и d)

    6. Нет правильных ответов

  • Что происходит с процессом в ОС Windows
    в случае завершения последнего потока?

    1. в Windows запрещено завершать
      последний (единственный) поток процесса

    2. процесс переходит в состояние «ожидание»
      до появления нового потока

    3. операционная система автоматически
      создает временный ничего-не-делающий
      (Idle) поток для сохранения
      процесса

    4. Процесс также завершается

    5. в ОС Windows потоки не
      реализованы

  • Предположим, что операционная система
    располагает свободными участками
    размером 35, 72, 3, 20 и 192 байта. Участок
    какого размера использует менеджер
    памяти для выделения 22 байт по алгоритму
    «самый подходящий»?

    1. 35

    2. 72

    3. 3

    4. 20

    5. 192

  • В операционной системе имеются 5
    свободных блоков по 256 килобайт каждый
    и 3 участка по 128 килобайт. Участок какого
    размера выделит операционная система
    при запросе 37 килобайт, если менеджер
    памяти использует алгоритм двоичного
    разбиения?

    1. 37 килобайт

    2. 32 килобайта

    3. 64 Килобайта

    4. 48 Килобайт

    5. 128 Килобайт

  • Для чего использовался граничный
    регистр в однозадачных системах
    управления памятью?

    1. для указания размера сегмента работающей
      программы

    2. для ограничения размера памяти, которую
      может занимать программа

    3. Для защиты кода операционной системы от ошибок программы

    4. для указания на границу между областями
      кода и данных программы

  • В ОС используется виртуальная память.
    Что можно сказать о расположении адреса
    25:[100] (сегмент:[смещение]) в физической
    памяти?

    1. такого адреса не может существовать,
      так как в виртуальной памяти нет
      сегментов, а есть страницы

    2. Ничего определенного о расположении этой ячейки сказать нельзя

    3. этот адрес располагается в 500-ой ячейке
      физической памяти

    4. этот адрес располагается в 125-ой ячейке
      физической памяти

    5. это ячейка в данный момент находится
      на внешнем носителе

  • Известны два адреса в линейном виртуальном
    страничном пространстве: 10C0F116
    и 10D0F116.
    Можно ли однозначно утверждать, что
    данные, расположенные по этим адресам,
    находятся на одном физическом носителе?

    1. да

    2. нет

  • Критическая секция – это

    1. Участок кода, который не может выполняться параллельно

    2. участок кода при выполнении которого
      запрещено планирование процессов

    3. участок код при выполнении которого
      запрещены прерывания

    4. участок кода, имеющий повышенный
      приоритет при планировании

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

  • Какой основной недостаток алгоритма
    выгрузки страниц (свопинга) «Самое
    старое обращение»?

    1. сложность алгоритмической реализации

    2. слишком огромные затраты памяти на
      реализацию алгоритма

    3. Необходимость усложнения аппаратуры управления памятью

    4. у алгоритма нет недостатков

  • Верно ли, что в каждой записи таблицы
    страниц виртуального преобразования
    содержатся сведения, какому пользователю
    принадлежит страница?

    1. да

    2. нет

  • Каков размер одной страницы виртуальной
    памяти в архитектуре Intel?

    1. 2 Кб

    2. 4 Кб

    3. 6 Кб

    4. 8 Кб

    5. 10 Кб

  • С помощью какой функции в системе
    Windows происходит
    резервирование участка адресов без
    выделения для них физической памяти?

    1. VirtualReserve

    2. VirtualAlloc

    3. VirtualRealloc

    4. VirtualAddress

    5. VirtualProtect

    6. HeapCreate

    7. архитектура Intel-процессоров
      не поддерживает резервирование

  • Что возвращает функция HeapCreate
    в случае ошибки

    1. NULL

    2. ERROR_NOT_ENOUGH_MEMORY

    3. INVALID_HEAP_OPERATION

    4. INVALID_HANDLE_VALUE

    5. такой функции не существует

  • Какой объем памяти будет выделен
    функцией VirtualAlloc, если
    производиться запрос 5192 байта?

    1. 1 Кб

    2. 4 Кб

    3. 8 Кб

    4. 16 Кб

  • Какое назначение функции kill
    в UNIX-системах?

    1. функция принудительно завершает
      процесс (аналогична функции
      TerminateProcess в Windows-системах)

    2. функция производит нормальное завершение
      процесса (аналогична ExitProcess
      в Windows-системах)

    3. функция завершает работу ОС (выключает
      машину)

    4. Функция посылает сигнал другому процессу

    5. функция используется для организации
      критической секции

    6. Нет правильных ответов

  • В системе используется виртуальная
    память со страничной организацией.
    Могут ли адреса 10:[4096] и 20:[8192] указывать
    на одну и ту же ячейку физической памяти?

    1. да, если начальные адреса сегментов
      10 и 20 совпадают

    2. Да, если сегмент 20 начинается на 4096 байт раньше, чем сегмент 10

    3. да, в независимости от адреса начала
      сегментов

    4. нет, это разные сегменты, значит разные
      участки в памяти

    5. нет, так как страницы не могут
      перекрываться

  • С какой целью используется концепция
    рабочего набора?

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

    2. Для увеличения быстродействия системы

    3. для защиты ядра ОС от вредоносных
      программ

    4. для описания множества запущенных в
      данный момент процессов

    5. для помощи в реализации алгоритма
      лотерейного планирования

  • В чем особенность лотерейной системы
    планирования процессов?

    1. равноправие процессов вследствие
      случайности выбора

    2. возможность увеличивать приоритет
      серверных процессов при выполнении
      запросов

    3. при выборе процесса предпочтение
      отдается процессам, освобождающим
      большее количество ресурсов

    4. Начальный приоритет процесса выбирается случайно (лотерея)

  • Что значит фраза «процесс А имеет более
    высокий приоритет чем Б»?

    1. численное значение приоритета А больше
      чем значение приоритета Б

    2. численное значение приоритета А меньше
      чем значение приоритета Б

    3. процесс А выполняется визуально быстрее
      чем Б

    4. процесс А будет быстрее занимать
      свободную память

    5. Процесс а получит при планировании больше ресурсов чем процесс б

    6. процессу А выделяются кванты большего
      размера чем Б

  • Тупиком называют ситуацию, когда

    1. системе не хватает скорости центрального
      процессора

    2. единственным выходом является
      перезагрузка ОС

    3. один из процессов «завис» и не отвечает
      на запросы

    4. Несколько процессов «зависли» и не отвечают на запросы

    5. для дальнейшей работы процесса
      недостаточно памяти

    6. ответы c) и e)

  • С помощью какой функции в системе Unix
    (POSIX) происходит создание нового процесса?

    1. fork

    2. creat

    3. execve

    4. allocate_process

    5. ShellExecute

    6. addproc

  • Задан участок кода для системы
    Unix
    main()
    {
    if( fork()
    == 0 ) exec(“/bin/my-prg”);
    printf(“Дочерний
    процесс запущен.”);
    }
    Что будет
    выведено на дисплей при выполнении
    программы, если заранее известно, что
    файла /bin/my-prg
    не существует?

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

    2. будет выведено четыре сообщения
      «Дочерний процесс запущен»

    3. Будет выведено два сообщения «Дочерний процесс запущен»

    4. при попытке запуска несуществующей
      программы на экран будет выведена
      информация о системной ошибке и работа
      процесса родителя будет остановлена

    5. дочерний процесс запущен не будет, а
      родительский выведет «Дочерний процесс
      запущен»

  • Какая функция должна использоваться
    для возобновления работы процесса,
    если в функции CreateProcess
    был использован флаг CREATE_SUSPENDED
    (остановить процесс при запуске)?

    1. UnsuspendProcess

    2. ResumeProcess

    3. UnsuspendThread

    4. ResumeThread

    5. SwitchToThread

    6. SetProcessMode

    7. StartProcess

  • Какой примитив синхронизации реализуется
    системным объектом Mutex
    в ОС Windows

    1. событие

    2. Критическая секция

    3. именованный канал

    4. общая память

    5. семафор

  • Можно ли с помощью только лишь сигнала
    передать от одного процесса другому
    текстовую строку?

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

    2. можно, но для этого необходимо
      использовать специальную форму сигнала
      (special-signal)

    3. нельзя, так как через сигнал можно
      передавать только численную информацию

    4. Нельзя, так как через сигнал можно передать только одно число

    5. нельзя, сигнал это способ синхронизации,
      а не взаимодействия

  • Какое максимальное количество блоков
    ФС может занимать файл в системе
    расположения «13 полей» (одна из
    классических ФС в ОС Unix) в случае если
    размер блока 4 Кб, а количество блоков
    на томе не более 232?

    1. 1 791 434

    2. 537 395 717

    3. 3 224 374 301

    4. 224

    5. неограниченно

    6. Нет правильных ответов

  • Операционные системы семейства Windows
    95/98/ME являются многозадачными
    ОС?

    1. да

    2. нет

  • В состояние «ожидание» процесс не
    может перейти из состояния(й)

    1. Пассивное

    2. пассивное, выполнение

    3. готовность

    4. выполнение

    5. готовность, выполнение

  • Какое основное достоинство общей памяти
    как средства взаимодействия двух или
    более процессов?

    1. возможность передавать неограниченный
      объем данных

    2. Самая высокая скорость передачи данных

    3. самая большая надежность при передаче
      данных

    4. возможность со стороны ОС контролировать
      правильность структур данных

    5. возможность проверки передаваемых
      данных на безопасность

    6. возможность быстро передавать данные
      по сети

    7. Нет правильных ответов


  • Исследование: системный таймер 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 — после. Перзагрузка ничего не меняет.

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

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

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

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

    Опи­сан­ные здесь функ­ции на­зы­ва­ют «тай­ме­ра­ми Windows».

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

    • timeGetTime() — воз­вра­ща­ет вре­мя в мил­ли­се­кун­дах с мо­мен­та стар­та опе­ра­ци­он­ной си­сте­мы («муль­ти­ме­дий­ный тай­мер»). Воз­вра­ща­е­мое зна­че­ние (ти­па DWORD) об­ну­ля­ет­ся каж­дые 49.71 дней. Не за­будь­те это учесть при из­ме­ре­нии вре­ме­ни. Точ­ность: от 5 до 1 мил­ли­се­кун­ды. Ес­ли вы хо­ти­те по­вы­сить точ­ность до 1 мил­ли­се­кун­ды, то мо­же­те по­ме­стить из­ме­ре­ние вре­ме­ни меж­ду вы­зо­ва­ми timeBeginPeriod(1); и timeEndPeriod(1);. Учти­те, что вы­зов timeBeginPeriod() мо­жет вре­мен­но по­вы­сить ча­сто­ту муль­ти­ме­дий­но­го тай­ме­ра во всей си­сте­ме, тем са­мым сни­зив её быст­ро­дей­ствие.
    • GetTickCount() — воз­вра­ща­ет вре­мя в мил­ли­се­кун­дах с мо­мен­та стар­та опе­ра­ци­он­ной си­сте­мы (об­ну­ля­ет­ся каж­дые 49.71 дней). Очень быст­рая функ­ция, так как про­сто воз­вра­ща­ет зна­че­ние со­от­вет­ствую­ще­го счёт­чи­ка. Од­на­ко точ­ность её низ­ка: обыч­но 55 мил­ли­се­кунд в Win 9X и 10 мил­ли­се­кунд в Win NT. Низ­кая точ­ность вы­зва­на тем, что для уве­ли­че­ния счёт­чи­ка ис­поль­зу­ют­ся пре­ры­ва­ния, ге­не­ри­руе­мые ча­са­ми ре­аль­но­го вре­ме­ни ком­пью­те­ра. Для ге­не­ра­ции со­об­ще­ний WM_TIMER, при­хо­дя­щих ва­ше­му при­ло­же­нию, то­же ис­поль­зу­ет­ся этот счёт­чик. Вы мо­же­те по­вы­сить точ­ность этой функ­ции до 1 мил­ли­се­кун­ды, ес­ли яв­ля­е­тесь счаст­ли­вым об­ла­да­те­лем Win 2000/XP. Для это­го нуж­но до­ба­вить ко­ман­ду /USEPMTIMER в boot.ini (в Win Vista это то­же мож­но сде­лать, но с ис­поль­зо­ва­ни­ем BCDedit). Это пе­ре­клю­чит HAL ва­шей си­сте­мы на ге­не­ра­цию пре­ры­ва­ний тай­ме­ра с ис­поль­зо­ва­ни­ем ACPI Power Management Timer (а не ча­сов ре­аль­но­го вре­ме­ни). Вы де­ла­е­те это на свой страх и риск. Будь­те го­то­вы к то­му, что функ­цио­ни­ро­ва­ние си­сте­мы мо­жет из­ме­нить­ся.
    • GetSystemTime() — те­ку­щее вре­мя (год, ме­сяц, день, ча­сы, ми­ну­ты, се­кун­ды, мил­ли­се­кун­ды). Точ­ность та же, что и у GetTickCount(), так как ис­поль­зу­ют­ся те же ча­сы ре­аль­но­го вре­ме­ни. Точ­ность то­же мож­но по­вы­сить с по­мо­щью клю­ча /USEPMTIMER. Ис­поль­зо­вать эту функ­цию для вы­чис­ле­ния ин­тер­ва­лов вре­ме­ни не­удоб­но, так как се­кун­ды, ми­ну­ты, ча­сы, и то­му по­доб­ные еди­ни­цы слож­но вы­чи­тать друг из дру­га. К то­му же си­стем­ное вре­мя в лю­бой мо­мент мо­жет из­ме­нить­ся (син­хро­ни­за­ция с сер­ве­ром вре­ме­ни, ли­бо пе­ре­ход на лет­нее вре­мя и об­рат­но).
    • Ма­шин­ная ин­ст­рук­ция RDTSC — чи­та­ет из про­цес­сор­но­го счёт­чи­ка чис­ло так­тов, про­шед­шее с мо­мен­та за­пус­ка про­цес­со­ра. Са­мый точ­ный счёт­чик из до­ступ­ных. Од­на­ко име­ют­ся сле­дую­щие про­бле­мы:
      • Ча­сто­та про­цес­со­ра не­из­ве­ст­на. Бо­лее то­го, она мо­жет ме­нять­ся со вре­ме­нем в за­ви­си­мо­сти от ре­жи­ма энер­го­сбе­ре­же­ния.
      • В мно­го­ядер­ных про­цес­со­рах и мно­го­про­цес­сор­ных си­сте­мах счёт­чик свой у каж­до­го яд­ра́/про­цес­со­ра. И эти счёт­чи­ки не син­хро­ни­зи­ро­ва­ны, так как я́дра/про­цес­со­ры мо­гут быть за­пу­ще­ны в раз­ные мо­мен­ты вре­ме­ни, и да­же мо­гут быть на не­ко­то­рое вре­мя при­оста­нов­ле­ны. По­это­му по­ка­за­ние это­го счёт­чи­ка бу­дет «пры­гать» при пе­ре­хо­де по­то­ка с од­но­го яд­ра на дру­гое.
    • QueryPerformance­Counter() — «тай­мер вы­со­ко­го раз­ре­ше­ния». Вве­дён фир­мой Microsoft, что­бы раз и на­все­гда по­ста­вить точ­ку в про­бле­мах из­ме­ре­ния вре­ме­ни. Ча­сто­та это­го тай­ме­ра (1 МГц и вы­ше) не ме­ня­ет­ся во вре­мя ра­бо­ты си­сте­мы. Ча­сто­ту мож­но узнать с по­мо­щью функ­ции Query­Per­for­mance­Fre­quen­cy(). Для каж­дой си­сте­мы Windows са­ма опре­де­ля­ет, с по­мо­щью ка­ких устройств реа­ли­зо­вать этот тай­мер. По­хо­же, это са­мый луч­ший спо­соб из­ме­ре­ния вре­ме­ни из всех, по­это­му о нём и по­го­во­рим.

    Для ра­бо­ты с тай­ме­ром вы­со­ко­го раз­ре­ше­ния име­ют­ся две API-функ­ции:

    BOOL QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency);
    BOOL QueryPerformanceCounter(LARGE_INTEGER *lpPerformanceCount);

    Не за­бы­вай­те, что сам вы­зов этих функ­ций за­ни­ма­ет не­ко­то­рое вре­мя, ко­то­рое да­же мо­жет ока­зать­ся боль­ше, чем из­ме­ряе­мый ин­тер­вал вре­ме­ни.

    QueryPerformanceFrequency

    Ар­гу­мент lpFrequency — ука­затель на пе­ре­мен­ную ти­па LARGE_INTEGER, в ко­то­рую бу­дет за­пи­са­на ча­сто­та тай­ме­ра (ти­ков в се­кун­ду).

    Воз­вра­ща­е­мое зна­че­ние: ес­ли ком­пью­тер име­ет тай­мер вы­со­ко­го раз­ре­ше­ния, то воз­вра­ща­е­мая ве­ли­чи­на не­ну­ле­вая. Ес­ли же тай­мер от­сут­ству­ет ли­бо не под­дер­жи­ва­ет­ся BIOS, то функ­ция вер­нёт 0.

    QueryPerformanceCounter

    Ар­гу­мент lpPerformanceCount — ука­затель на пе­ре­мен­ную ти­па LARGE_INTEGER, в ко­то­рую бу­дет за­пи­са­но зна­че­ние тай­ме­ра, «на­бе­жав­шее» на дан­ный мо­мент (в ти­ках).

    Воз­вра­ща­е­мое зна­че­ние: 0 в слу­чае ошиб­ки, ина­че не­ну­ле­вое зна­че­ние.

    LARGE_INTEGER

    Этот тип дан­ных опре­де­ля­ет­ся сле­дую­щим об­ра­зом:

    typedef union _LARGE_INTEGER {
        struct {
            DWORD LowPart;
            LONG HighPart;
        };
        struct {
            DWORD LowPart;
            LONG HighPart;
        } u;
        LONGLONG QuadPart;
    } LARGE_INTEGER;

    Мы ви­дим, что здесь 3 эле­мен­та, на­хо­дя­щие­ся в од­ном ме­сте опе­ра­тив­ной па­мя­ти: безы­мян­ная ст­рук­ту­ра, хра­ня­щая две 32-х бит­ных «по­ло­вин­ки» чис­ла, ст­рук­ту­ра u с тем же со­дер­жи­мым, и 64-х бит­ное чис­ло (__int64). Всё так слож­но по­то­му, что «не­ко­то­рые си­сте­мы и ком­пи­ля­то­ры мо­гут не под­дер­жи­вать 64-х бит­ные чис­ла». Мы бу­дем поль­зо­вать­ся толь­ко QuadPart.

    При­мер

    Опи­сан­ные вы­ше функ­ции мож­но ис­поль­зо­вать при­мер­но так:

    #include <iostream>
    #include <windows.h>

    using namespace std;

    int main(int argc, char **argv)
    {
        LARGE_INTEGER timerFrequency, timerStart, timerStop;

        QueryPerformanceFrequency(&timerFrequency);
        QueryPerformanceCounter(&timerStart);

        //
        //Здесь код, время работы которого замеряем
        //

        QueryPerformanceCounter(&timerStop);
        double const t( static_cast<double>( timerStop.QuadPart
            timerStart.QuadPart ) / timerFrequency.QuadPart );

        cout << «Time is « << t << » seconds.» << endl;

        return 0;
    }

    Не всё так хо­ро­шо!

    Так как в раз­ных си­сте­мах тай­мер вы­со­ко­го раз­ре­ше­ния реа­ли­зу­ет­ся с по­мо­щью раз­ных устройств, то будь­те го­то­вы к про­бле­мам, ес­ли этот тай­мер ра­бо­та­ет с по­мо­щью счёт­чи­ка так­тов про­цес­со­ра:

    • Встре­ча­ют­ся си­сте­мы, в ко­то­рых тай­мер вы­со­ко­го раз­ре­ше­ния ме­ня­ет ско­рость ра­бо­ты при пе­ре­клю­че­нии ча­сто­ты про­цес­со­ра.
    • Бы­ва­ет, что тай­мер «пры­га­ет ту­да-сю­да» при пе­ре­хо­де про­цес­са с од­но­го яд­ра на дру­гой.

    Но, в от­ли­чие от счёт­чи­ка так­тов, для ко­то­ро­го та­кие «ано­ма­лии» — нор­ма, для тай­ме­ра вы­со­ко­го раз­ре­ше­ния это — глю­ки. И про­из­во­ди­те­ли про­цес­со­ров вме­сте с Microsoft ста­ра­ют­ся их ис­прав­лять. На­при­мер: http://www.​reghardware.​co.​uk/​2006/​07/​04/​amd_dual-core_tweak_tool/.

    Ес­ли же вам нуж­на вы­со­кая на­дёж­ность, то от это­го тай­ме­ра при­дёт­ся от­ка­зать­ся. Ре­ко­мен­дую в этом слу­чае об­ра­тить свой взор на timeGetTime() в связ­ке с timeBeginPeriod() и timeEndPeriod().

    Биб­лио­те­ка для ра­бо­ты с тай­ме­ром

    Вид­но, что для из­ме­ре­ния ин­тер­ва­лов вре­ме­ни в про­грам­ме тре­бу­ет­ся за­во­дить до­пол­ни­тель­ные пе­ре­мен­ные, пи­сать лиш­ний и не­кра­си­вый код. По­это­му я на­пи­сал про­стей­шую биб­лио­те­ку для ра­бо­ты с тай­ме­ром, ко­то­рую вы мо­же­те ска­чать от­сю­да: SAnTimer.h (ли­цен­зия LGPL).

    Биб­лио­те­ка предо­став­ля­ет про­грам­ми­сту класс CTimer, ко­то­рый, су­дя по на­зва­нию, ими­ти­ру­ет тай­мер. Вы мо­же­те со­здать в про­грам­ме не­сколь­ко тай­ме­ров. Каж­дый тай­мер в мо­мент со­зда­ния име­ет по­ка­за­ние «0». Тай­мер мож­но за­пу­стить, по­ста­вить на па­у­зу, и сбро­сить в ноль. Ин­тер­фейс клас­са сле­дую­щий:

    class CTimer
    {
    public:
        typedef long long tTime;

    private:
        static CFrequency frequency;

       
        bool state;

       
        tTime timeTotal;
        tTime timeLastStart;

       
        static inline tTime Now(void);

    public:
        inline CTimer(bool start = false);
        inline CTimer(CTimer const &T);
        inline ~CTimer(void);
        inline CTimer &operator=(CTimer const &T);

        inline double Get(void) const;
        inline tTime GetTickCount(void) const;
        static inline tTime Frequency(void);

        inline void Start(void);
        inline double StartGet(void);

        inline void Pause(void);
        inline double PauseGet(void);

        inline void Stop(void);
        inline double StopGet(void);
    };

    Для то­го, что­бы со­здать тай­мер в про­грам­ме, нуж­но со­здать объ­ект клас­са CTimer. Не­обя­за­тель­ный ар­гу­мент кон­ст­рук­то­ра ука­зы­ва­ет на то, нуж­но ли за­пус­кать тай­мер сра­зу по­сле со­зда­ния, ли­бо он бу­дет за­пу­щен по­том с по­мо­щью вы­зо­ва со­от­вет­ствую­щей функ­ции.

    На­зна­че­ние дру­гих ме­то­дов клас­са:

    • Get(): узнать на­бе­жав­шее зна­че­ние тай­ме­ра (в се­кун­дах).
    • GetTickCount(): узнать на­бе­жав­шее зна­че­ние тай­ме­ра (в ти­ках).
    • Frequency(): узнать ча­сто­ту тай­ме­ра (ти­ков в се­кун­ду).
    • Start(): за­пу­стить тай­мер.
    • StartGet(): за­пу­стить тай­мер и узнать зна­че­ние тай­ме­ра на мо­мент за­пус­ка.
    • Pause(): при­оста­но­вить тай­мер.
    • PauseGet(): при­оста­но­вить тай­мер и узнать его зна­че­ние (в се­кун­дах).
    • Stop(): оста­но­вить тай­мер и сбро­сить его по­ка­за­ния в «0».
    • StopGet(): оста­но­вить тай­мер, узнать зна­че­ние тай­ме­ра на мо­мент оста­нов­ки, сбро­сить его по­ка­за­ния.

    Опи­сан­ный вы­ше при­мер те­перь мож­но пе­ре­пи­сать сле­дую­щим об­ра­зом:

    #include <iostream>
    #include «SAnTimer.h» //Подключаем заголовочный файл библиотеки

    using namespace std;

    int main(int argc, char **argv)
    {
        CTimer Timer(true); //Создаём таймер и сразу его запускаем

        //
        //Здесь код, время работы которого замеряем
        //

        //Таймер останавливается, и его значение сразу выводится:
        cout << «Time is « << Timer.StopGet() << » seconds.» << endl;

        return 0;
    }

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

    #include <iostream>
    #include «SAnTimer.h» //Подключаем заголовочный файл библиотеки

    using namespace std;

    CTimer Timer; //Создаём таймер

    void MeasuredFunc(void)
    {
        Timer.Start(); //Запускаем таймер на время работы функции

        //
        //Здесь код функции
        //

        Timer.Pause(); //Приостанавливаем таймер
    }

    int main(int argc, char **argv)
    {
        //Вызываем несколько раз функцию:
        for(int i(0); i<10000; ++i)
        {
            MeasuredFunc();

            //
            //Здесь выполняем какие-нибудь другие действия
            //
        }

        //Выводим время на экран:
        cout << «Time is « << Timer.Get() << » seconds.» << endl;

        return 0;
    }

    Биб­лио­те­ка реа­ли­зо­ва­на оп­ти­маль­но. При ком­пи­ля­ции с вклю­чён­ной под­ста­нов­кой inline-функ­ций за­мер вре­ме­ни бу­дет ид­ти не мед­лен­нее, чем ес­ли бы он был на­пи­сан пу­тём вы­зо­ва функ­ций GetPerformanceCounter(). Бо­лее то­го, вы­зов GetPerformanceFrequency() осу­ществ­ля­ет­ся один раз в са­мом на­ча­ле ра­бо­ты про­грам­мы, а за­тем по­лу­чен­ное зна­че­ние ис­поль­зу­ет­ся все­ми со­здан­ны­ми тай­ме­ра­ми.

    Ре­ко­мен­дую так­же по­чи­тать

    Beware of QueryPerformanceCounter()

    What the /usepmtimer switch does when placed in a Microsoft Windows boot.ini?

    Prototype implementations for a few simple Windows clocks

    ТЕСТЫ

    по дисциплине «Операционные системы»
     

    для студентов специальности 080500-
    Бизнес-информатика на 2014-2015 учебный год

    Указания
    по выполнению тестовых заданий

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

    Вариант1
    1.         Выберите из предложенного списка, что может являться критерием эффективности вычислительной системы:
    a.       пропускная способность;
    b.      занятость оперативной памяти;
    c.       загруженность центрального процессора;
    d.      реактивность системы.
    2.         Основным критерием эффективности систем пакетной обработки является:
    a.       решение максимального числа задач в единицу времени
    b.      одновременное нахождение максимального количества задач в ОП
    c.       удобство работы пользователя
    3.         Системы пакетной обработки предназначены для решения задач:
    a.       вычислительного характера
    b.      требующих постоянного диалога с пользователем
    c.       требующих решения конкретной задачи за определенный промежуток
    d.      времени
    4.         В каких системах гарантируется выполнение задания за определенный промежуток времени:
    a.       пакетной обработки
    b.      разделения времени
    c.       системах реального времени
    5.         В системах пакетной обработки суммарное время выполнения смеси задач:
    a.       равно сумме времен выполнения всех задач смеси
    b.      меньше или равно суммы времен выполнения всех задач смеси
    c.       больше или равно суммы времен выполнения всех задач смеси
    6.         В системах реального времени
    a.       набор задач неизвестен заранее
    b.      набор задач известен заранее
    c.       известен или нет набор задач зависит от характера системы
    7.         Самое неэффективное использование ресурсов вычислительной системы:
    a.       в системах пакетной обработки
    b.      в системах разделения времени
    c.       в системах реального времени
    8.         Планирование потоков игнорирует:
    a.       приоритет потока
    b.      время ожидания в очереди
    c.       принадлежность некоторому процессу
    9.         В каких системах тип планирования статический
    a.       реального времени
    b.      разделения времени
    c.       пакетной обработки
    10.     Состояние, которое не определено для потока в системе:
    a.       выполнение
    b.      синхронизация
    c.       ожидание
    d.      готовность
     
    Вариант 2
    1.Каких смен состояний не существует в системе:
    a.       выполнение → готовность
    b.      ожидание → выполнение
    c.       ожидание → готовность
    d.      готовность → ожидание
    2.В каком из алгоритмов планирования решение о переключении процессора на выполнение другого процесса принимает операционная система:
    a.       вытесняющий
    b.      невытесняющий
    3.Число, характеризующее степень привилегированности потока при использовании ресурсов называется ………………………………………………..
    4.Каких классов прерываний нет?
    a.       аппаратных
    b.      асинхронных
    c.       внутренних
    d.      программных
    5. Процессорное время распределяется между:
    a.       процессами
    b.      задачами
    c.       потоками
    6.Планирование и синхронизация процессов осуществляется на основе:
    a.       дескриптора процесса
    b.      контекста процесса
    c.       идентификатора процесса
    7.Для возобновления выполнения процесса после прерывания используется:
    a.       дескриптора процесса
    b.      контекста процесса
    c.       идентификатора процесса
    8.Планирование процессов может быть:
    a.       динамическим
    b.      статическим
    c.       изохронным
    d.      системным
    9.Потребность в синхронизации возникает в:
    a.       однопрограммных ОС
    b.      многопрограммных ОС
    10.Память с самой высокой стоимостью единицы хранения:
    a.       дисковая память
    b.      оперативная память
    c.       регистры процессора
     
    Вариант 3
    1.Виртуальные адреса являются результатом работы:
    a.       пользователя
    b.      транслятора
    c.       компоновщика
    d.      ассемблера
    2.Какого типа адреса могут быть одинаковыми в разных процессах:
    a.       виртуальные
    b.      физические
    c.       реальные
    d.      сегментные
    3.Какое действие обязательно должен выполняться в системе памяти с перемещаемыми образами процессов:
    a.       сжатие
    b.      перемещение
    c.       свопинг
    4.Что из ниже перечисленного верно для свопинга:
    a.       на диск выгружается неиспользуемая в настоящий момент часть процесса
    b.      на диск выгружаются неиспользуемые процессом данные
    c.       на диск выгружается не активный процесс
    5.Таблица страниц используется для:
    a.       преобразования виртуального адреса в физический
    b.      для ускорения работы процесса
    c.       для реализации свопинга
    6.Выберите свойства, на которых базируется схема преобразования виртуального    адреса в физический:
    a.       объем страницы кратен степени 2
    b.      объем страницы 418 байт и более
    c.       смещения в виртуальной и физической странице равны
    d.      адреса хранятся в шестнадцатеричном коде
    7.Объем страницы:
    a.       выбирается по возможности максимальный
    b.      выбирается минимальным
    c.       для процессоров Intel стандартно равен 4 Кбайта
    8.Преимущества сегментной организации памяти перед страничной:
    a.       более быстрый доступ к данным в памяти
    b.      отсутствие фрагментации
    c.       возможность задания дифференцированных прав доступа к различным сегментам
    9. Что может выступать в качестве кэша для оперативной памяти:
    a.       дисковые устройства
    b.      быстродействующая статическая память
    c.       виртуальная память
    10.Эффективность кэширования прямо пропорциональна
    a.       тактовой частоте центрального процессора
    b.      вероятности попадания в кэш
    c.       разности кэш-попаданий и кэш-промахов
     
    Вариант 4
    1.При страничном распределении памяти адрес виртуальной станицы задается
    a.       номером страницы
    b.      номером страницы и смещением внутри страницы
    c.       адресом дескриптора
    d.      адресом идентификатора
    2.Размер страницы может быть
    a.       418 байт, 516 байт, 1048 байт
    b.      512 байт, 1024 байт, 2048 байт
    c.       100 байт, 200 байт, 300 байт, 400 байт
    3.При сегментном распределении памяти виртуальной адрес задается
    a.       номером сегмента
    b.      номером сегмента и смещением в сегменте
    c.       адресом дескриптора
    d.      адресом идентификатора
    4.Таблица сегментов используется для:
    a.       преобразования виртуального адреса в физический
    b.      для ускорения работы процесса
    c.       для реализации свопинга
    5.При сегментно-страничном распределении перемещение данных между ОЗУ и диском осуществляется:
    a.       сегментами
    b.      страницами
    c.       блоками
    d.      разделами
    6.Файловая система является частью:
    a.       дисковых систем
    b.      драйверов дисков
    c.       операционной системы
    7.Какую структуру образуют файлы в ФС (файловой системе) FAT?
    a.       древовидную
    b.      сетевую
    c.       реляционную
    d.      плоскую
    8. Диски – это память:
    a.       с последовательным доступом
    b.      с индексно-последовательным доступом
    c.       с прямым доступом
    9.Какой разметки нет на диске?
    a.       дорожек
    b.      кластеров
    c.       цилиндров
    d.      секторов
    10.Минимальная единица, участвующая в операциях обмена с дисковым устройством:
    a.       байт
    b.      сектор
    c.       дорожка
    d.      цилиндр
     
    Вариант 5
    1.Низкоуровневое форматирование создает:
    a.       дорожки
    b.      секторы
    c.       кластеры
    d.      файловую систему
    2.ОС Windows поддерживают следующие типы разделов:
    a.       основной
    b.      базовый
    c.       подкачки
    d.      дополнительный
    3.Минимальный фактический размер файла на диске равен:
    a.       1 биту
    b.      1 байту
    c.       1 сектору
    d.      1 кластеру
    4.На диске не может быть кластера размером:
    a.       512 байт
    b.      1024 байта
    c.       1536 байт
    d.      2048 байт
    5.Числовое значение –12, 16, 32 – в файловой системе FAT отражает:
    a.       размер кластера на диске
    b.      разрядность элемента в таблице FAT
    c.       допустимое количество символов в имени файла
    6.Недостатки ФС FAT:
    a.       сложность реализации
    b.      не поддерживают разграничения доступа к файлам и каталогам
    c.       не поддерживают длинных имен файлов
    d.      не содержат средств поддержки отказоустойчивости
    7.Все файлы и каталоги в системе NTFS однозначно идентифицируются:
    a.       именем
    b.      индексным дескриптором
    c.       номером записи в MFT
    d.      системным идентификатором
     
    8.Занятость пространства на диске определяется
    a.       системными таблицами
    b.      битовой картой
    c.       специальными системными файлами
    d.      пустыми записями в MFT
    9.Данные небольшого файла в ФС NTFS хранятся:
    a.       в записи MFT, соответствующей файлу
    b.      за таблицей MFT в области размещения данных
    c.       в месте, указанном в битовой карте
    10.Неделимая работа, которая не может быть выполнена частично, называется ……
     
     
    Вариант 6
    1.В случае восстановления транзакция будет повторена, если
    a.       она зафиксирована в журнале
    b.      закончилась успешно
    c.       закончилась неуспешно
    2.При сжатии файла цвет шрифта названия файла:
    a.       не меняется
    b.      меняется на красный
    c.       меняется на зеленый
    d.      меняется на синий
    3.При шифровании файла цвет шрифта названия файла:
    a.       не меняется
    b.      меняется на красный
    c.       меняется на зеленый
    d.      меняется на синий
    4.Возможно ли одновременное шифрование и сжатие файла в NTFS
    a.       возможно
    b.      невозможно
    c.       возможно, если пользователь вошел в систему как администратор 
    5.Какая файловая система из перечисленных ниже является журналируемой:
    a.       FAT-16
    b.      FAT-32
    c.       NTFS
    d.      Ext2
    6.Где записана MBR:
    a.       На логическом диске D
    b.      в BIOS
    c.       в CMOS
    d.      в первом секторе располагающемся на нулевом цилиндре и нулевой головке (стороне)
    7.Размер MBR:
    a.       1 Кбайт
    b.      512 байт
    c.       4 Кбайта
    d.      1024 байт
    8.В конфигурации RAID-0 достигается:
    a.       приобретение свойства отказоустойчивости
    b.      повышение производительности
    c.       приобретение свойства восстанавливаемости
    9.Недостатки RAID-0:
    a.       сложность увеличения объема памяти
    b.      повышенная избыточность хранящихся данных
    c.       отсутствие восстанавливаемости данных
    10.В RAID-1 потери емкости в массиве при применении 4 дисков:
    a.       25 %
    b.      50 %
    c.       75 %
     
    Вариант 7
    1.В массиве RAID-3 для хранения корректирующей информации используется
    a.       1 диск
    b.      все диски поочередно
    c.       каждый второй диск
    2.Минимальное количество дисков, нужное для RAID-5:
    a.       2
    b.      3
    c.       5
    3.Установите соответствие технологии RAID количеству данных, записываемых за одну операцию записи на диск:
    a. RAID-2              1. байт
    b. RAID-3              2. бит
    c. RAID-4              3. блок
    4.В RAID-5 происходит замедление работы при:
    a.       при чтении
    b.      при записи
    c.       при обработке данных программами
    5.Максимальная избыточность в массиве RAID-5:
    a.       10 %
    b.      25 %
    c.       33 %
    d.      50 %
    6.Специальные системные программы, с помощью которых можно обслуживать как саму ОС, так всю вычислительную систему –это
    a.       система управления файлами
    b.      интерфейсная оболочка
    c.       операционная среда
    d.      утилиты
    e.       системы программирования
    7.Для удобства взаимодействия пользователя с ОС служит
    a.       система управления файлами
    b.      операционная оболочка
    c.       операционная среда
    d.      утилит
    8.Ресурсы могут быть
    a.       стабильными
    b.      активными
    c.       разделяемыми
    d.      неделимыми
    9.Программа в момент ее выполнения называется
    a.       этапом
    b.      процессом
    c.       адресным пространством
    d.      ресурсом
    10.Процесс может находиться в ….                   состоянии
    a.       активном
    b.      стабильном
    c.       разделяемом
    d.      неделимом
    e.       пассивном
     
    Вариант 8
    1.Специальная информационная структура, описатель процесса 
    a.       ресурс процесса
    b.      идентификатор процесса
    c.       дескриптор процесса
    d.      утилита процесса
    e.       прерывания процесса
    2.Способ диспетчеризации процессов, при котором активный процесс выполняется до тех пор, пока он сам, по своей инициативе, не отдаст управление диспетчеру задач для выбора из очереди другого, готового к исполнению процесса
    a.       вытесняющая многозадачность 
    b.      не вытесняющая многозадачность 
    3.Автоматизировать запуск одной программы за другой и тем самым увеличить коэффициент загрузки процессора позволило:  
    a.       появление систем пакетной обработки
    b.      появление алгоритмических языков 
    c.       появление компиляторов
    d.      появление IBM/360
    4.Аутентификация – это
    a.       проверка того, что пользователь является тем, за кого он себя выдает
    b.      проверка, что тот, за кого себя выдает пользователь, имеет право выполнять ту или иную операцию
    c.       проверка пользовательских процессов от ошибочных и зловредных действий
    d.      проверка пользовательских и системных процессов от ошибочных и зловредных действий
    5.Раздел NTFS делится на две части
    a.       12 % под MFT – зону и 88 % под обычное пространство для хранения файлов
    b.      20 % под MFT – зону и 80 % под обычное пространство для хранения файлов
    c.       10 % под MFT – зону и 90 % под обычное пространство для хранения файлов
    d.      1 % под MFT – зону и 99 % под обычное пространство для хранения файлов
    6.MFT – это
    a.       менеджер файловых транзакций
    b.      менеджер файловых таблиц
    c.       главная таблица файлов
    d.      механизм форматирования таблиц
    7.DLL – это
    a.       диспетчер локальных связей
    b.      дескриптор связанной логики
    c.       динамически подключаемая библиотека
    d.      диспетчер локальных меток
    8.Win32 (API) – это
    a.       недокументированные низкоуровневые функции
    b.      интерфейс прикладного программирования
    c.       транслятор динамических файлов
    d.      диспетчер пользователя
    9.Уровень абстрагирования от оборудования в ОС представлен файлом
    a.       Ntoskrnl.exe
    b.      Wi32sk.sys
    c.       Ntdll.dll
    d.      Hal.dll
    10.Исполнительная система и ядро в ОС представлены файлом
    a.       Ntoskrnl.exe
    b.      Wi32sk.sys
    c.       Ntdll.dll
    d.      Hal.dll

    Ключ

    Вопрос

    Ответ

    Вопрос

    Ответ

    1

    a, d

    41

    a, b

    2

    a

    42

    a, d

    3

    a

    43

    d

    4

    c

    44

    a, b, d

    5

    b

    45

    b

    6

    b

    46

    d

    7

    c

    47

    c

    8

    c

    48

    b

    9

    a

    49

    a

    10

    b

    50

    транзакция

    11

    b, d

    51

    c

    12

    a

    52

    d

    13

    приоритет

    53

    c

    14

    b

    54

    b

    15

    c

    55

    c

    16

    a

    56

    d

    17

    b

    57

    b

    18

    a, b

    58

    b

    19

    b

    59

    c

    20

    c

    60

    c

    21

    b

    61

    a

    22

    a

    62

    b

    23

    c

    63

    a-2, b-1,c-3

    24

    c

    64

    b

    25

    a

    65

    c

    26

    a, c

    66

    d

    27

    c

    67

    b

    28

    c

    68

    c

    29

    b

    69

    b

    30

    b

    70

    a

    31

    b

    71

    c

    32

    b

    72

    b

    33

    b

    73

    a

    34

    a

    74

    b

    35

    b

    75

    a

    36

    c

    76

    c

    37

    a

    77

    c

    38

    a

    78

    b

    39

    b

    79

    d

    40

    b

    80

    a

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

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии
  • Кастомизация иконок windows 10
  • Программа okular для windows
  • Восстановить загрузку windows 10 после обновления
  • Brackets download for windows 10
  • Lenovo b570e как установить windows 10