Review use of Qt module header includes
Wait a minute…
Having come across sources using include statements for some Qt module headers (like #include <QtDBus>
), memories arose about a check from the static analyzer tool krazy as once run conveniently on KDE’s former ebn.kde.org site. That check, called includes, poked one not to use Qt module headers. Due to resulting in the inclusion of all the headers of those modules, and then again that of the other Qt modules used by the module. Which them meant more stuff to process by the compiler for compilation units with such module header includes.
So is that perhaps in 2023 no longer a real-world noticeable issue? A first look at some preprocessor outputs (with Qt5) for a single line file with just an include statement hinted though it might still be true:
foo.cpp: #include <QtDBus> | foo.cpp.i: 137477 lines |
foo.cpp: #include <QDBusReply> | foo.cpp.i: 86615 lines |
So 50862 more code lines of mainly declarations and inline methods, where a good part might not be needed at all by other code in a file including the header, yet is processed each time. And if such includes are placed in headers, happening for a lot of compilation units. Given most normal source files are shorter, it seemed like as result this difference might still be noticeable given that order of magnitude in the extreme example above..
Wait some minutes less now
The KDE Frameworks module NetworkManagerQt was found to use quite a lot of QtDBus module header includes. While overall following mostly the include-only-what-you-need-and-forward-declare-otherwise mantra. Possibly those includes have been a result of tools generating code and using the module headers to speed up initial development experience.
A patch to replace those QtDBus module header includes with includes of headers as just needed for the classes & namespace used was done. It turned out that the number of additional include statements needed afterwards was rather small, so no bigger costs there.
For a simple test on the real world effects, an otherwise idle developer system, with hot cache for the source files by previous runs, with SSD and old i5-2520M 2.5 GHz CPU, was used. For both variants the build dir would be cleaned by make clean
and then a single job make run started, timed with the time tool, by time make
. The results were this (repeated runs hinted those numbers are representative):
#include <QtDBus> | #include <[headerasneeded]> | |
real (wall clock) | 18m51,032s | 14m6,925s |
user | 17m58,326s | 13m22,964s |
sys | 1m54,234s | 1m26,826s |
So an overall build time reduction by around a 1/4 for a clean(ed) build.
Incremental builds during development should also gain, but not measured, just assumed.
Wait* on the code, to not wait on the build
(*as in waiter)
So in the spirit of the old Krazy includes check, consider to take a look at your codebase if not some Qt module header includes (QtCore
, QtDBus
, QtQml
, QtGui
, QtWidgets
, QtNetwork
, …) have sneaked in which might be simple to replace by “normal” includes.
Note: there is at least one tricky include with QtConcurrent, as that module shares the name with the main C++ namespace. So one might have used #include <QtConcurrent>
, due to used-to Qt patterns and because the API documentation also tells to do. Just, that include gets one the module header, which then also pulls in #include <QtCore>
with all its headers. Looking at the include directory of that module, one can find dedicated headers to use instead, like QtConcurrentRun
. While many codebases e.g. in KDE projects rely on those, they still need to be also officially documented (QTBUG-114663).
In case one would like some more modern automation tool to check for the use of Qt module header includes, take a look at the current work to add a check to the static code analyzer Clazy.
PS: For those into riding their office chair into sword duels… you should have let me win more often?