Русский English Тэги View Sergey Zolotaryov's profile on LinkedIn Вход
Преимущества генерированного кода на примере сортировки
Постоянная ссылка 09-10-2008 anydoby java

Каждому, наверное, приходилось сталкиваться с проблемой сортировки данных из динамически формируемых источников. Проще говоря: есть у вас массив объектов Item и нужно сортировать по свойствам price, name, creationDate и т.д. Но писать Comparator на каждый случай не хочется, поэтому используем класс, использующий reflections:


package com.anydoby.comparator;

import java.util.Comparator;

import org.apache.commons.beanutils.PropertyUtils;

/**
 * This comparator retrieves objects to compare from the named objects'
 * properties {@link #compare(Object, Object)} and then performs comparison.
 * 
 * @author SergeyZ
 * 
 */
public final class PropertyBasedComparator implements Comparator<Object> {
    private final String propertyName;
    private final boolean ascending;

    public PropertyBasedComparator(String propertyName, boolean ascending) {
        this.propertyName = propertyName;
        this.ascending = ascending;
    }

    @SuppressWarnings("unchecked")
    public int compare(Object o1, Object o2) {
        try {
            o1 = PropertyUtils.getProperty(o1, propertyName);
            o2 = PropertyUtils.getProperty(o2, propertyName);
            return compare(o1, o2, ascending);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @SuppressWarnings("unchecked")
    public static int compare(Object o1, Object o2, boolean ascending) {
        int compareTo;
        if (o1 == null && o2 == null) {
            compareTo = 0;
        } else if (o1 == null) {
            compareTo = -1;
        } else if (o2 == null) {
            compareTo = 1;
        } else {
            if (o1.getClass() == String.class) {
                compareTo = o1.toString().compareToIgnoreCase(o2.toString());
            } else if (o1 instanceof Comparable) {
                compareTo = ((Comparable) o1).compareTo(o2);
            } else {
                return compare(o1.toString(), o2.toString(), ascending);
            }
        }
        return ascending ? compareTo : -compareTo;
    }

}

Ключевая фраза в этом классе (и главный тормоз производительности) - PropertyUtils.getProperty(o1, propertyName). Каждый раз приходится рефлективно вытаскивать нужное свойство из двух объектов и потом уже производить сравнение. В принципе операция довольно быстрая и не сильно сказывается на скорости сортировки небольших массивов. Однако, если размер массива 100 или 1000, это уже может быть существенно. Проблему я решил при помощи небольшой библиотеки генерации классов - ASM. Библиотека предоставляет довольно простой API для создания и изменения классов на лету. Единственный недостаток - нужно понимать, что такое байт-код и как из него строятся классы. А это тема довольно обширная. Чтобы не морочить голову деталями, есть простой способ - пишете нужный вам динамический класс на Java и потом в Eclipse устанавливаете вот этот плагин. При просмотре класса этим плагином можно не только увидеть нужный байт-код, но и получить Java код, который создает этот класс при помощи ASM - кнопка Show Asmified Code. Потом можно код немного почистить от лишних лейб с номерами строк (все равно дебажить нечего) и в нужных местах подставить изменяемые параметры. Затем класс можно загрузить своим ClassLoaderом и инстанциировать.

Для примера я взял массивы по 10000 элементов из объектов с тремя свойствами разных типов и провел замеры времени сортировки по каждому из полей (по 100 циклов для каждого поля):


Reflections (commons-beanutils)

STATS FOR: testString
Total duration: 37.382s.
Average sort: 0.37382s.
STATS FOR: testDate
Total duration: 34.757s.
Average sort: 0.34757s.
STATS FOR: testComparable
Total duration: 38.677s.
Average sort: 0.38677s.

ASM generated comparator

STATS FOR: testString
Total duration: 2.319s.
Average sort: 0.02319s.
STATS FOR: testDate
Total duration: 1.05s.
Average sort: 0.0105s.
STATS FOR: testComparable
Total duration: 0.948s.
Average sort: 0.00948s.

ASM generated comparator - no generics

STATS FOR: testString
Total duration: 2.29s.
Average sort: 0.0229s.
STATS FOR: testDate
Total duration: 0.949s.
Average sort: 0.00949s.
STATS FOR: testComparable
Total duration: 0.833s.
Average sort: 0.00833s.

Как видим - производительность для строковых полей отличается в 16 раз, для дат (они же long) - в 33 и для чисел - в 46! Забавно, что использование generics немного замедляет сортировку, так что для совершенной оптимизации лучше их не использовать. К слову, и размер сгенеренного класса тоже получается меньше - не нужно генерировать синтетический bridge метод.

Для сочувствующих и интересующихся прилагаю здесь Eclipse проект, с которым можно поиграться.

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

Предыдущая статья Почему JSF не обновляет состояние combobox Следующая статья Программная аутентификация в Tomcat