Вплив на ефективність використання instanceof в Java


314

Я працюю над додатком, і один підхід до проектування передбачає надзвичайно важке використання instanceofоператора. Хоча я знаю, що OO-дизайн, як правило, намагається уникати використання instanceof, це вже інша історія, і це питання суто пов'язане з продуктивністю. Мені було цікаво, чи є вплив на продуктивність? Чи так само швидко ==?

Наприклад, у мене базовий клас з 10 підкласами. В одній функції, яка приймає базовий клас, я перевіряю, чи клас є екземпляром підкласу і виконуючи якусь процедуру.

Одним із інших способів, які я думав вирішити, було використання цільового примітиву "id id" замість цього, а також використання бітової маски для представлення категорій підкласів, а потім просто порівняння біт маски порівняння підкласів "type id" з a постійна маска, що представляє категорію.

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


81
Я думаю, що питанням було, однак, було відмовитись від питання найкращої практики ОО та вивчити результативність.
Дейв Л.

3
@Dave L. Як правило, я погодився б, але ОП зазначає, що він шукає деякі загальні методи оптимізації, і він не впевнений, чи пов’язана його проблема з "instanceof". Я думаю, що варто принаймні згадати «правильний» дизайн, щоб він міг розглянути обидва варіанти.
Програматор поза законом

51
Фу ... Чому всі відповіді пропускають суть питання і подають ту саму стару кнутську риторику щодо оптимізації? Ваше запитання полягає в тому, чи є instanceof значно / дивно повільнішим, ніж перевірка об’єкта класу за допомогою ==, і я виявив, що це не так.
губбі

3
Продуктивність instanceof та кастингу є досить хорошою. Я відправив трохи часу в Java7 навколо різних підходів до цієї проблеми тут: stackoverflow.com/questions/16320014 / ...
Wheezil

Відповіді:


268

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

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


13
Сучасний JVM / JIC .. Можливо, ви згадаєте, з якої версії java були охоплені ці оптимізації?
Равіша

138
Завжди є хтось, хто цитує Кнут, коли продуктивність є темою ... Забувши, що Кнут також заявив (у тій же статті) "У встановлених інженерних дисциплінах поліпшення на 12%, що легко отримується, ніколи не вважається маргінальним, і я вважаю, що однакова точка зору має переважати в інженерії програмного забезпечення ", майже вся його робота стосувалася ефективності алгоритмів, і він писав алгоритми в зборах, щоб (серед іншого) досягти кращих показників. Мех ...
kgadek

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

279

Підхід

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

  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 .

Для повноти: Існує попередня версія цієї відповіді та мій орієнтир .

Результати

| Операція | Виконання в наносекундах за операцію | Відносно екземпляру |
| ------------ | ------------------------------------ - | ------------------------ |
| ВСТАНОВКА | 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 екземпляр дав мені 63-96 мс, а рядок дорівнює 106-230 мс

Я використовував java jvm 6.

Тож у моєму простому тесті швидше зробити instanceOf замість порівняння рядків з одним символом.

використання .equals () Integer () замість рядка дало мені той самий результат, лише коли я використав == i був швидше, ніж 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. Розподіл ваших даних - чи більшість випадків операцій вирішено з першої чи другої спроби? Ви хочете, щоб ваш перший, швидше за все, повернув справжні операції.
  3. Середовище розгортання. Робота на Sun Solaris VM суттєво відрізняється, ніж у JVM Sun's. Solaris запускається у режимі "сервер" за замовчуванням, тоді як Windows працюватиме в режимі клієнта. Оптимізація JIT на Solaris зробить доступ до всіх методів однаковим.

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

InstanceOf 3156
class== 2925 
OO 3083 
Id 3067 

18

Відповідаючи на ваше останнє запитання: Якщо профайлер не скаже вам, що ви витрачаєте смішні кількості часу на екземпляр: Так, ви нишпорите.

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

У часи сильно оптимізуючих компіляторів ваші здогадки про вузькі місця, ймовірно, будуть абсолютно помилковими.

І в справжньому дусі цієї відповіді (в яку я цілком вважаю): я абсолютно не знаю, як відносяться instanceof і ==, як тільки компілятор jit отримав шанс його оптимізувати.

Я забув: Ніколи не вимірюйте перший пробіг.


1
Але оригінальний плакат згаданих показників був критичним для цієї програми, тому оптимізувати її на ранній стадії нерозумно. Іншими словами, ви б не писали 3D-гру в GWBasic, а потім в кінці сказали, добре, давайте почнемо оптимізувати це, перший крок - перенести її на c ++.
LegendLength

GWBasic може стати чудовим початком для 3d-ігор, якщо є відповідні бібліотеки. Але це вбік (оскільки це штучний аргумент): ОП не вимагає повного переписування як оптимізації. Йдеться про єдину конструкцію, де ми навіть не знаємо, чи є вплив суттєвим (навіть якщо є більш ефективний спосіб зробити те ж саме в поточній версії компілятора ). Я твердо стою за 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()));
    }
}

Який результат отримав java 6, а який java 7? Ви переглянули це під Java 8? Більш суттєво тут, ви порівнюєте довжину if ofofof з тим, що є суттєвим викладом справи на ints. Я думаю, ми очікуємо, що int-перемикач швидко загоряється.
Azeroth2b

1
Я не можу точно згадати, що відбувалося 5 років тому - я думаю, що і Java 6, і Java 7 мали подібний результат, тому передбачено лише один результат (за умови, що два рядки мають різну глибину ієрархії класів) ... і ні , я не намагався порівнювати з Java 8. Весь код тесту надається - ви можете скопіювати / вставити його та перевірити в потрібних середовищах (зверніть увагу - зараз я для цього використовую тест JMH).
Xtra Coder

9

instanceof дійсно швидко, приймаючи лише кілька інструкцій процесора.

Мабуть, якщо в класі Xнемає завантажених підкласів (знає JVM), instanceofйого можна оптимізувати як:

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

Основна вартість - це лише читання!

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

Гарні новини всім!


2
можуть бути оптимізовані або як оптимізувати? джерело?

@vaxquis може бути його jvm impl специфічним
RecursiveExceptionException

@itzJanuary зітхати ви пропустили точку мого питання тут: все знають , що компілятор може оптимізувати foo- але це на fooсамому справі в даний час оптимізується в Oracle JAVAC / VM - або це тільки можливо , що він буде робити це в майбутньому? Крім того, я запитав відповідача, чи є у нього якесь резервне джерело (будь то документи, вихідний код, блог розробників), що підтверджує, що це дійсно можна оптимізувати чи оптимізувати ? Без цього ця відповідь є лише деяким випадковим роздумом про те, що може зробити компілятор .

@vaxquis Ви ніколи не згадували про Hotspot VM, але в такому випадку я не знаю, чи він "оптимізований".
RecursiveExceptionException

1
Нещодавно читайте, що JIT (JVM 8) оптимізує сайт для викликів для 1 або 2 типів прямими дзвінками, але повертається до vtable, якщо зустрічається більше двох реальних типів. Тому лише наявність двох конкретних типів, що проходять через сайт виклику під час виконання, є перевагою у виконанні.
simon.watts

5

Instanceof дуже швидкий. Він зводиться до байт-коду, який використовується для порівняння посилань класів. Спробуйте кілька мільйонів екземплярів у циклі і переконайтеся самі.


5

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

Чому так? Тому що, мабуть, станеться, що у вас є кілька інтерфейсів, які забезпечують певну функціональність (скажімо, інтерфейси x, y і z), а також деякі об'єкти для маніпулювання, які можуть (або ні) реалізувати один з цих інтерфейсів ... але не безпосередньо. Скажіть, наприклад, у мене:

w поширюється на x

А знаряддя ш

B подовжує A

C поширюється B, реалізує y

D розширює C, реалізує z

Припустимо, я обробляю екземпляр D, об’єкт d. Для обчислень (d instanceof x) потрібно взяти d.getClass (), провести цикл через інтерфейси, які він реалізує, щоб знати, чи є один == до x, а якщо цього не зробити, знову рекурсивно для всіх їхніх предків ... У нашому випадку, якщо ви вперше вивчите це дерево, ви отримаєте щонайменше 8 порівнянь, припустимо, що y і z нічого не поширюють ...

Складність дерева деривації в реальному світі, ймовірно, буде вище. У деяких випадках JIT може оптимізувати більшу частину його за межами, якщо він здатний заздалегідь вирішити d як те, що у всіх можливих випадках є екземпляром чогось, що розширює х. Реально, однак, ви збираєтеся пройти цей обхід дерева більшу частину часу.

Якщо це стане проблемою, я б запропонував використовувати замість цього оброблювальну карту, пов'язуючи конкретний клас об'єкта із закриттям, яке виконує обробку. Це видаляє фазу обходу дерев на користь прямого відображення. Однак майте на увазі, що якщо ви встановили обробник для C.class, мій об’єкт d вище не буде розпізнаний.

ось мої 2 копійки, я сподіваюся, що вони допоможуть ...


5

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

Якщо ви можете використовувати xClass == String.class, це швидше. Примітка: для фінальних занять вам не потрібен instanceof.


1
Btw, що ви маєте на увазі під "не потрібні instanceof для заключних занять"?
Pacerier

Остаточний клас не може мати підкласи. У цьому випадку x.getClass() == Class.classте саме, щоx instanceof Class
Пітер Лорі

Класно, якщо припустити, що х не є нульовим, що б ви хотіли?
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 ()", а в Class2 викликати "doThat ()" тощо.


11
Але іноді ти не можеш. Якщо ви реалізуєте інтерфейс, який має вас взяти в Object, і вам потрібно сказати, який тип він є, то instanceof - це дійсно єдиний варіант. Ви можете спробувати кастинг, але instanceof, як правило, чистіше.
Гермс

4

'instanceof' насправді є оператором, як + або -, і я вважаю, що у нього є своя інструкція щодо байт-коду JVM. Це повинно бути досить швидко.

Я не повинен цього робити, якщо у вас є комутатор, де ви протестуєте, чи об’єкт є екземпляром якогось підкласу, то ваш дизайн, можливо, буде потрібно переробити. Поміркуйте, як піднести специфічну поведінку підкласу до самих підкласів.


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-бенчмарка-архетипу: 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();
}
}

Якби я міркував, те, що робить, можливо, складніше. Перевірка getClass () == зробить точну перевірку 1: 1, коли екземпляр перевіряє ієрархію, тобто екземпляр myHashSet, колекція колекції передав би, але 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 досить поганою. Навіть той, у якого немає компілятора JIT і інтерпретує тільки код, повинен бути в змозі зробити екземпляр тесту практично за короткий час.


1
Хіба це не повинно з’ясувати, чи o успадковує від java.lang.String?
ВВ.

1
Ось чому я сказав, що це "може" так само швидко. Насправді він виконує цикл, спочатку перевіряючи iAmInstanceOf проти відповідного класу, потім він піднімає вгору дерево спадкування o і повторює цю перевірку для кожного суперкласу o (тому, можливо, доведеться запустити цю петлю пару разів на матч)
Mecki

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 є об'єктами цінності, то ви не хочете вкладати в них логіку. Наприклад, пиріг та смаження можуть бути не правильним місцем для логіки putInOven () та putInMouth ().
ск.

Самостійне приготування пирога та смаження було б дивним, хоча
binboavetonik

2

У сучасній версії Java оператор instanceof швидше, як виклик простого методу. Це означає:

if(a instanceof AnyObject){
}

швидше:

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

Інша справа, якщо вам потрібно зробити каскад багатьох instanceof. Тоді перемикач, який викликає лише один раз getType (), відбувається швидше.


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;
}

жахливий дизайн OO, але якщо ваш аналіз продуктивності показує, що це саме те місце, можливо. У моєму коді диспетчерський код займає 10% від загального часу виконання, і це, можливо, сприяло покращенню загальної швидкості на 1%.


0

Ви повинні виміряти / профайл, якщо це дійсно проблема ефективності у вашому проекті. Якщо це так, я рекомендую переробити дизайн - якщо це можливо. Я впевнений, що ви не можете перемогти власну реалізацію платформи (написана на С). Ви також повинні врахувати багаторазове успадкування в цьому випадку.

Ви повинні розповісти більше про проблему, можливо, ви могли б використовувати асоціативний магазин, наприклад, Карта <Клас, Об'єкт>, якщо вас цікавлять лише конкретні типи.


0

Що стосується зауваження Пітера Лорі, що вам не потрібні екземпляри для фінальних занять, а ви можете просто використовувати еталонну рівність, будьте обережні! Незважаючи на те, що підсумкові класи не можна продовжувати, вони не гарантуються, що будуть завантажені тим самим завантажувачем класів. Використовуйте лише x.getClass () == SomeFinal.class або його ilk, якщо ви абсолютно впевнені, що для цього розділу коду є лише один завантажувач класів.


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мс, незважаючи на те, що в циклі відбуваються досить важкі речі, як перетворення рядка в подвійне. Звичайно, можливо, що прискорення швидше пов'язане з усуненням умовної гілки, ніж з усуненням самого оператора instance; але я вважав, що це варто згадати.


Це може бути саме через ifсебе. Якщо розподіл trues і falses близький до рівномірного, спекулятивне виконання стає марним, що призводить до значних відставань.
Дмитро

-4

Ви орієнтуєтесь на неправильну річ. Різниця між instanceof та будь-яким іншим методом перевірки того ж самого, ймовірно, навіть не може бути вимірною. Якщо продуктивність є критичною, тоді, мабуть, Java - це неправильна мова. Основна причина полягає в тому, що ви не можете контролювати, коли ВМ вирішує, що хоче збирати сміття, що може зайняти центральний процесор на 100% протягом декількох секунд у великій програмі (MagicDraw 10 був чудовим для цього). Якщо ви керуєте будь-яким комп’ютером, запускається ця програма, ви не можете гарантувати, на якій версії JVM вона буде включена, і у багатьох старих виникли основні проблеми зі швидкістю. Якщо це невеликий додаток , ви можете бути добре з Java, але якщо ви постійно читаєте і відкидаючи дані , то ви будете помічати , коли GC стусани в.


7
Це набагато менше стосується сучасних алгоритмів збору сміття, ніж раніше. Навіть найпростіші алгоритми вже не хвилюються, скільки пам’яті ви викинете відразу після використання - їх цікавить лише те, скільки зберігається в колекціях молодого покоління.
Білл Мішель

3
Чудово, за винятком того, що я перебуваю на останньому JVM і мій комп'ютер все ще повзає, коли працює GC. На двоядерному оперативному сервері об'ємом 3 ГБ. Java не є мовою, якою користуватися, якщо продуктивність насправді має значення.
tloach

@David: Вам не потрібно вимагати проблем у режимі реального часу, коли ваша програма відключається протягом певного періоду часу. Весело, з яким я стикався, - це програма java, яка підключилася до потоку TCP, який загинув, коли GC запустився, тому що спочатку не закрив потік і не міг впоратися з перевантаженням мережевого трафіку, коли повернувся - він негайно переходьте до циклу, де працює GC, коли поновлює додаток, він намагається виправити купу даних, завдяки чому у нього не вистачає пам’яті, що запускає GC тощо. Java чудово підходить для безлічі завдань, але не для завдань, де дуже високі показники - це вимога.
tloach

6
@tloach мені звучить як поганий дизайн додатків. ви говорите про "виставу" так, ніби вона була одновимірною. Я працював з (і далі) безліччю програм Java, які, наприклад, були успішними в забезпеченні швидкого інтерактивного статистичного аналізу та візуалізації дуже великих наборів даних, або виконавцем в обробці дуже великих обсягів транзакцій дуже швидко. "продуктивність" - це не одне, і те, що хтось може написати програму, яка погано керує пам'яттю і дозволяє GC отримувати по-своєму, не означає нічого, що вимагає "продуктивності", слід писати щось інше.
Девід Молес
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.