16 Software
Breevy: A text expander that lets you insert long words or phrases and launch apps, websites, and more just by typing abbreviations.
IcyScreen: Automatic screenshots. Have them saved, e-mailed, and uploaded.

Even faster row-insertion in GTK+: Sort it yourself!

A little followup to my last blog post, I actually discovered that temporarily disabling sorting in your list or tree store before populating it:

     gtk_tree_sortable_get_sort_column_id(sortable, &sort_col, &sort_order);
gtk_tree_sortable_set_sort_column_id(sortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID,
GTK_SORT_ASCENDING);

... and resetting the sort column after populating:

     gtk_tree_sortable_set_sort_column_id(sortable, sort_col, sort_order);

... is slightly faster than just calling gtk_list_store_insert_with_values()... about 8-10% faster by my calculation, actually. So it's definitely worth doing.

But still, even with this optimization, and the optimization I talked about in my last blog post, populating a sorted store with thousands of rows was just still way too slow IMO. Unacceptably slow.

So the other day I wondered if it would be faster to:

  • Turn off sorting permanently by setting the sort column ID to GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID.
  • Manually sort the data in memory before populating the store.
  • Insert the rows into the unsorted store in the order that the data was sorted in memory.

I figured it was worth a shot... it didn't take too long to implement at all, and I was happy to find I was right: This method is just barely slower than populating an unsorted store without sorting the abbreviations in memory first, and about 10x faster overall -- very, very noticeable -- than when letting GTK+ do the sorting for me. A *huge* speedup. Totally sweet.

Of course, it's slightly more inconvenient, and there's also small bit of work involved, to visually simulate what GTK+ does when you click a sortable column's button, but it's not difficult at all... it's basically a matter of manipulating the TreeViewColumn's arrow widget with gtk_widget_show() and gtk_arrow_set().

Another benefit to handling the sorting yourself: By default, GTK+ darkens the sort column if there are more than 3 sortable columns in a tree view, and there's not any way to avoid this yet (though I submitted a patch that created a style property that would let you disable this). Handling the sorting yourself means no darkened sort column.

I'm really happy about this major speedup, because I had a long list of abbreviations (over 13,500) for medical transcriptionists that I really wanted to include with Breevy, but before this optimization (<= 2.05), they took far too long to load in the main window, so I put off including them until I could find a way to speed things up.

But now, with this speedup included in version 2.06 (out now), they load in about half a second on a 6+ year old Pentium 4 @ 2.8GHz... so the medical transcription list is now included with Breevy. 8)

So to wrap this post up: If you're going to be inserting thousands of rows into a GtkTreeStore or GtkListStore, and need some sort of sorting, I would definitely recommend handling the sorting yourself instead of letting GTK+ do it. I love GTK+, but this is one area where I think GTK+ could use some improvement, performance wise. hahahah

Side note: GTK+'s default column sort function uses g_utf8_collate() when comparing string data. This function is pretty darn slow (it's the nature of the beast, apparently). If you don't need any of it's features, I would definitely implement your own sorting function for your sorted columns and use strcmp/strcasecmp instead. You'll get a pretty noticeable speedup from this optimization alone, though not nearly as much as if you were to handle all of the sorting yourself.

You might check out the g_utf8_collate_key() function if you still want to use g_utf8_collate().

Posted by Patrick on November 17, 2009 at 3:32pm | 0 Comments
Tagged: , and

Faster sorted-store row insertion in GTK+.

So I was adding a huge list of abbreviations (over 15K) to Breevy about a week ago and noticed that it took *way* too long for the GtkListStore to be populated. Way too long, as in over a minute on a 3GHz P4.

After a bit of investigating I finally figured out that the bottleneck was the fact that the list store was a sorted store, and that it appeared GTK+ was resorting the store after each row insertion. I turned off sorting, and populating the store was almost instant.

So first I worked around this issue by setting the sort column ID for the list store to GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID before populating. This disabled any sorting during the insertions. Then, after the insertions, I set the sort column ID back to what it was before, triggering a final sort of all of the inserted rows. I knew this was a bit hackish, but it worked.

But then I found out how to do it The Right Way: by calling the gtk_list_store_insert_with_values() function. Basically, you should use this instead of a gtk_list_store_append() and gtk_list_store_set() when inserting rows into a sorted store because these two latter functions basically tell the list store to resort itself after each new row is inserted (i.e., number of sorts == number of rows inserted)... something that's not necessary, and something that takes a heckuva long time.

The speedup I experienced using the insert_with_values() function was absolutely massive. Thanks to the folks on #gtk+ on irc.gnome.org for pointing the function out to me.

Posted by Patrick on November 10, 2009 at 4:03pm | 0 Comments
Tagged: and

"Dynamic" label wrapping in GTK

As it stands, the wrapping functionality of a GtkLabel isn't the best. You can call gtk_label_set_line_wrap() on your label, and it will wrap... but it'll wrap at a fixed width, and not dynamically. In other words, the label won't rewrap it's text as the parent container of the label (for example, the program window) changes size, as does text in a GtkTextView, or text in your typical text editor.

Here's an example of how GTK wraps a label normally:

GTK label wrapping without workaround

Here's an example of how the wrapping will look if you use the workaround I describe below:

GTK label wrapping with workaround

For the longest time I wondered why this was, and how to fix it, until I finally asked if there was a solution a while back on the #gtk IRC channel. I was provided with an example for a workaround, and thought I'd share it. It's hackish, for sure, and it's not perfect, but all you have to do is connect your GtkLabel (with line wrapping enabled, using gtk_label_set_line_wrap()) to a callback for the "size-allocate" signal:

     g_signal_connect(G_OBJECT(label), "size-allocate",
G_CALLBACK(wrapped_label_size_allocate_callback), NULL);

The callback should look like this:

void wrapped_label_size_allocate_callback(GtkWidget *label,
GtkAllocation *allocation,
gpointer data) {

gtk_widget_set_size_request(label, allocation->width, -1);
}

Apparently, (and according to the GTK+ docs) GtkLabels can't/don't detect when their parent has modified their size allocation. By connecting the label widget to the "size-allocate" signal, we can detect when this modification occurs, and then call gtk_widget_set_size_request() to tell GTK we want the label to utilize all of that allocated space.

There's one flaw with this method, and that is, you'll no longer be able to make your parent GtkWindow any smaller than the current width/height of the label, because the GtkLabel will be forcing it to keep a minimum width/height of however much space it requested with the gtk_widget_set_size_request() call. So you'll never be able to shrink the parent window, and if you make the window bigger, it'll stay at least that big, forever -- at least, until the window is recreated. Which isn't necessarily bad, especially if you've forbidden window resizing with a call to gtk_window_set_resizable().

A workaround for this issue is to utilize the gtk_window_set_policy() function, to unconditionally allow both window growing and shrinking:

     gtk_window_set_policy(GTK_WINDOW(win), TRUE, TRUE, FALSE);

Unfortunately, this brings up issues of it's own: it'll allow the user to make the window as small as he/she wants to (even 1x1), instead of restricting it to the minimum size necessary to show the entirety of all of the widgets in the window, as is the default GTK functionality. You can "fix" this by calling gtk_window_set_geometry_hints() with a minimum size for the window:

     GdkGeometry geom;
geom.min_width = 175;
geom.min_height = 50;

gtk_window_set_geometry_hints(GTK_WINDOW(win), GTK_WIDGET(win),
&geom, GDK_HINT_MIN_SIZE);

The only caveat is that you're forced to provide a hardcoded value.

A better solution?

In many situations, including in some of mine, these benefits of the workarounds outweigh the caveats. If there's anyone out there who knows of fix for the label wrapping issue that gives you the best of both worlds -- "dynamic" wrapping, as well as the ability to make the window shrink while still showing the entirety of all widgets, without forcing a minimum hardcoded value -- I'd love to hear your solution. I've had a few ideas, but all seem overly complex...

Edited on 4/25/09 to make the windows in both screenshots look a bit nicer.

Posted by Patrick on April 24, 2009 at 1:47pm | 11 Comments
Tagged: , , , and

 1