There are many unhelpful ideas online about how to do timing in Games. Here we show you a very basic, compilable, example of just a timer that also calculates the Frames Per Second and the MegaCycles.
This timer has a sufficient resolution to make optimization within the normal Game Engine frame of 16ms possible. See HMH10 for a more in depth explanation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | #include <windows.h> #include <x86intrin.h> #include <stdint.h> #include <stdio.h> bool Running; int main(int argc, char* argv[]) { Running = true; int Cap = 200; int i = 0; LARGE_INTEGER PerfCountFrequencyResult; QueryPerformanceFrequency(&PerfCountFrequencyResult); int64_t PerfCountFrequency = PerfCountFrequencyResult.QuadPart; LARGE_INTEGER LastCounter; QueryPerformanceCounter(&LastCounter); uint64_t LastCycleCount = __rdtsc(); uint64_t EndCycleCount; LARGE_INTEGER EndCounter; uint64_t CyclesElapsed; int64_t CounterElapsed; double MSPerFrame; double FPS; double MCPF; while (Running) { // .. do stuff for your game EndCycleCount = __rdtsc(); QueryPerformanceCounter(&EndCounter); CyclesElapsed = EndCycleCount - LastCycleCount; CounterElapsed = EndCounter.QuadPart - LastCounter.QuadPart; MSPerFrame = (((1000.0f*(double)CounterElapsed) / (double)PerfCountFrequency)); FPS = (double)PerfCountFrequency / (double)CounterElapsed; MCPF = ((double)CyclesElapsed / (1000.0f * 1000.0f)); LastCounter = EndCounter; LastCycleCount = EndCycleCount; printf("%.02fms/f, %.02ff/s, %.02fmc/f\n", MSPerFrame, FPS, MCPF); // To keep us from running forever ++i; if ( i >= Cap ) { Running = false; } } return(0); } |
Build with: clang++ timers.cpp -o timers.exe
Note that #include <x86intrin.h>
is for the call to __rdtsc()
which is a compiler intrinsic.
Frames per Second is not a particularly useful number because it is always going to be the inverse of the Milliseconds per Frame anyway. One second has 1,000 milliseconds, if the rendering of each frame took 1 millisecond, then you would have an FPS of 1,000.
When we talk about optimizations and performance, the change in Frames Per Second will not communicate how successful we were in our optimizations.
If for instance the rendering of a single frame took 4 milliseconds, you would have a framerate of 250 Frames per Second. If you optimized the rendering down to 2 milliseconds, you would gain an extra 250 Frames per Second, i.e. you would now be rendering 500 Frames per Second.
You might say: "Wow, I have 250 more frames." But really all you've done is save 2 milliseconds. At the higher end, the step from 33 milliseconds to 31 milliseconds, the same 2 millisecond savings, you would gain only 2 Frames per Second.
The thing that you want to, in fact you must, optimize and be wary of is the number of milliseconds you spend doing some game task, like rendering the frame. Focusing on Frames per Second, requires an extra level of reasoning to get to the core information that has the most impact, the milliseconds, or even the cycles being used for the task. It's' not that Frames per Second is useless per se, only that it is superfluous, outside of product advertising. For a more detailed discussion see HMH11.
Milliseconds per Frame displayed to the user is an important metric because it allows you to target specific platform classes, if you can manage to render at 11 milliseconds, you can do VR. If you can do it at or under 16 milliseconds (while producing 1080p frames), you can generally do 60 hertz. If you can do it at 33 milliseconds and under, you still have smooth animation (30 Frames per Second). These are three key targets for game creation and optimization that you have to keep in mind.
These industry benchmarks are also featured in Data Oriented Design, Mike Acton. Acton makes a point of illustrating that engine developers tend to go even finer grained than milliseconds, often targeting objectives measured in Microseconds.
In this talk much emphasis is also placed on the Cycle counts for assembly instructions, and also the fallibility of the compiler to optimize even trivial functions. As a general rule of thumb, the compiler's optimizations can only help with 10% of performance issues, the other 90% have to be discovered through benchmarking, and optimized by hand, with special attention being paid to the actual assembly output of the compiler.
Many of these problems can be handled by "back of the envelope" calculations using instruction tables for the specific platform you are optimizing for.