C# : Stabil időmérő eszköz(ök)
2012-04-17T12:36:26+02:00
2012-04-21T13:15:31+02:00
2022-07-19T05:27:51+02:00
  • Ma is tanultam valamit. Nem is tudtam, hogy a Sleep is lehet pontatlan. Bár ami függ a processzortól, arról feltételezhetjük ezt. Akkor a Sleep is rosszalkodik. Nem 30ms-et fog várni pontosan, hanem a processzor terheltségétől függően akkor többet is. Ez rossz hír. Főleg ha nincs olyan alternatív megoldás, ami a processzortól függetlenül sleep-el, mint az időmérés esetében a QueryPerformanceCounter.

    Értem az ötlet lényegét. Tetszik is, csak kételyeim vannak vele kapcsolatban. Egyrészt nem tudom, hogy fel szabadna-e bontani a buffert kisebb részekre. Nem alakul ki ebből megint bonyodalom. Oké hogy 12,5 MB/sec-nek a tized 1.25MB/ 100ms. Ennek a többszöröse között megtalálható a 10 MegaByte, de mi van akkor ha maradunk a 12,5MB/sec-nél, de a buffer méret csak 2 mega. 1,25-nek most már nem esik bele a többszörösébe a kívánt méret. Rendben akkor az utolsó buffer méret nem annyi lesz mint előtte. Akkor meg lehet hogy az hamarabb kiírásra kerül és mivel lehet, hogy addig csúszásban volt a bitrate, de ezt a kisebb méretet hamarabb kiírja és behozza a bitrate-t. Akkor azt fogjuk leszűrni hogy ez idáig végig tudtuk tartani a bitrate-t. Holott csak a kisebb buffer méret miatt sikerült lehozni a bemaradást. (szerk: ezt de szépen megfogalmaztam )
    Nem tudom, hogy ezt érdemes lenne-e használni.
    Ezen kívül az is cél, hogy az adott buffer méretet írjuk ki amilyen gyorsan csak tudjuk. Ha sikerült a becsült időn belül akkor megnyugodhatunk, sleepelünk egy kicsit, addig a RAID tudja organizálni magát és akkor megint jön a következő buffer. Éppen az a cél szerintem ezzel, hogy terheljük meg egy írás erejéig a háttértárat, és majd pihen utána, ne osszuk el neki a munkát, hogy kényelmes legyen neki. Terheljük meg egy komplett buffer-ral, nézzük meg hogy sikerült-e, majd utána pihen egy kicsit és kezdjük megint.
    Másik dolog, hogy oké nézzük, hogy az események egymásra tolódnak-e,de mi van olyankor, hogy az említett kisebb buffer méret miatt csak + 3-4 iterációra derül, ki hogy voltaképpen már torlódunk egy ideje, csak az említett buffer miatt ezt eddig nem tapasztaltuk.
    Mutasd a teljes hozzászólást!
  • Azt vedd figyelembe, hogy a Sleep()-nek is van pontatlansága, ami legalább annyi, mint a GetTickCount() granularitása. Simán megeshet, hogy pár millisec-kel később kapod vissza a vezérlést, mint amennyit a Sleep()-nek megadtál. (Leterhelt rendszer esetén persze több is lehet, hiszen az ütemező választhat más taszkokat futtatásra a tiéd helyett.)

    De miért nem jó a "mezítlábas" megoldás? Ha 12.5 MB/sec a célérték, az 1.25 MB tizedmásodpercenként. Beraksz egy timer-t 100ms-os intervallummal, és minden elsülésekor kiírsz 1.25MB-nyi adatot. Még valahogy azt kell detektálnod, hogy egymásra torlódtak-e az események, és akkor már azt is tudni fogod, hogy alatta vagy-e a kívánt bitrátának.
    Mutasd a teljes hozzászólást!
  • Értem. Akkor leírom itt neked mire akarom használni:

    Beadtunk egy Buffer méretet. Legyen az mondjuk most 10MB. Ekkora méretű bufferekkel akarjuk írni egy iterációs lépésben a file-unkat. Ámde van egy Bitrate nevezetű input paraméter. Ez azt mondja, hogy tartsuk be vagy legalább is próbáljuk meg betartani ezt a rátát. Tehát mit csinálok. Megbecsülök egy értéket, amennyi idő alatt a 10MB-ot a megadott Bitrate-tel kiír. Legyen 100Megabit/sec a Bitrate. Kis matek után kijön, hogy elosztod 8-cal akkor kiderül hogy 12,5 MB/sec-et kellene kiírnunk egy másodperc alatt. De nekünk a buffersize 10 MB. Ne számold ki ezt 800 ms alatt kellene kiírnia. Oké. Ennyi a becslés. Nézzük a valóságot. Lementem az időt a kiírás előtt, lementem az időt a kiírás után. A kettő különbsége adja, hogy a kiírás mennyi ideig tartott. Legyen az mondjuk mittomén 500ms. Akkor ezt megnézzük, hogy a 800ms-től kevesebb, akkor Sleep-eljünk 300 ms-et (800-500 ms). Így betartjuk a Bitrate-et, és folyamatosan azonos sebességet tudunk tartani. Az más kérdés, hogy mi van ha mondjuk 1 sec alatt írta ki a 10MB-ot mi meg 800ms-re saccoltunk. Akkor az van hogy nem fogjuk tartani a Bitrate-et, mert már most késében vagyunk. Ekkor jelzem ki, hogy nem sikerült tartani a kívánt értéket és mittomén valami, dobunk egy messageboxot.
    Mutasd a teljes hozzászólást!
  • Ilyen mérési pontosságnál elképzelehtő, hogy a GUI kiírás / interakció tovább tart, mint maga a művelet.

    Nem tudom, hogy mire akarod használni, de ha csak tájékoztató adat, akkor egy művelet során elég csak 2-300 ms-enként kiírni az adatot.

    De kiírhatod gyakrabban is, csak senki sem fogja felfogni.
    Tehát a te esetedben ha csak tájékoztató adatról van szó, akkor kb. 32 csomag kiírásának a sebességét nézném. Azért 32, mert az 1 32 bites uint, és a shift right egy olcsó művelet, s amikor az uint 0 lesz, akkor tudod, hogy 32-ig elszámoltál.
    Mutasd a teljes hozzászólást!
  • A számból vetted ki a szót. Ezt akartam valahogy megfogalmazni az előző hozzászólásomban is, csak nem vagyok jó fogalmazásból
    Mutasd a teljes hozzászólást!
  • Na most képzeld el, ha ezeknél a mért érték mind 10 ms lett volna, mert nincs kisebb intervallum.


    Ennél még bonyolultabb a helyzet, mert nem simán a pontos eredmény kerekedik felfelé. Helyette van két lefelé (mondjuk a legközelebbi 10ms-re) kerekített időbélyeged, amiket kivonsz egymásból. Csak annyit tudunk biztosan mondani erről az értékről, hogy 10ms többszöröse lesz, és +/-10ms lehet a maximális eltérése a helyes értéktől. Ha tegyük fel 1ms volt a két "mérés" között, de pont akkor billent át a TickCount értéke, akkor 10ms lesz az eredményed. Viszont lehet az is, hogy 9ms telt el, de ez alatt pont nem változott az érték, és 0ms-et mérsz.

    Egyetértünk, hogy az ilyen felbontás csak akkor lenne elfogadható, ha a felbontás sokszorosának megfelelő idő mérésére használnánk. (Mondjuk több másodperces időtartamnál már nem lenne gond a 10ms hiba.)
    Mutasd a teljes hozzászólást!
  • Oké Csaboka2 igazat adok neked. Tényleg ilyen 10ms-es felbontással nem elegendő.

    Ha "megfelelően" alkalmazta volna (sok iteráció futtatása után mér, és az értékeket utólag még feldolgozza), akkor ez is elég jó


    Az rendben van hogy több iteráció után alkalmaznám, ezt de sajnos a feladat úgy kívánja meg, hogy minden egyes kiírt buffernek mérjük külön az idejét (nem 10ms-es pontossággal, hanem jó ha inkább 1 ms-essel) és minden egyes kiírt buffer után végre kell hajtani valamit. Itt ennél a résznél nem az iteráció végeztével kell feldolgozni valamit, hanem minden egyes iterációs lépésnél végre kell hajtani egy műveletet. Amit én mértem régebben egy buffer kiírása 2-3, nagyobb buffernél pedig 6-7 ms volt. Na most képzeld el, ha ezeknél a mért érték mind 10 ms lett volna, mert nincs kisebb intervallum. Akkor az derült volna ki, hogy az 1 KB-os intervallumtól a 10KB-os intervallumig mindnek ugyan annyi lenne az ideje(10 ms). Ez nem lenne elég pontos. 10MB-os buffer méretnél azért már jócskán túllépjük szerintem a 10ms-es időt, de akkor megint csak 10 ms-os eltérésekkel tudunk mérni. Tegyük fel, hogy 22 ms a 10MB-os buffer kiírásának pontos ideje. Emiatt aztán, mi 30ms-et mérünk. 8 ms alatt azért már belekezdenénk a következő írásba is. Ha ezek az eltérések pedig egymásra rakódnak akkor a hiba is egyre nagyobb lesz, azt hiszem.... Pillangó hatás, meg numerikus matematikából is volt erről szó.
    Mutasd a teljes hozzászólást!
  • Most őszintén, a fenti feladatra, ha megfelelően van alkalmazva, akkor ez nem elég?


    Szerdzso azt panaszolta, hogy két Environment.TickCount lekérés között "nem telik el idő", és erre mondtam, hogy nem meglepő, mert 10ms a felbontása, addig meg sok minden le tud futni egy modern CPU-n. Ha "megfelelően" alkalmazta volna (sok iteráció futtatása után mér, és az értékeket utólag még feldolgozza), akkor ez is elég jó, de én nem erre reagáltam.

    Egyébként a TickCount-ot alacsony szinten le lehet kérdezni long-ként is, s akkor sokkal később fordul át, de át tud fordulna az is.


    Az átfordulásról egyáltalán nem volt szó. A 32 bites TickCount is cirka 49 nap után fordul át, szóval az, hogy emiatt kapjon valaki negatív futásidő értéket, havonta kevesebb, mint egyszer fordulhatna elő.
    Egyébként is, a 64 bites TickCount az cirka 580 millió évenként fordul át. Na már most ha egy mai Windowst-t 580 millió évig futtatsz (mert ugye a TickCount az indulás óta eltelt ezredmásodperceket adja), akkor azzal már bevonulhatsz a történelemkönyvekbe
    Mutasd a teljes hozzászólást!
  • Most őszintén, a fenti feladatra, ha megfelelően van alkalmazva, akkor ez nem elég?

    Egyébként a TickCount-ot alacsony szinten le lehet kérdezni long-ként is, s akkor sokkal később fordul át, de át tud fordulna az is.
    Mutasd a teljes hozzászólást!
  • Ez az, hogy futás közben a megadott kóddal nem tudtam előhozni, csak így módosítva...

    ---

    De azért ez valamire rávilágít, hogy kb. mi is történhet Nálad, nem?

    Ez a sor:
    id = timeSetEvent(msec, 0, Timevent, ref user, TIME_PERIODIC);

    után a GC "elkönyveli", hogy ez unmanaged, ez után bármely managed kódból való hozzáférési kísérlet inkorrekt, ami azonban csak a legközelebbi "nagytakarításnál" derül ki. A módosított kódban ezt a takarítást végeztem azonnal, hogy a hibát elő tudjam idézni. A valóságban a takarítás időpontját nem lehet tudni, vagyis a kivételt jóval a inkorrekt művelet után dobja(ez azért pl. felhasználható hibakeresésre is).

    Persze a teszt kódodat pontosan nem ismerem, ezért nem tudom, hogy pontosan hogy alakul ki ez a helyzet Nálad. Hozzáteszem, hogy ez a kód közel sincs kész(nincs felkészítve különleges helyzetekre).

    Amit javasolni tudok, hogy a konstruktorba ez helyett:
    Timevent = Tick;
    ez szerepeljen:
    if (Timevent == null) Timevent = new TimerEventHandler(Tick);

    Persze célszerű bővíteni ezt az osztályt. Kialakítható pl. az eredeti Stopwatch-hez hasonló szerkezet is, sőt a Idisposable-t sem lenne rossz implementálni...
    Mutasd a teljes hozzászólást!
  • Nagyszerű, hogy te is elő bírtad hozni, de most inkább arra kellene megoldást találni, hogy ne történjen ilyen hiba.
    Mutasd a teljes hozzászólást!
  • Hát hallod... majdnem vért izzadtam, mire elő tudtam hozni ezt a hibát. Már azt hittem, hogy a vs-emben nincs is benne.

    Végül azért így sikerült:

    A hívás:

    GC.Collect(); GC.WaitForFullGCComplete(); PrecTimer pc = new PrecTimer(1); GC.Collect(); GC.WaitForFullGCComplete();

    A konstruktor átírva:

    int user = 0; Timevent = Tick; id = timeSetEvent(msec, 0, Timevent, ref user, TIME_PERIODIC); Timevent = new TimerEventHandler(Tick);

    Így már sikerült indításnál előcsalogatnom, úgyhogy mégiscsak van a vs-ben.

    Szerintem valami hasonló lehet nálad is...
    Lásd a mellékelt képet.
    Mutasd a teljes hozzászólást!
    Csatolt állomány
  • Kedves monitor!

    Beleraktam a programba a kódodat. Tényleg van rajta finomítani való. Az a kisebbik gond, hogy a számítás elején még "végtelen"-t ír ki, mert gondolom a kettő különbsége még nulla. Később aztán beáll és szépen mér. Negatív értéket nem dobott és a mért érték is nagyjából korrekt. Azonban a test folyamán egy ilyen hibaüzenetet kapok :

    A callback was made on a garbage collected delegate of type 'Tester!Tester.PreciseTimer+TimerEventHandler::Invoke'. This may cause application crashes, corruption and data loss. When passing delegates to unmanaged code, they must be kept alive by the managed application until it is guaranteed that they will never be called.


    Lehet, hogy én gépeltem be valamit rosszul.
    Mutasd a teljes hozzászólást!
  • Értem imolnar. Tényleg jó lett volna rájönni,de engem is sürget az idő. Megnézem holnap monitor ötletét. Ha az sem működik akkor megnézzük a te ötleted és végre kiderül, hogy az is ad-e vissza negatív értéket. Ha monitor ötlete beválik akkor megelégszek azzal is, csak menjen.

    Köszönöm, hogy te is segítetted a boldogulásomat.
    Mutasd a teljes hozzászólást!
  • Nincs szükségem, hogy mikrosecundum pontossággal meg tudjam határozni a sebességet.

    Jó.
    De, azért érdekes lett volna megtudni, ez a cucc is ad-e vissza negatív számot - ugye ez volt a fő probléma -, vagy nem.
    Mindegy, nem erőltetem.
    Mutasd a teljes hozzászólást!
  • Holnap kiderül
    Mutasd a teljes hozzászólást!
  • Az nem előre megfontolt szándék, csak az összehasonlítás működőképességére koncentráltam, úgyhogy lehet, hogy van benn 1-2 felesleg, ha megfelel, akkor azért még bőven van rajt finomitanivaló.
    Mutasd a teljes hozzászólást!
  • Hujjujuj elég lett volna nekem egy két apró bejegyzés a forráskód fontosabb részeiből, te pedig betoltad ide a komplett kódot. Hát köszönöm szépen. Reméljük ez megfelel a feladatra. Megírni és letesztelni már csak holnap fogom tudni, de megkérdezném, hogy a PrecTimer-nek miért van 2 konstruktora? Előre megfontolt szándék vagy valamiért lehet szükség a paraméter nélküli konstruktorára is?
    Mutasd a teljes hozzászólást!
  • Ezt a kódot ha olyan gépen próbálod ki, ahol működik a QueryPerformanceCounter() is, akkor össze tudod hasonlítani a kettő pontosságát(ezért iratom ki állandóan a stop értékét).
    Az utolsó 2 sor értéke teljesen megegyezik(persze kerekítve).
    A QueryPerformanceCounter()-t itt csak az egész kód lefutásának mérésére használom.
    A folyamatos kiíratás miatt persze sokkal lassabban fut le a teljes kód.

    using System; using System.IO; using System.Runtime.InteropServices; namespace ConsoleApplication1 { class Program { [DllImport("Kernel32.dll")] static extern bool QueryPerformanceCounter(out long lpPerformanceCount); [DllImport("Kernel32.dll")] static extern bool QueryPerformanceFrequency(out long lpFrequency); static void Main(string[] args) { long start, stop; long frekv, strt, stp; QueryPerformanceFrequency(out frekv); PrecTimer pc = new PrecTimer(1); string fajl = @"c:\out.bin"; byte[] tomb = new byte[1000]; start = pc.Ertek; QueryPerformanceCounter(out strt); for (int i = 0; i < 2; i++) { if (File.Exists(fajl)) File.Delete(fajl); using (FileStream fs = new FileStream(fajl, FileMode.OpenOrCreate)) { using (BinaryWriter bw = new BinaryWriter(fs)) { for (int j = 0; j < 100000; j++) { bw.Write(tomb); stop = pc.Ertek; //itt állandóan kiíratom Console.WriteLine(stop); start = stop; } } } } QueryPerformanceCounter(out stp); pc.Stop(); // ez lesz az utolsó sor. Ez és az előtte levő sor értéke látható, hogy gyakorlatilag ugyanaz Console.WriteLine(((double)stp-strt)/frekv); Console.ReadLine(); } } class PrecTimer { public delegate void TimerEventHandler(Int32 id, Int32 msg, ref Int32 userCtx, Int32 rsv1, Int32 rsv2); const int TIME_PERIODIC = 0x0001; int id = 0; TimerEventHandler Timevent; [DllImport("winmm.dll", SetLastError = true, EntryPoint = "timeSetEvent")] static extern Int32 timeSetEvent(Int32 msDelay, Int32 msResolution, TimerEventHandler handler, ref Int32 userCtx, Int32 eventType); [DllImport("winmm.dll", SetLastError = true)] static extern void timeKillEvent(Int32 uTimerID); public long Ertek { get; private set; } public PrecTimer() { } public PrecTimer(int msec) { int user = 0; Timevent = Tick; id = timeSetEvent(msec, 0, Timevent, ref user, TIME_PERIODIC); } public void Stop() { if (id != 0) timeKillEvent(id); id = 0; } void Tick(Int32 id, Int32 msg, ref Int32 userCtx, Int32 rsv1, Int32 rsv2) { Ertek++; } } }
    Mutasd a teljes hozzászólást!
  • Aha. Na ez is egy jó ötlet. Kipróbálom ezt is.

    Be tudnál tenni néhány valamit mondó forráskód sort egy kis magyarázattal fűszerezve ?

    Csak hogy hogyan kell elképzelni ?
    Mutasd a teljes hozzászólást!
  • Csak annyi, hogy bele kell tenni egy osztályba. Amikor az esemény bekövetkezik, akkor egy változó(property) értékét növelni 1-el. A főprogramban pedig csak ennek az objektumnak ezt a property-jét kell kiolvasni. Ez úgy viselkedik, mint a QueryPerformanceCounter(out stop); csak éppen ilyen szintaxissal:
    stop = objektumpéldány.Ertek;
    Mutasd a teljes hozzászólást!
  • Ez egy hatalmas előny a Timerhez képest. Azonban a Form-os Timer egy saját szálban szórakozik ott magának és amikor a Tick esemény lefut akkor arra maximum csak fel tudok iratkozni. Nem az kell, hogy ő értesítsen engem, hanem éppen hogy fordítva. Én lekérem, hogy hol tart a kiírás előtt, utána megint lekérem az idejét és a kettő különbsége kell nekem. Az rendben van hogy 1 ms-onként fog nekem szólni,de a Tick események nagy részét figyelmen kívül kell hagyni, nekünk csak néhány meghatározott időpont kell. Ez azt hiszem ellentétben áll a Hollywood Principle-vel. Az általad említett timer-nél ez máshogy van megvalósítva ?
    Mutasd a teljes hozzászólást!
  • Éppen úgy mintha lenne a Form-on egy Timer és annak az Interval property-jét beállítod 1-re ?


    Igen, úgy. A különbség annyi, hogy ez pontosabb a Form timer-nél.
    Mutasd a teljes hozzászólást!
  • Értem. Nos meglátjuk a mostani, ami a programban van hogy szuperál és szükség van-e másikra. Nekem a milisecundumos nagyságrend bőven megfelel. Régebben a DateTime sem tudott pontosabb értéket kiállítani. Viszont a
    legkisebb intervallum 1 ms
    , ezt úgy érted, hogy amikor lekérdezed az eltelt időt akkor a legkisebb lehetséges érték 1, azaz 1 ms ? Éppen úgy mintha lenne a Form-on egy Timer és annak az Interval property-jét beállítod 1-re ?
    Mutasd a teljes hozzászólást!
  • Nincs szükségem, hogy mikrosecundum pontossággal meg tudjam határozni a sebességet. +-1 milisecundum eltérés még belefér.


    Közben találtam egy ugyanolyan pontos időzítést, mint a QueryPerformanceCounter(), viszont a legkisebb intervallum 1 ms.
    A pontosságát leellenőriztem. Viszont a gond az, hogy mivel 1 ms nagyon sok, ezért a tesztelésnél elég sok egyenlő érték alakult ki.
    Tehát akkor vagy meg kell elégedned a 1 ms-al, vagy le kell menned millisecundum intervallum alá, ekkor szerintem imolnar 8253-as chip-es verzióját kellene működésre bírnod.

    Egyébként ebben a pontos időzítőben a timeSetEvent()-et és a timeKillEvent()-et használtam fel. Ha úgy gondolod, hogy ez így megfelelő, vagy kipróbálnád, akkor szívesen beteszem a kódját.
    Mutasd a teljes hozzászólást!
  • 1) Átírtam igen Int32-re. -> Nem változott semmi.
    2) a Write után beraktam a
    stream.Flush();
    -t, de még mindig nincs eltérés a két mért időpontban.

    2.1) Autoflush property-je pedig nincs, mert FileStream-et használok. Lehet ez a gond ?
    Mutasd a teljes hozzászólást!
  • Az Environment.TickCount jó eséllyel a GetTickCount() API függvényt használja, arról pedig tudni kell, hogy nem ezredmásodpercre pontos. A doksi is rámutat, hogy 10-16ms pontosságot szabad csak elvárni tőle. Ennyi idő alatt azért egy mai gépen sok minden le tud futni.
    Mutasd a teljes hozzászólást!
  • 1.) A TickCount az Int32 és nem Int64
    2.)
    A stream.Write(...) után egy
    stream.Flush();-t még tegyél be...
    vagy pedig a stream-et állítsd autoflush-re
    Mutasd a teljes hozzászólást!
  • Viszont most debuggolva a kódot kiderült, hogy a local időmérésnél a két érték megegyezik.

    Int64 localStartTime = Environment.TickCount & Int64.MaxValue; stream.Write(bufferSize, 0, bufferSize.Length); Int64 localStopTime = Environment.TickCount & Int64.MaxValue;

    Néhány sorral lejjebb pedig megnéztem, hogy tényleg egyezik-e:


    if (localStopTime == localStartTime) { int c = 0; }

    Ebbe a részbe folyton belelép. Értem én, hogy egy buffer kiírása nem tart sokáig, de nehogy már tényleg 1-et se tickeljen a számláló, olyan gyors legyen.

    A globális időmérőnél van jelentős eltérés. De azt ugye az írás megkezdése előttről mérem, addig amíg nem számítok sebességet.

    globalStartTime = 12872500
    globalStopTime = 12872515
    Mutasd a teljes hozzászólást!
Tetszett amit olvastál? Szeretnél a jövőben is értesülni a hasonló érdekességekről?
abcd