Wednesday, December 21, 2005

iterating in qt4

qt4 has given us more ways than ever to iterate over collections such as QList: traditional "stl style", java style iterators and foreach. which is best to use? i suggest that foreach is great to use when you need to iterate over the whole collection; java style iterators are often clearer to read and code for, especially when removing elements (or otherwise doing things that would invalidate stl style iterators); stl style is good when performance is an absolute requirement, when you feel like being verbose or you are using stl algorithms (obviously =).

i wrote a small test app the other day to answer the performance quandry. turns out that for collections of POD types, stl and foreach are essentially equivalent (no surprise, since foreach is syntactic sugar for an stl style iteration) while java style is slightly slower. for more complex types (i tested with QList<QString>) foreach is within 10% and java style is within 20% of stl style.

but you have to be careful. for instance:

List::const_iterator itEnd = list.constEnd();

for (List::const_iterator it = list.constBegin();
     it != itEnd;
     ++it)
{
    // do something
}


if you don't (e.g. because you can't) use const_iterators, it's measurably slower. if you don't cache the constEnd you also take a large time penalty. and if you invalidate the iterator while iterating (e.g. you remove an element) you need extra code to ensure your loop doesn't end up resulting in poor results (e.g. crashing)

another example:

foreach (const ListType& i, list)
{
    // do something
}


note the use of "const ListType& i"; with just "ListType i" where ListType is a QString you'll incur a good 40% performance penalty due to the code that foreach expands out to. (thanks to matthias ettrich for pointing this one out to me on irc)

so what are the actual differences in times? iterating over a 1,000,000 element collection of QStrings using each of the three methods results in data that tends to look like this:


list has 1000000 items in it
start for loop: "28 118"
elapsed: 362
start java iterator: "28 481"
elapsed: 422
start foreach: "28 904"
elapsed: 393


the numbers came from running the test app on a 1.7GHz laptop and are in milliseconds, so you can see that we're not talking significant (e.g. user-visible) differences even with a set of a million strings.

3 comments:

ruurd said...

Hmmmm. What was Rule #1 again? Thou shalt not optimize. And Rule #2? Thou shalt not optimize. Yet. :-)

mahoutsukai said...

Since the documentation for QList tells us in form of examples that we should simply use for(int i = 0; i < list.size(); ++i)-loops iterating over QLists it would be interesting to see how this way of iterating over a QList performs in comparison to the other ways.

Aaron J. Seigo said...

@ruurd: indeed. all the same, i've heard people not wanting to use foreach() in kdelibs due to performance concerns. hopefully this addresses that concern.

@mahoutsukai: that style (index based) of iteration is not any faster than foreach or java style iterators. it's not significantly slower either though.