-
Notifications
You must be signed in to change notification settings - Fork 184
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Optimisation of compilation speed with CMake/Ninja #2877
Conversation
clang-tidy review says "All clean, LGTM! 👍" |
clang-tidy review says "All clean, LGTM! 👍" |
Managed to obtain further improvements by enabling PCHs for executables too (previously they were only used for the shared libraries).
|
clang-tidy review says "All clean, LGTM! 👍" |
clang-tidy review says "All clean, LGTM! 👍" |
core/core_pch.h
Outdated
@@ -0,0 +1,3 @@ | |||
#include "exception.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should decide whether inclusion of MRtrix header files here is to be based on pragmatism (ie. what provides the greatest speedup vs. is least likely to be modified) vs. some sort of principle (ie. what are "the most fundamental" core header files). There might be an argument to be made for core/*.h
(ie. root of core/
but not its sub-directories). But I'm not currently able to form a robust opinion on this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a nice balance between stability and speed gain should be prioritised. Additionally, we should probably include header files that are likely to affect most of the codebase. For example, this was the reasoning to add #include "exception.h"
, since it showed up in ClangBuilderAnalyzer
's report and I think there are little chances for a developer to modify it. Furthermore, this header is indirectly included in the majority of our codebase and thus even without precompiled headers, its modification will trigger a recompilation of a substantial chunk of the project (on this point, it's worth noting that you can always turn off PCHs by setting cmake -DMRTRIX_USE_PCH=OFF
).
Overall, I do think that we should be very conservative with the inclusion of user headers in a PCH.
Are you able to glean insight into what it is that makes the |
Looking at the output of Here is the json trace file for the command obtained by compiling with Hence I think the best course of action would be to split this file into multiple files (or at least move parts of its content somewhere else) so that the compiler can operate multithreaded. |
Further finding on |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
clang-tidy made some suggestions
src/registration/transform/base.cpp
Outdated
namespace MR::Registration::Transform { | ||
|
||
Base::Base(size_t number_of_parameters) | ||
: number_of_parameters(number_of_parameters), optimiser_weights(number_of_parameters), nonsymmetric(false) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: narrowing conversion from 'size_t' (aka 'unsigned long') to signed type 'Eigen::VectorXd' (aka 'int') is implementation-defined [bugprone-narrowing-conversions]
: number_of_parameters(number_of_parameters), optimiser_weights(number_of_parameters), nonsymmetric(false) {
^
src/registration/transform/base.cpp
Outdated
|
||
Eigen::Matrix<default_type, 4, 1> Base::get_jacobian_vector_wrt_params(const Eigen::Vector3d &p) const { | ||
throw Exception("FIXME: get_jacobian_vector_wrt_params not implemented for this metric"); | ||
Eigen::Matrix<default_type, 4, 1> jac; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: variable 'jac' is not initialized [cppcoreguidelines-init-variables]
Eigen::Matrix<default_type, 4, 1> jac; | |
Eigen::Matrix<default_type, 4, 1> jac = 0; |
src/registration/transform/base.cpp
Outdated
} | ||
|
||
Eigen::Transform<Base::ParameterType, 3, Eigen::AffineCompact> Base::get_transform() const { | ||
Eigen::Transform<ParameterType, 3, Eigen::AffineCompact> transform; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: variable 'transform' is not initialized [cppcoreguidelines-init-variables]
Eigen::Transform<ParameterType, 3, Eigen::AffineCompact> transform; | |
Eigen::Transform<ParameterType, 3, Eigen::AffineCompact> transform = 0; |
|
||
void Base::compute_offset() { trafo.translation() = (trafo.translation() + centre - trafo.linear() * centre).eval(); } | ||
|
||
void Base::compute_halfspace_transformations() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: method 'compute_halfspace_transformations' can be made const [readability-make-member-function-const]
void Base::compute_halfspace_transformations() { | |
void Base::compute_halfspace_transformations() const { |
src/registration/transform/base.h:177:
- void compute_halfspace_transformations();
+ void compute_halfspace_transformations() const;
src/registration/transform/base.cpp
Outdated
void Base::compute_offset() { trafo.translation() = (trafo.translation() + centre - trafo.linear() * centre).eval(); } | ||
|
||
void Base::compute_halfspace_transformations() { | ||
Eigen::Matrix<ParameterType, 4, 4> tmp; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: variable 'tmp' is not initialized [cppcoreguidelines-init-variables]
Eigen::Matrix<ParameterType, 4, 4> tmp; | |
Eigen::Matrix<ParameterType, 4, 4> tmp = 0; |
I noticed that Another finding is that the main culprit of large compilation times for |
612a14a
to
8a827a8
Compare
mrregister and mrtransform can take a long time to build. If their compilation starts towards the end of the build, Ninja may be compiling a single source file for quite sometime. This may result in suboptimal compilation times (due to part of the compilation not being multi-threaded). This hack tries to avoid this by starting their compilation as soon as possible (atm there's no alternative nice way of doing this in Ninja or CMake).
We use extern template to reduce the number of instantiations of some templates to reduce compilation time.
This is now ready for review. I've cleaned up a few things and added more explicit instantiations to reduce the build time for the registration code. The compilation of A point of concern, raised by @jdtournier, was that explicit instantiation of a templated function in a .cpp file will lead the compiler to not inline that function. While it's true that @Lestropie @jdtournier any comments are welcome. |
I would have just pushed the change but I wasn't able to verify compilation because of:
|
This avoids their recompilation every time the project is configured
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
clang-tidy made some suggestions
centre.setZero(); | ||
} | ||
|
||
Eigen::Matrix<default_type, 4, 1> Base::get_jacobian_vector_wrt_params(const Eigen::Vector3d &) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: all parameters should be named in a function [readability-named-parameter]
Eigen::Matrix<default_type, 4, 1> Base::get_jacobian_vector_wrt_params(const Eigen::Vector3d &) { | |
Eigen::Matrix<default_type, 4, 1> Base::get_jacobian_vector_wrt_params(const Eigen::Vector3d & /*unused*/) { |
src/registration/transform/base.h
Outdated
Eigen::Transform<ParameterType, 3, Eigen::AffineCompact> trafo_half; | ||
Eigen::Transform<ParameterType, 3, Eigen::AffineCompact> trafo_half_inverse; | ||
Eigen::Vector3d centre; | ||
Eigen::Transform<ParameterType, 3, Eigen::AffineCompact> trafo{}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: member variable 'trafo' has protected visibility [cppcoreguidelines-non-private-member-variables-in-classes]
Eigen::Transform<ParameterType, 3, Eigen::AffineCompact> trafo{};
^
src/registration/transform/base.h
Outdated
Eigen::Transform<ParameterType, 3, Eigen::AffineCompact> trafo_half_inverse; | ||
Eigen::Vector3d centre; | ||
Eigen::Transform<ParameterType, 3, Eigen::AffineCompact> trafo{}; | ||
Eigen::Transform<ParameterType, 3, Eigen::AffineCompact> trafo_half{}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: member variable 'trafo_half' has protected visibility [cppcoreguidelines-non-private-member-variables-in-classes]
Eigen::Transform<ParameterType, 3, Eigen::AffineCompact> trafo_half{};
^
src/registration/transform/base.h
Outdated
Eigen::Vector3d centre; | ||
Eigen::Transform<ParameterType, 3, Eigen::AffineCompact> trafo{}; | ||
Eigen::Transform<ParameterType, 3, Eigen::AffineCompact> trafo_half{}; | ||
Eigen::Transform<ParameterType, 3, Eigen::AffineCompact> trafo_half_inverse{}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: member variable 'trafo_half_inverse' has protected visibility [cppcoreguidelines-non-private-member-variables-in-classes]
Eigen::Transform<ParameterType, 3, Eigen::AffineCompact> trafo_half_inverse{};
^
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure about this check. While I understand that having classes with private and public data members can be undesirable, I don't really find this to be a huge concern. Especially in a codebase like ours.
src/registration/transform/base.h
Outdated
Eigen::Transform<ParameterType, 3, Eigen::AffineCompact> trafo{}; | ||
Eigen::Transform<ParameterType, 3, Eigen::AffineCompact> trafo_half{}; | ||
Eigen::Transform<ParameterType, 3, Eigen::AffineCompact> trafo_half_inverse{}; | ||
Eigen::Vector3d centre{}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: member variable 'centre' has protected visibility [cppcoreguidelines-non-private-member-variables-in-classes]
Eigen::Vector3d centre{};
^
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
clang-tidy made some suggestions
There were too many comments to post at once. Showing the first 25 out of 56. Check the log or trigger a new build to see more.
} | ||
|
||
std::string Config::get(const std::string &key, const std::string &default_value) { | ||
KeyValues::iterator i = config.find(key); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: variable 'i' of type 'KeyValues::iterator' (aka '_Rb_tree_iterator<std::pair<const std::basic_string, std::basic_string>>') can be declared 'const' [misc-const-correctness]
KeyValues::iterator i = config.find(key); | |
KeyValues::iterator const i = config.find(key); |
core/file/utils.cpp
Outdated
|
||
namespace { | ||
inline char random_char() { | ||
char c = rand() % 62; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: variable 'c' of type 'char' can be declared 'const' [misc-const-correctness]
char c = rand() % 62; | |
char const c = rand() % 62; |
core/file/utils.cpp
Outdated
|
||
namespace { | ||
inline char random_char() { | ||
char c = rand() % 62; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: narrowing conversion from 'int' to signed type 'char' is implementation-defined [bugprone-narrowing-conversions]
char c = rand() % 62;
^
core/file/utils.cpp
Outdated
|
||
namespace { | ||
inline char random_char() { | ||
char c = rand() % 62; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: function is not thread safe [concurrency-mt-unsafe]
char c = rand() % 62;
^
inline char random_char() { | ||
char c = rand() % 62; | ||
if (c < 10) | ||
return c + 48; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: narrowing conversion from 'int' to signed type 'char' is implementation-defined [bugprone-narrowing-conversions]
return c + 48;
^
|
||
void remove(const std::string &file) { | ||
if (std::remove(file.c_str())) | ||
throw Exception("error deleting file \"" + file + "\": " + strerror(errno)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: function is not thread safe [concurrency-mt-unsafe]
throw Exception("error deleting file \"" + file + "\": " + strerror(errno));
^
core/file/utils.cpp
Outdated
DEBUG(std::string("creating ") + (size ? "" : "empty ") + "file \"" + filename + "\"" + | ||
(size ? " with size " + str(size) : "")); | ||
|
||
int fid; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: variable 'fid' is not initialized [cppcoreguidelines-init-variables]
int fid; | |
int fid = 0; |
(size ? " with size " + str(size) : "")); | ||
|
||
int fid; | ||
while ((fid = open(filename.c_str(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: do not call c-style vararg functions [cppcoreguidelines-pro-type-vararg]
while ((fid = open(filename.c_str(),
^
INFO("file \"" + filename + "\" already exists - removing"); | ||
remove(filename); | ||
} else | ||
throw Exception("error creating output file \"" + filename + "\": " + std::strerror(errno)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: function is not thread safe [concurrency-mt-unsafe]
throw Exception("error creating output file \"" + filename + "\": " + std::strerror(errno));
^
throw Exception("error creating output file \"" + filename + "\": " + std::strerror(errno)); | ||
} | ||
if (fid < 0) { | ||
std::string mesg("error creating file \"" + filename + "\": " + strerror(errno)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: function is not thread safe [concurrency-mt-unsafe]
std::string mesg("error creating file \"" + filename + "\": " + strerror(errno));
^
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
clang-tidy made some suggestions
There were too many comments to post at once. Showing the first 25 out of 31. Check the log or trigger a new build to see more.
core/file/utils.cpp
Outdated
throw Exception(mesg); | ||
} | ||
|
||
if (size) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: implicit conversion 'int64_t' (aka 'long') -> bool [readability-implicit-bool-conversion]
if (size) | |
if (size != 0) |
core/file/utils.cpp
Outdated
size = ftruncate(fid, size); | ||
close(fid); | ||
|
||
if (size) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: implicit conversion 'int64_t' (aka 'long') -> bool [readability-implicit-bool-conversion]
if (size) | |
if (size != 0) |
close(fid); | ||
|
||
if (size) | ||
throw Exception("cannot resize file \"" + filename + "\": " + strerror(errno)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: function is not thread safe [concurrency-mt-unsafe]
throw Exception("cannot resize file \"" + filename + "\": " + strerror(errno));
^
core/file/utils.cpp
Outdated
void resize(const std::string &filename, int64_t size) { | ||
DEBUG("resizing file \"" + filename + "\" to " + str(size)); | ||
|
||
int fd = open(filename.c_str(), O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: variable 'fd' of type 'int' can be declared 'const' [misc-const-correctness]
int fd = open(filename.c_str(), O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); | |
int const fd = open(filename.c_str(), O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); |
core/file/utils.cpp
Outdated
void resize(const std::string &filename, int64_t size) { | ||
DEBUG("resizing file \"" + filename + "\" to " + str(size)); | ||
|
||
int fd = open(filename.c_str(), O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: do not call c-style vararg functions [cppcoreguidelines-pro-type-vararg]
int fd = open(filename.c_str(), O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
^
|
||
int status = size ? ftruncate(fid, size) : 0; | ||
close(fid); | ||
if (status) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: implicit conversion 'int' -> bool [readability-implicit-bool-conversion]
if (status) | |
if (status != 0) |
int status = size ? ftruncate(fid, size) : 0; | ||
close(fid); | ||
if (status) | ||
throw Exception("cannot resize file \"" + filename + "\": " + strerror(errno)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: function is not thread safe [concurrency-mt-unsafe]
throw Exception("cannot resize file \"" + filename + "\": " + strerror(errno));
^
} | ||
|
||
void mkdir(const std::string &folder) { | ||
if (::mkdir(folder.c_str() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: implicit conversion 'int' -> bool [readability-implicit-bool-conversion]
core/file/utils.cpp:204:
- ))
+ ) != 0)
0777 | ||
#endif | ||
)) | ||
throw Exception("error creating folder \"" + folder + "\": " + strerror(errno)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: function is not thread safe [concurrency-mt-unsafe]
throw Exception("error creating folder \"" + folder + "\": " + strerror(errno));
^
throw Exception("error creating folder \"" + folder + "\": " + strerror(errno)); | ||
} | ||
|
||
void rmdir(const std::string &folder, bool recursive) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: function 'rmdir' is within a recursive call chain [misc-no-recursion]
void rmdir(const std::string &folder, bool recursive) {
^
Additional context
core/file/utils.cpp:208: example recursive call chain, starting from function 'rmdir'
void rmdir(const std::string &folder, bool recursive) {
^
core/file/utils.cpp:215: Frame #1: function 'rmdir' calls function 'rmdir' here:
rmdir(path, true);
^
core/file/utils.cpp:215: ... which was the starting point of the recursive call chain; there may be other cycles
rmdir(path, true);
^
CMake seems to have issue with using PCHs together with sccache on Mac. Disabling them also is a good test to ensure that we can compile the project with MRTRIX_USE_PCH=OFF
Windows 11 machine through MSYS2, 16 cores, Ninja 1.12 (already available on MSYS2), average of 3 runs:
Can't currently test on my work Linux laptop as it's partway through a script doing 72 I agree regarding abuse of inlining / too many implementations in header files / would benefit from use of |
Conflicts: core/file/utils.h
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Merge conflicts resolved; should be good to go. Nice work!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
clang-tidy made some suggestions
|
||
namespace { | ||
inline char random_char() { | ||
const char c = rand() % 62; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: narrowing conversion from 'int' to signed type 'char' is implementation-defined [bugprone-narrowing-conversions]
const char c = rand() % 62;
^
|
||
namespace { | ||
inline char random_char() { | ||
const char c = rand() % 62; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: function is not thread safe [concurrency-mt-unsafe]
const char c = rand() % 62;
^
void resize(const std::string &filename, int64_t size) { | ||
DEBUG("resizing file \"" + filename + "\" to " + str(size)); | ||
|
||
const int fd = open(filename.c_str(), O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: do not call c-style vararg functions [cppcoreguidelines-pro-type-vararg]
const int fd = open(filename.c_str(), O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
^
DEBUG("creating temporary file of size " + str(size)); | ||
|
||
std::string filename(Path::join(tmpfile_dir(), tmpfile_prefix()) + "XXXXXX."); | ||
const int rand_index = filename.size() - 7; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: narrowing conversion from 'size_type' (aka 'unsigned long') to signed type 'int' is implementation-defined [bugprone-narrowing-conversions]
const int rand_index = filename.size() - 7;
^
Path::Dir dir(folder); | ||
std::string entry; | ||
while (!(entry = dir.read_name()).empty()) { | ||
std::string path = Path::join(folder, entry); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: variable 'path' of type 'std::string' (aka 'basic_string') can be declared 'const' [misc-const-correctness]
std::string path = Path::join(folder, entry); | |
std::string const path = Path::join(folder, entry); |
} | ||
} | ||
DEBUG("deleting folder \"" + folder + "\"..."); | ||
if (::rmdir(folder.c_str())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: implicit conversion 'int' -> bool [readability-implicit-bool-conversion]
if (::rmdir(folder.c_str())) | |
if (::rmdir(folder.c_str()) != 0) |
} | ||
DEBUG("deleting folder \"" + folder + "\"..."); | ||
if (::rmdir(folder.c_str())) | ||
throw Exception("error deleting folder \"" + folder + "\": " + strerror(errno)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: function is not thread safe [concurrency-mt-unsafe]
throw Exception("error deleting folder \"" + folder + "\": " + strerror(errno));
^
trafo_half(decltype(trafo_half)::Identity()), | ||
trafo_half_inverse(decltype(trafo_half_inverse)::Identity()), | ||
centre(decltype(centre)::Zero()), | ||
optimiser_weights(decltype(optimiser_weights)::Zero(number_of_parameters)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: narrowing conversion from 'size_t' (aka 'unsigned long') to signed type 'Index' (aka 'long') is implementation-defined [bugprone-narrowing-conversions]
optimiser_weights(decltype(optimiser_weights)::Zero(number_of_parameters)),
^
This PR aims to significantly reduce the time to compile MRtrix3 and address the issue raised in #2876.
I have tested the changes locally on my laptop on Fedora Asahi and Mac OS 14 and, roughly speaking, I see an improvement of almost 30% compared to
dev
(without usingccache
).Here is a table showing indicative build times (in seconds) on my Macbook M2 Pro 2023 (I took the best of 3 successive builds).
This was achieved with a lot of trial and error and the help of
ClangBuildAnalyzer
. @MRtrix3/mrtrix3-devs it'd be great if you could test this on your machines to see if you can reproduce similar results. One caveat is that to achieve the most optimal results, you must use the latest version ofNinja
(v 1.12), released last week (see first point below). On MacOS, it's just a matter of runningbrew upgrade
. On Linux you will probably need to download it directly from their Github releases page and then runexport PATH=/ninja_dir/:$PATH
(it's unlikely that your distro has packaged the latest version). Before rebuilding, you obviously need to clear your build directory and compiler cache (ccache -C
).More in detail, there are three changes in this PR:
Increased build parallelism. During the initial stages of our CMake effort, it was pointed out by @jdtournier that the CPU load during the build phase didn't stay 100% throughout its entirety. So looking more into this, I found that when building
dev
, the Ninja build graph looks like this:If you look at the end of the graph, you can see that
mrregister.cpp.o
takes a long time to compile (about 45s) and in fact the rest of the object files finish compiling much earlier. So there is a period when the compiler is effectively operating using a single thread. Ideally, the compilation ofmrregister
should start at the beginning of the compilation phase, however, there is effectively no way to control the order of compilation in CMake (or in Ninja). To get around this, I used a simple trick (seecmd/CMakeLists.txt
): create a dummy executable and then addmrregister
as a dependency of this dummy (this is the relevant change in Ninja 1.12 that makes this possible). This is enough to trick Ninja and change the build graph to the following:Now, you see that we are operating multi-threaded throughout the entire compilation phase.
Precompiled headers. Precompiled headers speed up compilation by storing parsed header information in a special file. During subsequent compilations, the compiler reuses this precompiled data instead of re-parsing headers. They are particularly suited for "stable" header files, i.e. files that do not change very often (e.g. STL headers). If files listed in a precompiled header file change, then a target using the precompiled header will need to be recompiled from scratch. In this PR, I only included the following headers (other than STL and third parties) in precompiled headers:
gl.h
andexception.h
. The first one is only used formrview/shview
and the second one is for all the code. If a dev needs to frequently edit these files, it'd be best to turn offMRTRIX_USE_PCH
as otherwise, any change in those files will trigger a rebuild of the whole project.Explicit template instantiations. C++11 introduced the
extern template
syntax, which lets you say, "Hey, the template's definition? It's elsewhere!". So if we use this in a header file, we can tell the compiler to avoid automatically instantiating a specific template in every .cpp file that includes the header (of course we have to make sure that the template is instantiated somewhere, e.g. in the corresponding .cpp for the header).After looking at the report by
ClangBuildAnalyzer
, I used this feature to explicitly instantiate some class and function templates to reduce compilation time. I did this fairly conservatively (I really don't like dealing with the complexity that templates bring), but the few changes I made still made a significant difference in build time.