Java에서 instanceof를 사용하면 성능에 미치는 영향



Answers

접근

다른 구현을 평가 하는 벤치 마크 프로그램 을 작성 했습니다 .

  1. instanceof 구현 (참조)
  2. 추상 클래스를 통해 지향 된 객체와 @Override 테스트 메소드를 @Override 합니다.
  3. 자체 유형 구현 사용
  4. getClass() == _.class 구현

jmh 를 사용하여 100 회의 워밍업 호출, 1000 회 반복 측정 및 10 회의 포크로 벤치 마크를 실행했습니다. 따라서 각 옵션은 10,000 회 측정되었습니다. MacOS Pro에서 macOS 10.12.4 및 Java 1.8을 사용하여 전체 벤치 마크를 실행하는 데 12:18:57이 걸립니다. 벤치 마크는 각 옵션의 평균 시간을 측정합니다. 자세한 내용은 GitHub의 구현을 참조하십시오.

완전성을 위해 : 이 답변 의 이전 버전과 벤치 마크가 있습니다.

결과

| Operation  | Runtime in nanoseconds per operation | Relative to instanceof |
|------------|--------------------------------------|------------------------|
| INSTANCEOF | 39,598 ± 0,022 ns/op                 | 100,00 %               |
| GETCLASS   | 39,687 ± 0,021 ns/op                 | 100,22 %               |
| TYPE       | 46,295 ± 0,026 ns/op                 | 116,91 %               |
| OO         | 48,078 ± 0,026 ns/op                 | 121,42 %               |

tl; dr

Java 1.8에서는 getClass() 가 매우 가까웠지만 instanceof 가 가장 빠른 접근 방식입니다.

Question

나는 응용 프로그램에서 일하고 있으며 한 가지 설계 접근법은 instanceof 연산자를 상당히 많이 사용하는 것과 관련이 있습니다. OO 디자인은 일반적으로 instanceof 사용을 피하려고 시도하지만, 이는 다른 이야기이며이 질문은 단순히 성능과 관련이 있습니다. 성능에 영향이 있는지 궁금합니다. 그것은 == 와 똑같은 속도입니까?

예를 들어, 10 개의 하위 클래스가있는 기본 클래스가 있습니다. 기본 클래스를 취하는 단일 함수에서 클래스가 하위 클래스의 인스턴스인지 확인하고 루틴을 수행합니다.

내가 그것을 해결하기 위해 생각한 다른 방법 중 하나는 대신 "형식 ID"정수 기본을 사용하고 하위 클래스의 범주를 나타내는 데 비트 마스크를 사용한 다음 서브 클래스의 "유형 ID"를 비트 마스크 비교를 수행하여 카테고리를 나타내는 정수 마스크.

instanceof 가 JVM에 의해 최적화 된 것보다 빠릅니다. Java에 충실하고 싶지만 앱의 성능이 중요합니다. 이 길을 걷던 누군가가 약간의 충고를 할 수 있으면 멋질 것입니다. 지나치게 질식 시키거나 잘못된 것을 집중하여 최적화할까요?




'instanceof'는 실제로 + 또는 -와 같은 연산자이며, 자체 JVM 바이트 코드 명령어가 있다고 생각합니다. 그것은 충분히 빠르다.

객체가 일부 집속의 인스턴스인지 테스트 할 스위치가있는 경우 디자인을 다시 작성해야 할 수도 있습니다. 하위 클래스 별 동작을 하위 클래스 자체로 푸시 다운하는 것이 좋습니다.




피터로 레이 (Peter Lawrey)의 말에 관해서는 최종 수업에 instanceof가 필요없고 단지 참조 평등을 사용할 수 있다는 점에주의하십시오! 최종 클래스를 확장 할 수는 없지만 동일한 클래스 로더가로드 할 수있는 것은 아닙니다. x.getClass () == SomeFinal.class 또는 해당 섹션에 대해 오직 하나의 클래스 로더 만 있다는 절대적으로 긍정적 인 경우 해당 ilk 만 사용하십시오.




나는 동일한 질문을 가지고있다. 그러나 나는 나의 것과 유사한 유스 케이스를위한 '성능 메트릭'을 찾지 못했기 때문에, 나는 좀 더 많은 샘플 코드를 작성했다. 내 하드웨어와 Java 6 & 7에서는 instanceof와 10mln iterations의 스위치가 다릅니다.

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



instanceof는 아마 대부분의 실제 구현에서 단순한 같음보다 비용이 많이 드는 것입니다 (즉, instanceof가 실제로 필요한 곳 ​​이죠. 모든 초급 교과서와 같은 일반적인 방법을 재정 의하여 해결할 수는 없습니다). 위의 Demian 참조).

왜 그런가요? 어쩌면 일어날 일은 몇 가지 인터페이스를 가지고 있고 인터페이스 x, y, z라고하는 일부 기능을 제공하고 일부 인터페이스를 구현할 수도있는 객체도 있습니다. 직접적으로. 예를 들어 다음과 같이 말합니다.

w는 x를 확장한다.

A를 구현

B는 A를 연장한다.

C는 B를 확장하고, y를 구현합니다.

D는 C를 확장하고, z를 구현합니다.

개체 D를 인스턴스 D로 처리한다고 가정 해 보겠습니다. Computing (d instanceof x)은 d.getClass ()를 취하고 인터페이스가 == ~ x인지를 알기 위해 구현 한 인터페이스를 반복해야하고 그렇지 않다면 모든 조상을 재귀 적으로 반복해야한다. 우리의 경우, 당신이 그 나무에 대한 폭 넓은 탐사를한다면 적어도 8 번의 비교를 할 것입니다. y와 z가 아무 것도 연장하지 않는다고 가정하면 ...

실제 파생 트리의 복잡성은 더 높아질 수 있습니다. 경우에 따라 JIT는 가능한 모든 경우에 x를 확장하는 무언가의 인스턴스가되는 것으로 미리 d를 해결할 수 있다면 대부분의 경우이를 최적화 할 수 있습니다. 현실적으로 그러나, 당신은 그 나무를 거의 통과 할 것입니다.

그게 문제가된다면, 대신 처리기 맵을 사용하여 객체의 구체적인 클래스를 처리를 수행하는 클로저에 연결하는 것이 좋습니다. 직접 매핑을 위해 트리 탐색 단계를 제거합니다. 그러나 C.class에 대한 처리기를 설정 한 경우 위의 내 개체 d는 인식되지 않습니다.

여기 내 2 센트, 그들이 도와주기를 바랍니다 ...




현대 Java 버전에서 instanceof 연산자는 간단한 메소드 호출처럼 빠릅니다. 이것은 다음을 의미합니다.

if(a instanceof AnyObject){
}

다음과 같이 빠릅니다.

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

또 다른 한가지는 많은 instanceof를 캐스 케이 딩해야하는 경우입니다. 그런 다음 getType ()을 한 번만 호출하는 스위치가 빠릅니다.




성능 영향을 결정할 항목은 다음과 같습니다.

  1. instanceof 연산자가 true를 반환 할 수있는 가능한 클래스의 수입니다.
  2. 데이터 배포 - 첫 번째 또는 두 번째 시도에서 대부분 해결되는 작업의 대부분입니까? 먼저 진정한 작업을 반환 할 가능성이 가장 높습니다.
  3. 전개 환경. Sun Solaris VM에서 실행하는 것은 Sun의 Windows JVM과 크게 다릅니다. Solaris는 기본적으로 '서버'모드로 실행되지만 Windows는 클라이언트 모드로 실행됩니다. Solaris에서의 JIT 최적화는 모든 메소드 액세스를 동일하게 할 수 있습니다.

나는 4 개의 다른 파견 방법을위한 마이크로 벤치 마크를 만들었다. Solaris의 결과는 다음과 같습니다. 그 수가 적 으면 빠릅니다 :

InstanceOf 3156
class== 2925 
OO 3083 
Id 3067 



Demian과 Paul은 좋은 점을 언급합니다. 그러나 실제로 실행할 코드 배치는 데이터 사용 방법에 달려 있습니다.

나는 여러 가지 방법으로 사용할 수있는 작은 데이터 객체를 좋아합니다. 재정의 (다형성) 접근법을 따르는 경우 객체는 "편도"로만 사용할 수 있습니다.

이것은 패턴이 들어오는 곳입니다 ...

방문자 패턴과 같이 이중 디스패치를 ​​사용하여 각 객체가 자신을 지나쳐 "호출"하도록 요청할 수 있습니다. 그러면 객체의 유형이 해결됩니다. 그러나 (다시) 모든 가능한 하위 유형을 "할 수있는"수업이 필요합니다.

전략 패턴을 사용하는 것이 좋습니다. 여기서 전략 패턴을 사용하면 처리하려는 각 하위 유형에 대한 전략을 등록 할 수 있습니다. 다음과 같은 것. 정확한 유형 일치에만 도움이되지만 확장이 가능하다는 장점이 있습니다. 타사 기고가가 자신의 유형과 처리기를 추가 할 수 있습니다. (이는 새로운 번들을 추가 할 수있는 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);
    }
}



instanceof 성능에 대해 다시 알려 드리겠습니다. 그러나 문제를 피하는 방법 (또는 그것의 부족)은 instanceof를 수행해야하는 모든 하위 클래스에 대한 부모 인터페이스를 만드는 것입니다. 인터페이스는 instanceof check를 수행해야하는 하위 클래스의 모든 메소드의 수퍼 세트입니다. 메소드가 특정 서브 클래스에 적용되지 않는 경우,이 메소드의 더미 구현을 제공하기 만하면됩니다. 내가이 문제를 오해하지 않는다면, 이것은 내가 과거에 그 문제를 어떻게 풀어 냈는지에 대한 것입니다.




나는 "instanceof"가 충분히 비싸지 않다는이 페이지의 일반적인 합의에 반하는 모범을 제출할 가치가 있다고 생각했다. 내부 루프에 몇 가지 코드가 있음을 발견했습니다 (최적화시 일부 역사적인 시도에서).

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

여기서 SingleItem에 대한 head () 호출은 변경되지 않은 값을 반환합니다. 코드를 다음으로 대체

seq = seq.head();

스트링에서 더블로 변환과 같이 루프에서 일어나는 일들이 상당히 많음에도 불구하고 269ms에서 169ms로 더 빨라졌습니다. 물론 속도 향상은 instanceof 연산자 자체를 제거하는 것보다 조건부 분기 제거로 인한 것일 수 있습니다. 그러나 나는 언급 할 가치가 있다고 생각했다.




특정 JVM이 인스턴스를 구현하는 방법을 말하는 것은 어렵지만 대부분의 경우 객체는 구조체 및 클래스와 유사하며 모든 객체 구조체는 인스턴스 인 클래스 구조체에 대한 포인터를 갖습니다. 그래서 실제로 instanceof

if (o instanceof java.lang.String)

다음 C 코드만큼 빠름

if (objectStruct->iAmInstanceOf == &java_lang_String_class)

JIT 컴파일러가 제자리에 있고 괜찮은 일을한다고 가정합니다.

이 포인터를 액세스하는 것을 고려하면 포인터가 가리키는 특정 오프셋에서 포인터를 가져 와서 이것을 다른 포인터와 비교하면 (기본적으로 동일한 32 비트 숫자로 테스트하는 것과 동일 함), 실제로 연산이 수행 될 수 있다고 말하고 싶습니다. 매우 빠르다.

그것은 JVM에 많은 영향을 미칠 필요는 없습니다. 그러나 이것이 코드에서 병목 현상으로 판명되면 JVM 구현이 다소 불량하다고 생각할 것입니다. JIT 컴파일러가없고 코드 만 해석하는 경우조차 사실상 즉시 인스턴스 테스트를 수행 할 수 있어야합니다.




Related