Версия для печати
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум на Исходниках.RU > Delphi: Система, Windows API > Sleep - что внутри?


Автор: Jin X 17.09.16, 16:56
Бесконечный цикл со Sleep(1) почти не грузит процессор.
Что там такого зашито внутри? Как это можно реализовать кроме как через Sleep?

Автор: Славян 17.09.16, 18:35
Цитата Jin X @
Бесконечный цикл со Sleep(1) почти не грузит процессор.
Довольно логично: задача Sleep'а - указать ОС, что "такой-то процесс не работает, можете заняться своими делами", что она и делает.
Цитата Jin X @
Как это можно реализовать кроме как через Sleep?
Уточните, что подразумевается под словом 'это'? Если "загрузку ЦП", то можно банально в цикле считать x=cos(x) и загрузка будет выше крыши (но одно ядро, конечно). (Тривиальное распараллеливание загрузит все ядра).

Автор: leo 17.09.16, 20:46
Цитата Jin X @
Бесконечный цикл со Sleep(1) почти не грузит процессор.
Что там такого зашито внутри?

Во-первых, зашито само название функции - Sleep, т.е. поток "засыпает" (приостанавливается) на время >= указанного значения. Во-вторых, "зашита" специфика отсчета времени в ОС - счет ведется дискретно по прерываниям системного таймера со стандартным (дефолтным) интервалом в 1/64 сек = 15.625 мс. Отсюда и существенная разница Sleep со значением 0 и > 0. В любом случае вызов Sleep передает управление ОС, которая должна запустить следующий по очереди "простаивающий и готовый к исполнению" поток на данном процессоре. Но если система не загружена и таковых потоков нет, то при Sleep(0) управление практически сразу возвращается вызвавшему эту функцию потоку. А в случае Sleep(X > 0) ОС обязана выдержать интервал времени не менее заданного, но поскольку счет времени ведется дискретно, то возобновление исполнения потока происходит только по очередному тику сис.таймера, т.е. с задержкой от 0 до 15.625 мс при первом или однократном вызове Sleep и с задержкой, близкой к 15 мс при вызове Sleep в "пустом\холостом" цикле (за счет авто-синхронизации с сис. таймером). В итоге получается, что при вызове в холостом цикле Sleep(1) практически эквивалентна Sleep(15). Вот и прикинь загрузку процессора - несколько "жалких" сотен (или даже тысяч - роли не играет) тактов на вызов Sleep с переходом в ядро и ~15 мс*(частота_процессора) тактов чистого сна. Ес-но, что "Бесконечный цикл со Sleep(1) почти не грузит процессор"

Автор: shm 17.09.16, 23:40
Цитата Jin X @
Что там такого зашито внутри?

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    event = CreateEvent(NULL, FALSE, FALSE, NULL);
    //...
    void Sleep(DWORD timeot)
    {
       WaitForSingleObject(event, timeout);
    }


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

Добавлено
Цитата leo @
В итоге получается, что при вызове в холостом цикле Sleep(1) практически эквивалентна Sleep(15).

Я конечно не знаю как там в виндах реализовано, но я делал пробуждалку не только по таймеру.

Добавлено
Цитата Jin X @
Как это можно реализовать кроме как через Sleep?

А зачем оно надо?

Автор: Jin X 18.09.16, 17:56
Цитата Славян @
Уточните, что подразумевается под словом 'это'?
Наоборот разгрузку. Мне нужно сделать цикл (spin-lock, в частности), который будут грузить проц минимально.
Полагаю, что Sleep(1) - это не самый оптимальный способ сделать это.

Автор: Славян 18.09.16, 18:53
Вот ещё такой вариант нашёлся:
Цитата \win2k\private\windows\media\avi\drawdib.16\ddt\ddt.c
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    void Sleep(UINT uSleep)
    {
        MSG msg;
        int id;
     
        id = SetTimer(NULL, 42, uSleep, NULL);
     
        while (GetMessage(&msg, NULL, 0, 0))
        {
            if (msg.message == WM_TIMER && msg.wParam == (WORD)id)
                break;
     
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
     
        KillTimer(NULL, id);
    }
Он выглядит несколько лучше, ибо всё приложение не "виснет" во время такой спячки, а хоть как-то реагирует.

Автор: Jin X 19.09.16, 09:54
Ок. Чем же можно разгрузить проц на очень короткий срок (мкс, грубо говоря), кроме Sleep'а?

Добавлено
К сожалению, pause не разгружает проц вообще никак :(

Автор: shm 19.09.16, 19:57
Цитата Jin X @
К сожалению, pause не разгружает проц вообще никак

Разгружает, но не передает управление остальным потокам, так что как минимум стоит вставить еще и SwitchToThread. Загрузка проца в Винде имеет лишь посредственное отношение к фактической загрузке, а конкретно она высчитывается из фактического времени работы потока. Т. е. если в системе все потоки кроме твоего спят, а твой занят хоть чем-то кроме сна, то загрузка и будет 100%. Мне кажется в твоем случае стоит задуматься над событийной (асинхронной) моделью программы. Т. е. вместо твоего пустого цикла использовать WaitForSingleObject для ожидания результата чего-то. Тогда проблемы с пустой нагрузкой отпадут сами собой.

Добавлено
И да, если поток gui'шный, то можно использовать обработку сообщений потока через PostThreadMessage.

Автор: Jin X 20.09.16, 07:39
Цитата shm @
Разгружает, но не передает управление остальным потокам, так что как минимум стоит вставить еще и SwitchToThread. Загрузка проца в Винде имеет лишь посредственное отношение к фактической загрузке, а конкретно она высчитывается из фактического времени работы потока. Т. е. если в системе все потоки кроме твоего спят, а твой занят хоть чем-то кроме сна, то загрузка и будет 100%. Мне кажется в твоем случае стоит задуматься над событийной (асинхронной) моделью программы. Т. е. вместо твоего пустого цикла использовать WaitForSingleObject для ожидания результата чего-то. Тогда проблемы с пустой нагрузкой отпадут сами собой.
А как я могу ждать обнуления 32-битной переменной через WaitForSingleObject?

Я запускал несколько потоков @: pause jmp @ и смотрел на температуру процессоров. Так вот, наличие pause никак не неё не влияет. Так что, у меня с разгрузкой всё-таки вопрос...

На данный момент решил сделать spin-loop-циклы вот так:

( ( 265 spin-циклов, затем SwitchToThread ) * 4 раза, затем Sleep(1) ) * 4 раза.
Итого выходит 4096 циклов. Не так много, но и не мало.
После этого переходим на Sleep(1) после каждой проверки, максимально разгружая таким образом проц.

Готов обсудить цифры 256, 4, 4 на предмет оптимальности :)

Автор: Jin X 20.09.16, 10:39
Цитата shm @
Загрузка проца в Винде имеет лишь посредственное отношение к фактической загрузке
В продолжение темы: каким образом ядро делает так, что температура процессора остаётся низкой? А любой поток, делая даже pause в цикле будет нагревать его? На ум приходит только hlt, но...?

Автор: shm 20.09.16, 10:47
Цитата Jin X @
А как я могу ждать обнуления 32-битной переменной через WaitForSingleObject?

Явно никак. Но ты можешь в том месте, где ее устаналиаешь, добавить еще SetEvent. И все будет ОК.

Добавлено
Цитата Jin X @
Так вот, наличие pause никак не неё не влияет.

Довольно странно. Видимо в твоем процессоре такая реализация.
Цитата Jin X @
( ( 265 spin-циклов, затем SwitchToThread ) * 4 раза, затем Sleep(1) ) * 4 раза.
Итого выходит 4096 циклов. Не так много, но и не мало.
После этого переходим на Sleep(1) после каждой проверки, максимально разгружая таким образом проц.

Криво и косо.
Цитата Jin X @
В продолжение темы: каким образом ядро делает так, что температура процессора остаётся низкой?

В системе есть System Idle потоки на каждое ядро. Они выполняют либо hlt, либо специальный aml код из acpi (и то и другое недоступно для user mode).

Автор: Jin X 20.09.16, 13:22
Цитата shm @
Довольно странно. Видимо в твоем процессоре такая реализация.
Я проверил это на 2-х процессорах: у меня на i5 (декстоп), на другом i5 (ноутбук) (позже ещё на i3 проверю, на другом ноуте). Результат один. Я даже больше скажу: с pause чуть больше греет :)

Цитата shm @
Криво и косо.
В чём кривизна? Как тогда не криво и не косо сделать?
Сделать через ивенты? (Но по любому же первые N (например, 4096) циклов надо будет оставить) :whistle:

Автор: shm 20.09.16, 13:27
Цитата Jin X @
Я даже больше скажу: с pause чуть больше греет

Будет время - я у себя протестирую.
Цитата Jin X @
В чём кривизна?

В том, что ты вместо использования готовых средств синхронизации занимаешься их эмуляцией.
Цитата Jin X @
(Но по любому же первые N (например, 4096) циклов надо будет оставить)

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    if N < 4094 then
    ...
    else
       WaitFor...

Автор: Jin X 20.09.16, 14:05
Цитата shm @
В том, что ты вместо использования готовых средств синхронизации занимаешься их эмуляцией.
Изначально была идея просто сделать spin-lock, но с экономией ресурсов, если процесс затянется. С использованием 1 простой переменной типа DWord или Int64, без Initialize и Delete. Т.е. наиболее быстрый и с наименьшими заморочками.

Сдаётся мне, что установка/ожидание event'а процесс тяжёлый... Хотя, могу ошибаться.

Автор: shm 20.09.16, 14:40
Я что скажу: если у тебя spin-локи грузят проц, то значит нужны эвенты или иные средства синхронизации (ибо цикл синхронизации отнюдь не маленький), которые могут усыпить поток. Во всяком случае что это процесс не тяжелее, чем sleep.

Автор: Shaggy 20.09.16, 15:53
Synchronization Functions

Critical section
Slim Reader/Writer (SRW)

и возможно
One-time initialization
Synchronization barrier

Автор: Jin X 21.09.16, 07:19
Цитата Jin X @
Сдаётся мне, что установка/ожидание event'а процесс тяжёлый... Хотя, могу ошибаться.
Цитата shm @
Во всяком случае что это процесс не тяжелее, чем sleep.
Реально...
Замерил скорость выполнения разного кода (в тактах), получилось вот что:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    Холостой ход - 30-60
    cmpxchg (10 раз) - 60-120
    lock cmpxchg (10 раз) - 400-550
    SpinLoop 1024 циклов (cmp) - 26000-36000
    SpinLoop 1024 циклов (locked cmpxchg) - 50000-70000
     
    CreateEvent - 30000-50000
    Set/ResetEvent - 2000-4000
    CloseHandle - 4000-7000
    SwitchToThread - 2500-4000
    Sleep(0) - 3000-5000
    Sleep(1) - 160 тыс-3 млн
    WaitForSingleObject (signaled) - 2000-5000
    InitializeCriticalSectionAndSpinCount - 1800-2800
    InitializeCriticalSection - 1400-2400
    EnterCriticalSection (свободная) - 80-150
    EnterCriticalSection (рекурсивно) - 100-300
    DeleteCriticalSection - 500-1500
CreateEvent хоть и долгая тема, но CreateEvent+Set/ResetEvent+WaitForSingleObject по любому шустрее...

Добавлено
Shaggy, гляну, thanx!

Автор: Jin X 22.09.16, 07:23
Вопрос: есть ли гарантия, что WaitForSingleObject успеет сработать, если другой поток сделает SetEvent и CloseHandle подряд?

Автор: shm 25.09.16, 01:13
Цитата Jin X @
CreateEvent хоть и долгая тема

Его нужно дернуть 1 раз при запуске и все.
Цитата Jin X @
есть ли гарантия, что WaitForSingleObject успеет сработать, если другой поток сделает SetEvent и CloseHandle подряд?

А зачем так делать? Нет, нету гарантий. Если уж так надо, то после WaitForSingleObject и закрывай.

Автор: Jin X 25.09.16, 15:02
Цитата shm @
Его нужно дернуть 1 раз при запуске и все.
Если сделать инициализацию 1 раз, значит нужно делать и удаление (типа DeleteCriticalSection), а я хочу сделать только Lock (Enter) и Unlock (Leave). Есть прикольные функции: WaitOnAddress и WakeByAddressSingle/WakeByAddressAll, жаль только, что существуют они в Windows 8+ (посему придётся комбинировать их и Event'ы для поддержки работы в Windows 7/XP).

Автор: Pavia 25.09.16, 15:15
Jin X
А свою написать?

Мне не понравилось то что для критической секции нужна глобальная переменная. И я сделал вой вариант.

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    procedure TaskSwitch;
    var
      Cis:PCriticalSection;
      Status:Boolean;
    begin
      Status:=GetIntStatus;
      DisableInt;
      CisBegin(Cis);
        if TaskSetModeCmd.Lock=MUnLocked then
           ExecuteCMD;
        AlgTaskSwitch;
      CisEnd(Cis);  
      if Status=False then
         EnableInt;
    end;

USynchronization.pas (, : 321)

Добавлено
PS. Код сырой и не тестированный. Просто как пример.

Автор: shm 25.09.16, 17:57
Цитата Jin X @
Если сделать инициализацию 1 раз, значит нужно делать и удаление (типа DeleteCriticalSection), а я хочу сделать только Lock (Enter) и Unlock (Leave)

Это неэффективно. Неужели так лень пару лишних функций вызвать?
Pavia, :wacko:
Я не претендую на правильность, но все же.
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    class CriticalSection: private Event
    {
        static void EnterSingle(CriticalSection *cs)
        {
            //std::cout << "EnterSingle" << std::endl;
        }
        static void LeaveSingle(CriticalSection *cs)
        {
            //std::cout << "LeaveSingle" << std::endl;
        }
        static void EnterMultitasking(CriticalSection *cs);
        static void LeaveMultitasking(CriticalSection *cs);
        static void EnterSmp(CriticalSection *cs);
        static void LeaveSmp(CriticalSection *cs);
        static void (*EnterProc)(CriticalSection *cs);
        static void (*LeaveProc)(CriticalSection *cs);
        volatile atom State;
    //  atom LockCounter;
        //locked
        bool OnWaitBegin();
        //bool OnSetStateSingle(Thread *);
    public:
        class Lock
        {
            CriticalSection *section;
        public:
            Lock(CriticalSection &cs)
            {
                section = &cs;
                cs.enter();
            }
            ~Lock()
            {
                section->leave();
            }
        };
        CriticalSection();
        CriticalSection(bool Busy);
        static void SetMutlitaskingMode()
        {
            EnterProc = &EnterMultitasking;
            LeaveProc = &LeaveMultitasking;
        }
        static void SetSmpMode()
        {
            EnterProc = &EnterSmp;
            LeaveProc = &LeaveSmp;
        }
        void enter()
        {
            EnterProc(this);
        }
        void leave()
        {
            LeaveProc(this);
        }
        bool IsLock() const
        {
            return (State != 0);
        }
        bool enter(DWORD Timeout);
    };
    //...
    void CriticalSection::EnterSmp(CriticalSection *cs)
    {
        static const int max_spin_it = 1024;
        unsigned int cpu_id, result;
        int i;
        cpu_id = GetCurrentCpuId() + 1;
        result = CpuCompareAndSwapLock<volatile atom>(&cs->State, 0, cpu_id);
        if(result == 0)
            return;
        else if(result != cpu_id)
        {
            for(i = 0; i < max_spin_it; i++)
            {
                if(cs->State == 0)
                {
                    cpu_id = GetCurrentCpuId() + 1;
                    result = CpuCompareAndSwapLock<volatile atom>(&cs->State, 0, cpu_id);
                    if(result == 0)
                        return;
                }
                CpuPause();
            }
        }
        cs->Wait();
        cs->State = GetCurrentCpuId() + 1;
    }
     
    void CriticalSection::LeaveSmp(CriticalSection *cs)
    {
        if(cs->HaveWaitingThreads())
            cs->SetState();
        else
            cs->State = 0;
    }

Автор: Jin X 25.09.16, 18:50
Pavia, what's that?
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    uses SysSupport, UMemoryManager, UMemoryMap;
:)
Добавлено
Цитата Pavia @
А свою написать?
Что именно написать своё?



shm, то ли неполный код, то ли... х/з.
GetCurrentCpuId - ?
CpuCompareAndSwapLock - ?
Wait - ?
SetState - ?
HaveWaitingThreads - ?
...

Автор: Pavia 25.09.16, 19:34
shm
Цитата shm @
Pavia, :wacko:

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

Для задачи Jin X:
Цитата Jin X @
Если сделать инициализацию 1 раз, значит нужно делать и удаление (типа DeleteCriticalSection), а я хочу сделать только Lock (Enter) и Unlock (Leave).

Можно сделать по аналогии патч который накладывает 5-байтовый NOP.

Автор: shm 25.09.16, 20:25
Цитата Jin X @
shm, то ли неполный код, то ли... х/з.

Это для Pavia. Код не имеет никакого отношения к Win, как и код Pavia. Да простят нас админы за оффтоп.

Добавлено
Pavia, я у тебя не вижу усыпления потока. Без этого твои мьютексы это скорее спинлок, что крайне неэффективно в силу очевидных причин. Связанный список я у себя храню прямо в структуре потока, которую очень легко достать средствами TLS.

Автор: leo 26.09.16, 06:33
Цитата Jin X @
Замерил скорость выполнения разного кода (в тактах), получилось вот что:
lock cmpxchg (10 раз) - 400-550
pinLoop 1024 циклов (locked cmpxchg) - 50000-70000

Это для случая, когда только один поток крутит спин-луп. Если же его будут крутить два и более потока одновременно, то эти цифирьки могут увеличиться в несколько раз (где-то от 3 до 5). Дело в том, что lock cmpxchg в любом случае производит запись в переменную (записывается либо новое, либо старое значение), поэтому соответствующая линейка кэша оказывается модифицированной, со всеми вытекающими последствиями синхронизации кэшей 1-2 уровня при многопоточном доступе. В случае одного потока модифицированная линейка остается в кэше L1 того ядра, на котором работает поток. А в случае двух и более потоков, она постоянно перебрасывается из L1 одного ядра в L1 другого через общий L3. Поэтому, чтобы избежать этих перебросов, в приведенных тобой примерах от Intel перед lock-обменом используется простое сравнение переменной "только на чтение". В этом случае, при наличии нескольких ожидающих потоков, они до снятия лока только читают линейку, не изменяя ее, и соотв-но она находится в неизменном shared состоянии в L1 этих потоков\ядер

Автор: Jin X 26.09.16, 09:31
Цитата leo @
Дело в том, что lock cmpxchg в любом случае производит запись в переменную (записывается либо новое, либо старое значение)
Я даже скажу, что lock вообще сильно тормозит процесс. Особенно если это будут делать одновременно несколько потоков...

Из Intel Instruction Set Reference (cmpxchg):
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    TEMP ← DEST
    IF accumulator = TEMP
      THEN
        ZF ← 1;
        DEST ← SRC;
      ELSE
        ZF ← 0;
        accumulator ← TEMP;
        DEST ← TEMP;
    FI;
А вообще, непонятно, зачем cmpxchg делает запись во втором случае. Можно было бы обойтись и без неё.

Автор: shm 26.09.16, 15:52
Цитата Jin X @
Можно было бы обойтись и без неё.

Как?

Автор: Jin X 26.09.16, 17:30
Цитата shm @
Как?
А в чём её суть?

Temp := Dest
Dest := Temp

Автор: Славян 26.09.16, 17:50
Быть может там прописана технология реализации. Т.е. TEMP - a'la кэш, а в конце команды обязательно надо что-то послать в память, ну а потому как совпадёт кэш с DEST, то не страшно его и послать обратно!?? :unsure:

Автор: shm 26.09.16, 18:25
Цитата Jin X @
А в чём её суть?

В атомарности.
Цитата Jin X @
Temp := Dest
Dest := Temp

Ни разу не атомарно.

Автор: leo 27.09.16, 06:31
Цитата Jin X @
А вообще, непонятно, зачем cmpxchg делает запись во втором случае. Можно было бы обойтись и без неё.

Это делается для упрощения "железной логики":
Цитата Intel
To simplify the interface to the processor’s bus, the destination operand receives a write cycle without regard to the result of the comparison. ... (The processor never produces a locked read without also producing a locked write.)
А избежать лишних тормозов при вызове cmpxchg в спин-цикле можно программно, добавив перед lock обычное сравнение (как рекомендует интел - “test, test-and-set” technique).

Автор: Jin X 27.09.16, 12:44
Цитата shm @
В атомарности.
Цитата shm @
Ни разу не атомарно.
Иии?
Я не понимаю, что ты хочешь сказать :)
Приведённый мной выше код - это внутренняя логика инструкции cmpxchg.

leo, теперь ясно.

Автор: shm 27.09.16, 15:25
Цитата Jin X @
Я не понимаю, что ты хочешь сказать

То, что именно такая форма инструкции удобна для реализации lock-free алгоритмов и те же примитивов синхронизации.

Автор: Jin X 27.09.16, 16:14
shm, ничуть не удобна. Потому что пока она будет делать Temp = Dest и Dest = Accumulator (в случае успешной проверки входа критической секции), то же самое может сделать и другой. И оба войдут в неё.

Temp1 = Dest
Temp2 = Dest
Temp1 = Src ? (Yes)
Temp2 = Src ? (Yes)
Dest = Accumulator
Dest = Accumulator

Добавлено
Или мы о разном? :)

Автор: shm 27.09.16, 16:17
Цитата Jin X @
Потому что пока она будет делать

Она это делает атомарно (при наличии lock префикса и для SMP). Ну как еще написать не знаю.

Добавлено
Аппаратно эта инструкция, насколько я понимаю, реализована совсем не так, как ты расписал.

Добавлено
Я с трудом представляю как без этой инструкции можно реализовать тот же lock-free список. Если у тебя есть идеи - покажи. Кстати эта функция именно в таком виде используется в реализации практически всех примитивов синхронизации.

Автор: Jin X 28.09.16, 06:39
Цитата shm @
Она это делает атомарно (при наличии lock префикса и для SMP). Ну как еще написать не знаю.
При наличии lock - естественно. Но ты ж сам написал:
Цитата shm @
То, что именно такая форма инструкции удобна для реализации lock-free алгоритмов и те же примитивов синхронизации.
Если же имелось в виду:
Цитата
Неблокирующая синхронизация — подход в параллельном программировании на симметрично-многопроцессорных системах, проповедующий отказ от традиционных примитивов блокировки, таких, как семафоры, мьютексы и события. Разделение доступа между потоками идёт за счёт атомарных операций и специальных, разработанных под конкретную задачу, механизмов блокировки.
© Википедия
тогда без вопросов ;)

Цитата shm @
Аппаратно эта инструкция, насколько я понимаю, реализована совсем не так, как ты расписал.
Это код из свежего Intel Instruction Reference (сообщение #27).

Добавлено
Цитата shm @
Кстати эта функция именно в таком виде используется в реализации практически всех примитивов синхронизации.
Так, а кто ж спорит-то? :)

Автор: shm 28.09.16, 12:01
Цитата Jin X @
тогда без вопросов

именно.
Цитата Jin X @
Это код из свежего Intel Instruction Reference

Это псевдо-код поясняющий поведение инструкции. Он не имеет никакого отношения к ее аппаратной реализации.

Добавлено
Цитата Jin X @
Так, а кто ж спорит-то?

А тогда ивзини, я неправильно понял твою реплику про cmpxchg. Тогда leo правильно написал.

Автор: Jin X 29.09.16, 06:15
Нашёл интересную вещь в недрах AcquireSRWLockExclusive: NtWaitForKeyedEvent (ntdll.dll).
Возможно, это то, что мне нужно (если вернуться к первоначальной теме)...
Буду изучать... http://www.locklessinc.com/articles/keyed_events/ :whistle:

Автор: shm 29.09.16, 15:35
Jin X, так обычные события чем не угодили я все понять не могу?

Автор: Jin X 29.09.16, 18:55
Уффф... Надо делать Init и Detele (в которых делается Init и Close... ну или просто Delete (Close) (либо создавать и удалять на каждом входе/выходе в/из крит.секции). Они более громоздкие/долгие. Надо делать отдельное событие под каждую критическую секцию (под каждый мьютекс). Короче, этот вариант мне кажется более интересным. Правда, это не совсем то, что я подумал изначально. Изначально мне казалось, что это альтернатива Sleep(1). Но всё равно лучше, чем Event. Вероятно. Я ещё не изучил этот вопрос :)

Автор: shm 29.09.16, 20:03
Цитата Jin X @
Надо делать Init и Detele

Ой да целых пара наносекунд при запуске и завершении твоей программы, ога.
Цитата Jin X @
либо создавать и удалять на каждом входе/выходе в/из крит.секции

Не надо ничего создавать и удалять во время работы. Надо:
Enter
...
Leve
инициализацию и удаления нужно вынести из основного цикла.
Цитата Jin X @
Надо делать отдельное событие под каждую критическую секцию

:wacko:
Цитата Jin X @
Но всё равно лучше, чем Event

Что лучше?

Автор: leo 01.10.16, 08:10
Цитата Jin X @
Нашёл интересную вещь в недрах AcquireSRWLockExclusive: NtWaitForKeyedEvent (ntdll.dll).
Возможно, это то, что мне нужно (если вернуться к первоначальной теме)...

Во-первых, NtWaitForKeyedEvent работает через тот же event, с той разницей, что 1) к одному эвенту можно прицепить несколько ключей (что несколько замедляет быстродействие, о котором ты так печешься), 2) если вместо первого параметра передать nil\null, то будет использован системный эвент (типа CritSecOutOfMemoryEvent) - возможно это "то, что тебе нужно", если тебе так хочется избежать "лишних" Create\Close, Init\Delete и т.п.
Во-вторых, механизм использования NtWaitForKeyedEvent давно встроен в виндовые крит.секции (по кр.мере со времен XP) на случай облома создания собственного эвента крит.секции (по причине OutOfMemory или исчерпания лимита хэндлов). Поэтому, если тебе так нравится изобретать нестандартные велосипеды, то можно использовать небольшой хак с обычной крит.секцией - сразу после InitializeCriticalSectionAndSpinCount(cs,...), установить cs.LockSemaphore:=INVALID_HANDLE_VALUE - и всё, получишь два в одном: и готовый спин-луп с заданным числом повторений и ожидание NtWaitForKeyedEvent на системном эвенте (вместо создания собственного эвента для крит.секции и его ожидания на WaitForSingleObject).

Автор: Jin X 01.10.16, 17:25
leo, интересно... :)
Почему же AcquireSRWLockShared, AcquireSRWLockExclusive не используют обычные Event'ы, а работают с KeyedEvent'ами?

Автор: leo 02.10.16, 06:48
Цитата Jin X @
Почему же AcquireSRWLockShared, AcquireSRWLockExclusive не используют обычные Event'ы, а работают с KeyedEvent'ами?

Потому, что это slim - упрощенный "до безобразия" вариант крит.секции. Структура SRWLOCK это набор битовых полей, упакованных в размер указателя (32 или 64 бита). В ней просто нет места не только для указателя на event, но и для OwningThreadID и RecursionCount (со всеми вытекающими особенностями использования по сравнению с крит.секцией). Индивидуального SpinCount-а тоже нет, используется некое дефолтное значение. Соотв-но и для ожидания освобождения лока используется не "обычный", а некий глобальный системный KeyedEvent.

PS: Что касается быстродействия поиска ключа, то оно зависит от кол-ва, этих самых ключей, привязанных к одному KeyedEvent-у, ну и от используемой структуры хранения и алгоритма поиска ключей. В XP KeyedEvent использовался только в критической ситуации нехватки памяти, поэтому о быстродействии особо не беспокоились и использовали для хранения ключей простой список. А начиная с висты KeyedEvent-ы стали использовать для SRW-lock, условных переменных и т.п., и соотв-но стали хранить ключи в хэш-таблице для ускорения их поиска. Однако как бы не было реализовано хранение и поиск ключей, при малом\небольшом их кол-ве накладные расходы на их поиск\добавление\удаление составляют незначительный процент от общего времени ожидания эвента на NtWaitForKeyedEvent -> WaitForSingleObject

Powered by Invision Power Board (https://www.invisionboard.com)
© Invision Power Services (https://www.invisionpower.com)