Исследование: системный таймер 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-ом срабатывании таймера. В этом и только в это заключается влияение точности системного таймера на отсчёт кванта.


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

Из этого следует, что хоть изменение точности системного таймера и должны повлиять на точность отсчёта времени в функции 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
Оценка:
был такой "эпичный" тред/треп: http://www.rsdn.ru/forum/winapi/3704837.flat.2.aspx
Автор: gear nuke
Дата: 15.02.10

вывод такой же
Как много веселых ребят, и все делают велосипед...
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 — после. Перзагрузка ничего не меняет.

Пока не знаю, в чём дело Попробую завтра разобраться.
"Нормальные герои всегда идут в обход!"
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.