Disentangle kernel-manager include tree#3544
Disentangle kernel-manager include tree#3544JanVogelsang wants to merge 146 commits intonest:mainfrom
Conversation
…cular include chains
|
Important to note: I kept the internal API identical. All I've done essentially was to use references to the managers instead which allowed to forward-declare the managers in the kernel_manager.h file. The reason there are that many changes is that including the kernel_manager.h no longer means that one also includes ALL other managers. So each file using a specific manager has to include that manager explicitly. This is much cleaner and will also speed up compilation a bit. |
|
I have no idea what's supposed to be wrong with the 2 copyright headers. @terhorstd Could you take a look please? |
|
@JanVogelsang This looks very interesting! Just to make sure I understand your solution correctly: Instead of having the individual managers as actual data members of the This is certainly a great code cleanup. I am just a tiny bit concerned about performance, since we now have essentially pointers to the individual managers. Have you run any benchmarks yet? |
Very strange. Have you tried to run the copyright check locally on your machine? |
|
@heplesser Yes, this is correct! I benchmarked the performance and the PR's solution is indeed around 5% slower (locally and on Juwels). I have to admit that this surprises me a lot, as while this introduces another pointer, I would have assumed that all these pointers (or mostly just the simulation_manager and event_delivery_manager pointers during simulation) would be cached anyway. This seems to not be possible and I am not sure why. Then again, the cause of the performance degradation might also be something entirely different, for example growing code size which harms instruction cache efficiency due to more function inlining. I implemented two other solutions now, which both require the internal API to change, though:
namespace kernel {
template<typename ManagerT, std::enable_if_t<std::is_base_of_v<ManagerInterface, ManagerT>>* = nullptr>
ManagerT& manager()
{
static ManagerT static_manager;
return static_manager;
}
}
namespace kernel {
template <class T>
inline T g_manager_instance;
template <class T>
T& manager() noexcept {
return g_manager_instance<T>;
}
}I'm currently benchmarking both solutions, but local benchmarks suggest that the second approach performs best. |
I managed to solve this, it was just two empty files which I deleted but somehow they were added back in again, just empty. |
|
@JanVogelsang Thanks for checking and the new solutions! I slightly prefer the second option, but that is mainly based on the fact that I still haven't entirely wrapped my mind around the Is it necessary to place this into a separate namespace |
| */ | ||
| #include "grid_layer.h" | ||
| #include "layer.h" | ||
|
|
There was a problem hiding this comment.
Did these lines move the wrong way around? layer.h was deleted, but the _impl file now has all code?
What is the reason for dropping the include-guards?
There was a problem hiding this comment.
Thanks for catching this, it definitely wasn't intentional. I think I first removed the layer_impl.h, but then added it back again as there was a circular dependency with the grid_layer.h. So I must have accidentally copied too much from the layer.h over to the layer_impl.h
There was a problem hiding this comment.
@terhorstd Then again, it might actually make more sense to move all template function definitions to the impl file (in general, not only here), as the header files should ideally contain declarations only. Finn is currently doing that for all other files as well (at least header-to-cpp, not header-to-impl yet).
There was a problem hiding this comment.
Just to be clear: This is fixed now, the .cpp file contains all non-template function definitions and the .impl file contains the template function definitions.
| #include <algorithm> | ||
| #include <iostream> | ||
| #include <vector> |
There was a problem hiding this comment.
@jessica-mitchell, this would be a perfect place to link into the Doxygen documentation instead of copy-pasting some random code file into the RST.
For now however some reference syntax could shorten this somewhat unrelated change in this PR. Isn't there a possibility to link to a code file to be included? Instead of this PR updating the docs with the new file, it would be preferable to have something like:
.. code: cpp
file: ../nestkernel/stopwatch.h
Would avoid this excessive change in an rst file.
There was a problem hiding this comment.
Talked with @terhorstd, so not to prevent this PR from moving forward, we will discuss this documentation issue in issue #3607 and make a decision in a separate PR.
There was a problem hiding this comment.
Perfect, thanks!
|
@JanVogelsang, this is big work! Thanks a lot for this big cleaning effort! Could you also check the relation to #3465 ? |
That's totally fine as well. The additional/nested namespace ( |
|
@heplesser There is some issue when destructing the managers. Compared to For some reason, the order in which the managers are destructed seems to matter and incorrect order leads to segmentation faults due to double-frees. I couldn't figure out why, but noticed that removing the ModelManager destructor fixes the issue. I am not sure what implications this has though. I would argue that the destructor is not required for the ModelManager, but please take a look at that yourself. |
|
The remaining issues might actually be solved by #3748 as those are related to how integer values are passed between Python and C++. So let's get that merged first. |
…rnel-manager-refactoring
|
@JanVogelsang I am happy to report that your branch nicely builds on macOS 26.4.1 with Apple Clang 21 and that all tests pass. The new conflicts are due to #3721 being merged with the flush events for eprop. That should not be difficult to disentangle (@akorgor Could you try to take a look at those and create a PR sorting them out towards @JanVogelsang's repo? Note that this PR is from his |
|
@JanVogelsang While all works, I do get from Apple Clang the warning and corresponding ones for I did a default CMake run. How do I turn on LTO? |
Done in JanVogelsang#14. Note that after merging these changes there is a todo left for @JanVogelsang to
|
|
@JanVogelsang I now tried to build with LTO on macOS using |
Merge flush events into PR 3544
|
It turned out that I used CMake's |
…rnel-manager-refactoring
So do you use llvm-ar/ranlib to link under Ubuntu even when compiling with GCC? |
|
No, for GCC the default ar and ranlib are used ( |
Fix warnings about undefined template member when building with Clang
|
@JanVogelsang I sent you a new PR with much simplified ProcessOptions code which now works (with limits) on macOS (with Clang) and under Linux (with GCC; Clang not tested). Under macOS, gcc-15 from Homebrew does not work on my system (macOS 26.4.1, Apple Silicon). This seems to be due to a bug in the newest Apple SDK (https://github.com/orgs/Homebrew/discussions/6778#discussioncomment-16557516). Under Linux with gcc-13, I get a curious error from I find this curious because |
|
@heplesser The PR unfortunately doesn't work for Clang 18 on Ubuntu. But strangely, I also don't get the spin_detector error with GCC 13. |
|
Interestingly, the spin_detector issue also occurs when building with CI-beNNch. But also not here in our CI testsuite. |
|
@JanVogelsang Thanks for the update. Here a few more observations (and a question) from my side:
with my 3544-branch (ie minimal settings for LTO) and everything built nicely out of the box. The duration of the final linking stage and the size of the
Any idea what might be going on here? |
|
@heplesser But in the CMake call you shared you specified "-Dwith-lto=OFF", so are you sure that LTO ran? For the nest::sort issue, I have no idea, but an LLM suggested replacing the line by |
Summary of changes
This PR fixes the horrible kernel-manager singleton design and cleans up all circular include chains which were just hidden behind the kernel-manager and plenty "*_impl.h" files.
Even though this PR touches 250+ files, the only functional changes were done in the kernel_manager.h/.cpp and the other changes are just follow-up refactorings enabled by the changes. I also got rid of most of the impl-files, which are not required anymore and just complicated things.
Why this was necessary
Anyone who ever worked on the kernel will immediately know about the pain the kernel-manager caused. As the kernel-manager included ALL other manager in the kernel_manager.h file, any other class using the kernel-manager therefore also included ALL other managers. This resulted in either of two things:
2.1. The kernel-manager could only be included in the .cpp file, which meant that the function using the kernel-manager (and thus any of the other managers) could not be inlined, which in some cases is detrimental for performance.
2.2. We had to create those impl.h files and use the kernel-manager in there. However, impl.h files and correctly including them is a nightmare and leads to various linker issues which are extremely difficult to debug.
The only time when one should use impl.h files is for some template specializations.