it-swarm.com.ru

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

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

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

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

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

282
Josh

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

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

246
Steve

Подход

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

  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

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

Результаты

 | Операция | Время выполнения в наносекундах за операцию | По отношению к экземпляру | 
 | ------------ | ----------------------------- --------- | ------------------------ | 
 | INSTANCEOF | 39,598 ± 0,022 нс/оп | 100,00% | 
 | GETCLASS | 39 687 ± 0,021 нс/оп | 100,22% | 
 | ТИП | 46,295 ± 0,026 нс/оп | 116,91% | 
 | OO | 48 078 ± 0,026 нс/оп | 121,42% | 

тЛ; др

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

230
Michael Dorner

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

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

Я использовал Java JVM 6.

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

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

72
Dan Mihai Ile

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

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

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

InstanceOf 3156
class== 2925 
OO 3083 
Id 3067 
17
brianegge

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

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

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

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

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

16
Olaf Kock

У меня такой же вопрос, но поскольку я не нашел «метрики производительности» для варианта использования, аналогичного моему, я сделал еще несколько примеров кода. На моем оборудовании и 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()));
    }
}
12
Xtra Coder

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

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

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

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

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

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

8
irreputable

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

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

ш расширяет х

А реализует ш

Б расширяет А

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

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

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

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

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

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

5
Varkhan

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

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

4
Peter Lawrey

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

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

Это где шаблоны приходят ...

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

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

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

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

4
Outlaw Programmer

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

4
Apocalisp

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

if (o instanceof Java.lang.String)

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

if (objectStruct->iAmInstanceOf == &Java_lang_String_class)

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

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

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

3
Mecki

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

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

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

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

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

Someobject.doSomething();
3
Demian Krige

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

3
Jose Quijada

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

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

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

o.doEverything();

и затем иметь реализацию doEverything () в Class1, вызывая doThis (), а в Class2 вызывая doThat (), и так далее.

2
Paul Tomblin

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

if(a instanceof AnyObject){
}

быстрее как:

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

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

2
Horcrux7

Если ваша единственная цель - скорость, то использование 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%.

1
Salix alba

Я также предпочитаю подход 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;
    }
  }
}
0
mike

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

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

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

seq = seq.head();

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

0
Michael Kay

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

0
Miles Elam

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

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

0
Karl