user11383585
user11383585

Reputation:

Multithreading slows down main thread

I'll do my best to replicate this issue, since it's quite complicated. As an overview, I have a program that displays graphs using OpenGL. This is thread specific so I know the rendering is only done on one thread. My other thread queries a database and stores the data in a copy vector. Once the thread is finished, it swaps the data with the data the OpenGL thread is using (After joining the thread with the main one). In theory there is nothing about this that should make the program run so slow?

The extremely odd part of this is how it eventually "warms up" and runs much faster after a while (it varies quite a bit, sometimes almost instantaneously, sometimes after 30s of runtime). From value's side of thing to compare, the program begins running at about 30-60 fps whilst querying the data (as in, constantly loading it and swapping it and joining the threads), but then once it has warmed up it runs at 1000 fps.

I have tested some things out, beginning with making the query take A LONG time to run. During this, the fps is at a max of what it would be (3000+). It is only when the data is constantly being changed (swapping vectors) that is starts to run very slow. It doesn't make sense that this alone is causing the performance hit since it runs very well after it's "warmed up".

Edit:

I've managed to make a reasonable minimal reproducable example, and i've found some interesting result.

Here is the code:

#include <iostream>
#include <string>
#include <thread>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include "ImGui/imgui.h"
#include "ImGui/imgui_impl_glfw.h"
#include "ImGui/imgui_impl_opengl3.h"

bool querying = false;
std::thread thread;
int m_Window_Width = 1280;
int m_Window_Height = 720;


static void LoadData()
{
    querying = true;
    
    std::this_thread::sleep_for(std::chrono::milliseconds(100));

    querying = false;
}



int main()
{


    glfwInit();

    const char* m_GLSL_Version = "#version 460";
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
    GLFWwindow* m_Window = glfwCreateWindow(m_Window_Width, m_Window_Height, "Program", NULL, NULL);
    glfwMakeContextCurrent(m_Window);
    glfwSwapInterval(0); //  vsync

    glewInit();

    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImGui::StyleColorsClassic();

    // Setup Platform/Renderer backends
    ImGui_ImplGlfw_InitForOpenGL(m_Window, true);
    ImGui_ImplOpenGL3_Init(m_GLSL_Version);



    thread = std::thread(LoadData);


    while (!glfwWindowShouldClose(m_Window))
    {
        glfwPollEvents();
        
        ImGui_ImplOpenGL3_NewFrame();
        ImGui_ImplGlfw_NewFrame();
        ImGui::NewFrame();


        char fps[12];
        sprintf_s(fps, "%f", ImGui::GetIO().Framerate);
        glfwSetWindowTitle(m_Window, fps);


        //Load the data
        if (thread.joinable() == false && querying == false) {
            thread = std::thread(LoadData);
        }
        //Swap the data after thread is finished
        if (thread.joinable() == true && querying == false) {
            thread.join();
        }

        // Rendering
        ImGui::Render();
        glfwGetFramebufferSize(m_Window, &m_Window_Width, &m_Window_Height);
        glViewport(0, 0, m_Window_Width, m_Window_Height);
        glClearColor(0.45f, 0.55f, 0.60f, 1.00f);
        glClear(GL_COLOR_BUFFER_BIT);
        ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
        glfwSwapBuffers(m_Window);
    }





    ImGui_ImplOpenGL3_Shutdown();
    ImGui_ImplGlfw_Shutdown();
    ImGui::DestroyContext();

    glfwDestroyWindow(m_Window);
    glfwTerminate();


    

    return 0;
}

Now the interesting thing here is playing around with std::this_thread::sleep_for(). I have implemented this in so I can simulate the speed it actually takes when running the query on the main database. What is interesting is that it actually causes the main thread to stop running and freezes it. These threads should be separate and not impact one another, however that is not the case here. Is there any explanation for this? This seems to be the root issue for my main program and boiled down to this.

Edit 2

To use the libraries (in Visual Studio), download from here, the binaries, https://www.glfw.org/download.html and here aswell, http://glew.sourceforge.net/ and lastly, ImGui from here, https://github.com/ocornut/imgui

Preprocessor: GLEW_STATIC; WIN32;

Linker: glfw3.lib;glew32s.lib;opengl32.lib;Gdi32.lib;Shell32.lib;user32.lib;Gdi32.lib

Upvotes: 0

Views: 820

Answers (1)

Vlad Feinstein
Vlad Feinstein

Reputation: 11311

This may or may not be your issue, but here:

    //Load the data
    if (thread.joinable() == false && querying == false) {
        thread = std::thread(LoadData);
    }
    //Swap the data after thread is finished
    if (thread.joinable() == true && querying == false) {
        thread.join();
    }

it is possible that you start the thread in the first if block, then get to the second one before LoadData modifies that bool, causing the wait for that tread to finish.

I would set querying = true; in the main thread, right after you created LoadData thread. Also, I would use some kind of synchronization, for example declare querying as atomic<bool>.

EDIT:

It appears that you do not need to check joinable() - you know when the thread is joinable: when you enter the loop, and after you re-start that thread. This looks cleaner:

std::atomic<bool> querying = true;

void LoadData()
{
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    querying = false;
}

and later in your loop:

//Swap the data after thread is finished
if (!querying) {
    thread.join();
    querying = true;
    thread = std::thread(LoadData);
}

Upvotes: 2

Related Questions