Как стать автором
Обновить

Разбор выражений, bytecode-way

Время на прочтение3 мин
Количество просмотров3.7K
Приходилось ли вам разбирать выражение? Рисовать график функции по введенной с клавиатуры пользователем строке?

Согласитесь, занятие приносит больше головной боли, чем радости от результата. Возможно вы знакомы с библиотеками antlr или javacc, тогда вы отделаетесь малой кровью. Но приобретете хвост из некрасивых сгенерированных классов, который как можно быстрее скроете от посторонних глаз в самом дальнем пакете.

Написав вчера о cglib, я заметил в документации главу о модификации байт-кода. И само собой напрашивается вопрос, а можно ли в runtime заставить класс выполнять, то что очень хочется, а не то что хочет класс?


Я честно взялся за изучение документации и быстро вышел на популярный редактор байт-кода asm. Однако к моему разочарованию модифицировать байт-код можно только неплохо зная и разбираясь в нём. Точнее это означает, что если вам необходимо вернуть результат выполнения «x * x + x /2» — вы должны скомпилировать это выражение в инструкции.

Не желая писать компилятор, я всё-таки сформулировал запрос к google и нашёл, то что искал

К моему счастью подобный компилятор уже реализован в другом редакторе байт-кода javassist. К слову, javassist более документирован, однако в open-source community бытует мнение, что cglib много быстрее. В нашем случае скорость создания класса, не такая такая важная составляющая, как скорость выполнения его методов. Итак, что же требуется сделать:

Copy Source | Copy HTML
  1. public interface Evaluator {
  2.     public double eval(double x);
  3. }


Создание класса:

Copy Source | Copy HTML
  1. ClassPool pool = ClassPool.getDefault();
  2.  
  3. CtClass evalClass = pool.makeClass("Formula");
  4. evalClass.setInterfaces(
  5.         new CtClass[]{
  6.                 pool.makeClass("com.micro.bench.Evaluator")
  7.         });
  8.  
  9. String expession = "x * x + x / 2";
  10.  
  11. evalClass.addMethod(
  12.         CtNewMethod.make(
  13.                 "public double eval (double x) { return (" + expession + ") ; }",
  14.                 evalClass));
  15.  
  16. Class clazz = evalClass.toClass();
  17. runtime = (Evaluator) clazz.newInstance();


Всё! В наших руках объект класса, реализующий интерфейс Evaluator, который вернёт нам x * x + x / 2

Что ж, теперь не плохо было бы сравнить производительность. Спасибо TheShade за комментарий, я немного модифицировал Timer, и использовал его для тестирования. Результат тестирования, показывающий, что оба метода выполняются за одно и тоже время:

     total|    amount|      last|    last 5|   last 10|       avg|       dev|         operation
   6856.00|     20.00|    351.00|    343.00|    341.00|    342.80|      1.24|      .. runtime calc
   6874.00|     20.00|    337.00|    343.00|    344.00|    343.70|      1.03|      .. compile-time calc


  • total — всего мс
  • amount — количество запусков шт
  • last — последний мс
  • last 5 — среднее по последним 5 запускам мс
  • last 10 — среднее по последним 10 запускам мс
  • avg — среднее вообще мс
  • dev — отклонение

Класс Evaluation.java — Сравнение двух способов
Класс Timer.java — Замер скорости и статистика
Теги:
Хабы:
Всего голосов 18: ↑17 и ↓1+16
Комментарии21

Публикации