- PVSM.RU - https://www.pvsm.ru -
Привет!
Недавно, путешествуя по коду своего рабочего проекта набрел на довольно высоконагруженный spring бин, который производил обращения к методам объектов (иногда и объектов сгенерированных на лету классов) вызывая геттеры и сеттеры объекта через reflection. В бине уже был реализован кэш геттеров, однако я задался вопросом — насколько быстр reflection и можно ли сделать быстрее.
С быстрой руки был написан микробенчмарк на JMH, который меряет производительность различных способов вызова методов. Процесс написания микробенчмарка — дело неблагодарное, существует миллион способов ошибиться и измерить совсем не то, что хотел. Так я упустил из головы боксинг-анбоксинг и в результате в первой версии бенчмарка измерял его, а не сам вызов метода. А ошибку свою нашел, только когда посмотрел PrintAssembly.
Результаты получились интересными, однако на хабре уже были статьи, сравнивающие вызов методов через reflection и напрямую, поэтому, посмотрев на результаты, собрался убрать их в ящик до лучших времен, но внезапно ленту твиттера, заполненную политикой, разбавили твиты про релиз java8. Обуздав радость, я решил сравнить производительность reflection в JDK7 и JDK8.
Кратко про обозначения в результатах фреймворка для правильного бенчмаркинга JMH:
Первое, что я замерил это доступ к полям класса напрямую:
Аналогично для статических полей.
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
@State(Scope.Thread)
public class ReflectionFieldAccess {
private static final Class<TestedClass> clazz = TestedClass.class;
private TestedClass testedObject;
Field simpleField;
Field fieldAccessible;
@Setup
public void init() {
try {
testedObject = new TestedClass();
simpleField = clazz.getField("a");
Field Field = clazz.getField("b");
Field.setAccessible(true);
fieldAccessible = Field;
} catch (Exception e) {
// do nothing
}
}
@GenerateMicroBenchmark
public Object testFieldSaveAccessible() throws Exception {
return fieldAccessible.get(testedObject);
}
@GenerateMicroBenchmark
public Object testFieldSaveNotAccessible() throws Exception {
return simpleField.get(testedObject);
}
@GenerateMicroBenchmark
public Object testFieldStraighforward() throws Exception {
return testedObject.c;
}
}
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
@State(Scope.Thread)
public class ReflectionFieldStaticAccess {
private static final Class<TestedClass> clazz = TestedClass.class;
Field simpleField;
Field fieldAccessible;
@Setup
public void init() {
try {
simpleField = clazz.getField("aStat");
Field Field = clazz.getField("bStat");
Field.setAccessible(true);
fieldAccessible = Field;
} catch (Exception e) {
// do nothing
}
}
@GenerateMicroBenchmark
public Object testFieldSaveAccessible() throws Exception {
return fieldAccessible.get(null);
}
@GenerateMicroBenchmark
public Object testFieldSaveNotAccessible() throws Exception {
return simpleField.get(null);
}
@GenerateMicroBenchmark
public Object testFieldStraighforward() throws Exception {
return TestedClass.cStat;
}
}
Результаты для JDK7:
Результаты для JDK8:
Результаты в сравнении:
Собственно, результаты вполне ожидаемы:
Перейдем к сравнению результатов для вызовов методов, здесь у нас гораздо больший выбор исследуемых средств.
Последние два теста на использование API MethodHandle, часть JSR 292, доступного c jdk7.
Аналогично для статических методов.
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
@State(Scope.Thread)
public class ReflectionMethodAccess {
private static final Class<TestedClass> clazz = TestedClass.class;
private TestedClass testedObject;
Method simpleMethod;
Method methodAccessible;
FastMethod fastMethod;
MethodHandle methodHandle;
@Setup
public void init() {
try {
testedObject = new TestedClass();
simpleMethod = clazz.getMethod("getA", null);
Method method = clazz.getMethod("getB", null);
method.setAccessible(true);
methodAccessible = method;
fastMethod = FastClass.create(clazz).getMethod("getC", null);
methodHandle = MethodHandles.lookup().findVirtual(clazz, "getD", MethodType.methodType(Integer.class));
} catch (Exception e) {
// do nothing
}
}
@GenerateMicroBenchmark
public Object testFastMethod() throws Throwable {
return fastMethod.invoke(testedObject, null);
}
@GenerateMicroBenchmark
public Object testMethodAccessible() throws Throwable {
return methodAccessible.invoke(testedObject, null);
}
@GenerateMicroBenchmark
public Object testMethodNotAccessible() throws Throwable {
return simpleMethod.invoke(testedObject, null);
}
@GenerateMicroBenchmark
public Object testMethodHandleExact() throws Throwable {
return (Integer)methodHandle.invokeExact(testedObject);
}
@GenerateMicroBenchmark
public Object testMethodHandle() throws Throwable {
return (Integer)methodHandle.invoke(testedObject);
}
@GenerateMicroBenchmark
public Object testMethodDirect() throws Throwable {
return testedObject.getA();
}
}
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
@State(Scope.Thread)
public class ReflectionMethodStaticAccess {
private static final Class<TestedClass> clazz = TestedClass.class;
Method simpleMethod;
Method methodAccessible;
MethodHandle methodHandle;
FastMethod fastMethod;
@Setup
public void init() {
try {
simpleMethod = clazz.getMethod("getAStatic", null);
Method method = clazz.getMethod("getBStatic", null);
method.setAccessible(true);
methodAccessible = method;
fastMethod = FastClass.create(clazz).getMethod("getCStatic", null);
methodHandle = MethodHandles.lookup().findStatic(clazz, "getDStatic", MethodType.methodType(Integer.class));
} catch (Exception e) {
// do nothing
}
}
@GenerateMicroBenchmark
public Object testFastMethod() throws Throwable {
return fastMethod.invoke(null, null);
}
@GenerateMicroBenchmark
public Object testMethodAccessible() throws Throwable {
return methodAccessible.invoke(null, null);
}
@GenerateMicroBenchmark
public Object testMethodNotAccessible() throws Throwable {
return simpleMethod.invoke(null, null);
}
@GenerateMicroBenchmark
public Object testMethodHandleExact() throws Throwable {
return (Integer)methodHandle.invokeExact();
}
@GenerateMicroBenchmark
public Object testMethodHandle() throws Throwable {
return (Integer)methodHandle.invoke();
}
@GenerateMicroBenchmark
public Object testMethodDirect() throws Throwable {
return TestedClass.getAStatic();
}
}
Подробнее про MethodHandle можно послушать, например, в докладе Владимира Иванова про invokedynamics [1]
Результаты для JDK7:
Результаты для JDK8:
Результаты в сравнении:
Из графиков можно сделать несколько выводов:
Собственно вывод простой если вы используете в своем проекте reflection — то вот вам лишний повод для перехода на jdk8.
Если вы хотите поиграть с бенчмарком, измерить производительность reflection на своей архитектуре или поискать ошибки то добро пожаловать на github [2].
P.S. Буду рад комментариям экспертов, которые смогут объяснить те или иные эффекты, повлиявшие на результат.
Автор: SerCe
Источник [3]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/news/57394
Ссылки в тексте:
[1] докладе Владимира Иванова про invokedynamics : http://www.youtube.com/watch?v=oeFejrCcqDI
[2] на github: https://github.com/SerCeMan/reflection-access-tests
[3] Источник: http://habrahabr.ru/post/216435/
Нажмите здесь для печати.