openjdk/test/hotspot/jtreg/runtime/FieldStream/LocalFieldLookupTest.java
Radim Vansa e18277b470 8352075: Perf regression accessing fields
Reviewed-by: coleenp, iklam, jsjolen
2025-06-12 12:29:15 +00:00

141 lines
5.8 KiB
Java

/*
* Copyright (c) 2025, 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.
*/
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES;
import static org.objectweb.asm.ClassWriter.COMPUTE_MAXS;
import static org.objectweb.asm.Opcodes.*;
/*
* @test id=defaults
* @bug 8352075
* @library /test/lib
* @library /testlibrary/asm
* @run main/othervm LocalFieldLookupTest
*/
/*
* @test id=custom-threshold
* @bug 8352075
* @library /test/lib
* @library /testlibrary/asm
* @requires vm.debug == true
* @run main/othervm LocalFieldLookupTest
* @run main/othervm -XX:BinarySearchThreshold=0 LocalFieldLookupTest
* @run main/othervm -XX:BinarySearchThreshold=1 LocalFieldLookupTest
* @run main/othervm -XX:BinarySearchThreshold=15 LocalFieldLookupTest
* @run main/othervm -XX:BinarySearchThreshold=100000 LocalFieldLookupTest
*/
public class LocalFieldLookupTest {
private static final String TEST_CLASS_NAME = "Test";
private static final int MAX_FIELDS_IN_METHOD = 10000;
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
// Test small classes, covering the tested thresholds
for (int i = 0; i <= 33; ++i) {
makeClass(i).newInstance();
}
// Test classes around 256 fields (index encoding 1/2 bytes) to check off-by-one errors
for (int i = 254; i <= 259; ++i) {
makeClass(255).newInstance();
}
// We would like to test #fields that create have the stream about 65536 bytes long;
// this value is not exposed, though, so these are rather experimentally found values,
// hence fragile. Moreover, since the stream length is incremented by about 8 bytes
// for each field we cannot test for off-by-one errors reliably.
for (int i = 8433; i <= 8437; ++i) {
makeClass(i).newInstance();
}
// The largest class we can create - this one has 65533 entries in the constant pool
makeClass(26205).newInstance();
}
public static Class<?> makeClass(int fields) throws ClassNotFoundException {
ClassWriter writer = new ClassWriter(COMPUTE_MAXS | COMPUTE_FRAMES);
writer.visit(49, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, TEST_CLASS_NAME,null, "java/lang/Object", null);
for (int i = 0; i < fields; i += 2) {
writer.visitField(ACC_PUBLIC, "f" + i, "I", null, null);
// Let's use duplicate names to confirm search takes signatures into account
if (i + 1 < fields) {
writer.visitField(ACC_PUBLIC, "f" + i, "J", null, null);
}
}
// We initialize fields in multiple methods to avoid running into bytecode limit per method
MethodVisitor fi = null;
for (int i = 0; i < fields; i+= 2) {
if (fi == null) {
fi = writer.visitMethod(ACC_PRIVATE, "init" + i, "()V", null, null);
fi.visitCode();
}
fi.visitVarInsn(Opcodes.ALOAD, 0);
fi.visitInsn(Opcodes.ICONST_2);
fi.visitFieldInsn(PUTFIELD, TEST_CLASS_NAME, "f" + i, "I");
if (i + 1 < fields) {
fi.visitVarInsn(Opcodes.ALOAD, 0);
fi.visitInsn(Opcodes.LCONST_1);
fi.visitFieldInsn(PUTFIELD, TEST_CLASS_NAME, "f" + i, "J");
}
if (i % MAX_FIELDS_IN_METHOD == MAX_FIELDS_IN_METHOD - 2) {
fi.visitInsn(Opcodes.RETURN);
fi.visitMaxs(0, 0);
fi.visitEnd();
fi = null;
}
}
if (fi != null) {
fi.visitInsn(Opcodes.RETURN);
fi.visitMaxs(0, 0);
fi.visitEnd();
}
{
MethodVisitor mv = writer.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
for (int i = 0; i < fields; i += MAX_FIELDS_IN_METHOD) {
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, TEST_CLASS_NAME, "init" + i, "()V", false);
}
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
writer.visitEnd();
byte[] bytecode = writer.toByteArray();
ClassLoader cl = new ClassLoader() {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
if (!TEST_CLASS_NAME.equals(name)) {
throw new ClassNotFoundException();
}
return defineClass(TEST_CLASS_NAME, bytecode, 0, bytecode.length);
}
};
return cl.loadClass(TEST_CLASS_NAME);
}
}