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

<p>я работаю над приложением, и один из подходов к проектированию предполагает чрезвычайно интенсивное использование оператора <code>instanceof</code>. хотя я знаю, что дизайн oo обычно старается избегать использования <code>instanceof</code>, это другая история, и этот вопрос связан исключительно с производительностью. мне было интересно, есть ли какое-либо влияние на производительность? это так же быстро, как <code>==</code>?</p><p>например, у меня есть базовый класс с 10 подклассами. в единственной функции, которая принимает базовый класс, я проверяю, является ли класс экземпляром подкласса, и выполняю некоторую процедуру. </p><p>один из других способов решения этой проблемы - использовать вместо этого целочисленный примитив «id типа» и использовать битовую маску для представления категорий подклассов, а затем просто сравнить битовую маску подклассов «id типа» постоянная маска, представляющая категорию.</p><p><code>instanceof</code> как-то оптимизируется jvm, чтобы быть быстрее, чем это? я хочу придерживаться java, но производительность приложения имеет решающее значение. было бы здорово, если бы кто-то, кто был на этом пути раньше, мог бы дать совет. не слишком ли я придираюсь к тому, чтобы оптимизировать?</p>

<p>современные jvm/jic-компиляторы устранили снижение производительности большинства традиционно «медленных» операций, включая instanceof, обработку исключений, отражение и т.д.</p><p>как писал дональд кнут, «мы должны забыть о малой эффективности, скажем, в 97% случаев: преждевременная оптимизация - корень всего зла». производительность instanceof, вероятно, не будет проблемой, так что не тратьте свое время на экзотические обходные пути, пока не убедитесь, что это проблема.</p>

<h1>подход</h1><p>я написал <a href="https://github.com/michaeldorner/instanceofbenchmark">эталонную программу</a> для оценки различных реализаций:</p><ol> <li><code>instanceof</code> реализация (как ссылка) </li> <li>объект ориентирован через абстрактный класс и <code>@override</code> тестовый метод</li> <li>используя реализацию собственного типа</li> <li><code>getclass() == \_.class</code> реализация</li> </ol><p>я использовал <a href="http://openjdk.java.net/projects/code-tools/jmh/">jmh</a> , чтобы запустить тест с 100 разминочными вызовами, 1000 итерациями при измерении и с 10 форками. таким образом, каждая опция была измерена с 10 000 раз, что требует 12:18:57 для запуска полного теста на моем macbook pro с macos 10.12.4 и java 1.8. тест измеряет среднее время каждого варианта. для более подробной информации смотрите <a href="https://github.com/michaeldorner/instanceofbenchmark/blob/master/src/main/java/de/michaeldorner/mybenchmark.java">мою реализацию на github</a> . </p><p>для полноты: есть <a href="https://stackoverflow.com/revisions/26514984/10">предыдущая версия этого ответа и мой тест</a> . </p><h1>результаты</h1><pre> | операция | время выполнения в наносекундах за операцию | по отношению к экземпляру | | ------------ | ----------------------------- --------- | ------------------------ | | 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% | </pre><h1>тл; др</h1><p>в java 1.8 <code>instanceof</code> - самый быстрый подход, хотя <code>getclass()</code> очень близок. </p>

<p>я только что сделал простой тест, чтобы увидеть, как производительность instanceof сравнивается с простым вызовом s.equals () строкового объекта с одной буквой.</p><p>в цикле 10.000.000 instanceo дал мне 63-96мс, а строка равных - 106-230мс</p><p>я использовал java jvm 6.</p><p>так что в моем простом тесте быстрее сделать instanceof вместо сравнения строк из одного символа.</p><p>использование integer .equals () вместо строк дало мне тот же результат, только когда я использовал == я был быстрее, чем instanceof на 20 мс (в цикле 10.000.000)</p>

<p>элементы, которые будут определять влияние на производительность:</p><ol> <li>количество возможных классов, для которых оператор instanceof может вернуть true</li> <li>распределение ваших данных - разрешены ли большинство операций instanceof с первой или второй попытки? вы будете хотеть поместить свою наиболее вероятную, чтобы возвратить истинные операции сначала.</li> <li>среда развертывания. работа в sun solaris vm существенно отличается от jvm в windows. по умолчанию solaris будет работать в режиме «сервер», а windows - в режиме клиента. оптимизация jit в solaris сделает доступ ко всем методам одинаковым. </li> </ol><p>я создал <a href="http://www.theeggeadventure.com/wikimedia/index.php/instanceof\_performance">микробенчмарк для четырех разных способов отправки</a> . результаты solaris следующие: меньшее число быстрее:</p><pre><code>instanceof 3156 class== 2925 oo 3083 id 3067 </code></pre>

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

<p>у меня такой же вопрос, но поскольку я не нашел «метрики производительности» для варианта использования, аналогичного моему, я сделал еще несколько примеров кода. на моем оборудовании и java 6 & 7 разница между instanceof и переключением на 10 млн итераций</p><pre><code>for 10 child classes - instanceof: 1200ms vs switch: 470ms for 5 child classes - instanceof: 375ms vs switch: 204ms </code></pre><p>таким образом, instanceof действительно медленнее, особенно для огромного числа операторов if-else-if, однако в реальном приложении разница будет незначительной.</p><pre><code>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())); } } </code></pre>

<p><code>instanceof</code> действительно быстрый, занимает всего несколько инструкций процессора.</p><p>очевидно, если класс <code>x</code> не имеет загруженных подклассов (jvm знает), <code>instanceof</code> можно оптимизировать как:</p><pre><code> x instanceof x ==> x.getclass()==x.class ==> x.classid == constant\_x\_id </code></pre><p>основная стоимость только чтение!</p><p>если <code>x</code> имеет загруженные подклассы, требуется еще несколько чтений; они, вероятно, расположены рядом, поэтому дополнительные расходы тоже очень низкие.</p><p>хорошие новости всем!</p>

<p>instanceof, вероятно, будет более дорогостоящим, чем простое равенство в большинстве реализаций реального мира (то есть тех, где instanceof действительно необходим, и вы не можете просто решить его путем переопределения общего метода, как любой учебник для начинающих, а также демиан выше предлагаю).</p><p>это почему? потому что, вероятно, произойдет то, что у вас есть несколько интерфейсов, которые предоставляют некоторую функциональность (скажем, интерфейсы x, y и z) и некоторые объекты для манипулирования, которые могут (или нет) реализовывать один из этих интерфейсов ... но не напрямую. скажем, например, у меня есть:</p><p>ш расширяет х</p><p>а реализует ш</p><p>б расширяет а</p><p>c расширяет b, реализует у</p><p>d расширяет c, реализует z</p><p>предположим, я обрабатываю экземпляр d, объект d. для вычислений (d instanceof x) требуется взять d.getclass (), пройтись по циклам через интерфейсы, которые он реализует, чтобы узнать, является ли один == к x, и если нет, сделать это снова рекурсивно для всех их предков ... в в нашем случае, если вы сначала исследуете это дерево в ширину, вы получите как минимум 8 сравнений, предположив, что y и z ничего не расширяют ...</p><p>сложность дерева деривации в реальном мире, вероятно, будет выше. в некоторых случаях jit может оптимизировать большую часть его, если он может заранее разрешить d как во всех возможных случаях экземпляр чего-то, что расширяет x. реально, однако, вы будете проходить через это дерево большую часть времени.</p><p>если это станет проблемой, я бы предложил вместо этого использовать карту обработчика, связав конкретный класс объекта с замыканием, которое выполняет обработку. это удаляет фазу обхода дерева в пользу прямого отображения. однако помните, что если вы установили обработчик для c.class, мой объект d выше не будет распознан.</p><p>вот мои 2 цента, надеюсь они помогут ...</p>

<p>instanceof очень эффективен, поэтому ваша производительность вряд ли пострадает .. однако использование большого количества instanceof наводит на мысль о проблеме дизайна.</p><p>если вы можете использовать xclass == string.class, это быстрее. примечание: вам не нужен instanceof для финальных классов.</p>

<p>демиан и пол упоминают хороший момент;<em> однако </em>, размещение кода для выполнения действительно зависит от того, как вы хотите использовать данные ...</p><p>я большой поклонник небольших объектов данных, которые можно использовать по-разному. если вы используете переопределенный (полиморфный) подход, ваши объекты могут использоваться только «в одну сторону».</p><p>это где шаблоны приходят ...</p><p>вы можете использовать двойную диспетчеризацию (как в шаблоне посетителя), чтобы попросить каждый объект «позвонить вам», передавая себя - это разрешит тип объекта.<em> однако </em>(опять же) вам понадобится класс, который может "делать вещи" со всеми возможными подтипами.</p><p>я предпочитаю использовать шаблон стратегии, где вы можете зарегистрировать стратегии для каждого подтипа, который вы хотите обработать. что-то вроде следующего. обратите внимание, что это помогает только для точных совпадений типов, но имеет то преимущество, что оно расширяемое - сторонние участники могут добавлять свои собственные типы и обработчики. (это хорошо для динамических сред, таких как osgi, где можно добавлять новые пакеты)</p><p>надеюсь, это вдохновит на другие идеи ...</p><pre><code>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); } } </code></pre>

<p>'instanceof' на самом деле является оператором, как + или -, и я считаю, что у него есть собственная инструкция байт-кода jvm. это должно быть достаточно быстро.</p><p>я не должен делать так, что если у вас есть переключатель, где вы тестируете, является ли объект экземпляром некоторого подкласса, то ваш дизайн, возможно, придется переделать. подумайте о том, чтобы внедрить специфичное для подкласса поведение в сами подклассы.</p>

<p>экземпляр очень быстрый. это сводится к байт-коду, который используется для сравнения ссылок на классы. попробуйте несколько миллионов instanceofs в цикле и убедитесь сами.</p>

<p>трудно сказать, как определенная jvm реализует экземпляр, но в большинстве случаев объекты сопоставимы со структурами, а также с классами, и каждая структура объекта имеет указатель на структуру класса, экземпляром которой он является. так на самом деле instanceof для</p><pre><code>if (o instanceof java.lang.string) </code></pre><p>может быть так же быстро, как следующий код c</p><pre><code>if (objectstruct->iaminstanceof == &java\_lang\_string\_class) </code></pre><p>предполагая, что jit-компилятор на месте и делает достойную работу.</p><p>учитывая, что это только доступ к указателю, получение указателя с определенным смещением, на которое указывает указатель, и сравнение его с другим указателем (что в основном совпадает с проверкой на равенство 32-битных чисел), я бы сказал, что операция на самом деле может быть очень быстрым.</p><p>это не обязательно, однако, это во многом зависит от jvm. однако, если это окажется узким местом в вашем коде, я бы посчитал реализацию jvm довольно плохой. даже тот, у которого нет jit-компилятора и только интерпретирует код, должен иметь возможность сделать экземпляр теста практически мгновенно.</p>

<p><strong> instanceof </strong>является предупреждением о плохом объектно-ориентированном дизайне.</p><p>текущие jvm действительно означают, что<strong> instanceof </strong>сам по себе не является проблемой производительности. если вы часто используете его, особенно для основной функциональности, возможно, пришло время взглянуть на дизайн. выигрыш в производительности (и простоте/поддерживаемости) от рефакторинга к лучшему дизайну значительно перевесит любые фактические циклы процессора, потраченные на фактический вызов<strong> instanceof </strong>.</p><p>чтобы привести очень маленький пример упрощенного программирования.</p><pre><code>if (someobject instanceof integer) { [do something] } if (someobject instanceof double) { [do something different] } </code></pre><p>в плохой архитектуре лучшим выбором было бы, чтобы someobject был родительским классом двух дочерних классов, где каждый дочерний класс переопределяет метод (dosomething), поэтому код будет выглядеть так:</p><pre><code>someobject.dosomething(); </code></pre>

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

<p>обычно причина, по которой оператор instanceof не одобряется в подобном случае (где instanceof проверяет подклассы этого базового класса), заключается в том, что вы должны делать, перемещая операции в метод и переопределяя его для соответствующего подклассы. например, если у вас есть:</p><pre><code>if (o instanceof class1) dothis(); else if (o instanceof class2) dothat(); //... </code></pre><p>вы можете заменить это на</p><pre><code>o.doeverything(); </code></pre><p>и затем иметь реализацию doeverything () в class1, вызывая dothis (), а в class2 вызывая dothat (), и так далее.</p>

<p>в современной версии java оператор instanceof работает быстрее, чем простой вызов метода. это означает:</p><pre><code>if(a instanceof anyobject){ } </code></pre><p>быстрее как:</p><pre><code>if(a.gettype() == xyz){ } </code></pre><p>другое дело, если вам нужно каскадировать много экземпляров. тогда переключатель, который вызывает только один раз gettype (), быстрее.</p>

<p>если ваша единственная цель - скорость, то использование int-констант для идентификации подклассов, похоже, экономит миллисекунды времени.</p><pre><code>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; } </code></pre><p>ужасный oo дизайн, но если ваш анализ производительности показывает, что это то место, где у вас есть узкое место, то, возможно,. в моем коде код отправки занимает 10% от общего времени выполнения, и это может способствовать повышению общей скорости на 1%.</p>

<p>я также предпочитаю подход enum, но я бы использовал абстрактный базовый класс, чтобы заставить подклассы реализовать метод <code>gettype()</code>.</p><pre><code>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; } } } </code></pre>

<p>я подумал, что, возможно, стоит представить контрпример к общему мнению на этой странице, что «instanceof» не настолько дорог, чтобы о нем беспокоиться. я обнаружил, что у меня есть некоторый код во внутреннем цикле, который (в какой-то исторической попытке оптимизации) сделал</p><pre><code>if (!(seq instanceof singleitem)) { seq = seq.head(); } </code></pre><p>где вызов head () для singleitem возвращает значение без изменений. замена кода на</p><pre><code>seq = seq.head(); </code></pre><p>дает мне ускорение с 269 мс до 169 мс, несмотря на то, что в цикле происходят довольно тяжелые вещи, такие как преобразование строки в двойное. конечно, возможно, что ускорение происходит больше за счет устранения условного перехода, чем за счет исключения самого оператора; но я думал, что стоит упомянуть. </p>

<p>что касается замечания питера лоури, что вам не нужен instanceof для выпускных классов и вы можете просто использовать равенство ссылок, будьте осторожны! хотя конечные классы не могут быть расширены, они не гарантированно загружаются одним и тем же загрузчиком классов. используйте x.getclass () == somefinal.class или тому подобное, только если вы абсолютно уверены, что для этой части кода в игре только один загрузчик классов.</p>

<p>вы должны измерить/профиль, если это действительно проблема производительности в вашем проекте. если это так, я бы порекомендовал редизайн - если это возможно. я почти уверен, что вы не можете превзойти нативную реализацию платформы (написано на c). в этом случае также следует учитывать множественное наследование.</p><p>вы должны рассказать больше о проблеме, может быть, вы могли бы использовать ассоциативный магазин, например, карта <class, object>, если вас интересуют только конкретные типы.</p>