it-swarm-eu.dev

Jak provést podproces podproces méně než milisekundu v systému Windows

V systému Windows mám problém, se kterým jsem se nikdy na Unixu nesetkal. To je způsob, jak dostat vlákno spát méně než jednu milisekundu. Na Unixu máte obvykle řadu možností (spánek, usleep a nanosleep), aby vyhovovaly vašim potřebám. Ve Windows je však pouze Sleep s milisekundovou granularitou. 

Na Unixu mohu použít systémové volání select k vytvoření mikrosekundového spánku, který je velmi jednoduchý:

int usleep(long usec)
{
    struct timeval tv;
    tv.tv_sec = usec/1000000L;
    tv.tv_usec = usec%1000000L;
    return select(0, 0, 0, 0, &tv);
}

Jak lze dosáhnout stejného v systému Windows?

50
Jorge Ferreira

To znamená nesprávné pochopení funkcí spánku. Parametr, který projdete, je minimální čas pro spaní. Není zaručeno, že se vlákno probudí přesně po zadaném čase. Ve skutečnosti se vlákna „neprobudí“ vůbec, ale jsou spíše vybrána pro provádění plánovačem. Plánovač se může rozhodnout, že po aktivaci podprocesu bude čekat mnohem déle než požadovaná doba spánku, zejména pokud je v daném okamžiku stále aktivní jiné vlákno.

88
Joel Coehoorn

Jak říká Joel, nemůžete smysluplně „spát“ (tj. Vzdát se naplánovaného CPU) po tak krátkou dobu. Pokud chcete odložit na krátkou dobu, pak je třeba točit, opakovaně kontrolovat časovač s vysokým rozlišením (např. „Časovač výkonu“) a doufat, že vás něco s vysokou prioritou stejně nevyprázdní.

Pokud vám opravdu záleží na přesném zpoždění takových krátkých časů, neměli byste používat Windows.

47
Will Dean

Použijte časovače s vysokým rozlišením dostupné v winmm.lib. Příklad viz toto .

26
Joe Schneider

Ano, musíte porozumět kvantovým časům vašeho operačního systému. V systému Windows nebudete ani dostávat časy 1 ms, pokud nezměníte čas na 1 ms. (Použití například timeBeginPeriod ()/timeEndPeriod ()) To ještě nezaručuje nic. Dokonce i malé zatížení nebo jeden mizerný ovladač zařízení vyhodí všechno pryč.

SetThreadPriority () pomáhá, ale je to docela nebezpečné. Špatné ovladače zařízení vás stále mohou zničit.

K tomu, abyste tuto ošklivou práci pracovali, potřebujete velmi řízené počítačové prostředí.

9
darron
#include <Windows.h>

static NTSTATUS(__stdcall *NtDelayExecution)(BOOL Alertable, PLARGE_INTEGER DelayInterval) = (NTSTATUS(__stdcall*)(BOOL, PLARGE_INTEGER)) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtDelayExecution");

static NTSTATUS(__stdcall *ZwSetTimerResolution)(IN ULONG RequestedResolution, IN BOOLEAN Set, OUT PULONG ActualResolution) = (NTSTATUS(__stdcall*)(ULONG, BOOLEAN, PULONG)) GetProcAddress(GetModuleHandle("ntdll.dll"), "ZwSetTimerResolution");




static void SleepShort(float milliseconds) {
    static bool once = true;
    if (once) {
        ULONG actualResolution;
        ZwSetTimerResolution(1, true, &actualResolution);
        once = false;
    }

    LARGE_INTEGER interval;
    interval.QuadPart = -1 * (int)(milliseconds * 10000.0f);
    NtDelayExecution(false, &interval);
}

ano používá některé undocumented kernel funkce, ale funguje to velmi dobře, používám SleepShort (0,5); v některých mých věcech

8
Oskar Dahlberg

Pokud chcete tolik zrnitosti, jste na nesprávném místě (v uživatelském prostoru). 

Pamatujte, že pokud jste v uživatelském prostoru, váš čas není vždy přesný. 

Plánovač může spustit váš podproces (nebo aplikaci) a naplánovat jej, takže jste závislí na plánovači operačního systému. 

Pokud hledáte něco přesného, ​​musíte jít: 1) V prostoru jádra (jako ovladače) 2) Vyberte RTOS.

Pokud hledáte nějakou granularitu (ale nezapomeňte na problém s uživatelským prostorem), podívejte se na funkci Funkce QueryPerformanceCounter a QueryPerformanceFrequency v MSDN.

6
user16523

Jak poukázalo několik lidí, spánek a další související funkce jsou standardně závislé na "systémovém klíště". Toto je minimální doba mezi úkoly OS; Plánovač například nebude běžet rychleji než toto. I s OS v reálném čase není zaškrtnutí systému obvykle menší než 1 ms. I když je laditelný, má to dopad na celý systém, nikoli pouze na funkci spánku, protože plánovač bude běžet častěji a potenciálně zvýší režii vašeho operačního systému (doba, po kterou má plánovač běžet, oproti množství spuštění úlohy).

Řešením je použití externího vysokorychlostního hodinového zařízení. Většina systémů Unix vám umožní určit časovačům a jiným hodinám, které se mají použít, na rozdíl od výchozích systémových hodin.

5
mbyrne215

Obecně spánek vydrží alespoň do příštího přerušení systému. Toto Však závisí na nastavení zdrojů časovače multimédií. Může být nastaveno na něco blízkého 1 ms, některé hardwarové možnosti dokonce umožňují spuštění v intervalu přerušení 0,9765625 (ActualResolution poskytnutý NtQueryTimerResolution bude zobrazovat 0.9766, ale to je ve skutečnosti špatně. Správné číslo do formátu ActualResolution je 0,9765625ms při 1024 přerušeních za sekundu.

Existuje jedna výjimka, která nám umožňuje uniknout ze skutečnosti, že může být nemožné spát méně než období přerušení: Je to slavný Sleep(0). Jedná se o velmi silný nástroj , Který se nepoužívá tak často, jak by měl! Zruší připomenutí časového řezu podprocesu. Tímto způsobem se vlákno zastaví, dokud plánovač nenutí vlákno znovu získat službu cpu. Sleep(0) je asynchronní služba, volání donutí plánovač reagovat nezávisle na přerušení.

Druhým způsobem je použití waitable object. Funkce čekání jako WaitForSingleObject() může počkat na událost. Aby vlákno spalo na libovolnou dobu, také časy v režimu mikrosekund, vlákno musí nastavit nějaký podproces služby, který vygeneruje událost s požadovaným zpožděním. Podproces "spaní" nastaví toto vlákno a pak pozastaví funkci čekání, dokud podproces služby nenastaví signalizovanou událost.

Tímto způsobem může každý vlákno "spát" nebo čekat kdykoliv. Servisní vlákno může být velmi složité a může nabídnout široké systémové služby, jako jsou časované události při rozlišení mikrosekund. Rozlišení mikrosekund však může vynutit, aby se podproces služby otáčel na časové službě s vysokým rozlišením po dobu maximálně jednoho intervalu přerušení (~ 1 ms). Pokud se jedná o péči, může […] fungovat velmi dobře, zejména na víceprocesorových nebo vícejádrových systémech. Otáčení v délce jednoho ms nebolí na vícejádrovém systému, když je pečlivě zpracována maska ​​spřažení pro volající vlákno a servisní vlákno.

Kód, popis a testování lze navštívit na projektu Windows Timestamp Project

4
Arno

Na co čekáte, vyžaduje takovou přesnost? Obecně platí, že pokud potřebujete specifikovat tuto úroveň přesnosti (např. Kvůli závislosti na nějakém externím hardwaru) jste na špatné platformě a měli byste se podívat na OS v reálném čase.

V opačném případě byste měli zvážit, zda existuje událost, na které můžete synchronizovat, nebo v horším případě jen zaneprázdněně čekat na procesor a použít vysokovýkonný čítač API k měření uplynulého času.

4
Rob Walker

Vlastní použití této funkce usleep způsobí velkou paměť/únik zdroje. (podle toho, jak často se nazývá)

použít tuto opravenou verzi (omlouvám se, že ji nemůžu upravit?)

bool usleep(unsigned long usec)
{
    struct timeval tv;
    fd_set dummy;
    SOCKET s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    FD_ZERO(&dummy);
    FD_SET(s, &dummy);
    tv.tv_sec = usec / 1000000ul;
    tv.tv_usec = usec % 1000000ul;
    bool success = (0 == select(0, 0, 0, &dummy, &tv));
    closesocket(s);
    return success;
}
2
Hendrik

Mám stejný problém a nic se zdá být rychlejší než ms, dokonce i spánek (0). Můj problém je komunikace mezi klientem a serverovou aplikací, kde používám funkci _InterlockedExchange k testování a nastavování bitů a pak I Sleep (0).

Opravdu musím provést tisíce operací za sekundu tímto způsobem a to nefunguje tak rychle, jak jsem plánoval.

Vzhledem k tomu, že mám tenkého klienta zabývajícího se uživatelem, který zase vyvolává agenta, který pak mluví s vláknem, brzy se přesunu na sloučení podprocesu s agentem, takže nebude nutné žádné rozhraní událostí.

Abych vám dal představu o tom, jak je tento Spánek pomalý, běžel jsem na 10 vteřin testem s prázdnou smyčkou (něco jako 18 000 000 smyček), zatímco na místě jsem dostal jen 180 000 smyček. To je 100 krát pomalejší!

2
Celso Bressan

Stejně jako všichni zmínění, neexistuje žádná záruka na dobu spánku. Ale nikdo nechce připustit, že někdy v klidovém systému může být příkaz usleep velmi přesný. Zvláště s bezklíčovým jádrem. Windows Vista má to a Linux má to od 2.6.16.

Jádra Tickless existují, aby pomohla zlepšit životnost notebooků: c.f. Intel Powertop utility.

V tomhle stavu jsem se snažil měřit příkaz Linux usleep, který velmi pečlivě respektoval požadovaný čas spánku, až na půl tuctu mikrosekund.

Takže možná chce OP něco, co bude po většinu času pracovat na volnoběhu, a bude schopen požádat o mikrosekundové plánování!.

Také spánek (0) zní jako boost :: thread :: yield (), která je jasnější.

Zajímalo by mě, zda Boost - časované zámky mají lepší přesnost. Protože pak byste mohli jen zamknout mutex, který nikdo nikdy nezveřejní, a když je časový limit dosažen, pokračujte na ... Časové limity jsou nastaveny s boost :: system_time + boost :: milliseconds & cie (xtime je zastaralý ).

1
Lightness1024

Zkuste použít SetWaitableTimer ...

0
andrewrk

Vyzkoušejte boost :: xtime a a timed_wait ()

má nanosekundovou přesnost.

0
theschmitzer

Stačí použít režim spánku (0). 0 je jednoznačně menší než milisekunda. Teď to zní vtipně, ale vážně. Spánek (0) říká systému Windows, že právě teď nemáte co dělat, ale že chcete, aby byl znovu zvážen, jakmile plánovač znovu spustí. A vzhledem k tomu, že vlákno nemůže být naplánováno tak, aby běžel dříve, než samotný plánovač běží, je to nejkratší možné zpoždění.

Všimněte si, že můžete uspět v mikrosekundovém čísle do vašeho usleepu. } - žádné záruky na to, že se toto období skutečně spí.

0
MSalters

Pokud je vaším cílem "čekat na velmi krátkou dobu" protože děláte spinwait , pak se zvyšují úrovně čekajících výkonů.

void SpinOnce(ref Int32 spin)
{
   /*
      SpinOnce is called each time we need to wait. 
      But the action it takes depends on how many times we've been spinning:

      1..12 spins: spin 2..4096 cycles
      12..32: call SwitchToThread (allow another thread ready to go on time core to execute)
      over 32 spins: Sleep(0) (give up the remainder of our timeslice to any other thread ready to run, also allows APC and I/O callbacks)
   */
   spin += 1;

   if (spin > 32)
      Sleep(0); //give up the remainder of our timeslice
   else if (spin > 12)
      SwitchTothread(); //allow another thread on our CPU to have the remainder of our timeslice
   else
   {
      int loops = (1 << spin); //1..12 ==> 2..4096
      while (loops > 0)
         loops -= 1;
   }
}

Takže pokud je vaším cílem vlastně čekat jen na trochu , můžete použít něco jako:

int spin = 0;
while (!TryAcquireLock()) 
{ 
   SpinOne(ref spin);
}

Cností zde je, že pokaždé budeme čekat déle, až nakonec úplně usneme. 

0
Ian Boyd

Funkce spánku, která je méně než milisekunda možná

Zjistil jsem, že spánek (0) pracoval pro mě. V systému s téměř 0% zátěží na cpu ve správci úloh jsem napsal jednoduchý konzolový program a funkce spánku (0) spala po dobu 1-3 mikrosekund, což je méně než milisekunda.

Ale z výše uvedených odpovědí v tomto vlákně vím, že množství spánku (0) spí může lišit mnohem více divoce než v systémech s velkým zatížením CPU.

Ale jak to chápu, funkce spánku by neměla být používána jako časovač. Mělo by být použito k tomu, aby program používal co nejmenší procento z procesoru a co nejrychleji. Pro mé účely, jako je pohyb projektilu přes obrazovku ve videohře mnohem rychlejší než jeden pixel za milisekundu, myslím, že spánek (0) funguje.

Měli byste se jen ujistit, že interval spánku je menší než největší čas, který by spal. Nepoužíváte spánek jako časovač, ale jen proto, aby bylo možné použít minimální množství procenta cpu. Použili byste samostatnou funkci, která nemá co dělat, je spát, abyste se dozvěděli, kdy uplynulo určité množství času, a pak pohybujte projektilem o jeden pixel napříč obrazovkou - v době řekněme 1/10 milisekund nebo 100 mikrosekund .

Pseudo-kód by šel něco takového.

while (timer1 < 100 microseconds) {
sleep(0);
}

if (timer2 >=100 microseconds) {
move projectile one pixel
}

//Rest of code in iteration here

Vím, že odpověď nemusí fungovat pro pokročilé problémy nebo programy, ale může fungovat pro některé nebo mnoho programů. 

0
rauprog