Делаем reflection быстрой как прямые вызовы

. Posted in Fox populi - FoxPro

Большинство программистов знают о reflection, которая (она — рефлексия) упрощает добавление динамических возможностей в статические языки, такие как Java/C#. Однако reflection упрекают в том, что вызовы работают очень медленно — до 500 раз медленнее. Все же это можно c легкостью исправить — покажем в этой статье как сделать reflection-вызов таким же быстрым, как и прямой (direct) вызов.




Замерим скорость выполнения следующего класса:

Copy Source | Copy HTML
class A {
public int value = 0;

public void add(int x) {
value += x;
}
}


Я измерял скорость на Core 2 Duo E6300, Windows Vista, JRE 1.6.0_16. Класс Timer — вспомогательный класс для измерения времени. Для измерения будем делать 5 000 000 вызовов каждого из способов. Начнём с прямого доступа к полю класса:

Copy Source | Copy HTML
t.start("Direct field access");
for (int i = 0; i < LOOPS_IN_STEP_COUNT; ++i) {
a.value += i;
}
t.stop();


5000000 итераций выполнилось за 44мс. Время может различаться от вызова к вызову, однако в целом результат получается такой. Сделаем тоже самое с использованием reflection:

Copy Source | Copy HTML
t.start("Preparing for reflective field access");
Field f = A.class.getField("value");
t.stop();

t.start("Reflective field access");
for (int i = 0; i < LOOPS_IN_STEP_COUNT; ++i) {
f.set(a, ((Integer) f.get(a)) + i);
}
t.stop();


Выполнилось за 11233мс — более чем 250 раз медленнее! Повторим тоже самое, заменив прямую работу с полями на вызовы методов.

Copy Source | Copy HTML
t.start("Direct method access");
for (int i = 0; i < LOOPS_IN_STEP_COUNT; ++i) {
a.add(i);
}
t.stop();

t.start("Preparing for reflective method access");
Method m = A.class.getDeclaredMethod("add", int.class);
t.stop();

t.start("Reflective method access");
for (int i = 0; i < LOOPS_IN_STEP_COUNT; ++i) {
m.invoke(a, i);
}
t.stop();


Прямой вызов метода чуть медленнее, чем прямой доступ к полю и выполняется за 51 мс. При этом reflection-доступ к методу чуть быстрее — 4177 ms – около 100 медленнее прямого доступа.

Как же это оптимизировать?

Reflection средствами JVM реализована с использованием медленных JNI-вызовов. CGLIB предоставляет класс FastMethod, предоставляющий ту же функциональность без использования JNI, делая вызовы много, много быстрее:

Copy Source | Copy HTML
t.start("Preparing for fast reflective method access");
FastClass fc = FastClass.create(A.class);
FastMethod fm = fc.getMethod(m);
t.stop();

t.start("Fast reflective method access");
for (int i = 0; i < LOOPS_IN_STEP_COUNT; ++i) {
fm.invoke(a, new Object[]{i});
}
t.stop();


Результат — 353 мс, что всего в 7 раз медленнее, чем обычный вызов метода. Если бы CGLIB имел аналог FastMethod для полей, мы могли бы сравнить и доступ к полям.

Давайте подумаем, как можно добиться лучшего результата в предыдущем фрагменте. Мы имеем несколько дорогостоящих операций: первая — создание массива в цикле, мы можем обойтись одним массивом для всех итераций цикла. Другая — боксинг (auto-boxing) чисел. Предположим, что число — не число, а обычный объект и не требует боксинга:

Copy Source | Copy HTML
class Ref {
int value;
}

class A {
public int value = 0;

public void add(Ref ref) {
value += ref.value;
}
}

...

t.start("Preparing for fast reflective method access (2)");
FastClass fc2 = FastClass.create(A.class);
FastMethod fm2 = fc2.getMethod("add", new Class[]{Ref.class});
Ref ref = new Ref();
Object[] arguments = new Object[]{ref};
t.stop();

t.start("Fast reflective method access (2)");
for (int i = 0; i < LOOPS_IN_STEP_COUNT; ++i) {
ref.value = i;
fm2.invoke(a, arguments);
}
t.stop();


Результат — 112мс. Всего лишь в 2 раза медленее, чем обычный вызов метода! Однако, почему же медленее? Дело в том, что FastMethod реализован с использованием шаблона проектирования Decorator. Попробуем декорировать наш класс A и оценим результат:

Copy Source | Copy HTML
interface AIf {
public void add(int x);
}

class A implements AIf {
public int value = 0;

public void add(int x) {
value += x;
}
}

class ADecorator implements AIf {
private AIf a;

public ADecorator(AIf a) {
this.a = a;
}

public void add(int x) {
a.add(x);
}
}
...
t.start("Preparing decorator method access");
AIf d = new ADecorator(a);
t.stop();

t.start("Decorator method access");
for (int i = 0; i < LOOPS_IN_STEP_COUNT; ++i) {
d.add(i);
}
t.stop();


Выполнилось за 124 мс — получилось даже медленнее, чем FastMethod. И так как каждый хороший программист использует шаблоны проектирования — мы можем сказать, что с некоторой оптимизацией reflection может быть столь же быстрой, как и прямые вызовы без reflection.

Полный листинг:

Copy Source | Copy HTML
package reflection;

import net.sf.cglib.reflect.FastClass;
import net.sf.cglib.reflect.FastMethod;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.Map;

class Timer {
private long startTime = 0;

private String msg = null;

private Map map = new LinkedHashMap();

public void start(String msg) {
if (startTime != 0) {
throw new IllegalStateException("Already started");
}
startTime = System.currentTimeMillis();
this.msg = msg;
}

public void stop() {
if (startTime == 0) {
throw new IllegalStateException("Not started");
}
long now = System.currentTimeMillis();
Long n = map.get(msg);
if (n == null) {
n = 0l;
}
n += (now - startTime);
map.put(msg, n);
startTime = 0;
msg = null;
}

public void output() {
for (String msg : map.keySet()) {
System.out.println(msg + ": " + map.get(msg));
}
}
}

class Ref {
int value;
}

interface AIf {
public void add(int x);
}

class A implements AIf {
public int value = 0;

public void add(int x) {
value += x;
}

public void add(Ref ref) {
value += ref.value;
}
}

class ADecorator implements AIf {
private AIf a;

public ADecorator(AIf a) {
this.a = a;
}

public void add(int x) {
a.add(x);
}
}

public class Reflect {
private static final int TOTAL_LOOP_COUNT = 5000000;

/**
* How many loops to do in one step.
*/
private static final int LOOPS_IN_STEP_COUNT = 100;

/**
* How many steps to do - in each step there will be
* {@link #LOOPS_IN_STEP_COUNT} loops.
*/
private static final int STEP_COUNT = TOTAL_LOOP_COUNT / LOOPS_IN_STEP_COUNT;

public static void main(String[] args) throws SecurityException,
NoSuchFieldException, IllegalArgumentException,
IllegalAccessException, NoSuchMethodException,
InvocationTargetException {

Timer t = new Timer();
A a = new A();

for (int j = 0; j < STEP_COUNT; ++j) {

t.start("Direct field access");
for (int i = 0; i < LOOPS_IN_STEP_COUNT; ++i) {
a.value += i;
}
t.stop();

t.start("Preparing for reflective field access");
Field f = A.class.getField("value");
t.stop();

t.start("Reflective field access");
for (int i = 0; i < LOOPS_IN_STEP_COUNT; ++i) {
f.set(a, ((Integer) f.get(a)) + i);
}
t.stop();

t.start("Direct method access");
for (int i = 0; i < LOOPS_IN_STEP_COUNT; ++i) {
a.add(i);
}
t.stop();

t.start("Preparing for reflective method access");
Method m = A.class.getDeclaredMethod("add", int.class);
t.stop();

t.start("Reflective method access");
for (int i = 0; i < LOOPS_IN_STEP_COUNT; ++i) {
m.invoke(a, i);
}
t.stop();

t.start("Preparing for fast reflective method access");
FastClass fc = FastClass.create(A.class);
FastMethod fm = fc.getMethod(m);
t.stop();

t.start("Fast reflective method access");
for (int i = 0; i < LOOPS_IN_STEP_COUNT; ++i) {
fm.invoke(a, new Object[]{i});
}
t.stop();

t.start("Preparing for fast reflective method access (2)");
FastClass fc2 = FastClass.create(A.class);
FastMethod fm2 = fc2.getMethod("add", new Class[]{
Ref.class
});
Ref ref = new Ref();
Object[] arguments = new Object[]{ref};
t.stop();

t.start("Fast reflective method access (2)");
for (int i = 0; i < LOOPS_IN_STEP_COUNT; ++i) {
ref.value = i;
fm2.invoke(a, arguments);
}
t.stop();

t.start("Preparing decorator method access");
AIf d = new ADecorator(a);
t.stop();

t.start("Decorator method access");
for (int i = 0; i < LOOPS_IN_STEP_COUNT; ++i) {
d.add(i);
}
t.stop();

}

t.output();
}
}

 

Добавить комментарий


Защитный код
Обновить

Команды

Релиз Firefox 8, Thunderbird 8 и сопутствующих проектов Mozilla
Проект Mozilla официально представил релиз web-браузера Firefox 8.0, первый выпуск в рамках нового сокращенного цикла разработки, которому будет присвоен статус релиза с пр...14-11-2011

Хороший ход

События объекта Database Container
События объекта Database Container (DBC) предоставляют связь между событиями, написанными разработчиком, и активностью базы данных во время работы пользователя, такой как от...14-11-2011

Руководства

О правилах хорошего тона программирования на Лисе
1. Рекомендуется использовать на каждой рабочей станции копию Лисы. 2. Для ускорения необходимо разделить общедоступные базы и библиотеки. 3. Разделить функции для к...12-11-2011