Use of the pure attribute for GObject convenience getters

In my quest to make my code completely correct and beautiful, largely through the liberal application of obscure and anal-retentive attributes like __warn_unused_result__ and __malloc__, I've hit a problem. Is it safe and correct to use __pure__ with GObject convenience getter functions?

As far as the literature explains it, “pure” C functions (no relation to pure mathematical or functional functions) can access pointers and global memory, but must not write to them, and cannot use system resources. In return, gcc can apply extra optimisations to calls to pure functions, since it can assume the return value of a pure function (for a given set of arguments) will not change, no matter how many times the function is called, until memory is written or a non-pure function is called. This allows for elimination of redundant calls to pure functions (since they're known to not have any side-effects) or, conversely, for gcc to add in calls to a pure function in preference to storing the result in memory, if it thinks that will perform better. Furthermore, gcc can perform loop optimisations on loops which just use pure functions.

This is all good, and I think I've done a good job of regurgitating the gcc manual here, but it doesn't answer the question. There are loads of GObject-based libraries out there, and none of them (as far as I can tell) are using pure convenience getters. I must be missing something from the wisdom of the masses.

The only situation I can think of where using the __pure__ attribute on a convenience getter function could result in incorrect code is if the object in question is modified from a thread (2) other than the one (1) calling the getter. For example, thread 1's frobnicate() function calls my_object_get_property_foo() a couple of times in a function at the same time as thread 2 calls my_object_set_property_foo(). Normally, let's say, the first call to my_object_get_property_foo() would return the old value, and the second call would return the new value. If my_object_get_property_foo() was a pure function, though, the compiler could potentially optimise out the second call, and so thread 1 would never see the new value of the “foo” property.

There are fairly few places where this is the desired behaviour with or without use of __pure__, though. The frobnicate() function is likely to have locking in this situation, which would prevent the race condition as normal, while still allowing the compiler to optimise out some of the calls to the pure my_object_get_property_foo() function.

In summary, what I'm proposing is that people use the G_GNUC_PURE attribute on their GObject convenience getter functions as much as appropriate. From my tests adding the attribute to functions in libgdata, it doesn't make much of a difference in performance but does reduce the code size of applications which use libgdata. (These tests weren't particularly thorough, though; they were actually just done against one function, looking at the disassembly of a test program which called it.)

Just as an example, here's a pure convenience getter function I wrote earlier:

GList *gdata_entry_get_categories (GDataEntry *self) G_GNUC_PURE;

GList *
gdata_entry_get_categories (GDataEntry *self)
{
	g_return_val_if_fail (GDATA_IS_ENTRY (self), NULL);
	return self->priv->categories;
}

On another note, that return type should really be const GList*, but GLib's list functions aren't const-correct.

8 thoughts on “Use of the pure attribute for GObject convenience getters

  1. Christian

    I take it the header is where the attribute needs to be applied so that calling/linking code can be optimized appropriately?

    1. Philip Withnall Post author

      Yeah; I just merged the declaration into the definition in the example to make it shorter. I think I'll split them up, since it isn't clear enough this way.

  2. db

    Declaring these functions pure only saves if two calls to the function occur in succession (or if the return value isn't used -- but why call "get" then?) -- so the gain is extremely marginal. not worth the typing.

    1. Philip Withnall Post author

      There are more situations than that which could be optimised, but you're right that it probably wouldn't give many gains. Still, it's only 12 characters per function.

  3. fscan

    as i understand it, methods with locking can never be __pure__, because a lock is a system resource. and how should the compiler know at compile time if an other thread has changed the value, even with locking?
    the only methods i can think of using pure are getters for variables that do not change or methods that do not access object members.

    1. Philip Withnall Post author

      In my example, the my_object_get_property_foo() would be the pure one, while frobnicate() (which is calling it) has the locking. I expect that would be the normal solution, since there'd be a potential race in frobnicate() otherwise.

      You are correct though: functions with locking can never be pure, because the locks use volatile memory.

      My argument is that pure could be used for all trivial getters of an object, under the assumption that the object is only modified from one thread.

      1. fscan

        this only works if my_object_get_property_foo() is only called from frobnicate() (or you have to somehow guarantee that it is called between locks, assuming my_object_set_property_foo() locks too). locking outside of my_object would not work, because there is nothing to tell the compiler the value could have changed.

        so, the only _save_ use is the single threaded case (modified and read from 1 thread)...

        1. Philip Withnall Post author

          That's my conclusion, yes. Of course, objects using pure getters could still be used in a multi-threaded app, as long as all changes to them were marshalled through the main thread.

Comments are closed.