BoundaryOp refactor performance regression fix? - #1351
Conversation
BoundaryOp implementations (e.g. BoundaryNeumann, etc.) that use a default 'apply()' method provided by a base class now inherit from BoundaryOpWithApply, which is templated on the derived class type. This means that its 'apply()' method can use static methods of the derived class directly, so that they can be inlined. This should improve performance when optimization is turned on. CRTP pattern: https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern
Only calculate xnorm/ynorm if FieldGenerator fg is set Use #pragma's to suppress 'maybe-uninitialized' warnings. xnorm/ynorm will never be used uninitialized, so the warnings are not needed.
|
I believe inlining might also interact with the instruction cache as well, so inlining everything might plausibly have negative effects. |
|
@ZedThree thanks for the quick reply. What you say confirms my vague ideas about what 29757e4 only added |
|
Could it be just timing jitter? |
|
I should also note that there are a few examples of performance tests with helpful macros in the |
| for(int z=0; z<nz; z++) { | ||
| if (fg) { | ||
| BoutReal znorm = BoutReal(z)/BoutReal(nz); | ||
| #pragma GCC diagnostic push |
There was a problem hiding this comment.
I think these would need to be removed should this go into the main code -- we really want to avoid this level of (compiler family specific) detail where possible. I think the simplest fix here would be to just explicitly say xnorm=ynorm=0 in the initial declaration of the variables.
There was a problem hiding this comment.
Yes, absolutely agree. It's good practice to always initialise variables
There was a problem hiding this comment.
👍 sorry, I think I must have been in caffeine withdrawal when I thought that was a good idea 😫
Just zero-initialize xnorm, ynorm outside the loop instead.
I don't think so, the timings vary by maybe +/-0.02s between repeated runs. The difference between the cases with/without more inlines is >1s. |
|
The answer could also be that the way I've written the |
|
Comparing to @dschwoerer's code in #1179, I realised that the main thing slowing down the boundary conditions was conditionals checking if the There's now one branch with a |
Introduces some code duplication, but significantly decreases time to apply boundary ops.
a773f07 to
e41e0fa
Compare
|
Parsing expressions is much slower, so I updated the boundary conditions to apply a constant value (in without using the expression parser. I think this PR is ready to merge into #1334 now. Should I keep the |
|
I think the test is useful 👍 |
@ZedThree @JosephThomasParker @d7919, I made an example to check the performance of the
BoundaryOps. At least on my laptop, it seems that the refactored version was a lot slower than next. For 10000 calls tof.applyBoundary():dirichlet1.05137 s (next) -> 4.63342 s (BoundaryOp-refactor)neumann3.37127 s (next) -> 6.1994 s (BoundaryOp-refactor)dirichlet_o37.34798 s (next) -> 8.08621 s (BoundaryOp-refactor)I've tried to jig things around to get more functions to inline and get back to the same performance. Getting rid of calls to
f.getNZ()andlocalmesh->GlobalZ(z)seemed to help withBoundaryDirichlet. ButBoundaryDirichletimplements its ownapplymethod, so was pretty much the same as the old implementation anyway.BoundaryNeumannandBoundaryDirichlet_O3use the new base-classapply()and implement methods to apply the boundary condition at a single point that get called inside the loop. I thought using CRTP pattern to allow theBoundaryOpWithApply<typename Derived>::applyTemplatemethod to usestaticmethods of theDerivedclass so that they could be inlined, which helps a bit.The thing that's confusing me is: adding
inlinekeywords to some of the methods; in particular makingBoundaryNeumann::applyAtPoint(Field3D...)inlinereduced the execution time for theneumannboundary condition from about 2.7 s to 0.9s. However, addinginlineto all the methods slows down bothdirichletandneumann(even though those methods haven't been changed). Is there some kind of limit to the number of methods that can be inlined in a file, and the compiler just stops inlining when the limit is reached?The current state of this branch makes
examples/BoundaryOp_timingrun as fast as I've managed: adding moreinlinekeywords slows it down (once I add a certain number of them).