Windows: destruction of wxTreeCtrl with many items is very slow

Do you have a typical platform dependent issue you're battling with ? Ask it here. Make sure you mention your platform, compiler, and wxWidgets version.
Post Reply
ButtonPolisher
Earned a small fee
Earned a small fee
Posts: 14
Joined: Wed Jun 30, 2021 3:22 am

Windows: destruction of wxTreeCtrl with many items is very slow

Post by ButtonPolisher »

On Windows, destruction of a wxTreeCtrl with 10k items takes several seconds, and that time seems to grow non-linearly with the tree item count. I looked into the source and it seems that TreeView_DeleteAllItems is called from destructor:
https://github.com/wxWidgets/wxWidgets/ ... l.cpp#L853
https://github.com/wxWidgets/wxWidgets/ ... .cpp#L1739

TreeView_DeleteAllItems is known to be slow for trees with many items (for example see http://www.delphigroups.info/2/dc/320301.html). Is there a way to have an option of "quick" wxTreeCtrl destruction? A workaround to just disable TreeView_DeleteAllItems/DeleteAllItems invocation somehow would suffice (as there should be no significant memory leaks because I do not use tree item user data).
User avatar
doublemax
Moderator
Moderator
Posts: 19114
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: Windows: destruction of wxTreeCtrl with many items is very slow

Post by doublemax »

I took the "treectrl" sample and changed MyFrame::OnAddManyItems() to add 10000 instead of 1000 items. After selecting "add many items" from the menu, I didn't notice any delay on construction.

So there must be some other factors involved.
Use the source, Luke!
ButtonPolisher
Earned a small fee
Earned a small fee
Posts: 14
Joined: Wed Jun 30, 2021 3:22 am

Re: Windows: destruction of wxTreeCtrl with many items is very slow

Post by ButtonPolisher »

Thank you, I will try to reproduce this on the treectrl sample and come back with what I found.
PB
Part Of The Furniture
Part Of The Furniture
Posts: 4193
Joined: Sun Jan 03, 2010 5:45 pm

Re: Windows: destruction of wxTreeCtrl with many items is very slow

Post by PB »

I cannot reproduce it in the scenario where there are 10k items as children of the root, and each of those items has 10 items of its own, i.e. 100k items in total.

Creating such wxTreeCtrl takes about 1200 ms, deleting about 450 ms (wxWidgets master, Windows 10 21H1):

Code: Select all

#include <wx/wx.h>
#include <wx/stopwatch.h>
#include <wx/treectrl.h>

class MyFrame: public wxFrame
{
public:
    MyFrame() : wxFrame(nullptr, wxID_ANY, "Test")
    {
        m_mainPanel = new wxPanel(this);
        m_mainPanelSizer = new wxBoxSizer(wxVERTICAL);

        m_mainPanelSizer->Add(new wxButton(m_mainPanel, wxID_ANY, "Add/Remove wxTreeCtrl"), wxSizerFlags().Expand().Border());
        Bind(wxEVT_BUTTON, &MyFrame::OnAddOrRemoveTreeCtrl, this);

        wxTextCtrl* logCtrl = new wxTextCtrl(m_mainPanel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize,
            wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH2);
        wxLog::SetActiveTarget(new wxLogTextCtrl(logCtrl));
        wxLog::DisableTimestamp();
        m_mainPanelSizer->Add(logCtrl, wxSizerFlags().Proportion(1).Expand().Border());

        SetMinClientSize(FromDIP(wxSize(600,400)));
        m_mainPanel->SetSizer(m_mainPanelSizer);
    }
private:
    wxPanel*    m_mainPanel;
    wxSizer*    m_mainPanelSizer;
    wxTreeCtrl* m_treeCtrl{nullptr};

    void OnAddOrRemoveTreeCtrl(wxCommandEvent&)
    {
        const size_t itemCountRoot = 10000;
        const size_t itemCountL1   = 10;

        wxStopWatch stopWatch;

        stopWatch.Start();
        if ( m_treeCtrl )
        {
            m_mainPanelSizer->Detach(m_treeCtrl);
            wxDELETE(m_treeCtrl);
        }
        else
        {
            wxTreeItemId rootItemId;

            m_treeCtrl = new wxTreeCtrl(m_mainPanel);
            m_mainPanelSizer->Insert(1, m_treeCtrl, wxSizerFlags().Proportion(4).Expand().Border());
            rootItemId = m_treeCtrl->AddRoot("Root");
            for ( size_t i = 1; i <= itemCountRoot; ++i )
            {
                wxTreeItemId itemId = m_treeCtrl->AppendItem(rootItemId, wxString::Format("Item #%zu", i));
                for ( size_t j = 1; j <= itemCountL1; ++j )
                    m_treeCtrl->AppendItem(itemId, wxString::Format("Item #%zu/%zu", i, j));
            }
        }

        wxLogMessage("%s wxTreeCtrl (%zu items) took %ld ms.",
            m_treeCtrl ? "Adding" : "Removing", itemCountRoot * itemCountL1, stopWatch.Time());
        m_mainPanelSizer->Layout();
    }
};

class MyApp : public wxApp
{
public:
    bool OnInit() override
    {
        (new MyFrame())->Show();
        return true;
    }
}; wxIMPLEMENT_APP(MyApp);
Do you get vastly different times when running the above code? Or is this affected by the deepness of the tree hierarchy?
ButtonPolisher
Earned a small fee
Earned a small fee
Posts: 14
Joined: Wed Jun 30, 2021 3:22 am

Re: Windows: destruction of wxTreeCtrl with many items is very slow

Post by ButtonPolisher »

It seems I managed to reproduce this on the stock TreeCtrl sample:

1. Run the sample
2. Check "Style/Toggle Hidden Root"
3. Click "Tree/Append many items" 10 times (or change the amount items added from 1000 to 10000 in MyFrame::OnAddManyItems)
4. Close the window

Closing the window takes about 3 seconds on my machine.
It seems that wxTR_HIDE_ROOT is the culprit here, as it makes the TreeView_DeleteAllItems execution slow.
ONEEYEMAN
Part Of The Furniture
Part Of The Furniture
Posts: 7458
Joined: Sat Apr 16, 2005 7:22 am
Location: USA, Ukraine

Re: Windows: destruction of wxTreeCtrl with many items is very slow

Post by ONEEYEMAN »

Hi,
You are on Windows (10), right?
And using 3.0 release?

Can you try latest 3.1? Or even Git master?

Thank you.
User avatar
doublemax
Moderator
Moderator
Posts: 19114
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: Windows: destruction of wxTreeCtrl with many items is very slow

Post by doublemax »

I can confirm the issue. But as most of the time is spent inside the Win32 API TreeView_DeleteAllItems, i have no idea what to do about it.
Use the source, Luke!
PB
Part Of The Furniture
Part Of The Furniture
Posts: 4193
Joined: Sun Jan 03, 2010 5:45 pm

Re: Windows: destruction of wxTreeCtrl with many items is very slow

Post by PB »

I do not have a solution (as doublemax observed, this goes to Windows API) but I do offer a workaround.

Using Freeze() and Thaw() seems to decrease the time it takes to add or delete items tremendously, at least when wxTR_HIDE_ROOT is used. So as a workaround, I would delete the items myself in the frozen control, before it gets destroyed.

Using the code below (modified from my previous post), I see this:
Using Freeze()/Thaw(): Yes
Adding wxTreeCtrl (10000 root items) took 258 ms.
Using Freeze()/Thaw(): Yes
Removing wxTreeCtrl (10000 root items) took 256 ms.

Using Freeze()/Thaw(): No
Adding wxTreeCtrl (10000 root items) took 10160 ms.
Using Freeze()/Thaw(): No
Removing wxTreeCtrl (10000 root items) took 2673 ms.

Code: Select all

#include <wx/wx.h>
#include <wx/stopwatch.h>
#include <wx/treectrl.h>

class MyFrame: public wxFrame
{
public:
    MyFrame() : wxFrame(nullptr, wxID_ANY, "Test")
    {
        m_mainPanel = new wxPanel(this);
        m_mainPanelSizer = new wxBoxSizer(wxVERTICAL);

        m_mainPanelSizer->Add(new wxButton(m_mainPanel, wxID_ANY, "Add/Remove wxTreeCtrl"), wxSizerFlags().Expand().Border());
        Bind(wxEVT_BUTTON, &MyFrame::OnAddOrRemoveTreeCtrl, this);

        wxTextCtrl* logCtrl = new wxTextCtrl(m_mainPanel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize,
            wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH2);
        wxLog::SetActiveTarget(new wxLogTextCtrl(logCtrl));
        wxLog::DisableTimestamp();
        m_mainPanelSizer->Add(logCtrl, wxSizerFlags().Proportion(1).Expand().Border());

        SetMinClientSize(FromDIP(wxSize(600,400)));
        m_mainPanel->SetSizer(m_mainPanelSizer);
    }
private:
    wxPanel*    m_mainPanel;
    wxSizer*    m_mainPanelSizer;
    wxTreeCtrl* m_treeCtrl{nullptr};

    void OnAddOrRemoveTreeCtrl(wxCommandEvent&)
    {
        const size_t itemCountRoot = 10000;
        const bool   freezeAndThaw = wxMessageBox("Use Freeze()/Thaw()?", "Question", wxYES_NO, this) == wxYES;

        wxStopWatch  stopWatch;

        wxLogMessage("Using Freeze()/Thaw(): %s", freezeAndThaw ? "Yes" : "No");

        stopWatch.Start();
        if ( m_treeCtrl )
        {
            m_mainPanelSizer->Detach(m_treeCtrl);
            if ( freezeAndThaw )
                m_treeCtrl->Freeze();
            m_treeCtrl->DeleteAllItems();
            if ( freezeAndThaw )
                m_treeCtrl->Thaw();
            wxDELETE(m_treeCtrl);
        }
        else
        {
            wxTreeItemId rootItemId;

            m_treeCtrl = new wxTreeCtrl(m_mainPanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTR_DEFAULT_STYLE | wxTR_HIDE_ROOT);
            m_mainPanelSizer->Insert(1, m_treeCtrl, wxSizerFlags().Proportion(4).Expand().Border());

            if ( freezeAndThaw )
                m_treeCtrl->Freeze();

            rootItemId = m_treeCtrl->AddRoot("Root");
            for ( size_t i = 1; i <= itemCountRoot; ++i )
                wxTreeItemId itemId = m_treeCtrl->AppendItem(rootItemId, wxString::Format("Item #%zu", i));

            if ( freezeAndThaw )
                m_treeCtrl->Thaw();
        }

        wxLogMessage("%s wxTreeCtrl (%zu root items) took %ld ms.",
            m_treeCtrl ? "Adding" : "Removing", itemCountRoot, stopWatch.Time());
        m_mainPanelSizer->Layout();
    }
};

class MyApp : public wxApp
{
public:
    bool OnInit() override
    {
        (new MyFrame())->Show();
        return true;
    }
}; wxIMPLEMENT_APP(MyApp);
Post Reply