Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: How to lower CPU usage  (Read 4283 times)

0 Members and 1 Guest are viewing this topic.

dassie

  • Newbie
  • *
  • Posts: 13
    • View Profile
How to lower CPU usage
« on: September 08, 2017, 11:53:42 pm »
So I'm working on a game boy emulator and using SFML to handle the graphics.

My program uses 2 thread: one to run the emulator and another to handle the view port window.

The main thread goes through these steps:
1. Spawn a new thread that handles the viewport window (the window gets a 60fps limit)
2. Waits for user input at the console before starting execution of the game rom by the emulator core
3. Waits for the emulator to finish rendering a frame buffer
4. Sends the frame buffer to the window thread to be display (this step blocks on a mutex thereby meeting the 60fps limit)
5. Repeats from step 3

The window thread is best explained with the code. RunUntilQuitEvent is the entry point for its thread.
void VpWindow::ApplyFrameBuffer()
{
        int size = buffer_width * buffer_height;
        for (int i = 0; i < size; i++)
        {
                int index = i * 4;
                sf_frame_buffer[index + 0] = em_frame_buffer[i].Red * 8;
                sf_frame_buffer[index + 1] = em_frame_buffer[i].Green * 8;
                sf_frame_buffer[index + 2] = em_frame_buffer[i].Blue * 8;
                sf_frame_buffer[index + 3] = 0xFF;
        }

        window->clear();
        texture->update(sf_frame_buffer);
        window->draw(*rectangle, *transform);
        window->display();
}

void VpWindow::RunUntilQuitEvent()
{
        while (window->isOpen())
        {
                Event event;
                while (window->pollEvent(event))
                {
                        if (event.type == Event::Closed)
                        {
                                window->close();
                        }
                        else if (event.type == Event::KeyPressed && HandlesKeyPress)
                        {
                                KeyPressHandler(event);
                        }
                        else if (event.type == Event::KeyReleased && HandlesKeyRelease)
                        {
                                KeyReleaseHandler(event);
                        }
                }

                // Since a window can only be updated from the thread that created it, we can't call ApplyFrameBuffer() from the main thread
                // because the main thread spawns a separate thread for each window before constructing them.
                // The main thread also holds the frame buffer, so to update the window from there it calls the SetFrameBuffer()
                // function which sets the frame_buffer member to a non-null value which signals to this thread that it is ready to be drawn.
                buffer_lock.lock();
                if (em_frame_buffer != nullptr)
                {
                        ApplyFrameBuffer();
                        em_frame_buffer = nullptr;
                }
                buffer_lock.unlock();
        }
}

The problem is I'm getting a pretty high CPU usage (~30%) even before step 3 in the main thread (i.e. before the main thread begins the emulation).

I profiled the program. During the phase before the emulation begins 98% of the CPU time for the window thread is spent on this line: while (window->pollEvent(event))

Once emulation begins, CPU usage drops to around 5%, which is still a little high but more manageable. This time the "while (window->pollEvent(event))" line is using 18% of the window thread's time.

Looking through some other examples, I believe my problem is my window thread should look like this:
while (window->isOpen())
{
    Event event;
    while (window->pollEvent(event)) { ... }

    window->clear(); // optional
    window->draw(...);
    window->display(...);
}
 

Instead it looks more like this:
while (window->isOpen())
{
    Event event;
    while (window->pollEvent(event)) { ... }

    if (<false before emulation begins and true sometimes after it begins)
   {
      window->clear(); // optional
      window->draw(...);
      window->display(...);
   }
}
 

So what is the proper way to share a frame buffer between 2 threads? And what do I do in the window thread before a frame buffer is ready to avoid spinning too much?

Basically, I'm asking what I can do to minimize CPU usage by the window thread.

Any help appreciated :)

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 11034
    • View Profile
    • development blog
    • Email
Re: How to lower CPU usage
« Reply #1 on: September 09, 2017, 12:26:38 am »
To me it sounds like your second thread is pretty much useless, since your main thread simply waits for the second thread to do its job, so you have nothing that really runs in parallel.
How long does an emulation frame take to render? Unless it's closer to 30s, there's really not point in having one thread just for handling events.
Asked differently, what do you expect to gain from having the emulation in a separate thread?

As for the problem, ask yourself: How does a CPU get a high usage?
Answer: When a thread is doing processing none stop. When you just have one loop that runs as fast as it can, you're CPU usage will jump up even though you're not really doing any work.

while(window.isOpen()) { // Iterates as fast as possible
    sf::Event event;
    while(window.pollEvent(event)) { // Iterates as fast as possible
   
    }
   
    if(false) {
        // clear, draw, display
    }
}

Things to prevent this are to put the thread to sleep for a certain amount of time.
If you let the window draw, regardless if the buffer is ready or not, then you could use setFramerateLimit or activate VSync. Which then will sleep the thread in the display function (as long as your graphics driver implements VSync in a useful way).
« Last Edit: September 09, 2017, 12:28:46 am by eXpl0it3r »
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

Hapax

  • Hero Member
  • *****
  • Posts: 3379
  • My number of posts is shown in hexadecimal.
    • View Profile
    • Links
Re: How to lower CPU usage
« Reply #2 on: September 09, 2017, 12:27:47 am »
If you're using the same object in multiple threads, the simplest way to help make them thread safe is to use a mutex every time you want to access it.
Consider setting your window to use vertical synchronization or setting a limit for its framerate. (note: not both)

It's generally recommended that you create and control a window (and its events) from the main thread.


EDIT: More thorough information posted above by eXpl0it3r.
Selba Ward -SFML drawables
Cheese Map -Drawable Layered Tile Map
Kairos -Timing Library
Grambol
 *Hapaxia Links*

dassie

  • Newbie
  • *
  • Posts: 13
    • View Profile
Re: How to lower CPU usage
« Reply #3 on: September 09, 2017, 12:48:41 am »
To me it sounds like your second thread is pretty much useless, since your main thread simply waits for the second thread to do its job, so you have nothing that really runs in parallel.
How long does an emulation frame take to render? Unless it's closer to 30s, there's really not point in having one thread just for handling events.
Asked differently, what do you expect to gain from having the emulation in a separate thread?

As for the problem, ask yourself: How does a CPU get a high usage?
Answer: When a thread is doing processing none stop. When you just have one loop that runs as fast as it can, you're CPU usage will jump up even though you're not really doing any work.

while(window.isOpen()) { // Iterates as fast as possible
    sf::Event event;
    while(window.pollEvent(event)) { // Iterates as fast as possible
   
    }
   
    if(false) {
        // clear, draw, display
    }
}

Things to prevent this are to put the thread to sleep for a certain amount of time.
If you let the window draw, regardless if the buffer is ready or not, then you could use setFramerateLimit or activate VSync. Which then will sleep the thread in the display function (as long as your graphics driver implements VSync in a useful way).

2 reasons I want to keep them in separate threads. The main thread is going to handle user-input from the console without blocking the viewport. And I also plan to add another window into the mix that runs parallel to the view port. It was also just easier for me to wrap my head around the problem if I could I keep them in separate threads.

Regardless, if it's possible to do it in multiple threads, then that's the problem I want to solve.

Anyways, I tried changed that snippet in my while-window-open loop to this. The CPU usage went way down. <1% before emulation. And <3% after. But the framerate is not inconsistent (speeds up and slows down randomly).
                buffer_lock.lock();
                if (em_frame_buffer != nullptr)
                {
                        ApplyFrameBuffer();
                        em_frame_buffer = nullptr;
                }
                else
                {
                        window->clear();
                        window->draw(*rectangle, *transform);
                        window->display();
                }
                buffer_lock.unlock();

dassie

  • Newbie
  • *
  • Posts: 13
    • View Profile
Re: How to lower CPU usage
« Reply #4 on: September 09, 2017, 12:52:14 am »
If you're using the same object in multiple threads, the simplest way to help make them thread safe is to use a mutex every time you want to access it.
Consider setting your window to use vertical synchronization or setting a limit for its framerate. (note: not both)

It's generally recommended that you create and control a window (and its events) from the main thread.


EDIT: More thorough information posted above by eXpl0it3r.

Thanks for the response. I am actually using a mutex to share the data and limiting the framerate already.

Hapax

  • Hero Member
  • *****
  • Posts: 3379
  • My number of posts is shown in hexadecimal.
    • View Profile
    • Links
Re: How to lower CPU usage
« Reply #5 on: September 09, 2017, 01:17:38 am »
Thanks for the response.
You're welcome.

2 reasons I want to keep them in separate threads. The main thread is going to handle user-input from the console without blocking the viewport. And I also plan to add another window into the mix that runs parallel to the view port.
Why is the main "input" thread blocking the viewport? Is your input blocking? If not, you can update your input inside the window update loop as 'normal'. If it is blocking and you insist on keeping this, why not place the input in its own thread instead? This allows the window(s) to be fully controlled by the main thread and the input thread doesn't "spin".

It was also just easier for me to wrap my head around the problem if I could I keep them in separate threads.
Adding threads when they're not necessary almost always makes things less simple than without them...

Regardless, if it's possible to do it in multiple threads, then that's the problem I want to solve.
Why? A lot of things are possible but it doesn't mean that they should be done ;D
Selba Ward -SFML drawables
Cheese Map -Drawable Layered Tile Map
Kairos -Timing Library
Grambol
 *Hapaxia Links*

dassie

  • Newbie
  • *
  • Posts: 13
    • View Profile
Re: How to lower CPU usage
« Reply #6 on: September 09, 2017, 01:53:00 am »
Thanks for the response.
You're welcome.

2 reasons I want to keep them in separate threads. The main thread is going to handle user-input from the console without blocking the viewport. And I also plan to add another window into the mix that runs parallel to the view port.
Why is the main "input" thread blocking the viewport? Is your input blocking? If not, you can update your input inside the window update loop as 'normal'. If it is blocking and you insist on keeping this, why not place the input in its own thread instead? This allows the window(s) to be fully controlled by the main thread and the input thread doesn't "spin".

It was also just easier for me to wrap my head around the problem if I could I keep them in separate threads.
Adding threads when they're not necessary almost always makes things less simple than without them...

Regardless, if it's possible to do it in multiple threads, then that's the problem I want to solve.
Why? A lot of things are possible but it doesn't mean that they should be done ;D

It's only 2 threads sharing 1 resource. It's not that complicated.

I don't want to give up and change my design. I'm sure I can figure it out; I just wanted to see if anyone knew how I could fix this multithread situation.