Java optimization nitpick: быстрее ли что-то делать, и пусть оно генерирует исключение, чем вызов instanceof для проверки перед литьем?

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

Мой вопрос о скорости использования рефлексии и литья. Стандартное высказывание - "отражение медленное". Мой вопрос заключается в том, какая именно часть идет медленно, и почему; особенно при сравнении, если что-то является родителем другого экземпляра.

Я довольно уверен, что просто сравнение класса объекта с другим объектом класса примерно так же быстро, как и любое сравнение, по-видимому, просто прямое сравнение одиночных объектов, которые уже хранятся в состоянии Object; но что, если один класс является родителем другого?

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

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

Итак, что это. Действительно ли instanceof не так медленно? Являются ли оба instanceof и кастинг для родительского класса медленным? или есть ли какая-то причина, что бросок может быть выполнен быстрее, чем вызов instanceof?

Ответ 1

Как всегда, попробуйте и посмотрите в ваших конкретных ситуациях, но:

- Исключения стоят дорого, замечательно.

-Использование исключений для потока кода почти всегда плохая идея

Изменить: Хорошо, мне было интересно, поэтому я написал быструю тестовую систему

public class Test{

    public Test(){
        B b=new B();
        C c=new C();


        for(int i=0;i<10000;i++){
            testUsingInstanceOf(b);
            testUsingInstanceOf(c);
            testUsingException(b);
            testUsingException(c);
        }
    }

    public static void main(String[] args){

        Test test=new Test();

    }

    public static boolean testUsingInstanceOf(A possiblyB){
        if (possiblyB instanceof B){
            return true;
        }else{
            return false;
        }
    }
    public static boolean testUsingException(A possiblyB){
        try{
            B b=(B)possiblyB;
            return true;
        }catch(Exception e){
            return false;
        }
    }


    private class A{

    }
    private class B extends A{

    }
    private class C extends A{

    }        
}

Результаты поиска:

by InstanceOf: 4.43 ms
by Exception: 79.4 ms

как я говорю, удивительно дорого

И даже когда он всегда будет B (имитируя, когда вы на 99% уверены в его B, вам просто нужно быть уверенным, что он все еще не быстрее:

Результаты профиля, когда всегда B:

by InstanceOf: 4.48 ms
by Exception: 4.51 ms

Ответ 2

Существует общий ответ и конкретный ответ.

Общий случай

if (/* guard against exception */) {
    /* do something that would throw an exception */
} else {
    /* recover */
}

// versus

try {
   /* do something that would throw an exception */
} catch (TheException ex) {
   /* recover */
}

Это факт, что создание/бросание/улавливание исключения дорого. И они, вероятно, будут значительно дороже, чем тестирование. Однако это не означает, что "тестовая первая" версия всегда быстрее. Это связано с тем, что в версии "первый тест" тесты могут быть фактически выполнены: первый раз в if и второй раз в коде, который будет генерировать исключение.

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

if (file.exists() && file.isReadable()) {
    is = new FileInputStream(file);
} else {
    System.err.println("missing file");
}

против

try {
    is = new FileInputStream(file);
} catch (IOException ex) {
    System.err.println("missing file");
}

"первый тест" выполняет 2 дополнительных системных вызова, а системные вызовы стоят дорого. Если сценарий "отсутствует файл" также необычен....

Второй смешающий фактор заключается в том, что самые последние компиляторы HotSpot JIT делают некоторую значительную оптимизацию исключений. В частности, если компилятор JIT может понять, что состояние объекта исключения не используется, оно может превратить исключение create/throw/catch в простую инструкцию перехода.

Конкретный случай instanceof

В этом случае мы, скорее всего, сравниваем эти два:

if (o instanceof Foo) {
    Foo f = (Foo) o;
    /* ... */
} 

// versus

try {
    Foo f = (Foo) o;
} catch (ClassCastException ex) {
   /* */
}

Здесь происходит вторая оптимизация. instanceof, за которым следует type cast, является общим шаблоном. Компилятор HotSpot JIT часто может исключать проверку динамического типа, выполняемую типом cast... так как это повторяет тест, который только что сработал. Когда вы учитываете это, версия "первый тест" не может быть медленнее, чем версия "исключение"... даже если последняя оптимизирована для перехода.

Ответ 3

К сожалению, код Ричарда не может быть запущен напрямую для создания таймингов. Я немного изменил вопрос, предполагая, что вы действительно хотите "если A является B, сделать что-то на B", а не просто задавать вопрос "A a B?". Вот тестовый код.

ОБНОВЛЕНО ИЗ ОРИГИНАЛА Было довольно сложно написать тривиальный код, который компилятор HotSpot не сводил до нуля, но я думаю, что следующее хорошо:

package net.redpoint.utils;
public class Scratch {
    public long counter = 0;
    public class A { 
        public void inc() { counter++; } 
    }
    public class B extends A { 
        public void inc() { counter++; } 
    }
    public class C extends A {
        public void inc() { counter++; } 
    } 
    public A[] a = new A[3];
    public void test() {
        a[0] = new A();
        a[1] = new B();
        a[2] = new C();
        int iter = 100000000;
        long start = System.nanoTime();
        for(int i = iter; i > 0; i--) {
            testUsingInstanceOf(a[i%3]);
        }
        long end = System.nanoTime();
        System.out.println("instanceof: " + iter / ((end - start) / 1000000000.0) + " per second");

        start = System.nanoTime();
        for(int i = iter; i > 0; i--) {
            testUsingException(a[i%3]);
        }
        end = System.nanoTime();
        System.out.println("try{}: " + iter / ((end - start) / 1000000000.0) + " per second");

        start = System.nanoTime();
        for(int i = iter; i > 0; i--) {
            testUsingClassName(a[i%3]);
        }
        end = System.nanoTime();
        System.out.println("classname: " + iter / ((end - start) / 1000000000.0) + " per second");
    }

    public static void main(String[] args) {
        Scratch s = new Scratch();
        s.test();
    }

    public void testUsingInstanceOf(A possiblyB){
        if (possiblyB instanceof B){
            ((B)possiblyB).inc();
        }
    }

    public void testUsingException(A possiblyB){
        try{
            ((B)possiblyB).inc();
        } catch(Exception e){
        }
    }

    public void testUsingClassName(A possiblyB){
        if (possiblyB.getClass().getName().equals("net.redpoint.utils.Scratch$B")){
            ((B)possiblyB).inc();
        }
    }
}

Результат:

instanceof: 4.573174070960945E8 per second
try{}: 3.926650051387284E8 per second
classname: 7.689439655530204E7 per second

Тест был выполнен с использованием Oracle JRE7 SE для Windows 8 x64 с процессором Intel i7 с песчаным мостом.

Ответ 4

Если приведение может вызвать исключение, это означает, что он делает неявное выполнение instanceof. Итак, в обоих случаях вы неявно используете отражение, возможно, точно так же.

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

Вышеупомянутая логика говорит мне, что проверка instanceof должна быть быстрее. Конечно, бенчмаркинг для вашего конкретного случая даст вам окончательный ответ.

Ответ 5

Я не думаю, что вы обнаружите, что один из них явно лучше другого.

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

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