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