A for-each loop over ArrayList.Itr carries two phases of one recurrence through 
the loop: cursor (after the increment) and lastRet (before it). Both are live 
at the same time, so the allocator puts a copying mov in the loop body - on 
every iteration, for a value the loop never reads. This can be dealt with at 
the Java level, see #32344, but this change is an attempt to improve it at the 
level of C2 compilation.

In a loop compiled by C2, lastRet is never written to memory: the iterator is 
scalarized and the field lives in a register. In a plain for-each nobody reads 
it (it is only used by set() and remove()) so its only consumers are the Phi at 
the loop exit and the safepoint's debug info.

C2 already knows how to get rid of a second index: 
PhaseIdealLoop::replace_parallel_iv looks for a variable that walks alongside 
the trip counter and rewrites its uses in terms of the counter.
It only recognizes PARALLEL shape:


PARALLEL: int a = 5;     for (int iv = 0; iv < limit; iv++) { use(a);    a += 
3;    }   
PREVIOUS: int prev = -1; for (int iv = 0; iv < limit; iv++) { use(prev); prev = 
iv; }


The patch teaches the recognition step the second (PREVIOUS) shape: a phi in 
the loop head whose backedge value is the trip counter plus a constant. Its 
uses are then rewritten in terms of the trip counter, the phi becomes dead 
code, and the copy in the loop body is no longer generated.

With `-XX:LoopMaxUnroll=1` the resulting aarch64 assembly looks like the 
following:

@Benchmark
public void list_foreach(Blackhole bh) {
    for (Object o : list) {
        bh.consume(o);
    }
}

BEFORE                               AFTER 
  mov  w14, w11                        -
  add  x11, x10, w14, sxtw #2          add  x12, x10, w16, sxtw #2
  ldr  w11, [x11, #0xc]                ldr  w12, [x12, #0xc]
  lsl  x11, x11, #3                    add  w16, w16, #0x1
  add  w11, w14, #0x1                  lsl  x12, x12, #3
  cmp  w11, w12                        cmp  w16, w14
  b.lt #-0x18                          b.lt #-0x14


Removing one instruction gives a speedup of up to 40% with 
`-XX:LoopMaxUnroll=1`, while with default VM options the picture is mixed: a 
11–15% gain on some CPUs and nothing measurable on others.

---------
- [x] I confirm that I make this contribution in accordance with the [OpenJDK 
Interim AI Policy](https://openjdk.org/legal/ai).

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

Commit messages:
 - add the lagging index optimization to replace_parallel_iv
 - undo
 - enable benefit for unrolled loop
 - cut the regression: skip uses before the increment, where incr is not live 
yet
 - remove -XX:LoopMaxUnroll=1
 - regression benchmark
 - simplificatión
 - benchmark array_double_index
 - 8360517: C2: Incomplete ArrayList foreach optimization

Changes: https://git.openjdk.org/jdk/pull/32549/files
  Webrev: https://webrevs.openjdk.org/?repo=jdk&pr=32549&range=00
  Issue: https://bugs.openjdk.org/browse/JDK-8360517
  Stats: 141 lines in 3 files changed: 135 ins; 0 del; 6 mod
  Patch: https://git.openjdk.org/jdk/pull/32549.diff
  Fetch: git fetch https://git.openjdk.org/jdk.git pull/32549/head:pull/32549

PR: https://git.openjdk.org/jdk/pull/32549

Reply via email to