Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace gtk_combo_box_text_insert_text with direct model insertion #23443

Merged
merged 3 commits into from
Apr 12, 2023

Conversation

imciner2
Copy link
Contributor

There is a lot of overhead in the gtk_combo_box_text_insert_text function, so adding a lot of items to a choicebox can be an expensive operation when it is used. We have encountered this in KiCad when trying to use a wxChoiceBox as a font chooser on systems that have a lot of fonts installed (https://gitlab.com/kicad/code/kicad/-/issues/14277).

I traced back some of the slow performance on creation of the wxChoiceBox to the method that was being used to add the items to the combobox. Experiments showed that adding 10000 items to the choicebox takes 6.75s in the current implementation using gtk_combo_box_text_insert_text. The other way of adding items is to directly access the underlying data model using gtk_list_store_insert_with_values. Using the data model access, experiments showed the time needed to add 10000 items dropped to 438.2ms.

It turns out, that was already the method used in wxBitmapComboBox, so this actually collapses that down into the base wxChoice class, removing the overriden function from wxBitmapComboBox. I tried using the same 2-step insertion pattern from wxBitmapComboBox (calling gtk_list_store_insert followed by gtk_list_store_set_value, but it turns out this is just as slow as the original method, and using the 1-step method by calling gtk_list_store_insert_with_values was much faster).

I have split this into two separate commits. The first is ABI-compatible, so it would be good to backport it to 3.2. The second removes the now redundant function from wxBitmapComboBox, so it changes the ABI (but leaves the API). I would have preferred to just collapse out GTKInsertComboBoxTextItem entirely so each item didn't have to query for the model, but that would remove that from the API, and it could theoretically be being overriden by user's in their own implementations currently.

This doesn't completely solve the performance issues for the large choicebox though, there is still a large performance hit when computing the text size and showing the menu, but that needs to be handled separately (and I haven't quite figured out the best way to do that yet).

These experiments can be seen using the minimal sample with this diff:

diff --git a/samples/minimal/minimal.cpp b/samples/minimal/minimal.cpp
index 501caf9096..428f3db1b5 100644
--- a/samples/minimal/minimal.cpp
+++ b/samples/minimal/minimal.cpp
@@ -144,6 +144,9 @@ MyFrame::MyFrame(const wxString& title)
     // set the frame icon
     SetIcon(wxICON(sample));
 
+    wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
+    SetSizer(main_sizer);
+
 #if wxUSE_MENUBAR
     // create a menu bar
     wxMenu *fileMenu = new wxMenu;
@@ -162,12 +165,13 @@ MyFrame::MyFrame(const wxString& title)
     // ... and attach this menu bar to the frame
     SetMenuBar(menuBar);
 #else // !wxUSE_MENUBAR
+
     // If menus are not available add a button to access the about box
     wxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
     wxButton* aboutBtn = new wxButton(this, wxID_ANY, "About...");
     aboutBtn->Bind(wxEVT_BUTTON, &MyFrame::OnAbout, this);
     sizer->Add(aboutBtn, wxSizerFlags().Center());
-    SetSizer(sizer);
+    main_sizer->Add(sizer);
 #endif // wxUSE_MENUBAR/!wxUSE_MENUBAR
 
 #if wxUSE_STATUSBAR
@@ -175,6 +179,18 @@ MyFrame::MyFrame(const wxString& title)
     CreateStatusBar(2);
     SetStatusText("Welcome to wxWidgets!");
 #endif // wxUSE_STATUSBAR
+
+    wxArrayString labels;
+
+    for( int i=0; i < 10001; i++)
+    {
+        labels.Add( wxString::Format( "Item %d", i ) );
+    }
+
+    wxChoice* choice = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, labels);
+    main_sizer->Add(choice);
+
+    Layout();
 }

The flamegraph from the original implementation is:

Choicebox_Original

The flamegraph from the proposed implementation is (note the different scale on the time axis):
Choicebox_SingleStage

Copy link
Contributor

@vadz vadz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for improving this!

We can definitely merge this already or you could address the 2 minor remarks below if you'd like.

Concerning the API breakage: functions prefixed with GTK are specifically meant to be private and not part of the public API and so we can remove it without problem. Unfortunately we do need to keep it in 3.2 to avoid breaking the ABI...

src/gtk/choice.cpp Show resolved Hide resolved
src/gtk/choice.cpp Outdated Show resolved Hide resolved
@imciner2
Copy link
Contributor Author

Concerning the API breakage: functions prefixed with GTK are specifically meant to be private and not part of the public API and so we can remove it without problem.

Thanks for clarifying this. I have gone ahead and removed it from the class then, collapsing everything into DoInsertItems. It is a separate commit, so backporting the core of this change to 3.2 can still be done by taking the first commit and its fixup.

@vadz
Copy link
Contributor

vadz commented Apr 12, 2023

Thanks for the updates! I think it would be better if you rebased the commits here, so that I could cherry-pick just a single commit to 3.2.

There is a lot of overhead in the gtk_combo_box_text_insert_text
function, so adding a lot of items to a choicebox can be an expensive
operation when it is used. Instead directly access the underlying data
model and add the items to it.

Experiments show that for adding 10000 items to a wxChoice, the amount
of time spent in wxChoice::DoInsertItems for each method are:
    gtk_combo_box_text_insert_text: 6.75s
    gtk_list_store_insert_with_values: 438.2ms
The base function now has this exact functionality, but is faster.
Instead of having a special function for this, collapse it down into the
DoInsertItems function to allow for only querying the model once from
the combobox.

This function is considered private because of the GTK prefix, so its
removal isn't API-breaking.
@imciner2
Copy link
Contributor Author

Ok, I have rebased everything so now only 1 commit (the first) needs to be cherry-picked, everything else is master-only.

@vadz
Copy link
Contributor

vadz commented Apr 12, 2023

Great, thanks! Will merge (and backport) soon.

@vadz vadz merged commit 71a64c2 into wxWidgets:master Apr 12, 2023
20 checks passed
vadz pushed a commit to vadz/wxWidgets that referenced this pull request Apr 12, 2023
There is a lot of overhead in the gtk_combo_box_text_insert_text
function, so adding a lot of items to a choicebox can be an expensive
operation when it is used. Instead directly access the underlying data
model and add the items to it.

Experiments show that for adding 10000 items to a wxChoice, the amount
of time spent in wxChoice::DoInsertItems for each method are:
    gtk_combo_box_text_insert_text: 6.75s
    gtk_list_store_insert_with_values: 438.2ms

(cherry picked from commit 983608c)

See wxWidgets#23443.
jmaibaum added a commit to jmaibaum/org.kicad.KiCad that referenced this pull request Apr 17, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants