Влияние производительности использования instanceof в Java

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

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

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

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

Ответ 1

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

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

Ответ 2

Подход

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

  • instanceof реализация (как ссылка)
  • объект, ориентированный через абстрактный класс, и @Override метод проверки
  • с использованием реализации собственного типа
  • 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 %               |

TL;DR

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

Ответ 3

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

в цикле 10.000.000 экземплярOf дал мне 63-96 мс, а строка равнялась дам мне 106-230 мс

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

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

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

Ответ 4

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

  • Число возможных классов, для которых оператор instanceof мог бы вернуть true
  • Распределение ваших данных - это большая часть экземпляра операций, разрешенных при первой или второй попытке? Вы хотите, чтобы ваши шансы сначала вернуть истинные операции.
  • Среда развертывания. Работа на Sun Solaris VM значительно отличается от Sun Windows JVM. По умолчанию Solaris будет работать в режиме "сервер", а Windows будет работать в клиентском режиме. Оптимизация JIT для Solaris сделает доступ к всем методам одинаковым.

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

InstanceOf 3156
class== 2925 
OO 3083 
Id 3067 

Ответ 5

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

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

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

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

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

Ответ 6

У меня есть тот же вопрос, но поскольку я не нашел "метрики производительности" для случая использования, подобного моему, я сделал еще несколько примеров кода. На моем оборудовании и Java 6 и 7 разница между instanceof и switch на 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()));
    }
}

Ответ 7

instanceof работает очень быстро, принимая только несколько инструкций CPU.

По-видимому, если класс X не имеет подклассов (JVM знает), instanceof можно оптимизировать как:

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

Основная стоимость - это просто читать!

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

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

Ответ 8

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

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

w расширяет x

A реализует w

B продолжается A

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

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

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

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

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

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

Ответ 9

'instanceof' на самом деле является оператором, например + или -, и я считаю, что он имеет свою собственную инструкцию по байт-коду JVM. Это должно быть очень быстро.

Я не должен этого делать, если у вас есть переключатель, в котором вы тестируете, если объект является экземпляром какого-либо подкласса, тогда ваш дизайн, возможно, потребуется переработать. Подумайте о том, чтобы подтасовать поведение подкласса в самих подклассах.

Ответ 10

Экземпляр очень быстрый. Он сводится к байт-коду, который используется для сравнения ссылок класса. Попробуйте несколько миллионов экземпляров в цикле и убедитесь сами.

Ответ 11

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

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

Вот где шаблоны входят...

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

Я предпочитаю использовать шаблон стратегии, где вы можете регистрировать стратегии для каждого подтипа, который хотите обработать. Что-то вроде следующего. Обратите внимание, что это помогает только для точного совпадения типов, но имеет то преимущество, что он расширяемый - сторонние участники могут добавлять свои собственные типы и обработчики. (Это полезно для динамических фреймворков, таких как 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);
    }
}

Ответ 12

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

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

Ответ 13

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

if (o instanceof java.lang.String)

может быть таким же быстрым, как следующий код C

if (objectStruct->iAmInstanceOf == &java_lang_String_class)

предполагая, что компилятор JIT на месте и выполняет достойную работу.

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

Это не обязательно, однако, это сильно зависит от JVM. Однако, если это окажется причиной узкого места в вашем коде, я считаю, что реализация JVM довольно бедна. Даже тот, у которого нет JIT-компилятора и только интерпретирует код, должен иметь возможность сделать экземпляр теста практически без времени.

Ответ 14

InstanceOf является предупреждением о плохом объектно-ориентированном дизайне.

Текущие JVM действительно означают, что instanceOf не сильно беспокоится о производительности. Если вы часто используете его, особенно для основных функций, возможно, пора посмотреть на дизайн. Эффективность (и простота/ремонтопригодность) повышения рефакторинга до лучшего дизайна значительно перевешивает любые фактические циклы процессоров, потраченные на фактический вызов instanceOf.

Чтобы дать очень маленький пример упрощенного программирования.

if (SomeObject instanceOf Integer) {
  [do something]
}
if (SomeObject instanceOf Double) {
  [do something different]
}

Является ли бедной архитектурой лучшим выбором, чтобы SomeObject был родительским классом из двух дочерних классов, где каждый дочерний класс переопределяет метод (doSomething), поэтому код будет выглядеть следующим образом:

Someobject.doSomething();

Ответ 15

Я вернусь к вам в качестве примера. Но способ избежать проблемы (или ее отсутствия) в целом заключался бы в создании родительского интерфейса для всех подклассов, на которых вам нужно делать instanceof. Интерфейс будет супер-набором всех методов в подклассах, для которых вам нужно выполнить проверку экземпляра. Если метод не применяется к определенному подклассу, просто укажите фиктивную реализацию этого метода. Если бы я не понял это неправильно, я так и пережил проблему в прошлом.

Ответ 16

Если вы не делаете это во внутреннем цикле, я бы не стал беспокоиться об этом.

Ответ 17

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

if (o instanceof Class1)
   doThis();
else if (o instanceof Class2)
   doThat();
//...

Вы можете заменить это на

o.doEverything();

а затем выполнить "doEverything()" в вызове Class1 "doThis()", а в Class2 вызовите "doThat()" и т.д.

Ответ 18

В современной версии Java оператор instanceof быстрее, чем простой вызов метода. Это означает:

if(a instanceof AnyObject){
}

быстрее:

if(a.getType() == XYZ){
}

Другое дело, если вам нужно каскадировать много экземпляров. Затем переключатель, который только вызывает getType(), быстрее.

Ответ 19

Если скорость - ваша единственная цель, то использование констант int для идентификации подкласс, кажется, брит миллисекунды времени

static final int ID_A = 0;
static final int ID_B = 1;
abstract class Base {
  final int id;
  Base(int i) { id = i; }
}
class A extends Base {
 A() { super(ID_A); }
}
class B extends Base {
 B() { super(ID_B); }
}
...
Base obj = ...
switch(obj.id) {
case  ID_A: .... break;
case  ID_B: .... break;
}

ужасный дизайн OO, но если ваш анализ производительности указывает, что это место, где вы, возможно, узкое место. В моем коде код отправки занимает 10% от общего времени выполнения, и это, возможно, способствовало повышению общей скорости на 1%.

Ответ 20

Вы должны измерять/профиль, если это действительно проблема с производительностью в вашем проекте. Если это так, я бы рекомендовал редизайн - если это возможно. Я уверен, что вы не можете победить реализацию на платформе (написано на C). Вы также должны учитывать множественное наследование в этом случае.

Вы должны больше рассказать о проблеме, возможно, вы можете использовать ассоциативный магазин, например. Map < Class, Object > , если вас интересуют только конкретные типы.

Ответ 21

Что касается Питера Лоури, обратите внимание, что вам не нужен экземпляр для окончательных классов и вы можете просто использовать ссылочное равенство, будьте осторожны! Несмотря на то, что последние классы не могут быть расширены, они не гарантированно загружаются одним и тем же загрузчиком классов. Используйте только x.getClass() == SomeFinal.class или его ilk, если вы абсолютно уверены, что в этом разделе кода есть только один загрузчик классов.

Ответ 22

Я также предпочитаю подход enum, но я бы использовал абстрактный базовый класс, чтобы заставить подклассы реализовать метод getType().

public abstract class Base
{
  protected enum TYPE
  {
    DERIVED_A, DERIVED_B
  }

  public abstract TYPE getType();

  class DerivedA extends Base
  {
    @Override
    public TYPE getType()
    {
      return TYPE.DERIVED_A;
    }
  }

  class DerivedB extends Base
  {
    @Override
    public TYPE getType()
    {
      return TYPE.DERIVED_B;
    }
  }
}

Ответ 23

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

if (!(seq instanceof SingleItem)) {
  seq = seq.head();
}

когда вызов head() в SingleItem возвращает значение без изменений. Замена кода на

seq = seq.head();

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

Ответ 24

Я пишу тест производительности на основе jmh-java-benchmark-archetype: 2.21. JDK - openjdk, версия - 1.8.0_212. Тестовая машина Mac Pro. Результат теста:

Benchmark                Mode  Cnt    Score   Error   Units
MyBenchmark.getClasses  thrpt   30  510.818 ± 4.190  ops/us
MyBenchmark.instanceOf  thrpt   30  503.826 ± 5.546  ops/us

Результат показывает, что: getClass лучше, чем instanceOf, что противоречит другому тесту. Однако я не знаю почему.

Тестовый код ниже:

public class MyBenchmark {

public static final Object a = new LinkedHashMap<String, String>();

@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean instanceOf() {
    return a instanceof Map;
}

@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean getClasses() {
    return a.getClass() == HashMap.class;
}

public static void main(String[] args) throws RunnerException {
    Options opt =
        new OptionsBuilder().include(MyBenchmark.class.getSimpleName()).warmupIterations(20).measurementIterations(30).forks(1).build();
    new Runner(opt).run();
}
}

Ответ 25

Вы сосредотачиваетесь на неправильном. Разница между instanceof и любым другим методом проверки одного и того же объекта, вероятно, даже не измерима. Если производительность критическая, то Java, вероятно, является неправильным языком. Основная причина заключается в том, что вы не можете контролировать, когда VM решает, что он хочет собирать мусор, который может занять процессор до 100% в течение нескольких секунд в большой программе (MagicDraw 10 был для этого отличным вариантом). Если вы не контролируете каждый компьютер, на котором будет работать эта программа, вы не можете гарантировать, какая версия JVM будет включена, и многие из старших имеют серьезные проблемы с производительностью. Если это небольшое приложение, вы можете быть в порядке с Java, но если вы постоянно читаете и отбрасываете данные, тогда вы заметите, когда GC вздрагивает.