Monday, September 22, 2008

Think before you create GObjects

I had always been hearing that GObjects are slow and it's not always a good idea to use/write them but I never saw any evidence to support that. I had this desire to write a test application to get this evidence but felt too lazy to do it in C. I realized a few days ago that I can write such an app very easily in Vala without giving up much on my laziness. :) So here is an app that I wrote last evening after returning from vacation. Here are the results on my laptop:

$ ./test-perf
0.000182 seconds taken in creating 10000 structs.
0.001598 seconds taken in creating 10000 instances (compact).
0.003522 seconds taken in creating 10000 instances.
0.090455 seconds taken in creating 10000 instances (GObject).


The ranking is exactly how I expected it to be but didn't expect such a big difference between them all.

16 comments:

Hans Petter said...

Did you compile GLib with assertions and type checks turned off? I think that might make a significant difference, but I don't know how big.

mighmos said...

Perhaps this is where someone does more specific profiling to reduce this overhead by 2.26 or 2.28? Or has it already been determined that this is nessissary / very hard?

Rui Tiago said...

Profiling your test program (with the loops bumped to 1M) shows that around 50% of that program's time is spent inside g_object_constructor () which then calls into g_value_copy () and g_value_transform () which both take quite a slice of time.

Also, check http://bugzilla.gnome.org/show_bug.cgi?id=536939

Anonymous said...

You're probably running Linux with a 100Hz-1000Hz scheduler and your program uses elapsed time instead of cpu time, so measuring speed like this and getting result less than 1ms is not significant at all.

Use cpu time (times, /usr/bin/time, etc) and run your benchmark much more longer (let's say at least 10s)

Ben.

cracatau said...

Try to perform it in the reverse order. Processor cache can influence results.

Anonymous said...

Hallo,
very nice, but if you create the same object wich gob2 you get
0.000124 seconds taken in creating 10000 structs.
0.001661 seconds taken in creating 10000 instances (compact).
0.002564 seconds taken in creating 10000 instances.
0.093704 seconds taken in creating 10000 instances (GObject).
0.012818 seconds taken in creating 10000 instances (GObject mit gob2).

And I thing if you make it native the result will be better!

Here is the gob2 code
class Gob:Class from G:Object {
public guint f1;
property INT f1 (
nick = "f1",
default_value = 0,
export, link);
public gdouble f2;
property DOUBLE f2 (
nick = "f2",
default_value = 0.0,
export, link);
public GobClass *
new (guint f1, double f2) {
Self * self = (Self *)GET_NEW;
self->f1 = f1;
self->f2 = f2;
return self;
}
}

Anonymous said...

I'm far away from the C/GObject/Vala camp, but from my Java experience I learned to be careful about those kind of microbenchmarks.

I've tried running all those tests twice in your benchmark (simply copy&paste the four test_* calls) and got an interesting result:

0.000284 seconds taken in creating 10000 structs.
0.002364 seconds taken in creating 10000 instances (compact).
0.004611 seconds taken in creating 10000 instances.
0.086733 seconds taken in creating 10000 instances (GObject).
0.000204 seconds taken in creating 10000 structs.
0.001020 seconds taken in creating 10000 instances (compact).
0.002177 seconds taken in creating 10000 instances.
0.054395 seconds taken in creating 10000 instances (GObject).

For all 4 tests the second invocation is significantly faster. I'm used to these kind of changes in Java, but without a VM in the background, what would be the reason for this?

zeenix said...

Did you compile GLib with assertions and type checks turned off? I think that might make a significant difference, but I don't know how big.

Nope but I doubt that will make a significant difference in this context.

very nice, but if you create the same object wich gob2 you get

You code is different since the props are not being set and after reading the comment from rui and the bug he refered to, I am positive that prop get/set is taking most of the time.

zeenix said...

When I don't set the props in creation method, I get the same results as the gob2 code by Mr. Anonymous above. So g_object_set/get seems to slow thing down a lot more than it should.

That aside, the diff IMHO is still big enough to be careful about inheritence from GObject.

Renato Araujo said...

Hi nice tests.
But this is necessary to buil all structure used on a GObject if you do the same test whith a simple c++ class and a QObject will verify the same result.

zeenix said...

Hi nice tests.

Thanks.

But this is necessary to buil all structure used on a GObject

Why? It all depends on your requirements from the data-structure in question. If you need to create 10,000 instances within a microsecond, a GObject is certainly not what you want your DS to be. There is a reason why GstMiniObject exists in the gstreamer world. :)

if you do the same test whith a simple c++ class and a QObject will verify the same result.

I am not so sure about that but I hey what do I know about C++. :)

Isak said...

I don't know Vaia so I may be wrong here, but your struct allocation is on the stack which means yo miss out the overhead of malloc which you have in the other cases.
You've essentially just proved that two memory assignment instructions are faster than a memory allocation + memory assignment.. ;-)

I suggest you re-write the struct case by creating an array of pointers and then call g_new0 (or whatever vaia equivalent) on each iteration in the loop to make it more fair.

zeenix said...

You've essentially just proved that two memory assignment instructions are faster than a memory allocation + memory assignment.. ;-)

I am not trying to prove anything at all. I only did a small study and shared my findings and conclusion. I know full-well that comparing gobjects with structs is like comparing apples and oranges, all I am saying is that one should be careful in choosing the DS to use. One do not need to create 10,000 objects within a second in most cases so I am all for GObjects in those cases especially when Vala makes it so easy to write them. :)

Andrea Bolognani said...

Structs and compact classes are not registered to the type system, so are not a viable solution in some cases.

I personally think GObject is way too bloated to be the standard base class for all GLib-based libraries.

I would rather prefer to have a lightweight base class, with just the bare minimum (basically, just inheritance and ref counting), and be able to choose the current GObject implementation as an alternative when it really makes sense to do so.

Anonymous said...

Also note that non-construct properties are somewhat optimized in the vala generated creation methods giving slighter better results.
Personally, I don't like the non-gobject type instance generated classes in vala, there is no common base class and in the end we probably will be duplicating some mini like GObject, so you've definitely got my vote in getting something like this in glib.

Clément DAVID said...

I made a little project to benchmark Vala vs C, C++, C# . All the tests came from http://shootout.alioth.debian.org/ .

I think that Vala is performance-ready for classical Object Oriented apps. If you really want high perfs you can recode part of your program in pure C.