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

Особенности неявного приведения типов в Groovy

Время на прочтение3 мин
Количество просмотров4.9K
Не так давно я задавал вопрос в Groovy mail-list — есть ли какой-то устойчивый список вещей, которые надо избегать при написании высокогопроизводительного на Groovy. Среди прочих советов, один из главных разработчиков Groovy, Jochen «blackdrag» Theodorou указал, что в общем случае, зачастую использование конкретного типа при объявлении переменной (например, MyType var =… вместо def var = ...) может ухудшить производительсть из-за накладных расходов на проверку типов и, если нужно, их приведение.

По утвеждениям разработчиков Groovy, многие из проблем в этом области наблюдались в версиях вплоть до 1.7 и были затем исправлены во время большой работы по общей оптимизации рантайма, проделанной в версии 1.9. Ниже, однако, небольшой эксперимент, который показывают эти накладные расходы даже на Groovy 1.8.3.

Перед этим экспериментом будет полезно просмотреть следующую статью — groovy.codehaus.org/From+source+code+to+bytecode, где рассказывается про то, как по шагам исходный код на Groovy преобразуется в байткод JVM, а так же почитать какую-нибудь вводную статью в собственно байткод, например эту — www.ibm.com/developerworks/ibm/library/it-haggar_bytecode.



Итак, простейший код:

class NoStrictType {
   void myMethod1() {
     def a = 4.2
   }
}

class StrictType {
  void myMethod1() {
    int a = 4.2
  }
}


Единственное отличие — во втором классе тип локальной переменной определен явно. Скомпилируем оба класса и посмотрим на байткод этих методов.
Для первого случая (без конкретного типа):

image

Итак, код вполне очевиден. Опустим первую строчку, это получение массива кешированных CallSite-ов, они напрямую к операциям над типами не относятся. Далее мы создаем новый объект типа BigDecimal (все помнят, что по умолчанию в Groovy все нецелые числа представляются как BidDecimal?), дублируем текущее значение на вершине стека операндов, достаем значение 4.2 из пула констант, инициализируем объект BigDecimal, сохраняем ссылку на этот созданный объект во второй ячейке массива локальных переменных текущего фрейма, потом загружаем ее оттуда на вершину стека, и наконец, используем pop;return, чтобы вернуть эту ссылку из метода. Опять же, как все помнят, в Groovy даже без явного оператора return любой метод возвращает последнюю переменную, которая использовалась в вычислениях (точнее, последнюю сссылку, сохраненную на вершине стека операндов на момент окончания работы метода).

Теперь, байткод для второго класса, StrictType.

image

В чем разница по сравнению с первым случаем? Добавился вызов статического метода DefaultTypeTransformation.intUnbox(). Посмотрим в документацию к этому методу, что он делает.
groovy.codehaus.org/api/org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.html
Видим, что этот метод просто конвертит объект-ссылку в в объект типа Number, и возвращает примитив.

public static int intUnbox(Object value) {
   Number n = castToNumber(value, int.class);
   return n.intValue();
}


Смотрим, как именно выполняется эта конвертация типов:

 public static Number castToNumber(Object object, Class type) {
        if (object instanceof Number)
            return (Number) object;
        if (object instanceof Character) {
            return Integer.valueOf(((Character) object).charValue());
        }
        if (object instanceof GString) {
            String c = ((GString) object).toString();
            if (c.length() == 1) {
                return Integer.valueOf(c.charAt(0));
            } else {
                throw new GroovyCastException(c, type);
            }
        }
        if (object instanceof String) {
            String c = (String) object;
            if (c.length() == 1) {
                return Integer.valueOf(c.charAt(0));
            } else {
                throw new GroovyCastException(c, type);
            }
        }
        throw new GroovyCastException(object, type);
    }


Несколько операторов instanceof, которые не самые дешевые, несколько явных приведений типа, условные операторы, работа с исключениями. Я не мерил, как это влияет на скорость работы в реальных приложениях, но судя по самому факту, сколько дополнительных байткодов надо выполнить в этом случае для приведения типов, впечатляет. Вспомните — сам оригинальный метод — всего 10 байткодов.
Теги:
Хабы:
+21
Комментарии8

Публикации