An Interesting Timing Observation (with code)

Started by
1 comment, last by leiavoia 19 years, 6 months ago
This isn't new info, but it's interesting. I've been doing some research on timers, besides getting a cross-platform timing class made (easy), i also added some statistical tracking to it. Here is a live OpenGL graph that shows the per-frame time deltas in pink (the last 500), and a distribution graph in yellow. Interesting how they spike. My test platform is using gettimeofday() on linux. I'm posting my code if you want to give it a spin. It requires OpenGL, but i'm sure you can easily adapt it to whatever you want. I haven't tested it at all in a Windows evironment. Feedback would be good, otherwise, you just scored yourself a free (but incomplete) Timer class. I make no guarantees about it's quality. This class is meant to be used with other code so it won't open it's own window or anything. All it does is time and draw. Header:

#ifndef GAMETIMER_H
#define GAMETIMER_H

#include <stdlib.h>
#include <vector>
#include <deque>


#ifdef _WIN32

// Windows
#include <windows.h>

#else

// UNIX
#include <sys/time.h>

#endif




/** 
USAGE:
Creating a timer will automatically begin the timing process. 
Generally, you simply will want to call the GetTimeDelta() function
every trip through the game loop. This will update the timer's 
internal data in addition to returning the delta. A delta is then
defined as the time between calls to GetTimeDelta(). To visualize,
call DrawGraph() every frame;
*/

class Timer {

public:
	
	Timer();
	
	~Timer() {};
	
	/** Returns the time difference between now and the last time you punch out */
	float GetTimeDelta();
	
	/** */
	void LogFPSToScreen( bool x = true ) { log_to_screen = x; }
	
	/** */
	void LogFPSToFile( bool x = true ) { log_to_file = x; }
	
	/** */
	void SetFramerateSamples( int x ) { framerate_samples = x; }
		
	/** */
	void DrawGraph();
	
private:

	void Log();
	void Track( float delta );
	
	bool log_to_file;
	bool log_to_screen;
	
	int framerate_samples;
	int FrameCount;
	float FrameRate;

	std::deque<float> most_recent;
	float recent_max;
	std::vector<long int> distrib;
	bool distrib_inited;
	long int distrib_max;
	float distrib_unit;
	
#ifdef _WIN32
	LARGE_INTEGER _tstart, _tend;
	LARGE_INTEGER freq;
#else
	struct timeval _tstart, _tend;
	struct timezone tz;
#endif


	};
	

#endif




Body:

#include "Timer.h"
#include <iostream>
#include <float.h>
#include <GL/gl.h>




static const unsigned int GRAPH_SIZE = 500;




Timer::Timer() {
	log_to_file = false;
	log_to_screen = true;
	framerate_samples = 100;
	FrameCount = 0;
	FrameRate = 0;	
	
	// init the record keepers:
	most_recent = std::deque<float>();
	distrib = std::vector<long int>(GRAPH_SIZE,0);
	distrib_inited = false;
	distrib_max = 1;
	recent_max = 0;
	
	// Get the clock started:
	
#ifdef _WIN32

	QueryPerformanceFrequency(&freq);
	QueryPerformanceCounter(&_tstart);

#else

	gettimeofday(&_tstart, &tz);
	
#endif
	}
	
	
	
	
float Timer::GetTimeDelta() { 

	static double fps_delta = 0;
	static double fps_last = 0;
	
#ifdef _WIN32
	QueryPerformanceCounter(&_tend);

	float delta = ((double)_tend.QuadPart - (doublet)_tstart.QuadPart) / (double)freq.QuadPart;

	// calculate FPS
	if (++FrameCount >= framerate_samples) {
		fps_delta = t2 - fps_last ;
		FrameRate = double(framerate_samples) / fps_delta ;
		FrameCount = 0;
		if (fps_last != 0) { Log(); }
		fps_last = t2;
   		}
		
	_tstart = _tend;
	
	Track( float(delta) );
	
	return delta; 
	
#else

	gettimeofday(&_tend,&tz);
	
	double t1, t2;
	t1 =  (double)_tstart.tv_sec + (double)_tstart.tv_usec / 1000000;
	t2 =  (double)_tend.tv_sec + (double)_tend.tv_usec / 1000000;
	float delta = float(t2-t1);

	// calculate FPS
	if (++FrameCount >= framerate_samples) {
		fps_delta = t2 - fps_last ;
		FrameRate = double(framerate_samples) / fps_delta ;
		FrameCount = 0;
		if (fps_last != 0) { Log(); }
		fps_last = t2;
   		}
		
	_tstart = _tend;
	
	Track( float(delta) );
	
	return delta; 
	
	
#endif

	}
	
	

void Timer::Log() {
	 if (log_to_screen) {
      	std::cout << "FPS: " << FrameRate << std::endl;
		}
	 if (log_to_file) {
      	// TODO
		}
	 }

	 
	 
void Timer::Track( float delta ) {

	static bool first_trip = true;
	static unsigned int hits = 0;
	
	if (first_trip) { first_trip = false; return; }
	
	// log most recent
	if ( most_recent.size() == GRAPH_SIZE) { most_recent.pop_front(); }
	most_recent.push_back( delta );
	if (recent_max == 0) { recent_max = delta; }
	else if ( delta > recent_max ) { 
		if ( delta < recent_max * 4 ) { recent_max = delta;  }
		}
	
	// set up the distribution graph if needed
	if ( !distrib_inited  &&  ++hits >= GRAPH_SIZE ) {
	
		// first find the high and low points + average
		float high = 0, low = FLT_MAX;
		for ( std::deque<float>::iterator i = most_recent.begin(); i != most_recent.end(); i++ ) {
			if ( *i < low  ) { low = *i;  }
			if ( *i > high ) { high = *i; }
			}			
		
		// setup distrib vector
		//distrib_unit = (high - low) / float(GRAPH_SIZE);
		distrib_unit = (  (1.0f/10.0f) - (1.0f/250.0f)  ) / float(GRAPH_SIZE);
		distrib_inited = true;
			
		} 	

	else if (distrib_inited) {
		// log the distribution
		unsigned long int index = int( delta / distrib_unit );
		if (index >= GRAPH_SIZE) { index = GRAPH_SIZE - 1; }
		else if (index < 0) { index = 0; }
		distrib[index]++;
		if ( distrib[index] > distrib_max ) { distrib_max = distrib[index]; }			
		}
	}
	 
	 
void Timer::DrawGraph() {

	glBindTexture( GL_TEXTURE_2D, 0 );

	// draw base quad:
	glColor4f(0,0,0,0.75);
	glBegin(GL_QUADS);		
		glVertex2f( 0, 100 );
		glVertex2f( GRAPH_SIZE, 100 );
		glVertex2f( GRAPH_SIZE, 0 );
		glVertex2f( 0, 0 );
	glEnd();
	
	// draw most recent graph
	glBegin(GL_LINE_STRIP);	
		glColor4f(0.8, 0.6, 0.6, 0.5);
		glVertex2f( GRAPH_SIZE, 50 ); // top of bar
		glVertex2f( 0, 50 ); // top of bar
		glColor4f(1.0, 0.8, 0.8, 1.0);
		for ( unsigned int i = 0; i < most_recent.size(); i++ ) {
			glVertex2f( i, (1.0 - (most_recent / recent_max)) * 50); // top of bar
			}	
	glEnd();
	
	// draw distrib graph
	if (distrib_inited) {
		glBegin(GL_LINES);
			glColor4f(1.0, 1.0, 0.5, 1.0);
			for ( unsigned int i = 0; i < distrib.size(); i++ ) {
				glVertex2f( i, 100 ); // bottom of bar
				glVertex2f( i, (1.0 - ((float)distrib / (float)distrib_max)) * 50 + 50); // top of bar
				}				
		glEnd();
		}
	
	
	}
    




Advertisement
gettimeofday() - ah, memories of my undergrad thesis. (linked to the resulting source code, which is not particularly good quality; note custom license, and the need to fetch some other stuff on your own, because of someone else's custom license. EDIT: My webspace is teh sux, so never mind.) Don't you just love how interval timers, clock() and other such things described as being specifically intended for benchmarking, don't give you microsecond timing, but gettimeofday(), which is intended to let humans know what time it is, does?
Here is another tip for anyone reading this: I just implemented "delta smoothing" which averages a time delta with the last 15 recorded deltas. Although it has very little actual gameplay impact, it does reduce visual "stammering" for moving objects. Cool trick.

This topic is closed to new replies.

Advertisement