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).
Windows: destruction of wxTreeCtrl with many items is very slow
-
- 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
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.
So there must be some other factors involved.
Use the source, Luke!
-
- 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
Thank you, I will try to reproduce this on the treectrl sample and come back with what I found.
Re: Windows: destruction of wxTreeCtrl with many items is very slow
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):
Do you get vastly different times when running the above code? Or is this affected by the deepness of the tree hierarchy?
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);
-
- 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
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.
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.
Re: Windows: destruction of wxTreeCtrl with many items is very slow
Hi,
You are on Windows (10), right?
And using 3.0 release?
Can you try latest 3.1? Or even Git master?
Thank you.
You are on Windows (10), right?
And using 3.0 release?
Can you try latest 3.1? Or even Git master?
Thank you.
Re: Windows: destruction of wxTreeCtrl with many items is very slow
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!
Re: Windows: destruction of wxTreeCtrl with many items is very slow
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() 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);