Funktion die Millisekunden genau ist



  • Hallo,

    Ich suche ein Funktion die die Millisekunden seit Programmstart oder seit 1970 ausgibt (als float oder double). Ich meine diese Frage schon mal im Forum gesehen zu haben, allerdings habe ich sie nicht wiedergefunden.

    Ich brauche die Funktion um die Framerate zu messen und damit die Geschwindigkeit eines Objektes zu berechnen.

    .gez flammenvogel





  • Ist zu ungenau. Nimm QueryPerformanceCounter(), siehe dazu MSDN.



  • @Optimizer
    Die genauigkeit ist bei einem nicht Echtzeit OS eh illusion



  • Ähm, das ändert trotzdem nichts daran, dass GetTickCount() zu ungenau (bestenfalls auf ms) ist und QueryPerformanceCounter wesentlich genauer (auf meinem Prozessor 3.579.545 Tics/Sekunde).
    Ich weiss nicht, worauf du hinaus willst. Nur weil ich die Ereignisse nicht auf Tic genau steuern kann, ist das kein Grund etwas zu nehmen, was schon mal aus Prinzip um den Faktor 3500 ungenauer ist.



  • Optimizer schrieb:

    Ist zu ungenau. Nimm QueryPerformanceCounter(), siehe dazu MSDN.

    Wenn schon fein aufgeloest, dann bitte RDTSC.
    Perfekte Aufloesung in Processor Ticks, was will man mehr - genauer gehts nimmer.



  • Wie kann ich mit den LARGE_INTEGER Zahlen des PerformanceCounters rechnen?

    @kingruedi: Soweit ich weiß ist der PerformanceCounter ein extra Hardware Baustein und nicht Software abhängig. Die Genaugkeit ist desshalb wichtig, weil mein Spiel noch in der Anfangsphase steckt -> nicht viel Code hat -> die Ausführung der Spielschleife auf meinem Rechner weniger als eine Millisekunde dauert. Daher können die meisten Funktionen die ich kenne keine Differenz messen.



  • flammenvogel schrieb:

    Die Genaugkeit ist desshalb wichtig, weil mein Spiel noch in der Anfangsphase steckt -> nicht viel Code hat -> die Ausführung der Spielschleife auf meinem Rechner weniger als eine Millisekunde dauert. Daher können die meisten Funktionen die ich kenne keine Differenz messen.

    Dann nimm RDTSC. Genauer gehts nicht mehr.
    Hier mein ASM Code, irgendwo liegt das ganze auch noch als inline vc code rum:

    ;-; C Function declarations
    ;
    ; int CanCPUID(void);
    ; int CanRDTSC(void);
    ; void GetRDTSC( long &high, long &low );
    ;
    
    BITS 32
    
    GLOBAL CanCPUID
    GLOBAL CanRDTSC
    GLOBAL GetRDTSC
    
    SECTION .text
    
    CanCPUID
    	.386
    	push ebp			; Store stack ptr
    	mov ebp, esp		; into base ptr
    
    	push ebx			; Store ebx - because I hate SEGV
    
    	pushfd				; EEFlags
    	pop eax				; Copy to eax
    	mov ebx, eax		; Copy to ebx
    	xor eax, 00200000h	; Change bit 21
    
    	push eax			; Write changes
    	popfd				; Restore EEFlags
    	pushfd				; store new flags
    	pop eax				; Check if bit 21 changed
    	xor eax, ebx
    	jnz done			; Is ok - jump 2 done
    	mov eax, 0			; Set return value = false
    
    	pop ebx				; Restore ebx
    	leave				; End
    	ret					; Return
      done:
    	mov eax, 1			; Set return value = true
    	pop ebx
    	leave				; End
    	ret					; CanCPUID
    
    CanRDTSC
    	push ebp			; Store stack ptr
    	mov ebp, esp		; Into base ptr
    	push ebx			; Store registers
    	push ecx			;
    	push edx			;
    
    	call CanCPUID		; Check if CPUID is aviable
    	cmp eax, 1			; Check return value
    	je ok				; CPUID works
    	mov eax, 0			; Set return value = false
    
    	pop edx				; Restore registers
    	pop ecx				;
    	pop ebx				;
    	leave				; Cleanup
    	ret					; Return
    
      ok:
    	mov eax, 01h		; Processor Info
    	CPUID				; Get Info
    
    	; Is RDTSC aviable?
    	shr edx, 4			;
    	and edx, 1			; check stepping
    
    	mov eax, edx		; Store return
    
    	pop edx				; Restore registers
    	pop ecx				;
    	pop ebx				;
    
    	leave				; Cleanup
    	ret					; Return CanRDTSC
    
    GetRDTSC
    	push ebp			; Store stack ptr
    	mov ebp, esp		; Into base ptr
    
    	push ebx			; Store registers
    	push ecx			;
    	push edx
    
    	mov ebx, [ebp + 8]	; Store addresses
    	mov ecx, [ebp + 12]	;
    
    	RDTSC
    
    	mov DWORD [ebx], edx	; Store
    	mov DWORD [ecx], eax	;
    
    	pop edx				; Restore registers
    	pop ecx				;
    	pop ebx				;
    
    	leave				;
    	ret					; Return
    

    P.S. erst per canCPUID checken, ob der Befehl am Prozessor vorhanden ist, dann per canRDTSC checken, ob RDTSC vorhanden, danach die Ticks einfach per getRDTSC auslesen.

    Return Value canCPUID / canRDTSC: 1=true, 0 = false 😉



  • Ich glaube ich würde erst mal gerne die Funktion mit dem PerformanceCounter ausprobieren. Weiß jemmand von euch wie man mit LARGE_INTERGER Werten rechnen kann. Oder wie würdet ihr mein Problem lösen?



  • flammenvogel schrieb:

    Ich glaube ich würde erst mal gerne die Funktion mit dem PerformanceCounter ausprobieren. Weiß jemmand von euch wie man mit LARGE_INTERGER Werten rechnen kann. Oder wie würdet ihr mein Problem lösen?

    #ifdef WIN32
    #	include <windows.h>
    #else
    	//! Structure to hold our timing stuff for all non win32 systems
    	typedef union _LARGE_INTEGER {
    	struct {
    		//! Lower 32 bit
    		unsigned long   LowPart  ;
    
    		//! Higher 32 bit
    		signed   long   HighPart ;
    	};
    	//! long long wich holds the 64 bit integer
    	unsigned long long QuadPart;
    	} LARGE_INTEGER;
    #endif
    

    Jetzt rate mal, wie man damit rechnet?

    Ein Tip: LARGE_INTEGER::QuadPart



  • Könntest du das mal für Leihen formulieren. Am besten du sagst mir mal wie ich zwei Large Integer Werte von einander abziehe ( Code ).



  • LARGE_INTEGER a, b, c;
    
    c.QuadPart = a.QuadPart + b.QuadPart;
    


  • Noch viel einfacher. __int64 verwenden, Adresse nehmen, Pointer nach LARGE_INTEGER casten und das der Funktion übergeben.



  • SnorreDev schrieb:

    flammenvogel schrieb:

    Die Genaugkeit ist desshalb wichtig, weil mein Spiel noch in der Anfangsphase steckt -> nicht viel Code hat -> die Ausführung der Spielschleife auf meinem Rechner weniger als eine Millisekunde dauert. Daher können die meisten Funktionen die ich kenne keine Differenz messen.

    Dann nimm RDTSC. Genauer gehts nicht mehr.
    Hier mein ASM Code, irgendwo liegt das ganze auch noch als inline vc code rum:

    ;-; C Function declarations
    ;
    ; int CanCPUID(void);
    ; int CanRDTSC(void);
    ; void GetRDTSC( long &high, long &low );
    ;
    
    BITS 32
    
    GLOBAL CanCPUID
    GLOBAL CanRDTSC
    GLOBAL GetRDTSC
    
    SECTION .text
    
    CanCPUID
    	.386
    	push ebp			; Store stack ptr
    	mov ebp, esp		; into base ptr
    
    	push ebx			; Store ebx - because I hate SEGV
    
    	pushfd				; EEFlags
    	pop eax				; Copy to eax
    	mov ebx, eax		; Copy to ebx
    	xor eax, 00200000h	; Change bit 21
    
    	push eax			; Write changes
    	popfd				; Restore EEFlags
    	pushfd				; store new flags
    	pop eax				; Check if bit 21 changed
    	xor eax, ebx
    	jnz done			; Is ok - jump 2 done
    	mov eax, 0			; Set return value = false
    
    	pop ebx				; Restore ebx
    	leave				; End
    	ret					; Return
      done:
    	mov eax, 1			; Set return value = true
    	pop ebx
    	leave				; End
    	ret					; CanCPUID
    
    CanRDTSC
    	push ebp			; Store stack ptr
    	mov ebp, esp		; Into base ptr
    	push ebx			; Store registers
    	push ecx			;
    	push edx			;
    
    	call CanCPUID		; Check if CPUID is aviable
    	cmp eax, 1			; Check return value
    	je ok				; CPUID works
    	mov eax, 0			; Set return value = false
    
    	pop edx				; Restore registers
    	pop ecx				;
    	pop ebx				;
    	leave				; Cleanup
    	ret					; Return
    
      ok:
    	mov eax, 01h		; Processor Info
    	CPUID				; Get Info
    
    	; Is RDTSC aviable?
    	shr edx, 4			;
    	and edx, 1			; check stepping
    
    	mov eax, edx		; Store return
    
    	pop edx				; Restore registers
    	pop ecx				;
    	pop ebx				;
    
    	leave				; Cleanup
    	ret					; Return CanRDTSC
    
    GetRDTSC
    	push ebp			; Store stack ptr
    	mov ebp, esp		; Into base ptr
    
    	push ebx			; Store registers
    	push ecx			;
    	push edx
    
    	mov ebx, [ebp + 8]	; Store addresses
    	mov ecx, [ebp + 12]	;
    
    	RDTSC
    
    	mov DWORD [ebx], edx	; Store
    	mov DWORD [ecx], eax	;
    
    	pop edx				; Restore registers
    	pop ecx				;
    	pop ebx				;
    
    	leave				;
    	ret					; Return
    

    P.S. erst per canCPUID checken, ob der Befehl am Prozessor vorhanden ist, dann per canRDTSC checken, ob RDTSC vorhanden, danach die Ticks einfach per getRDTSC auslesen.

    Return Value canCPUID / canRDTSC: 1=true, 0 = false 😉

    Hab versucht das in VC (7.1) zu verwenden (keine Ahnung was man alles Ändern muss, hab einfach alles so gelassen) und diese 2 Fehler spuckt er aus:

    main.cpp(105) : error C2400: Inline-Assembler: Syntaxfehler in 'Erster Operand'; '[' gefunden
    main.cpp(106) : error C2400: Inline-Assembler: Syntaxfehler in 'Erster Operand'; '[' gefunden

    Damit sind folgende Zeilen gemeint:

    int CanCPUID(void) {
    	_asm {
    		push ebp           
    		mov ebp, esp      
    
    		push ebx            
    
    		pushfd                 
    		pop eax               
    		mov ebx, eax       
    		xor eax, 00200000h  
    
    		push eax            
    		popfd               
    		pushfd              
    		pop eax             
    		xor eax, ebx
    		jnz done            
    		mov eax, 0          
    
    		pop ebx             
    		leave                
    		ret                  
    	done:
    		mov eax, 1           
    		pop ebx
    		leave                
    		ret    
    	}
    }
    
    int CanRDTSC(void) {
    	_asm {
    		push ebp       
    		mov ebp, esp   
    		push ebx       
    		push ecx       
    		push edx       
    
    		call CanCPUID  
    		cmp eax, 1     
    		je ok          
    		mov eax, 0     
    
    		pop edx        
    		pop ecx        
    		pop ebx        
    		leave          
    		ret            
    
    	ok:
    		mov eax, 01h   
    		CPUID          
    		shr edx, 4     
    		and edx, 1     
    
    		mov eax, edx   
    
    		pop edx                
    		pop ecx                
    		pop ebx                
    
    		leave                
    		ret
    	}
    }
    
    void GetRDTSC( long &high, long &low ) {
    	_asm { 
    		push ebp           
    		mov ebp, esp       
    
    		push ebx           
    		push ecx           
    		push edx
    
    		mov ebx, [ebp + 8] 
    		mov ecx, [ebp + 12]
    
    		RDTSC
    
    		mov DWORD [ebx], edx  // die hier!
    		mov DWORD [ecx], eax  //
    
    		pop edx             
    		pop ecx             
    		pop ebx             
    
    		leave               
    		ret                 
    	}
    }
    

    Was muss ich noch ändern?
    Danke



  • Zählt dein Zähler die Anzahl der Takte? Wie funktioniert des bei meinem AMD64 3400+ der mit 2,2 GHz arbeitet? Wie rechnest du das in eine Zeiteinheit um?



  • spl@t schrieb:

    mov DWORD [ebx], edx  // die hier!
            mov DWORD [ecx], eax  //
    

    Was muss ich noch ändern?

    Entweder du lässt das DWORD weg, denn aufgrund von edx bzw eax kann der Compiler implizit die Datenbreite herleiten. Oder du schreibst:

    mov dword ptr [ebx], edx  // die hier!
            mov dword ptr [ecx], eax  //
    

    Für Zeitmessungen würde ich dir aber rdtsc nicht empfehlen. Meines Wissens gibt es zwar Möglichkeiten die Frequenz mit der rdtsc arbeitet zu ermitteln und damit Zeitwerte zu errechnen, wie portabel das aber von CPU Typ zu CPU Typ ist, kann ich dir nicht sagen.
    Nimm einfach die Performance Counter, dann brauchst du dich nicht mit Assemblercode rumzuschlagen und hast zumindest die Gewissheit, dass das auf aktuellen Windows Systemen funktioniert. Pack das ganze in 'ne schöne Klasse, dann wird die Benutzung einfacher und du bleibst portabel für andere Systeme.



  • flammenvogel schrieb:

    @kingruedi: Soweit ich weiß ist der PerformanceCounter ein extra Hardware Baustein und nicht Software abhängig. Die Genaugkeit ist desshalb wichtig, weil mein Spiel noch in der Anfangsphase steckt -> nicht viel Code hat -> die Ausführung der Spielschleife auf meinem Rechner weniger als eine Millisekunde dauert. Daher können die meisten Funktionen die ich kenne keine Differenz messen.

    Das Problem ist der Scheduler, der ja zwischen durch auch anderen Prozessen Zeit gewährt.



  • Das ist allerdings nicht so ausschlaggebend, wenn die restlichen Threads ihren Timeslice abgeben. Trotzdem wirst du dieses Problem bei Multitasking BSen immer haben, selbst mit Hardware Multithreading. Da helfen dann nur Critical Sections, nur sind diese nicht für zeitaufwändige Sachen gedacht.



  • Thx, compilieren tut ers jetzt, aber funktionieren tuts nicht. Ich lasse dann wohl wiklich lieber die Finger davon 😉



  • @spl@t:
    Klar, dass es bei dir Inline nicht so funktionieren kann. Es ist auch eine Externe Loesung gewesen, die sich um Ruecksprungadressen & Co kuemmert. Compiliert wurde sie mit NASM unter Linux & Windows.

    Hier VC Inline:

    Check ob RDTSC:

    bool canCPUID = true;
    			int canRDTSC = true;
    
    			__asm {
    				pushfd					; Pushing EFlags
    				pop eax					; Popping EFlags to ebx
    				mov ebx, eax			; Copy eax to ebx
    				xor eax, 00200000h		; Change bit 21
    				push eax				; Push eax
    				popfd					; Get EFlags
    				pushfd					; Store EFlags
    				pop eax					; Get eax
    				xor eax, ebx			; Check if something changed
    				jnz	done				; If it exists - it´s done :)
    				mov canCPUID, 0			; Delete flag
    			done:
    			}
    
    			if( canCPUID ) {
    				__asm {
    					mov eax, 01h		; Processor Info
    					CPUID				; Get Info´s
    
    					//
    					// Processor info
    					//
    
    					; Can it rdtsc?
    					shr edx, 4
    					and edx, 1b			; check stepping
    					mov canRDTSC, edx	; copy flag
    				}
    			} else {
    				canRDTSC = false;
    			}
    

    Aktuelle Ticks auslesen:

    //! RDTSC
    			static inline void GetTicksRDTSC( LARGE_INTEGER &ticks ) {
    				static LARGE_INTEGER tick; tick.QuadPart = 0;
    #ifdef __GNUC__
    				GetRDTSC( tick.HighPart, tick.LowPart );
    #else
    				__asm {
    					rdtsc
    					mov tick.HighPart, edx
    					mov tick.LowPart, eax
    				} 
    #endif
    				ticks = tick;
    			}
    

    Viel Spass damit!

    P.S. die ifdef fuer GNUC nehmen die externe Variante, also mit NASM Compiled.

    Deklarieren tust du den Externen stuff so:

    extern "C" {
    	//! Returns if the CPU suppurts the CPUID mnemonic - External in ASM File
    	extern int volatile CanCPUID(void);
    
    	//! Returns if the CPU suppurts the RDTSC ( Realtime CPU Tick counteer ) mnemonic - External in ASM File
    	extern int volatile CanRDTSC(void);
    
    	//! Returns if the current CPUtick - External in ASM File
    	extern void volatile GetRDTSC( long &high, unsigned long &low );
    }
    

    Brauchst die .o vom ASM nur beim Linken mit angeben, damit er sie verwurstet!

    @Optimizer:
    Auf einem AMD 64 musst du meines Wissens nur darauf achten, dass du eben 64 Bit fuer die Ticks hernimmst.

    Wie du rausbekommst, wieviele Ticks / Sek deine CPU macht?
    Suche dir eine moeglichkeit aus:
    - Lese es aus der CPU aus
    - Lese es unter Win aus der Registry, unter Linux gibts die Info's in einem File
    - Warte etwas, und rechne die Zeit hoch. Je laenger du wartest, desto genauer ist die Zeit. Allerdings bei 0,25 Sek hast du vielleicht 5% Toleranz beim warten, also nicht so krass.


Log in to reply