RFR: 8279888: Local variable independently used by multiple loops can interfere with loop optimizations

Roland Westrelin roland at openjdk.java.net
Fri Feb 4 15:10:09 UTC 2022


On Fri, 4 Feb 2022 14:41:55 GMT, Roland Westrelin <roland at openjdk.org> wrote:

> The bytecode of the 2 methods of the benchmark is structured
> differently: loopsWithSharedLocal(), the slowest one, has multiple
> backedges with a single head while loopsWithScopedLocal() has a single
> backedge and all the paths in the loop body merge before the
> backedge. loopsWithSharedLocal() has its head cloned which results in
> a 2 loops loop nest.
> 
> loopsWithSharedLocal() is slow when 2 of the backedges are most
> commonly taken with one taken only 3 times as often as the other
> one. So a thread executing that code only runs the inner loop for a
> few iterations before exiting it and executing the outer loop. I think
> what happens is that any time the inner loop is entered, some
> predicates are executed and the overhead of the setup of loop strip
> mining (if it's enabled) has to be paid. Also, if iteration
> splitting/unrolling was applied, the main loop is likely never
> executed and all time is spent in the pre/post loops where potentially
> some range checks remain.
> 
> The fix I propose is that ciTypeFlow, when it clone heads, not only
> rewires the most frequent loop but also all this other frequent loops
> that share the same head. loopsWithSharedLocal() and
> loopsWithScopedLocal() are then fairly similar once c2 parses them.
> 
> Without the patch I measure:
> 
> LoopLocals.loopsWithScopedLocal      mixed  avgt    5  1108.874 ± 250.463  ns/op
> LoopLocals.loopsWithSharedLocal      mixed  avgt    5  1575.665 ±  70.372  ns/op
> 
> with it:
> 
> LoopLocals.loopsWithScopedLocal      mixed  avgt    5  1108.180 ± 245.873  ns/op
> LoopLocals.loopsWithSharedLocal      mixed  avgt    5  1234.665 ± 157.912  ns/op
> 
> But this patch also causes a regression when running one of the
> benchmarks added by 8278518. From:
> 
> SharedLoopHeader.sharedHeader  avgt    5  505.993 ± 44.126  ns/op
> 
> to:
> 
> SharedLoopHeader.sharedHeader  avgt    5  724.253 ± 1.664  ns/op
> 
> The hot method of this benchmark used to be compiled with 2 loops, the
> inner one a counted loop. With the patch, it's now compiled with a
> single one which can't be converted into a counted loop because the
> loop variable is incremented by a different amount along the 2 paths
> in the loop body. What I propose to fix this is to add a new loop
> transformation that detects that, because of a merge point, a loop
> can't be turned into a counted loop and transforms it into 2
> loops. The benchmark performs better with this:
> 
> SharedLoopHeader.sharedHeader  avgt    5  567.150 ± 6.120  ns/op
> 
> Not quite on par with the previous score but AFAICT this is due to
> code generation not being as good (the loop head can't be aligned in
> particular).
> 
> In short, I propose:
> 
> - changing ciTypeFlow so that, when it pays off, a loop with
> multiple backedges is compiled as a single loop with a merge point in
> the loop body
> 
> - adding a new loop transformation so that, when it pays off, a loop
> with a merge point in the loop body is converted into a 2 loops loop
> nest, essentially the opposite transformation.

One thing I should have mentioned is that I refactored loop body cloning so that it now calls helper functions. The new loop transform is implemented by calling these functions so it's mostly based on well tested code.

-------------

PR: https://git.openjdk.java.net/jdk/pull/7352


More information about the hotspot-compiler-dev mailing list