JDK-8235844 : Non-constant memory segments are never treated as loop invariants
  • Type: Enhancement
  • Component: hotspot
  • Sub-Component: compiler
  • Affected Version: 14
  • Priority: P3
  • Status: Closed
  • Resolution: Won't Fix
  • Submitted: 2019-12-12
  • Updated: 2025-01-15
  • Resolved: 2025-01-15
Related Reports
Relates :  
Relates :  
Relates :  
Description
Consider the following class:

import jdk.incubator.foreign.MemoryAddress;
import jdk.incubator.foreign.MemoryHandles;
import jdk.incubator.foreign.MemoryLayout;
import jdk.incubator.foreign.MemoryLayouts;
import jdk.incubator.foreign.MemorySegment;
import jdk.incubator.foreign.SequenceLayout;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.VarHandle;
import java.nio.ByteOrder;

public class PanamaPoint implements AutoCloseable {
    private static final SequenceLayout LAYOUT = MemoryLayout.ofSequence(MemoryLayout.ofStruct(
            MemoryLayouts.JAVA_INT.withOrder(ByteOrder.nativeOrder()).withName("x"),
            MemoryLayouts.JAVA_INT.withOrder(ByteOrder.nativeOrder()).withName("y")));
    private static final VarHandle VH_x = LAYOUT.varHandle(int.class, MemoryLayout.PathElement.sequenceElement(), MemoryLayout.PathElement.groupElement("x"));
    private static final VarHandle VH_y = LAYOUT.varHandle(int.class, MemoryLayout.PathElement.sequenceElement(), MemoryLayout.PathElement.groupElement("y"));

    private final MemorySegment segment;
    private final MemoryAddress base;
    public PanamaPoint(int size) {
        this.segment = MemorySegment.allocateNative(LAYOUT.elementLayout().byteSize() * size);
        this.base = segment.baseAddress();
    }
    public void setX(int x, int pos) {
        VH_x.set(base, (long)pos, x);
    }
    public int getX(int pos) {
        return (int) VH_x.get(base, (long)pos);
    }
    public void setY(int y, int pos) {
        VH_y.set(base, (long)pos, y);
    }
    public int getY(int pos) {
        return (int) VH_y.get(base, (long)pos);
    }
    @Override
    public void close() {
        segment.close();
    }
}

And the following benchmark:

@Benchmark
    public int panama_get() throws Throwable {
        int res = 0;
        for (int i = 0 ; i < SIZE ; i++) {
            res+= panamaPoint.getX(i);
        }
        return res;
    }

Despite the segment being accessed stays the same across the entire loop, a quick look at the generated code reveals that no hoisting is taking place - that is, upon every access we pay full cost for ownership checks, liveness check, bounds check, alignment check, read only check.
Comments
Seems related to an earlier iteration of FFM
15-01-2025

The benchmark does not demonstrate issues with non-constant segments. The difference in scores is due to cached PanamaPoint.base vs MemorySegment.baseAddress(). @Benchmark public int panama_get_hoisted_manual() throws Throwable { int res = 0; PanamaPoint point = panamaPoint; for (int i = 0 ; i < SIZE ; i++) { res += (int)PanamaPoint.VH_x.get(point.base, (long)i); } return res; } @Benchmark public int panama_get_hoisted_manual1() throws Throwable { int res = 0; PanamaPoint point = panamaPoint; for (int i = 0 ; i < SIZE ; i++) { res += (int)PanamaPoint.VH_x.get(point.segment.baseAddress(), (long)i); } return res; } PanamaPointer.panama_get_hoisted_manual avgt 10 0.060 ± 0.019 ms/op PanamaPointer.panama_get_hoisted_manual1 avgt 10 0.029 ± 0.001 ms/op MemorySegment.baseAddress() constructs fresh address with 0 offset and it helps to eliminate a bunch of checks: (1) offset fits into int; (2) offset computation for indexed access doesn’t overflow.
11-06-2020

Manually hoisting the loop as follows: @Benchmark public int panama_get_hoisted_manual() throws Throwable { int res = 0; MemorySegment segment = panamaPoint.segment; for (int i = 0 ; i < SIZE ; i++) { res += (int)PanamaPoint.VH_x.get(segment.baseAddress(), (long)i); } return res; } Brings significant improvements as most checks (except liveness and bounds) are correctly factored out of the loop.
12-12-2019