Skip to content

Beware of Qt Module-wide Includes Save the planet by reducing compilation times!

You know the drill: in C++ you need to #include header files that declare the types and functions from the libraries that you use. Qt is no exception in this regard. Qt follows a very nice convention for its own datatypes:

if you need to use a type called QType, then use #include <QType>.

This is very nice, as you don’t need to guess the header name! Just include exactly the type that you want to use. You need QUrl? #include <QUrl>. You need QQmlComponent? #include <QQmlComponent>. Easy peasy.

(It’s extremely nicer, than, say, the organization of the headers in C++ Standard Library — quick quiz, which header do you need to include in order to use std::packaged_task?)

If you want to avoid clashes, you can even be more explicit: for a type QType in the module QtModule you can use #include <QtModule/QType>; that is, prefix the header name with the module it belongs to. You can find the module’s name in the documentation of each datatype in Qt. The examples above would then be written as #include <QtCore/QUrl> and #include <QtQml/QQmlComponent>, respectively.

There is even a third way: you can simply use #include <QtModule>, and this will include all the headers in that Qt module.

Wait, won’t that include a lot of headers?

That is absolutely correct. And to be pedantic, such an inclusion won’t simply include all the headers in that Qt module; it will also include all the headers from any module that module depends on!

If you peek for instance into the QtQuick header, one finds this:

#ifndef QT_QTQUICK_MODULE_H
#define QT_QTQUICK_MODULE_H
#include <QtQuick/QtQuickDepends>
#include "qtquickglobal.h"
#include "qquickframebufferobject.h"
#include "qquickimageprovider.h"
#include "qquickitem.h"
// .. many more includes

Line 3 includes, transitively, all the headers in the dependencies of the QtQuick module:

// ...
#include <QtCore/QtCore>
#include <QtGui/QtGui>
#include <QtQml/QtQml>
#include <QtQmlModels/QtQmlModels>
// ...

In turn, including QtQml will include QtNetwork’s headers, and so on. You can easily end up including thousands of header files! This will make your compilation times skyrocket.

Surely you can’t be serious?

I am serious, and don’t call me Shirley The module-wide header inclusion was introduced for a good reason: to exploit precompiled headers (PCH). Precompiled headers are a way to speed up compilation by having the compiler parse some headers and save them in a (binary) format, which is much faster for the compiler to reload later. When compiling some other file, the compiler will then load the precompiled header file and save time (compared to re-parsing the headers).

The theory goes like this: Qt can be built with support for precompiled headers (e.g. with the configure’s -pch command line option). In such a setup, each Qt module should generate a precompiled header, and using the module-wide inclusion (and only the module-wide inclusion!) would then use the precompiled header for that module, resulting in a significant speedup when compiling a project.

This actually is not the case! Support for precompiled headers does exist in Qt, but it’s only for Qt’s internal consumption (when Qt itself gets built). Projects using Qt won’t find any precompiled headers for them to use. So, actually using the module-wide inclusion is going to be dreadful!

The QtTest fiasco

Now, I shouldn’t be here telling you this story — it’s very rare to actually encounter such module-wide includes in “ordinary” software using Qt (which rightfully follows “include what you use“, and other similar good coding policies).

There’s, however, a major offender found all over the place: the Qt Test module and its module-wide inclusion. It’s extremely easy to misspell the right inclusion for QtTest functions, which results in the wrong inclusion getting used everywhere because, alas, test code is often copied and pasted.

The wrong way to include QtTest is:

// !!! WRONG !!! 
// This includes all QtTest headers, plus all of QtCore,
// plus other bits!
#include <QtTest>

// Same:
#include <QtTest/QtTest>

The right way is:

// Standalone:
#include <QTest>

// Same; if you prefer, with the module prefix:
#include <QtTest/QTest>

Yes, the difference is just a little ‘t’ that may slip in when typing. That little difference, on a codebase that has a significant amount of tests, has a noticeable impact.

For instance, Qt’s own auto-tests are plagued by the QtTest module-wide inclusion; in qtbase alone there are ~580 occurrences of #include <QtTest> in 775 C++ source files, about 75%. After a search and replace to fix these directives and some rounds on the CI to manually re-add all the includes statements (that were accidentally added transitively), the build times for the Qt test suite dropped by ~15%! Assuming a mostly CPU-bound build, that’s a 15% power save.

Sure, it’s still dwarfed by the running time of the tests (Qt has lots of GUI tests, which cannot be easily parallelized), but do your part, fix the QtTest includes today, and save energy!

Thanks for reading.

About KDAB

If you like this blog and want to read similar articles, consider subscribing via our RSS feed.

Subscribe to KDAB TV for similar informative short video content.

KDAB provides market leading software consulting and development services and training in Qt, C++ and 3D/OpenGL. Contact us.

FacebookTwitterLinkedInEmail

Categories: C++ / KDAB Blogs / KDAB on Qt / Qt / QtDevelopment / Technical

Tags: /

2 thoughts on “Beware of Qt Module-wide Includes”

  1. In our codebase, replacing module includes with ‘normal’ ones resulted in a 5% faster build. Not as impressive as the 15% mentioned here, but still very welcome. I was always looking to reduce our own code, but once you look at the preprocessed source, what is actually compiled ( -save-temps), it’s clear that includes make up most of it code. Thank you mentioning this!

    1. Giuseppe D'Angelo

      Hi,
      Glad that it helped. A further step you might want to evaluate is using precompiled headers; with a reasonably recent CMake, it’s actually super easy, and that’s where you might want to resort to use module-wide include again (to generate the precompiled header).

Leave a Reply

Your email address will not be published. Required fields are marked *