Zeit in millisec; _plattformübergreifend_



  • @Neku
    Ok, werde das entsprechend ändern. Danke!

    @Lolz
    Hab ich evt. schon so gemacht?!?

    if(QueryPerformanceFrequency(reinterpret_cast<_LARGE_INTEGER*>(&freq))){ // Versuchen, den hochauflösenden Timer zu benuetzen
            QueryPerformanceCounter(reinterpret_cast<_LARGE_INTEGER*>(&last));
            resolution = 1.0 / freq;
            QPCounter = true;
        }
    

    Ansonsten weitere Anregungen erwünscht.



  • Wieso benutzt du _LARGE_INTEGER, und nicht LARGE_INTEGER?
    Und warum deklarierst du deine Member als long long, und nicht LARGE_INTEGER?

    Ich würde zudem die Fallunterscheidung bei QPCounter vereinfachen. Problem dabei ist, dass du zwei unterschiedliche Aufrufe hast.

    QueryPerformanceCounter(reinterpret_cast<_LARGE_INTEGER*>(&cur));
    // und
    cur = timeGetTime();
    

    Das passt natürlich nicht zusammen. Da timeGetTime sowieso die "schlechtere" Wahl ist, und idR auch nie aufgerufen wird, da so gut wie jeder aktuelle Rechner über Hardware Counter verfügt, werden wir diese Funktion QueryPerformanceCounter anpassen.

    // private
    static BOOL timeGetTimeAlias(LARGE_INTEGER* ticks)
    {
        (*ticks).QuadPart = timeGetTime();
    }
    

    Als Member kommt nun ein entsprechender Funktionszeiger zum Einsatz

    // private
    BOOL (*GetTicks)(LARGE_INTEGER*);
    

    welcher im ctor entweder mit QueryPerformanceCounter oder timeGetTimeAlias initialisiert wird, abhängig davon, ob Performance Counter verfügbar sind oder nicht.
    QPCounter entfällt damit, und Timer::update vereinfacht sich zu

    GetTicks(reinterpret_cast<LARGE_INTEGER*>(&cur));
        elapsed = (cur - last) * resolution; //Zeitdifferenz berechnen und in Sekunden umwandeln
        last = cur;
    

    Zudem ist es einen Wimpernschlag schneller (bei vorhandenen Performance Countern). 🙂



  • System.currentTimeMillis();
    


  • *freu* Ich dachte schon, da kommt nichts mehr. 🙂

    groovemaster schrieb:

    Wieso benutzt du _LARGE_INTEGER, und nicht LARGE_INTEGER?

    Ähm ... hm ... gute Frage...

    groovemaster schrieb:

    Und warum deklarierst du deine Member als long long, und nicht LARGE_INTEGER?

    Um ehrlich zu sein: weil's Arbeit spart.

    groovemaster schrieb:

    Zudem ist es einen Wimpernschlag schneller (bei vorhandenen Performance Countern). 🙂

    Schon wenns nur einen Tick schneller ist, freu ich mich. 😉

    Aber leider taucht in der neuen Version ein Fehler auf:

    class Timer : public TSingleton<Timer>
    {
    	private:
    			long long freq; // Frequenz des Timers
    			long long cur, last;
    			double resolution, elapsed; // max. Auflösung des Timers und verstrichene Zeit in Sekunden
                BOOL (*GetTicks)(LARGE_INTEGER*);
    
    			static BOOL timeGetTimeAlias(LARGE_INTEGER* ticks);
    
    	public:
    			Timer::Timer();
    			void init();
    			void update();
    			double get_resolution(){return resolution;}
    			inline double get_elapsed(){return elapsed;}
    };
    
    void Timer::init()
    {
    	if(QueryPerformanceFrequency(reinterpret_cast<LARGE_INTEGER*>(&freq))){
    		GetTicks = QueryPerformanceCounter;  //hier wird der Fehler angezeigt
    		resolution = 1.0 / freq;
    	}
    
    	else{
    		GetTicks = timeGetTimeAlias;
    		resolution = 0.001;
    	}
    	GetTicks(reinterpret_cast<LARGE_INTEGER*>(&last));
    }
    
    void Timer::update()
    {
    	GetTicks(reinterpret_cast<LARGE_INTEGER*>(&cur));
    	elapsed = (cur - last) * resolution; //Zeitdifferenz berechnen und in Sekunden umwandeln
    	last = cur;
    }
    
    BOOL Timer::timeGetTimeAlias(LARGE_INTEGER* ticks)
    {
    	(*ticks).QuadPart = timeGetTime();
    }
    

    So sieht er aus:

    error: invalid conversion from 'BOOL (*)(_LARGE_INTEGER*)' to 'BOOL (*)(LARGE_INTEGER*)'
    

    Witzigerweise zeigt er mir folgendes an, wenn ich alles in _LARGE_INTEGER ändere:

    error: invalid conversion from 'BOOL (*)(_LARGE_INTEGER*)' to 'BOOL (*)(_LARGE_INTEGER*)'
    

    Logisch, gell?

    @In Java: WAS? Ihr habt nur ne maximale Aulösung von einer Millisekunde? Gut, dass ich C++ gelernt hab... 😃

    PS: Ich brauch dringend noch was für den Mac! Google sagt nicht viel dazu.



  • Winapi functionen verwenden __stcall und nicht __cdecl als Aufrufkonvention.



  • @In Java: WAS? Ihr habt nur ne maximale Aulösung von einer Millisekunde? Gut, dass ich C++ gelernt hab...

    Nö, wie kommst Du darauf? Aber es wurde nach der Zeit in Millisekunden gefragt.

    Geht auch genauer:

    System.nanoTime()
    

    Aber jetzt erzählst Du mir wahrscheinlich gleich, dass eine Sprache, die Zeit nicht yokto-sekunden genau darstellen kann, für nix taugt. Keep on flaming... 🤡



  • helper schrieb:

    Winapi functionen verwenden __stcall und nicht __cdecl als Aufrufkonvention.

    d.h. ich schau in die Röhre? Auf die schnelle fällt mir nicht ein, was ich da machen soll. Den Zeiger dynamisch anlegen?



  • Wie wär's, wenn Du einfach das if drinläßt? Wie oft in der Sekunde wirste Deinen Timer benutzen? Wieviele ticks werden Dir durch das if draufgehen? Ist das wirklich Dein Performance-Bottleneck?



  • Jester schrieb:

    Wie wär's, wenn Du einfach das if drinläßt? Wie oft in der Sekunde wirste Deinen Timer benutzen?

    ca. 600 - 1000 mal. Oft genug, um zu optimieren, oder?



  • kynarion schrieb:

    Jester schrieb:

    Wie wär's, wenn Du einfach das if drinläßt? Wie oft in der Sekunde wirste Deinen Timer benutzen?

    ca. 600 - 1000 mal. Oft genug, um zu optimieren, oder?

    😃 netter Joke.

    Lohnt sich also noch kein Stück.
    Laß doch Dein Programm einfach mal laufen und benutze nen Profiler um rauszufinden, wieviel Prozent der Zeit Du im Timer verbringst.

    Es gibt da sone wichtige Regel: die 80-20 Regel (oder war's 90-10?) 20% des Codes sind für 80% der Laufzeit verantwortlich. Gilt fast immer. Also finde erst raus welche 20% es bei Dir sind. Wenn Du außer dem timen noch irgendwas anderes machst (z.B. ein Dreieck rendern), dann wird sich der Großteil außerhalb des Timers abspielen.

    edit: angenommen, Du sparst Dir 5 Takte pro Timerzugriff, das macht dann 5000 Takte pro Sekunde. Das sind, wenn wir mal mit 2,5Ghz rechnen also 5000/(2,5*10^9) s Zeitersparnis, also 2/10^6 s, macht also 2 millionstel Sekunden Zeitersparnis pro Sekunde. Das lohnt sich nicht. Woanders verbrätst Du sicher mehr Deiner Zeit.

    edit2: Du kannst das Problem übrigens lösen, indem Du das QueryPerformanceCounter nochmal mit ner eigenen Funktion wrappst und dann auf die zeigst. Ich bezweifle aber ehrlich gesagt, daß das überhaupt schneller ist, als mit dem if zu trennen. Einer Epic-Games hat mal irgendwo nen Vortrag gehalten und meinte, sie würden gerne 10% Performance opfern, wenn sie dafür 10% schneller beim Entwickeln wären. Und die halten sich bestimmt nicht mit solchen Kleinigkeiten auf. Sollte sich das alles doch später nachdem Du alles fertig hast und nachgemessen hast als Problem herausstellen kannst Du die Implementation ja auch leicht ändern. Deswegen ist es imho momentan sinnvoller, Du konzentrierst Dich auf Dein eigentliches Ziel und läßt DIch nicht von solchen Kleinigkeiten ablenken. 🙂



  • Profiler hab ich schon probiert, aber der bringt mir seltsamer Weiser bei jedem Testlauf (ohne Programmänderung natürlich) ein anderes Ergebnis. Vielleicht sollte ich das Programm entschieden länger laufen lassen.

    Aber kann man die Lösung mit Funktionszeiger nicht irgendwie übernehmen?

    [EDIT] Was fällt dir ein meine Fragen zu beantworten, bevor ich sie gestellt habe 😃 [/EDIT]



  • Doch, kannste übernehmen. Aber wie gesagt mußte den QueryPerformanceCounter nochmal mit ner eigenen Funktion wrappen und die in den Pointer stopfen.

    Ja, sonen Profiler sollte man schon länger laufen lassen, damit er nen guten Durchschnitt kriegt. Allerdings sollte Dein Programm dazu nicht mehr ganz am Anfang stehen. Profiling macht erst dann Sinn, wenn Du bereits einen Teil der rechenaufwendigen Sachen implementiert hast. Wenn nur Kleinigkeiten gemacht werden, dann gehen die alle schnell und der langsamste wird sozusagen ausgelost.



  • So, hab mir deine Edits durchgelesen. 🙂 Danke für Lösung & Aufmunterung an meinem Projekt weiter zu arbeiten. Aber das "Projekt" besteht im Moment daraus, ein möglichst perfektes Framework für Spiele zu erstellen (siehe meine letzten Posts). Wie kann ich jetzt nur herausfinden, ob die if - oder Funktionszeiger-Version die schnellere ist? Da bräucht ich ja nen Timer... 😃
    Naja, warscheinlich werde ich wieder zur if-Lösung zurückkehren. Die ist IMO besser lesbar als die mit Funktionzeiger.

    Zum Profiling (zum Glück hab ich auf Vorschau geklickt, sonst hät's wieder ein Edit gegeben): Es ist (leider) genügend rechenintensives vorhanden. Vielleicht doch nochmal beide Versionen nach einer Stunde Profiling betrachten?

    -----
    Aber: Ceterum censeo Macintosh-Timer esse implemendam!



  • kynarion schrieb:

    Zum Profiling (zum Glück hab ich auf Vorschau geklickt, sonst hät's wieder ein Edit gegeben): Es ist (leider) genügend rechenintensives vorhanden. Vielleicht doch nochmal beide Versionen nach einer Stunde Profiling betrachten?

    Wenn Du schlicht wissen willst, welche Variante schneller ist solltest Du das auf jeden Fall mal machen. Das ist der sicherste (einzige?) Weg es rauszufinden. Wenn es Dir aber nur drum geht, daß Dein Framework performant ist, dann laß es einfach gut sein. Es ist mit 99,9% Sicherheit völlig egal welche Variante Du verwendest.



  • kynarion schrieb:

    d.h. ich schau in die Röhre?

    Nein. Wie schon erwähnt wurde, benutzt Windows eine andere Aufrufkonvention als __cdecl. Das hatte ich vergessen. Der Funktionszeiger bzw. die Wrapper Funktion sollten wie folgt aussehen:

    static BOOL WINAPI timeGetTimeAlias(LARGE_INTEGER* ticks)
    //...
    BOOL (WINAPI *GetTicks)(LARGE_INTEGER*);
    

    So sollte es nun laufen. Ein Wrappen von QueryPerformanceCounter, so wie Jester schrieb, ist nicht notwendig.

    btw:
    Das ist vllt. nicht so ganz rübergekommen, aber der Vorteil dieser Vorgehensweise liegt nicht darin, dass sie schneller ist. Das ist nur ein netter Nebeneffekt, und wie Jester schon schrieb, in der Praxis eher zu vernachlässigen. In Spielen wird die aktuelle Zeit idR sowieso nur einmal pro Frame abgefragt, um damit die Logik zu steuern. Der Vorteil liegt eher in der Kompaktheit. Ich habe vor einiger Zeit eine Stoppuhr Klasse geschrieben, und dort wird die GetTicks Funktion 4 mal aufgerufen. Dh, ohne dieses ständige if/else Gewürge ist der Code viel einfacher zu handhaben und natürlich auch zu lesen.



  • So, jetz hab ich die Änderung der Änderung wieder rückgängig gemacht, wie du geschrieben hast nochmal geändert und siehe da, es läuft tatsächlich. Damit ist der Timer für Windows wohl endgültig fertig. Wehe dem, der nochmal was dran auszusetzten hat!
    Das Gegenteil aber geschieht dem, der ein Lösung für den Macintosh parat hat. 🙂


Anmelden zum Antworten