RFR: 8338967: Improve performance for MemorySegment::fill [v4]

Per Minborg pminborg at openjdk.org
Mon Aug 26 14:28:06 UTC 2024


On Mon, 26 Aug 2024 13:29:40 GMT, Per Minborg <pminborg at openjdk.org> wrote:

>> src/java.base/share/classes/jdk/internal/foreign/AbstractMemorySegmentImpl.java line 200:
>> 
>>> 198:             switch ((int) length) {
>>> 199:                 case 0 : checkReadOnly(false); checkValidState(); break; // Explicit tests
>>> 200:                 case 1 : set(JAVA_BYTE, 0, value); break;
>> 
>> beware using a switch, because if this code if is too big to be inlined (or we're unlucky) will die due to branch-mispredict in case the different "small fills" are unstable/unpredictable.
>> Having a test which feed different fill sizes per each iteration + counting branch misses, will reveal if the improvement is worthy even with such cases
>
> It is true, that this is a compromise where we give up inline space, code-cache space, and introduce added complexity against the prospect of better small-size performance. Depending on the workload, this may or may not pay off. In the (presumably common) case where we allocate/fill small segments of constant sizes, this is likely a win. Writing a dynamic performance test sounds like a good idea.

Here is a benchmark that fills segments of various random sizes:



@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@State(Scope.Thread)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(value = 3)
public class TestFill {

    private static final int SIZE = 16;
    private static final int[] INDICES = new Random(42).ints(0, 8)
            .limit(SIZE)
            .toArray();


    private MemorySegment[] segments;

    @Setup
    public void setup() {
        segments = IntStream.of(INDICES)
                .mapToObj(i -> MemorySegment.ofArray(new byte[i]))
                .toArray(MemorySegment[]::new);
    }

    @Benchmark
    public void heap_segment_fill() {
        for (int i = 0; i < SIZE; i++) {
            segments[i].fill((byte) 0);
        }
    }

}


This produces the following on my Mac M1:


Benchmark                   Mode  Cnt   Score   Error  Units
TestFill.heap_segment_fill  avgt   30  59.054 ? 3.723  ns/op


On average, an operation will take 59/16 = ~3 ns per operation.

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

PR Review Comment: https://git.openjdk.org/jdk/pull/20712#discussion_r1731331461


More information about the core-libs-dev mailing list