Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to code for proper CPU utilization?

Bear with me, this might be a little difficult to explain clearly. I am trying to understand how to code a program which uses only the amount of CPU it needs. It's a little confusing to explain so I will just use a real example.

I made a Tetris game with an infinite main game loop. I have restricted it to 40 fps. But the loop still executes thousands or even millions of times per second. It just renders when enough time has passed by to restrict it to 40 fps.

Since I have a 4 core CPU, when I run the game, everything is fine and the game runs good. But the CPU usage is held at 25% for the game process. This is expected since it is an infinite loop and keeps running continuously.

I then read online to add a delay for 1 ms to the main loop. This immediately reduced the usage to around 1% or less. This is good, but now I am deliberately waiting 1 ms every loop. It works because my main loop takes a lot less time to execute and the 1ms delay does not affect the game.

But what if I make larger games. Games with longer and more processor intensive loops. What if I need that 1ms time slice for running the game smoothly. Then, if I remove the delay, the processor will jump to 25% again. If I add the delay, the game will be slow and maybe have some lag.

What is the ideal solution in this case ? How are real games/applications coded to prevent this problem ?

like image 615
Talha Sayed Avatar asked Feb 12 '23 17:02

Talha Sayed


2 Answers

Since you list three different languages in the tags, I'll keep this general and not provide code samples.

In general, to avoid burning CPU, never have a loop that does not on every iteration either:

  • do useful work
  • skip based on the loop counter
  • or invoke some blocking call, either to blocking I/O or a thread wait().

sleep() is one example of a blocking call, but as you've observed, it's a bit of a bodge in many cases.

So:

while(true) {
    if(some_condition) {
        foo();
    }
}

... is bad. (A friend of mine once brought a shared mainframe to its knees with code like this)

You need to find a call to your display API which blocks until a vertical sync. I believe in DirectX, device.Present() is one such call, if the device is set up appropriately.

In a single-threaded game, the logic might go:

 while(game is active)
    read user input
    calculate next frame
    blocking call to display API

So, the CPU gets a rest, waiting for the vertical sync each time.

It's more conventional to have at least two threads, one handling the rendering loop, another handling game state. In that case the rendering loop needs to wait for vertical syncs, as before. The game state loop needs to block until the rendering loop is ready.

Rendering thread loop:

  while(game is active)
      notify()
      prepare_frame(game_state)
      blocking call to display API

Game state thread loop:

  while(game is active)
      read user input
      update game_state
      wait(display_loop_thread)

Be sure to understand thread wait/notify/join in order to make sense of this.

This model allows you to have other threads that also affect the game state. For example another thread might control an AI enemy.

An alternative to this is to make the calculations event-driven, and trigger them after a vsync:

 while(game is active)
     calculate next frame
     blocking call to display API
     gameLogic.onFrame()

If onFrame() takes longer than a frame to complete, then the game's framerate will suffer. Whether this matters or not depends on the game; the solutions are beyond the scope of this answer -- if it matters to you it's probably time to buy a book on video game architecture.

like image 79
slim Avatar answered Feb 15 '23 07:02

slim


Instead of sleeping for 1 ms, you could sleep for X ms where X is is calculated with the formula max(NextDrawingTime-CurrentTime, 0)

like image 31
Paolo Brandoli Avatar answered Feb 15 '23 07:02

Paolo Brandoli