Unexpected behaviour when using Class.gerResource() with JDK ad hoc build
Сергей Цыпанов
sergei.tsypanov at yandex.ru
Tue May 11 16:04:35 UTC 2021
Hello
I've run into performance drop-down when using Class.gerResource().
When I run the following benchmark with JDK 16 downloaded from the internet
@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(jvmArgsAppend = {"-Xms2g", "-Xmx2g"})
public class ClassGetResourceBenchmark {
private final Class<?> clazz = getClass();
@Benchmark
public URL getResource() {
return clazz.getResource("/com/tsypanov/sbb/ClassGetResourceBenchmark.class");
}
}
getResource() method takes about 2 us and consumes 1704 bytes.
But then I take the code from https://github.com/openjdk/jdk and build it as
~ bash configure --with-boot-jdk=/home/s.tsypanov/.jdks/openjdk-16.0.1 --with-jtreg=/home/s.tsypanov/jtreg5
~ make images
the same benchmark run on this ad hoc build takes 129 us and consumes 111506 bytes.
The hot spot is the loop in jdk.internal.loader.URLClassPath.getResource():
private final ArrayList<URLClassPath.Loader> loaders;
public Resource getResource(String name, boolean check) {
Loader loader;
for (int i = 0; (loader = getLoader(i)) != null; i++) {
Resource res = loader.getResource(name, check);
if (res != null) {
return res;
}
}
return null;
}
in case of downloaded JDK `loaders` list contains 25 items and the one responsible for application's
classpath has index 0 (see screenshot https://disk.yandex.ru/i/yj16_tYs0xphSQ)
In case of JDK built locally the same list contains 90 items and the one responsible for application's
classpath has index 67 (see screenshot https://disk.yandex.ru/i/HXJOxpZrrdXPBA) while the first 66
items are responsible for modules of the JDK (see screenshot https://disk.yandex.ru/i/FkESrb_9Mo3nUw).
As a result the program iterates over the list until it comes to item 67 and at each iteration two URLs are
allocated in jdk.internal.loader.URLClassPath$FileLoader.getResource():
Resource getResource(final String name, boolean check) {
final URL url;
try {
URL normalizedBase = new URL(getBaseURL(), ".");
url = new URL(getBaseURL(), ParseUtil.encodePath(name, false));
//...
}
}
So the reason of the slow-down is different number and sequence of loaders in JDKs downloaded and built locally.
I've got two questions:
1) is there a way to build JDK locally with 'normal' list of loaders to have the same time for Class.getResource()?
2) would it be reasonable to cache 'normalizedBase' URL as a final field of FileLoader?
As getBaseURL() returns final field and is not overridden and the context is always "." we can initialize 'normalizedBase' in constructor.
In my case this allows to reduce consumed time from 129 to 104 us and allocation rate from 111506 to about 60000 bytes.
With best regards,
Sergey Tsypanov
More information about the core-libs-dev
mailing list