Влияние на производительность использования instanceof в Java - PullRequest
287 голосов
/ 19 сентября 2008

Я работаю над приложением, и один из подходов к проектированию предполагает чрезвычайно интенсивное использование оператора instanceof. Хотя я знаю, что дизайн ОО обычно старается избегать использования instanceof, это другая история, и этот вопрос связан исключительно с производительностью. Мне было интересно, есть ли какое-либо влияние на производительность? Это так же быстро, как ==?

Например, у меня есть базовый класс с 10 подклассами. В единственной функции, которая принимает базовый класс, я проверяю, является ли класс экземпляром подкласса, и выполняю некоторую процедуру.

Один из других способов решения этой проблемы - использовать вместо этого целочисленный примитив «id типа» и использовать битовую маску для представления категорий подклассов, а затем просто выполнить сравнение битовой маски для подкласса «id типа "постоянной маске, представляющей категорию.

instanceof каким-то образом оптимизируется JVM, чтобы быть быстрее, чем это? Я хочу придерживаться Java, но производительность приложения имеет решающее значение. Было бы здорово, если бы кто-то, кто был на этом пути раньше, мог бы дать совет. Не слишком ли я придираюсь к чему-то или сосредотачиваюсь не на том, что нужно оптимизировать?

Ответы [ 23 ]

248 голосов
/ 19 сентября 2008

Современные компиляторы JVM / JIC убрали снижение производительности большинства традиционно "медленных" операций, включая instanceof, обработку исключений, отражение и т. Д.

Как писал Дональд Кнут, «мы должны забыть о малой эффективности, скажем, в 97% случаев: преждевременная оптимизация - корень всего зла». Производительность instanceof, вероятно, не будет проблемой, поэтому не тратьте свое время на экзотические обходные пути, пока не убедитесь, что это проблема.

241 голосов
/ 22 октября 2014

подход

Я написал эталонную программу для оценки различных реализаций:

  1. instanceof реализация (как ссылка)
  2. объект, ориентированный через абстрактный класс и @Override тестовый метод
  3. с использованием собственной реализации типа
  4. getClass() == _.class реализация

Я использовал jmh , чтобы запустить тест с 100 разогревающими вызовами, 1000 итерациями при измерении и с 10 вилками. Таким образом, каждая опция была измерена в 10 000 раз, что требует 12:18:57 для запуска целого теста на моем MacBook Pro с macOS 10.12.4 и Java 1.8. Тест измеряет среднее время каждого варианта. Для получения более подробной информации см. моя реализация на GitHub .

Для полноты: существует предыдущая версия этого ответа и мой тест .

Результаты

| Operation  | Runtime in nanoseconds per operation | Relative to instanceof |
|------------|--------------------------------------|------------------------|
| INSTANCEOF | 39,598 ± 0,022 ns/op                 | 100,00 %               |
| GETCLASS   | 39,687 ± 0,021 ns/op                 | 100,22 %               |
| TYPE       | 46,295 ± 0,026 ns/op                 | 116,91 %               |
| OO         | 48,078 ± 0,026 ns/op                 | 121,42 %               |

ТЛ; др

В Java 1.8 instanceof - самый быстрый подход, хотя getClass() очень близок.

72 голосов
/ 29 декабря 2008

Я только что сделал простой тест, чтобы увидеть, как производительность instanceOf сравнивается с простым вызовом s.equals () строкового объекта с одной буквой.

в цикле 10.000.000 instanceO дал мне 63-96 мс, а строка равных дала мне 106-230 мс

Я использовал java jvm 6.

Так что в моем простом тесте быстрее сделать instanceOf вместо сравнения строк из одного символа.

Использование Integer's .equals () вместо строковых значений дало мне тот же результат, только когда я использовал == я был быстрее, чем instanceOf на 20 мс (в цикле 10.000.000)

18 голосов
/ 02 ноября 2009

Элементы, которые будут определять влияние на производительность:

  1. Количество возможных классов, для которых оператор instanceof мог бы вернуть true
  2. Распределение ваших данных - разрешены ли большинство операций instanceof с первой или второй попытки? Сначала вы захотите вернуть свои наиболее вероятные операции.
  3. Среда развертывания. Работа на виртуальной машине Sun Solaris существенно отличается от виртуальной виртуальной машины Sun Windows. По умолчанию Solaris будет работать в режиме «сервер», а Windows - в режиме клиента. Оптимизация JIT в Solaris сделает доступ ко всем методам одинаковым.

Я создал микробенч для четырех различных методов отправки . Результаты Solaris следующие: меньшее число быстрее:

InstanceOf 3156
class== 2925 
OO 3083 
Id 3067 
16 голосов
/ 19 сентября 2008

Отвечая на ваш последний вопрос: если профилировщик не скажет вам, что вы тратите смешное количество времени в экземпляре: Да, вы придирчивы.

Прежде чем задуматься об оптимизации чего-то, что никогда не нужно было оптимизировать: напишите свой алгоритм наиболее читабельным способом и запустите его. Запускайте его, пока jit-компилятор не получит возможность оптимизировать его самому. Если у вас возникли проблемы с этим фрагментом кода, используйте профилировщик, чтобы сообщить, где получить максимальную выгоду, и оптимизировать его.

Во времена высокооптимизирующих компиляторов ваши догадки о узких местах, скорее всего, будут совершенно неверными.

И в истинном духе этого ответа (в который я искренне верю): я совершенно не знаю, как соотносятся instanceof и ==, как только jit-компилятор получает возможность его оптимизировать.

Я забыл: никогда не измеряй первый прогон.

13 голосов
/ 23 марта 2012

У меня такой же вопрос, но поскольку я не нашел «метрики производительности» для варианта использования, аналогичного моему, я сделал еще несколько примеров кода. На моем оборудовании и Java 6 & 7 разница между instanceof и переключением на 10 миллионов итераций составляет

for 10 child classes - instanceof: 1200ms vs switch: 470ms
for 5 child classes  - instanceof:  375ms vs switch: 204ms

Итак, instanceof действительно медленнее, особенно для огромного количества операторов if-else-if, однако в реальном приложении разница будет незначительной.

import java.util.Date;

public class InstanceOfVsEnum {

    public static int c1, c2, c3, c4, c5, c6, c7, c8, c9, cA;

    public static class Handler {
        public enum Type { Type1, Type2, Type3, Type4, Type5, Type6, Type7, Type8, Type9, TypeA }
        protected Handler(Type type) { this.type = type; }
        public final Type type;

        public static void addHandlerInstanceOf(Handler h) {
            if( h instanceof H1) { c1++; }
            else if( h instanceof H2) { c2++; }
            else if( h instanceof H3) { c3++; }
            else if( h instanceof H4) { c4++; }
            else if( h instanceof H5) { c5++; }
            else if( h instanceof H6) { c6++; }
            else if( h instanceof H7) { c7++; }
            else if( h instanceof H8) { c8++; }
            else if( h instanceof H9) { c9++; }
            else if( h instanceof HA) { cA++; }
        }

        public static void addHandlerSwitch(Handler h) {
            switch( h.type ) {
                case Type1: c1++; break;
                case Type2: c2++; break;
                case Type3: c3++; break;
                case Type4: c4++; break;
                case Type5: c5++; break;
                case Type6: c6++; break;
                case Type7: c7++; break;
                case Type8: c8++; break;
                case Type9: c9++; break;
                case TypeA: cA++; break;
            }
        }
    }

    public static class H1 extends Handler { public H1() { super(Type.Type1); } }
    public static class H2 extends Handler { public H2() { super(Type.Type2); } }
    public static class H3 extends Handler { public H3() { super(Type.Type3); } }
    public static class H4 extends Handler { public H4() { super(Type.Type4); } }
    public static class H5 extends Handler { public H5() { super(Type.Type5); } }
    public static class H6 extends Handler { public H6() { super(Type.Type6); } }
    public static class H7 extends Handler { public H7() { super(Type.Type7); } }
    public static class H8 extends Handler { public H8() { super(Type.Type8); } }
    public static class H9 extends Handler { public H9() { super(Type.Type9); } }
    public static class HA extends Handler { public HA() { super(Type.TypeA); } }

    final static int cCycles = 10000000;

    public static void main(String[] args) {
        H1 h1 = new H1();
        H2 h2 = new H2();
        H3 h3 = new H3();
        H4 h4 = new H4();
        H5 h5 = new H5();
        H6 h6 = new H6();
        H7 h7 = new H7();
        H8 h8 = new H8();
        H9 h9 = new H9();
        HA hA = new HA();

        Date dtStart = new Date();
        for( int i = 0; i < cCycles; i++ ) {
            Handler.addHandlerInstanceOf(h1);
            Handler.addHandlerInstanceOf(h2);
            Handler.addHandlerInstanceOf(h3);
            Handler.addHandlerInstanceOf(h4);
            Handler.addHandlerInstanceOf(h5);
            Handler.addHandlerInstanceOf(h6);
            Handler.addHandlerInstanceOf(h7);
            Handler.addHandlerInstanceOf(h8);
            Handler.addHandlerInstanceOf(h9);
            Handler.addHandlerInstanceOf(hA);
        }
        System.out.println("Instance of - " + (new Date().getTime() - dtStart.getTime()));

        dtStart = new Date();
        for( int i = 0; i < cCycles; i++ ) {
            Handler.addHandlerSwitch(h1);
            Handler.addHandlerSwitch(h2);
            Handler.addHandlerSwitch(h3);
            Handler.addHandlerSwitch(h4);
            Handler.addHandlerSwitch(h5);
            Handler.addHandlerSwitch(h6);
            Handler.addHandlerSwitch(h7);
            Handler.addHandlerSwitch(h8);
            Handler.addHandlerSwitch(h9);
            Handler.addHandlerSwitch(hA);
        }
        System.out.println("Switch of - " + (new Date().getTime() - dtStart.getTime()));
    }
}
9 голосов
/ 16 июля 2011

instanceof действительно быстро, требует всего несколько инструкций процессора.

Очевидно, что если класс X не имеет загруженных подклассов (JVM знает), instanceof можно оптимизировать как:

     x instanceof X    
==>  x.getClass()==X.class  
==>  x.classID == constant_X_ID

Основная стоимость только чтение!

Если X имеет загруженные подклассы, необходимо еще несколько чтений; они, вероятно, расположены рядом, поэтому дополнительные расходы тоже очень низкие.

Хорошие новости всем!

5 голосов
/ 27 января 2009

instanceof, вероятно, будет более дорогостоящим, чем простое равенство, в большинстве реализаций реального мира (то есть тех, где instanceof действительно необходим, и вы не можете просто решить его путем переопределения общего метода, как каждый учебник для начинающих а Демиан выше предлагает).

Почему это? Потому что, вероятно, произойдет то, что у вас есть несколько интерфейсов, которые предоставляют некоторую функциональность (скажем, интерфейсы x, y и z) и некоторые объекты для манипулирования, которые могут (или нет) реализовывать один из этих интерфейсов ... но не напрямую. Скажем, например, у меня есть:

ш расширяет х

A реализует w

B расширяет A

C расширяет B, реализует y

D расширяет C, реализует z

Предположим, я обрабатываю экземпляр D, объект d. Для вычислений (d instanceof x) требуется взять d.getClass (), пройтись по циклам через интерфейсы, которые он реализует, чтобы узнать, является ли один == к x, и если нет, сделать это снова рекурсивно для всех их предков ... В нашем случае, если вы сначала исследуете это дерево в ширину, вы получите как минимум 8 сравнений, предположив, что y и z ничего не расширяют ...

Сложность дерева деривации в реальном мире, вероятно, будет выше. В некоторых случаях JIT может оптимизировать большую часть его, если он может заранее разрешить d как во всех возможных случаях экземпляр чего-то, что расширяет x. Реально, однако, вы будете проходить через это дерево большую часть времени.

Если это станет проблемой, я бы предложил вместо этого использовать карту обработчика, связав конкретный класс объекта с замыканием, которое выполняет обработку. Это удаляет фазу обхода дерева в пользу прямого отображения. Однако помните, что если вы установили обработчик для C.class, мой объект d выше не будет распознан.

вот мои 2 цента, надеюсь, они помогут ...

4 голосов
/ 21 сентября 2008

Демиан и Пол упоминают хороший момент; однако , размещение кода для выполнения действительно зависит от того, как вы хотите использовать данные ...

Я большой поклонник небольших объектов данных, которые можно использовать разными способами. Если вы используете переопределенный (полиморфный) подход, ваши объекты можно использовать только «в одну сторону».

Вот тут и появляются шаблоны ...

Вы можете использовать двойную диспетчеризацию (как в шаблоне посетителя), чтобы попросить каждый объект «позвонить вам», передавая себя - это разрешит тип объекта. Однако (опять же) вам понадобится класс, который может "делать вещи" со всеми возможными подтипами.

Я предпочитаю использовать шаблон стратегии, где вы можете зарегистрировать стратегии для каждого подтипа, который вы хотите обработать. Что-то вроде следующего. Обратите внимание, что это помогает только для точных совпадений типов, но имеет то преимущество, что оно расширяемое - сторонние участники могут добавлять свои собственные типы и обработчики. (Это хорошо для динамических сред, таких как OSGi, где можно добавлять новые пакеты)

Надеюсь, это вдохновит на некоторые другие идеи ...

package com.javadude.sample;

import java.util.HashMap;
import java.util.Map;

public class StrategyExample {
    static class SomeCommonSuperType {}
    static class SubType1 extends SomeCommonSuperType {}
    static class SubType2 extends SomeCommonSuperType {}
    static class SubType3 extends SomeCommonSuperType {}

    static interface Handler<T extends SomeCommonSuperType> {
        Object handle(T object);
    }

    static class HandlerMap {
        private Map<Class<? extends SomeCommonSuperType>, Handler<? extends SomeCommonSuperType>> handlers_ =
            new HashMap<Class<? extends SomeCommonSuperType>, Handler<? extends SomeCommonSuperType>>();
        public <T extends SomeCommonSuperType> void add(Class<T> c, Handler<T> handler) {
            handlers_.put(c, handler);
        }
        @SuppressWarnings("unchecked")
        public <T extends SomeCommonSuperType> Object handle(T o) {
            return ((Handler<T>) handlers_.get(o.getClass())).handle(o);
        }
    }

    public static void main(String[] args) {
        HandlerMap handlerMap = new HandlerMap();

        handlerMap.add(SubType1.class, new Handler<SubType1>() {
            @Override public Object handle(SubType1 object) {
                System.out.println("Handling SubType1");
                return null;
            } });
        handlerMap.add(SubType2.class, new Handler<SubType2>() {
            @Override public Object handle(SubType2 object) {
                System.out.println("Handling SubType2");
                return null;
            } });
        handlerMap.add(SubType3.class, new Handler<SubType3>() {
            @Override public Object handle(SubType3 object) {
                System.out.println("Handling SubType3");
                return null;
            } });

        SubType1 subType1 = new SubType1();
        handlerMap.handle(subType1);
        SubType2 subType2 = new SubType2();
        handlerMap.handle(subType2);
        SubType3 subType3 = new SubType3();
        handlerMap.handle(subType3);
    }
}
4 голосов
/ 27 января 2009

instanceof очень эффективен, поэтому ваша производительность вряд ли пострадает. Тем не менее, использование большого количества instanceof наводит на мысль о проблеме дизайна.

Если вы можете использовать xClass == String.class, это быстрее. Примечание: вам не нужен instanceof для выпускных классов.

...