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

315

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

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

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

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

мистифицировать
источник
81
Я думаю, что смысл вопроса, однако, заключался в том, чтобы отложить вопрос о наилучшей ОО-практике и проанализировать результаты.
Дейв Л.
3
@ Дэйв Л. Обычно я бы согласился, но ОП упоминает, что он ищет некоторые общие методы оптимизации, и он не уверен, связана ли его проблема с «instanceof». Я думаю, что стоит хотя бы упомянуть «правильный» дизайн, чтобы он мог профилировать оба варианта.
Outlaw Programmer
51
Тьфу ... Почему все ответы не соответствуют сути вопроса и содержат одну и ту же старую риторику Кнута об оптимизации? Ваш вопрос о том, является ли instanceof значительно / на удивление медленнее, чем проверка объекта класса с помощью ==, и я обнаружил, что это не так.
грязный
3
Производительность instanceof и кастинга довольно хорошие. Я опубликовал некоторые моменты в Java7 вокруг различных подходов к проблеме здесь: stackoverflow.com/questions/16320014/…
Wheezil

Ответы:

268

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

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

Стив
источник
13
Современная JVM / JIC. Не могли бы вы упомянуть, с какой версии Java была покрыта эта оптимизация?
Равиша
138
Всегда есть кто-то, кто цитирует Кнута, когда речь идет о производительности ... Забывая, что Кнут также заявил (в той же статье): «В устоявшихся инженерных дисциплинах улучшение, легко достижимое на 12%, никогда не считается незначительным, и я верю в ту же точку зрения. должен преобладать в разработке программного обеспечения ", почти вся его работа была посвящена эффективности алгоритмов, и он писал алгоритмы на ассемблере, чтобы (среди прочего) добиться лучшей производительности. Мех ...
кгадек
4
В стороне здесь, но было try { ObjT o = (ObjT)object } catch (e) { no not one of these }бы немного медленнее?
peterk
35
Если «объект» является экземпляром ObjT, приведение его происходит немного быстрее, чем создание instanceof, но разница, которую обнаружил мой быстрый тест, составляла 10–20 мс за 10 000 000 итераций. Если «объект» не является ObjT, тем не менее, перехват исключения был более чем в 3000 раз медленнее - более 31 000 мс против ~ 10 мс для instanceof.
Стив
19
Такой сильный аргумент без какой-либо «ссылки», совершенно бесполезен, потому что просто самоуверен.
marcorossi
280

Подходить

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

  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 |
| ------------ | ------------------------------------ - | ------------------------ |
| INSTANCEOF | 39,598 ± 0,022 нс / оп | 100,00% |
| GETCLASS | 39 687 ± 0,021 нс / оп | 100,22% |
| ТИП | 46,295 ± 0,026 нс / оп | 116,91% |
| ОО | 48 078 ± 0,026 нс / оп | 121,42% |

ТЛ; др

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

Майкл Дорнер
источник
58
+0.(9)для науки!
16
+ остальные 0,1 от меня: D
Тобиас Райх
14
@TobiasReich Итак, мы получили +1.0(9). :)
Павел
9
Я не думаю, что это измеряет что-либо значимое вообще. Код измеряет использование System.currentTimeMillis()операции, которая не намного больше, чем один вызов метода, что должно дать большую точность. Вместо этого используйте тестовую среду, такую ​​как JMH !
Лий
6
Или просто сделайте хронометраж всего миллиарда звонков, а не за звонок.
LegendLength
74

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

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

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

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

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


источник
4
Можно ли разместить код здесь? Это было бы круто!
Алхимик
7
Как instanceOf сравнивается с распределением полиморфных функций?
Крис
21
Почему вы сравниваете instanceof с String.equals ()? Если вы хотите проверить тип объекта object.getClass (). Equals (SomeType.class)
marsbear
4
@marsbear equals()не будет сокращать это, потому что подклассы; вам нужно isAssignableFrom().
Дэвид Моулз
1
@marsbear Правильно, но это не лучший тест того, о чем спрашивал ОП.
Дэвид Моулз
20

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

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

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

InstanceOf 3156
class== 2925 
OO 3083 
Id 3067 
brianegge
источник
18

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

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

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

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

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

Олаф Кок
источник
1
Но оригинальный постер упоминал, что производительность была критически важной для этого приложения, так что оптимизация в этой ситуации не является необоснованной. Другими словами, вы не написали бы 3d-игру на GWBasic, а потом в конце сказали: хорошо, давайте начнем оптимизировать это, первый шаг - перенести ее на c ++.
LegendLength
GWBasic может быть отличным началом для 3D-игр, если есть подходящие библиотеки. Но это в стороне (поскольку это искусственный аргумент): OP не требует полного переписывания в качестве оптимизации. Речь идет об одной конструкции, в которой мы даже не знаем, является ли влияние значительным (даже если в текущей версии компилятора есть более эффективный способ сделать то же самое ). Я твердо стою за c2.com/cgi/wiki?ProfileBeforeOptimizing и мой ответ. Предварительная оптимизация - корень всего зла! Это усложняет обслуживание - а обслуживание - это аспект, который стоит оптимизировать
Олаф Кок
15

У меня тот же вопрос, но поскольку я не нашел «метрики производительности» для варианта использования, аналогичного моему, я сделал еще несколько примеров кода. На моем оборудовании и 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()));
    }
}
Xtra Coder
источник
Какой результат был Java 6, а какой был Java 7? Вы пересмотрели это под Java 8? Более важно то, что вы сравниваете длину if instanceofs с тем, что важно в выражении case для целых чисел. Я думаю, что мы ожидаем, что Int-переключатель будет светиться быстро.
Azeroth2b
1
Я не могу точно вспомнить, что происходило 5 лет назад - я думаю, что и Java 6, и Java 7 имели схожий результат, поэтому предоставляется только один результат (при условии, что 2 строки предназначены для различной глубины иерархии классов) ... и нет Я не пробовал сравнивать с Java 8. Весь код теста предоставляется - вы можете скопировать / вставить его и проверить в нужных вам средах (обратите внимание - в настоящее время я бы использовал тест JMH для этого).
Xtra Coder
9

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

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

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

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

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

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

irreputable
источник
2
могут быть оптимизированы или как оптимизировать? источник?
@vaxquis может как свой специфичный для jvm
RecursiveExceptionException
@itzJanuary, вздохнули, вы упустили смысл моего вопроса здесь: все знают, что компилятор может оптимизировать foo- но на fooсамом деле в настоящее время оптимизируется с помощью Oracle javac / VM - или просто возможно, что он сделает это в будущем? Кроме того, я спросил ответчика, есть ли у него какой-либо вспомогательный источник (будь то документы, исходный код, блог разработчика), документирующий, что он действительно может быть оптимизирован или оптимизирован ? Без этого этот ответ - просто случайное размышление о том, что может сделать компилятор .
@vaxquis Вы никогда не упоминали виртуальную машину Hotspot, но в этом случае я не знаю, оптимизирована ли она.
RecursiveExceptionException
1
Недавно прочитали, что JIT (JVM 8) оптимизирует сайт вызовов для 1 или 2 типов путем прямых вызовов, но возвращается к vtable, если встречаются более двух фактических типов. Таким образом, наличие только двух конкретных типов, проходящих через сайт вызова во время выполнения, представляет собой преимущество в производительности.
simon.watts
5

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

Apocalisp
источник
5

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

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

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

Питер Лори
источник
1
Кстати, что вы подразумеваете под "не нужен instanceof для выпускных классов"?
Pacerier
Конечный класс не может иметь подклассов. В этом случае так x.getClass() == Class.classже, какx instanceof Class
Питер Лоури
Круто, предполагая, что x не нуль, что бы вы предпочли?
Pacerier
Хорошая точка зрения. Это будет зависеть от того, ожидаю ли я xбыть, nullя полагаю. (Или что будет яснее)
Питер Лори
Хм, я только что понял, что мы могли бы также использовать java.lang.class.isAssignableFrom. Знаете ли вы, если ключевое слово instanceof внутренне использует такие функции?
Pacerier
4

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

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

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

o.doEverything();

и затем иметь реализацию "doEverything ()" в вызове Class1 "doThis ()", а в классе 2 вызов "doThat ()" и так далее.

Пол Томблин
источник
11
Но иногда ты не можешь. Если вы реализуете интерфейс, в котором вы берете объект, и вам нужно сказать, какой это тип, то instanceof действительно единственный вариант. Вы можете попробовать кастовать, но instanceof обычно чище.
Herms
4

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

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

Outlaw Programmer
источник
4

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

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

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

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

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

Я пишу тест производительности на основе 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();
}
}
salexinx
источник
Если бы я размышлял, то, что делает instanceof, возможно, более сложно. Проверка getClass () == будет выполнять точную проверку 1: 1, где instanceof проверяет иерархию, т.е. myHashSet instanceof Collection будет проходить, а myHashSet.getClass () == Collection.class - нет. По сути, они не являются эквивалентными операциями, поэтому я не слишком удивлен, что производительность также отличается.
AMTerp
3

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

if (o instanceof java.lang.String)

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

if (objectStruct->iAmInstanceOf == &java_lang_String_class)

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

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

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

Mecki
источник
1
Разве он не должен выяснить, наследуется ли o от java.lang.String?
WW.
1
Вот почему я сказал, что это может быть так же быстро. На самом деле он выполняет цикл, сначала проверяя iAmInstanceOf на предмет соответствующего класса, затем идет вверх по дереву наследования o и повторяет эту проверку для каждого суперкласса o (так что может потребоваться выполнить этот цикл пару раз на матч)
Меки
3

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

Хосе Кихада
источник
2

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

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

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

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

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

Someobject.doSomething();
Демиан Криге
источник
61
Я знаю об этом. Это было не то, что я спросил.
Джош
Не уверен, стоит ли голосовать за это или нет, потому что это хороший вопрос, но он не отвечает на заданный вопрос ...
jklp
7
Я думаю, что пример кода на самом деле очень плохой: вы не можете расширить класс Double, а также вы не можете получить Double из некоторого другого класса. Если бы вы использовали другие классы для примера, все было бы в порядке.
Лена Шиммель
6
Также, если дочерние классы SomeObject являются объектами-значениями, вы не хотите помещать в них логику. Например, Pie и Roast могут быть неправильным местом для логики putInOven () и putInMouth ().
ск.
хотя сам пирог и жаркое было бы здорово
binboavetonik
2

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

if(a instanceof AnyObject){
}

быстрее как:

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

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

Horcrux7
источник
1

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

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

Salix alba
источник
0

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

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

Карл
источник
0

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


источник
4
Если класс загружается другим загрузчиком классов, я не думаю, что instanceof тоже подойдет.
Питер Лори
0

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

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

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

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

seq = seq.head();

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

Майкл Кей
источник
Это может быть из-за ifсебя. Если распределение trues и falses близко к четному, умозрительное исполнение становится бесполезным, что приводит к значительным задержкам.
Дмитрий
-4

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

tloach
источник
7
Это гораздо менее верно для более современных алгоритмов сборки мусора Java, чем когда-либо прежде. Даже самые простые алгоритмы больше не заботятся о том, сколько памяти вы выбрасываете сразу после использования, они заботятся только о том, сколько осталось в коллекциях молодого поколения.
Билл Мичелл
3
Отлично, за исключением того, что я использую последнюю версию JVM, и мой компьютер все еще сканирует, когда запускается GC. На двухъядерном оперативном сервере 3 ГБ. Java не является языком для использования, если производительность действительно имеет значение.
tloach
@ Дэвид: Вам не нужно требовать в режиме реального времени, чтобы иметь проблемы, когда ваше приложение исчезает на период времени. Самое интересное, с чем я столкнулся, это java-приложение, которое подключалось к потоку TCP, который умер при запуске GC, потому что он не закрыл поток первым и не смог справиться с перегрузкой сетевого трафика, когда он вернулся - он немедленно войти в цикл, в котором запускается GC, когда приложение возобновляет работу, оно пытается перетаскивать кучу данных, что приводит к нехватке памяти, что вызывает GC и т. д. Java отлично подходит для множества задач, но не для задач, где высокая производительность является требованием.
tloach
6
@ tloach звучит для меня как плохой дизайн приложения. Вы говорите о «производительности», как если бы она была одномерной. Я работал с множеством java-приложений (и над ними), которые, например, эффективно обеспечивали быстрый интерактивный статистический анализ и визуализацию очень больших наборов данных или очень быстро обрабатывали очень большие объемы транзакций. «производительность» - это не одно, а тот факт, что кто-то может написать приложение, которое плохо управляет памятью и позволяет GC работать по-своему, не означает, что что-то, требующее «производительности», должно быть написано в другом месте.
Дэвид Моулз