GTK4 ColumnView Performance Problem

I developed a small application by using GTK4 and Python3. ColumnView is used for showing column data. But adding data to Gio.ListStore model consumes high CPU.

Adding two column data takes about 0.04 seconds. Adding four column data takes about 0.08 seconds. And adding five column data takes about 0.098 seconds. Adding 10-15 columns blocks GUI. On the other hand, adding a few thousands of row works does not block the GUI.

Is there a way to reduce CPU load?

An example code:

import gi
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk, Gio, GObject

class User(GObject.Object):
    __gtype_name__ = 'User'
    name = GObject.Property(type=str)
    email = GObject.Property(type=str)
    city = GObject.Property(type=str)
    car = GObject.Property(type=str)
    country = GObject.Property(type=str)

    def __init__(self, name, email, city, car, country):
        super().__init__()
        self.name = name
        self.email = email
        self.city = city
        self.car = car
        self.country = country

class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, app):
        super().__init__(application=app, title="ColumnView", default_width=600, default_height=400)

        self.model = Gio.ListStore(item_type=User)
        self.selection_model = Gtk.SingleSelection(model=self.model)
        column_view = Gtk.ColumnView(model=self.selection_model)

        self.add_column(column_view, "Name", "name")
        self.add_column(column_view, "E-mail", "email")
        self.add_column(column_view, "City", "city")
        self.add_column(column_view, "Car", "car")
        self.add_column(column_view, "Country", "country")

        scrolled = Gtk.ScrolledWindow(child=column_view)
        self.set_child(scrolled)

        self.load_data()


    def add_column(self, column_view, title, prop_name):
        factory = Gtk.SignalListItemFactory()

        factory.connect("setup", lambda factory, item: item.set_child(Gtk.Label(xalign=0)))
        
        def on_bind(factory, list_item):
            label = list_item.get_child()
            user = list_item.get_item()
            label.set_label(getattr(user, prop_name))

        factory.connect("bind", on_bind)

        col = Gtk.ColumnViewColumn(title=title, factory=factory)
        column_view.append_column(col)

    def load_data(self):
        new_items = []
        for i in range(1500):
            new_items.append(User(name=f"User {i}", email=f"user{i}@example.com", city="city123", car="car123", country="country 123"))
        import time;time1=time.time()
        self.model.splice(0, 0, new_items)
        print(time.time()-time1)

def on_activate(app):
    win = AppWindow(app)
    win.present()

app = Gtk.Application(application_id='com.example.abcd')
app.connect('activate', on_activate)
app.run(None)

Hey :slightly_smiling_face:

This topic would make more sense in the platform category.

You should maybe try and see with a profiler where that time that the main thread is blocked for gets spent.

I see a wall of

(python3:44408): Gtk-CRITICAL **: 11:36:44.935: gtk_adjustment_configure: assertion 'lower + page_size <= upper' failed

Otherwise, adding a hundred columns, like this:

        for i in range(100):
            self.add_column(column_view, "Country " + str(i), "country")

makes load_data() report 0.48 (seconds?) here. Not super fast, but doesn’t really block the UI either.

Looking at the flame graph, it spends time creating a bunch of label widgets, which is expected. Also various things related to Gtk.ShortcutController take up a notable chunk of time, which is unexpected. Some small time is expectedly spent in PyGObject runtime.

Things maybe would’ve been faster if it didn’t instantiate GTK_LIST_VIEW_MAX_LIST_ITEMS (i.e. 200) rows upfront, but only like the 10 visible ones, but still, none of this should cause a full UI lock-up.

Please investigate what is being that slow on your end.

It is interesting that things related to `Gtk.ShortcutController` consumes high CPU.

0.48 seconds means about 50% CPU usage (one CPU core that runs the application) if the application updates its GUI every seconds.