From 08cd53c98eb7ff8529bfa372340d3614d0cc788a Mon Sep 17 00:00:00 2001 From: Maurizio Cimadamore Date: Fri, 27 Sep 2024 22:05:43 +0100 Subject: [PATCH 1/9] Add benchmark --- .../foreign/LoopOverNonConstantAsType.java | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantAsType.java diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantAsType.java b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantAsType.java new file mode 100644 index 0000000000000..d6fc910af41ca --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantAsType.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.java.lang.foreign; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.annotations.Warmup; +import sun.misc.Unsafe; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import static java.lang.foreign.ValueLayout.*; + +@BenchmarkMode(Mode.AverageTime) +@Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) +@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS) +@State(org.openjdk.jmh.annotations.Scope.Thread) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@Fork(3) +public class LoopOverNonConstantAsType extends JavaLayouts { + + static final Unsafe unsafe = Utils.unsafe; + + static final int ELEM_SIZE = 1_000_000; + static final int CARRIER_SIZE = (int)JAVA_LONG.byteSize(); + static final int ALLOC_SIZE = ELEM_SIZE * CARRIER_SIZE; + + @Param({"false", "true"}) + public boolean asTypeCompiled; + + Arena arena; + MemorySegment segment; + long unsafe_addr; + + @Setup + public void setup() { + unsafe_addr = unsafe.allocateMemory(ALLOC_SIZE); + for (int i = 0; i < ELEM_SIZE; i++) { + unsafe.putInt(unsafe_addr + (i * CARRIER_SIZE) , i); + } + arena = Arena.ofConfined(); + segment = arena.allocate(ALLOC_SIZE, 1); + for (int i = 0; i < ELEM_SIZE; i++) { + VH_INT.set(segment, (long) i, i); + } + if (asTypeCompiled) { + compileAsType(); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + void compileAsType() { + Class[] types = new Class[] { byte.class, short.class, int.class, long.class }; + for (int i = 0 ; i < 40 ; i++) { + for (Class type : types) { + MethodHandle handle = MethodHandles.zero(Object.class); + Class[] args = new Class[127]; + Arrays.fill(args, long.class); + handle = MethodHandles.dropArguments(handle, 0, args); + for (int j = 0; j < args.length ; j++) { + handle = handle.asType(handle.type().changeParameterType(j, type)); + } + } + } + } + + @TearDown + public void tearDown() { + arena.close(); + unsafe.freeMemory(unsafe_addr); + } + + @Benchmark + public long unsafe_loop() { + long res = 0; + for (int i = 0; i < ELEM_SIZE; i ++) { + res += unsafe.getLong(unsafe_addr + (i * CARRIER_SIZE)); + } + return res; + } + + @Benchmark + public long segment_loop() { + long sum = 0; + for (int i = 0; i < ELEM_SIZE; i++) { + sum += segment.get(JAVA_LONG, i * CARRIER_SIZE); + + } + return sum; + } +} From 12809eba8fa1c3534553ec801be022c8623b3155 Mon Sep 17 00:00:00 2001 From: Maurizio Cimadamore Date: Fri, 27 Sep 2024 22:17:47 +0100 Subject: [PATCH 2/9] Disable tiered compilation --- .../bench/java/lang/foreign/LoopOverNonConstantAsType.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantAsType.java b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantAsType.java index d6fc910af41ca..4cde8f3974f42 100644 --- a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantAsType.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantAsType.java @@ -51,7 +51,7 @@ @Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS) @State(org.openjdk.jmh.annotations.Scope.Thread) @OutputTimeUnit(TimeUnit.MILLISECONDS) -@Fork(3) +@Fork(value = 3, jvmArgsAppend = { "-XX:-TieredCompilation" }) public class LoopOverNonConstantAsType extends JavaLayouts { static final Unsafe unsafe = Utils.unsafe; From 2cc20452b749b5a4c36b6ba2e5322c09a9ea5934 Mon Sep 17 00:00:00 2001 From: Maurizio Cimadamore Date: Mon, 30 Sep 2024 12:14:49 +0100 Subject: [PATCH 3/9] Use different classloaders --- .../foreign/LoopOverNonConstantAsType.java | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantAsType.java b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantAsType.java index 4cde8f3974f42..8622a9f35a37b 100644 --- a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantAsType.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantAsType.java @@ -41,6 +41,9 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; +import java.lang.reflect.Proxy; +import java.net.URL; +import java.net.URLClassLoader; import java.util.Arrays; import java.util.concurrent.TimeUnit; @@ -51,7 +54,7 @@ @Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS) @State(org.openjdk.jmh.annotations.Scope.Thread) @OutputTimeUnit(TimeUnit.MILLISECONDS) -@Fork(value = 3, jvmArgsAppend = { "-XX:-TieredCompilation" }) +@Fork(3) public class LoopOverNonConstantAsType extends JavaLayouts { static final Unsafe unsafe = Utils.unsafe; @@ -83,18 +86,29 @@ public void setup() { } } + public interface T { } + + static final int TYPE_SIZE = 100; + static final Class[] types; + + static { + types = new Class[TYPE_SIZE]; + ClassLoader customLoader = new URLClassLoader(new URL[0], LoopOverNonConstantAsType.class.getClassLoader()); + for (int i = 0 ; i < TYPE_SIZE ; i++) { + types[i] = Proxy.newProxyInstance(customLoader, + new Class[] { T.class }, (_, _, _) -> null).getClass(); + } + } + @CompilerControl(CompilerControl.Mode.DONT_INLINE) void compileAsType() { - Class[] types = new Class[] { byte.class, short.class, int.class, long.class }; - for (int i = 0 ; i < 40 ; i++) { - for (Class type : types) { - MethodHandle handle = MethodHandles.zero(Object.class); - Class[] args = new Class[127]; - Arrays.fill(args, long.class); - handle = MethodHandles.dropArguments(handle, 0, args); - for (int j = 0; j < args.length ; j++) { - handle = handle.asType(handle.type().changeParameterType(j, type)); - } + for (Class type : types) { + MethodHandle handle = MethodHandles.zero(Object.class); + Class[] args = new Class[254]; + Arrays.fill(args, Object.class); + handle = MethodHandles.dropArguments(handle, 0, args); + for (int j = 0; j < args.length ; j++) { + handle = handle.asType(handle.type().changeParameterType(j, type)); } } } From c9c2f191bf2118ee04a4816e390b8f9e355b1e1d Mon Sep 17 00:00:00 2001 From: Maurizio Cimadamore Date: Mon, 30 Sep 2024 14:57:40 +0100 Subject: [PATCH 4/9] Fix bench --- .../bench/java/lang/foreign/LoopOverNonConstantAsType.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantAsType.java b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantAsType.java index 8622a9f35a37b..43a85acc19e2d 100644 --- a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantAsType.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantAsType.java @@ -54,7 +54,7 @@ @Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS) @State(org.openjdk.jmh.annotations.Scope.Thread) @OutputTimeUnit(TimeUnit.MILLISECONDS) -@Fork(3) +@Fork(value = 3, jvmArgsAppend = { "-XX:-TieredCompilation" }) public class LoopOverNonConstantAsType extends JavaLayouts { static final Unsafe unsafe = Utils.unsafe; From 8c2f978989607629dc370b7a54213ffb1f81bac8 Mon Sep 17 00:00:00 2001 From: Maurizio Cimadamore Date: Mon, 30 Sep 2024 16:23:56 +0100 Subject: [PATCH 5/9] DontInline slow path in MethodHandle::asType --- .../share/classes/java/lang/invoke/MethodHandle.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/java/lang/invoke/MethodHandle.java b/src/java.base/share/classes/java/lang/invoke/MethodHandle.java index edcecce37e05d..ac88fede6a8a9 100644 --- a/src/java.base/share/classes/java/lang/invoke/MethodHandle.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodHandle.java @@ -27,6 +27,7 @@ import jdk.internal.loader.ClassLoaders; +import jdk.internal.vm.annotation.DontInline; import jdk.internal.vm.annotation.IntrinsicCandidate; import java.lang.constant.ClassDesc; @@ -867,7 +868,7 @@ public final MethodHandle asType(MethodType newType) { if (at != null) { return at; } - return setAsTypeCache(asTypeUncached(newType)); + return setAsTypeCache(newType); } private MethodHandle asTypeCached(MethodType newType) { @@ -885,7 +886,9 @@ private MethodHandle asTypeCached(MethodType newType) { return null; } - private MethodHandle setAsTypeCache(MethodHandle at) { + @DontInline + private MethodHandle setAsTypeCache(MethodType newType) { + MethodHandle at = asTypeUncached(newType); // Don't introduce a strong reference in the cache if newType depends on any class loader other than // current method handle already does to avoid class loader leaks. if (isSafeToCache(at.type)) { From f644d52defecf82f9405046f981aa69a2176a0a3 Mon Sep 17 00:00:00 2001 From: Maurizio Cimadamore Date: Tue, 1 Oct 2024 11:14:30 +0100 Subject: [PATCH 6/9] Update copyright --- .../bench/java/lang/foreign/LoopOverNonConstantAsType.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantAsType.java b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantAsType.java index 43a85acc19e2d..92312b6fac51f 100644 --- a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantAsType.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantAsType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it From 4fee4d289da27a1823973851c887a16edf74897a Mon Sep 17 00:00:00 2001 From: Maurizio Cimadamore <54672762+mcimadamore@users.noreply.github.com> Date: Tue, 1 Oct 2024 16:50:08 +0100 Subject: [PATCH 7/9] Update test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantAsType.java Co-authored-by: Jorn Vernee --- .../bench/java/lang/foreign/LoopOverNonConstantAsType.java | 1 - 1 file changed, 1 deletion(-) diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantAsType.java b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantAsType.java index 92312b6fac51f..a986561d094ef 100644 --- a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantAsType.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantAsType.java @@ -133,7 +133,6 @@ public long segment_loop() { long sum = 0; for (int i = 0; i < ELEM_SIZE; i++) { sum += segment.get(JAVA_LONG, i * CARRIER_SIZE); - } return sum; } From 964a273f73636b7a45417d30f2756403b0c21323 Mon Sep 17 00:00:00 2001 From: Maurizio Cimadamore Date: Tue, 1 Oct 2024 18:40:13 +0100 Subject: [PATCH 8/9] Address review comments --- .../share/classes/java/lang/invoke/MethodHandle.java | 7 +++++++ .../bench/java/lang/foreign/LoopOverNonConstantAsType.java | 1 - 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/invoke/MethodHandle.java b/src/java.base/share/classes/java/lang/invoke/MethodHandle.java index ac88fede6a8a9..a6f8321635ebb 100644 --- a/src/java.base/share/classes/java/lang/invoke/MethodHandle.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodHandle.java @@ -886,6 +886,13 @@ private MethodHandle asTypeCached(MethodType newType) { return null; } + /* + * We disable inlining here to prevent complex code in the slow path + * of MethodHandle::asType from being inlined into that method. + * Excessive inlining into MethodHandle::asType can cause that method + * to become too big, which will then cause performance issues during + * var handle and method handle calls. + */ @DontInline private MethodHandle setAsTypeCache(MethodType newType) { MethodHandle at = asTypeUncached(newType); diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantAsType.java b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantAsType.java index a986561d094ef..1fbe431b2f23a 100644 --- a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantAsType.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantAsType.java @@ -100,7 +100,6 @@ public interface T { } } } - @CompilerControl(CompilerControl.Mode.DONT_INLINE) void compileAsType() { for (Class type : types) { MethodHandle handle = MethodHandles.zero(Object.class); From c4daec516d3f3f5ba84c706eeb1835b3a266f19e Mon Sep 17 00:00:00 2001 From: Maurizio Cimadamore Date: Wed, 2 Oct 2024 14:58:00 +0100 Subject: [PATCH 9/9] Add `@ForceInline` to `MethodHandle::asType`, as per review comment --- src/java.base/share/classes/java/lang/invoke/MethodHandle.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/java.base/share/classes/java/lang/invoke/MethodHandle.java b/src/java.base/share/classes/java/lang/invoke/MethodHandle.java index a6f8321635ebb..9d8c0f70e2dc7 100644 --- a/src/java.base/share/classes/java/lang/invoke/MethodHandle.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodHandle.java @@ -28,6 +28,7 @@ import jdk.internal.loader.ClassLoaders; import jdk.internal.vm.annotation.DontInline; +import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.IntrinsicCandidate; import java.lang.constant.ClassDesc; @@ -857,6 +858,7 @@ public Object invokeWithArguments(java.util.List arguments) throws Throwable * @throws WrongMethodTypeException if the conversion cannot be made * @see MethodHandles#explicitCastArguments */ + @ForceInline public final MethodHandle asType(MethodType newType) { // Fast path alternative to a heavyweight {@code asType} call. // Return 'this' if the conversion will be a no-op.