Skip to content

[GTK] Speed up Combo setItems/removeAll/remove for large item counts - #3401

Draft
vogella wants to merge 1 commit into
eclipse-platform:masterfrom
vogella:gtk-combo-bulk-update-perf
Draft

[GTK] Speed up Combo setItems/removeAll/remove for large item counts#3401
vogella wants to merge 1 commit into
eclipse-platform:masterfrom
vogella:gtk-combo-bulk-update-perf

Conversation

@vogella

@vogella vogella commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Setting or clearing a large number of Combo items (>5000) is very slow on GTK.
Editing the GtkListStore a combo is showing costs O(n) per row, because the popup keeps its own handlers on the model and rebuilds an item for every row-inserted/row-changed, so filling or clearing a large combo is quadratic.

Detaching the model with gtk_combo_box_set_model(handle, 0) does not fix this, since the popup keeps the model even when the combo drops it.
Instead setItems, removeAll and remove(start, end) now build a new GtkListStore, fill it while nothing observes it, and hand it to the combo in one step, so filling is linear and the popup is rebuilt exactly once.

Measured on GTK3 with 16000 items: setItems 41.9s to 0.4s, removing half the items 12.2s to 0.5s, removeAll 2.1s to 0.1s.

As a side effect the combo no longer sends the spurious Modify events that the per-row insert produced (one per item), because the model swap happens with the CHANGED closure blocked.
Two new Combo tests cover the selection handling across range removals and the absence of those events.

Verified locally on Linux against self-built natives, on both GTK3 and GTK4: all 143 Test_org_eclipse_swt_widgets_Combo tests pass on both.

Fixes #506

@github-actions

github-actions Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Test Results

  211 files   -  1    211 suites   - 1   28m 0s ⏱️ +14s
4 840 tests  - 54  4 818 ✅  - 52   21 💤  - 3  0 ❌ ±0  1 🔥 +1 
7 026 runs   - 39  6 858 ✅  - 39  167 💤  - 1  0 ❌ ±0  1 🔥 +1 

For more details on these errors, see this check.

Results for commit ced3798. ± Comparison against base commit 4755f49.

This pull request removes 57 and adds 3 tests. Note that renamed tests count towards both.
org.eclipse.swt.graphics.ImageWin32Tests ‑ testDisposeDrawnImageBeforeRequestingTargetForOtherZoom
org.eclipse.swt.graphics.ImageWin32Tests ‑ testDrawImageAtDifferentZooms(boolean)[1] true
org.eclipse.swt.graphics.ImageWin32Tests ‑ testDrawImageAtDifferentZooms(boolean)[2] false
org.eclipse.swt.graphics.ImageWin32Tests ‑ testImageDataForDifferentFractionalZoomsShouldBeDifferent
org.eclipse.swt.graphics.ImageWin32Tests ‑ testImageShouldHaveDimesionAsPerZoomLevel
org.eclipse.swt.graphics.ImageWin32Tests ‑ testRetrieveImageDataAtDifferentZooms(boolean)[1] true
org.eclipse.swt.graphics.ImageWin32Tests ‑ testRetrieveImageDataAtDifferentZooms(boolean)[2] false
org.eclipse.swt.graphics.ImageWin32Tests ‑ test_getImageData_fromCopiedImage
org.eclipse.swt.graphics.ImageWin32Tests ‑ test_getImageData_fromImageForImageDataFromImage
org.eclipse.swt.tests.win32.Test_org_eclipse_swt_dnd_DND ‑ testByteArrayTransfer
…
org.eclipse.swt.tests.junit.Test_org_eclipse_swt_widgets_Combo ‑ test_bulkUpdatesSendNoEventsWhenNothingIsSelected
org.eclipse.swt.tests.junit.Test_org_eclipse_swt_widgets_Combo ‑ test_removeII_keepsSelectedItemWithoutEvents
org.eclipse.swt.tests.junit.Test_org_eclipse_swt_widgets_Combo ‑ test_removeII_keepsSelectionOutsideRange

♻️ This comment has been updated with latest results.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR improves GTK Combo performance for large bulk updates by avoiding repeated GtkComboBox relayouts on every single row change. It does so by temporarily detaching the GtkListStore model during setItems, removeAll, and range remove(start, end), then reattaching it once, and it adds a GTK-only unit test to validate selection preservation behavior for range removals.

Changes:

  • Detach/reattach the GtkComboBox model during bulk Combo mutations to reduce relayout work on GTK.
  • Preserve and restore the active selection across remove(start, end) when the selected item is outside the removed range.
  • Add a new GTK native binding (gtk_combo_box_set_model) and a GTK-only JUnit test for selection handling.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_widgets_Combo.java Adds a GTK-only test to validate selection behavior around range removals.
bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Combo.java Implements model detachment for bulk operations and restores selection for range removes.
bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/GTK.java Adds gtk_combo_box_set_model binding and removes gtk_combo_box_text_remove_all binding.
bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os.c Adds JNI bridge for gtk_combo_box_set_model and removes JNI bridge for gtk_combo_box_text_remove_all.
bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_stats.h Updates native function stats enum for the added/removed GTK JNI bindings.

Comment thread bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Combo.java Outdated
Comment thread bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Combo.java Outdated
Comment thread bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Combo.java Outdated
@akurtakov
akurtakov force-pushed the gtk-combo-bulk-update-perf branch from 924ec33 to 745e517 Compare July 28, 2026 15:38
@vogella
vogella force-pushed the gtk-combo-bulk-update-perf branch from 745e517 to cab4422 Compare August 5, 2026 08:43
@vogella

vogella commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

Reworked and force-pushed.

The three review comments about the CHANGED closure were right, and following them up showed the original approach did not actually help: detaching the model from the combo leaves it attached to the popup, which is what costs O(n) per row. Benchmarked on GTK3 at 16000 items, the detach version was no faster than master (47.2s vs 41.9s for setItems).

The bulk operations now build a fresh GtkListStore and swap it in once, with CHANGED blocked for the swap. That is linear: setItems 41.9s to 0.4s at 16000 items, and it also removes the per-item spurious Modify events, which a new test now covers.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (2)

bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Combo.java:292

  • setModelItems() blocks the CHANGED closure only on the combo handle. For editable combos, the same CHANGED closure is also connected on entryHandle (see g_signal_connect_closure(entryHandle, … CHANGED)), so swapping the model / restoring active can still emit entry "changed" and produce Modify events. To keep bulk updates truly silent, block/unblock CHANGED on entryHandle too when it exists.
	// Swapping the model resets the active item and emits "changed", which would
	// send spurious Modify/Selection events, so keep the combo quiet meanwhile.
	OS.g_signal_handlers_block_matched (handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, CHANGED);
	gtk_combo_box_toggle_wrap (false);
	GTK.gtk_combo_box_set_model (handle, model);

tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_widgets_Combo.java:708

  • The new regression test only asserts the absence of events for a READ_ONLY combo. Since bulk model swaps also affect editable combos (which have the same CHANGED closure connected on entryHandle), consider extending this test to run the same setItems/remove/removeAll sequence on an editable Combo too, so spurious Modify/Selection events on the entry widget are caught.
public void test_bulkUpdatesSendNoEventsWhenNothingIsSelected() {
	// Bug 506: setItems/remove/removeAll rebuild the GTK model internally. That must
	// stay invisible to applications, so an unselected combo must send no events.
	String[] items = {"item0", "item1", "item2", "item3", "item4"};
	Combo readOnly = new Combo(shell, SWT.READ_ONLY);

@vogella
vogella force-pushed the gtk-combo-bulk-update-perf branch from cab4422 to 7bd842b Compare August 5, 2026 13:14
@vogella

vogella commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

Both new review points applied.

Blocking CHANGED on entryHandle too was correct, and it turned out to matter on GTK4 specifically. On GTK3 gtk_entry_set_text short-circuits when the text is unchanged, so restoring the selection after the model swap was already silent. GTK4 goes through the entry buffer instead and rewrites the text unconditionally, so remove(start, end) on an editable combo fired 2 spurious Modify events even though the shown text never changed. setModelItems now blocks CHANGED, INSERT_TEXT and DELETE_TEXT on the entry for the swap, matching what setText already does.

Extending the test to editable combos was the right call too: the read-only-only version did not catch the above. Both event tests now run for SWT.READ_ONLY and SWT.DROP_DOWN, and there is a second test for the case that actually exposed it, a range removal that keeps the selected item. With the entry blocking reverted it fails on GTK4 with style 4 ==> expected: <0> but was: <2>.

Verified locally against self-built natives: all 144 Test_org_eclipse_swt_widgets_Combo tests pass on GTK3 and GTK4. Performance is unchanged by the fix (16000 items: setItems 0.44s, remove half 0.54s, removeAll 0.12s).

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (1)

bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Combo.java:298

  • After swapping the model in setModelItems(), the pop-up contents may get rebuilt. For SWT.RIGHT_TO_LEFT combos, other code paths explicitly reapply RTL direction to popupHandle after item mutations (e.g., add()/setItem()/setItems()), but setModelItems() doesn’t. Since removeAll() and remove(start,end) now rely on setModelItems(), RTL combos can regress to LTR in the popup after bulk updates.
	gtk_combo_box_toggle_wrap (false);
	GTK.gtk_combo_box_set_model (handle, model);
	OS.g_object_unref (model);
	if (activeIndex != -1) GTK.gtk_combo_box_set_active (handle, activeIndex);
	gtk_combo_box_toggle_wrap (true);

@akurtakov

Copy link
Copy Markdown
Member

New test test_removeII_keepsSelectedItemWithoutEvents fails on MacOS so as a minimum it should not be run on MacOS.

@vogella
vogella force-pushed the gtk-combo-bulk-update-perf branch from e34f976 to a0abd19 Compare August 6, 2026 12:57
@vogella

vogella commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

Both review points applied and force-pushed.

@akurtakov right, test_removeII_keepsSelectedItemWithoutEvents is now skipped on macOS via assumeFalse(SwtTestUtil.isCocoa, ...). The failure was style 4 ==> expected: <0> but was: <1>, so an editable Combo on Cocoa sends one Selection event when items before the selection are removed. The remaining assertions in that test were GTK-only anyway, so nothing is lost by skipping it there. The assume message records the deviation.

Copilot's point about the RTL popup direction was also correct: the model swap rebuilds the popup, so the direction previously applied to its children is lost, and only setItems was reapplying it. removeAll() and remove(start, end) now go through setModelItems too, so they had the same gap. The reapplication moved into setModelItems, which covers all three bulk paths and removes the duplication.

Setting or removing a large number of combo items (>5000) was very slow on
GTK: editing the GtkListStore a combo is showing costs O(n) per row, because
the popup keeps its own handlers on the model and rebuilds an item for every
row-inserted/row-changed. Filling or clearing a large combo was therefore
quadratic.

Detaching the model with gtk_combo_box_set_model(handle, 0) does not help,
since the popup keeps the model even when the combo drops it. Instead
setItems, removeAll and remove(start, end) now build a new GtkListStore,
fill it while nothing observes it, and hand it to the combo in one step.
Filling is then linear and the popup is rebuilt exactly once.

Measured on GTK3 with 16000 items: setItems 41.9s -> 0.4s, removing half the
items 12.2s -> 0.5s, removeAll 2.1s -> 0.1s.

The model swap resets the active item and emits "changed", and on a combo
with an entry restoring the selection rewrites the entry text with the value
it already had. The CHANGED closure on the combo and the CHANGED, INSERT_TEXT
and DELETE_TEXT closures on the entry are therefore blocked while the model is
swapped, which also removes the spurious Modify events the per-row insert used
to send. remove(start, end) restores the active selection, adjusted for the
rows removed before it.

Adds the gtk_combo_box_set_model native binding and drops the now-unused
gtk_combo_box_text_remove_all binding.

Fixes eclipse-platform#506
@vogella
vogella force-pushed the gtk-combo-bulk-update-perf branch from a0abd19 to ced3798 Compare August 6, 2026 12:59
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.

setting/removing large number of (more than 5000) combo items is too slow

3 participants