treeview tips

While doing my treeview related optimizations, I encountered quite a few things that help improving performance of tree views. (Performance here means both CPU usage and responsiveness of the application.) So in case you’re using tree views or entry completions and have issues with their performance, these tips might help. I often found them to be noticable for tree views with more than 100 rows, though they were usually measurable before that. With 1000 rows or more they do help a lot.

Don’t use GtkTreeModelSort

While GtkTreeModelSort is a very convenient way to implement sorting for small amounts of data, it has quite a bit of overhead, both with handling inserts and deletions. If you’re using a custom model, try implementing GtkTreeSortable yourself. When doing so, you can even use your own sort functions to make sorting really fast. If you are using GtkTreeModelFilter, you should hope this bug gets fixed.

Use custom sort functions whenever possible

As sorting a large corpus of data calls the sort function at least O(N*log(N)) times, it’s a very performance critical function. The default sort functions operate on the GValues and use gtk_tree_model_get() which copies the values for every call. For strings, it even uses g_utf_collate(). This is very bad for sort performance. So especially when sorting strings, it’s vital to have the collated strings available in your model and avoid using gtk_tree_model_get() to get good performance with thousands of rows.

Do not use gtk_tree_view_column_set_cell_data_func()

Unless you exactly know what you are doing, use gtk_tree_view_column_set_attributes(). Cell data function have to fulfill some assumptions (that probably aren’t documented) and are very performance sensitive. The implicit assumption is that they set identical properties on the cell renderer for the same iter – until the iter is changed with gtk_tree_model_row_changed(). I’ve seen quite a few places with code like this:

/* FIXME: without this the tree view looks wrong. Why? */
gtk_widget_queue_resize (tree_view);

The performance problem is that this function is called at least once for every row when figuring out the size of the model, and again whenever a row needs to be resized or rendered. So better don’t do anything that takes longer than 0.5ms. Certainly don’t load icons here or anything like this…

Don’t do fancy stuff in your tree model’s get_value function

This applies when you write your own tree model. The same restrictions as in the cell data functions apply here. Doing them wrong has the same problems as I said in the last paragraph, as this function is more low level; it gets called O(N*log(N)) times when sorting for example. Luckily there’s a simple way around it: Cache the values in your model, that’s what it’s supposed to do. And you don’t forget to call gtk_tree_model_row_changed() when a value changes.

Batch your additions

This mostly applies when writing your own tree model or when sorting. If you want to add lots of rows, it is best to first add all the rows, then make sure all the values are correct, then resort the model and only then emit thegtk_tree_model_row_added() signal for all the rows. When not writing a custom model, use gtk_list_store_insert_with_values() and gtk_tree_store_insert_with_values(). These functions do all of this already.

Fix sizes of your cell renderers if possible

This one is particularly useful when using tree views with only one column. If you have a know width and height in advance, you can use gtk_cell_renderer_set_fixed_size() to fix the size of the cell renderer in advance. This will cause the tree view to not call the get_size function for the cell renderer for every row. And this will make in particular text columns a lot quicker, because there’s no need to layout the text to compute its size anymore.
A good example for where this is really useful is GtkEntryCompletion when displaying text. As the width of the entry completion is known in advance (as big as the entry you are completing on) and the height of a cell displaying text is always the same, you can use this code on the cell renderer:

gtk_cell_renderer_set_fixed_size (text_renderer, 1, -1);
gtk_cell_renderer_text_set_fixed_height_from_font (text_renderer, 1);

Now no get_size functions will be called at all and the completion popup will still look the same.

And with these tips, there should be no reason to not stuff millions of rows into a tree view and be happy. :)

8 comments ↓

#1 lariq on 07.10.09 at 19:38

GtkTreeView can give you a hard time. Especially if you do things like using a custom model forwarded to a GtkTreeViewFilter with a custom view and then you want to do drag’n’drop from different sources. Fun

#2 Andreas Tunek on 07.10.09 at 20:09

You rock! Please keep up the good work!

#3 Benoît on 07.10.09 at 20:54

I’m quite disappointed about gtk_tree_view_column_set_cell_data_func . A while ago, i moved a lot of code to it, thinking it was the best way to format numbers. Now you tell me it’s quite slow …

#4 otte on 07.10.09 at 21:07

Benoît, that is one of the cases where it probably is useful. In fact, this is what the _set_attributes() function does when you set a number column as the text property of a text cell renderer.
You just have to be aware of the issues I pointed out above.

#5 Rui on 07.10.09 at 21:28

Ei, nice tips! Maybe some of this text should go into the gtk+ docs?

#6 Richard on 07.11.09 at 00:45

Is it possible to “fix” GtkTreeView to avoid these issues?

#7 otte on 07.12.09 at 11:30

While there might be some optimizations that can be done to rduce the pain, when people do bad things, in general these problems cannot be “fixed”. All of the things I mentioned are things that are at least O(n), so need to be done at least once per row. And the more rows you add, the more time it takes.

So in the end this blog post is just a call to people to be aware that a lot of stuff they do in tree views is stuff that’s in an inner loop. And inner loops just have to be fast.

#8 Stefan Kost on 07.16.09 at 10:58

Isn’t gtk_cell_renderer_set_fixed_size(renderer, 1, -1); setting the width to “1”?