Java에서 instanceof 사용의 성능 영향


314

나는 응용 프로그램을 작업 중이며 한 가지 디자인 방식에는 instanceof운영자를 극도로 많이 사용하는 것이 포함됩니다 . OO 디자인은 일반적으로을 사용하지 않으려 고 instanceof하지만 다른 이야기 이며이 질문은 성능과 전적으로 관련이 있습니다. 성능에 영향이 있는지 궁금합니다. 만큼 빠릅니다 ==?

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

내가 해결하려고 생각한 다른 방법 중 하나는 대신 "type id"정수 프리미티브를 사용하고 비트 마스크를 사용하여 서브 클래스의 범주를 표시 한 다음 서브 클래스 "type id"를 카테고리를 나타내는 상수 마스크.

되어 instanceof어떻게 든 빨리보다 수 JVM에 의해 최적화? Java를 고수하고 싶지만 앱의 성능이 중요합니다. 이 길을 가본 적이있는 사람이 조언을 해줄 수 있다면 좋을 것입니다. 너무 많이 nitpicking하거나 최적화하기 위해 잘못된 것에 집중하고 있습니까?


81
그러나 질문의 ​​요점은 모범 OO 연습 문제를 제쳐두고 성능을 조사하는 것이라고 생각합니다.
Dave L.

3
@Dave L. 일반적으로 동의하지만 OP는 일반적인 최적화 기술을 찾고 있다고 말하면서 문제가 'instanceof'와 관련이 있는지 확실하지 않습니다. 나는 적어도 '올바른'디자인을 언급 할 가치가 있다고 생각하므로 두 가지 선택을 모두 프로파일 할 수 있습니다.
무법자 프로그래머

51
어 ... 왜 모든 대답이 문제의 요점을 놓치고 최적화에 관한 동일한 오래된 Knuth 수사학을 제공합니까? 귀하의 질문은 instanceof가 ==로 클래스 객체를 확인하는 것보다 현저히 / 놀랍게도 느린 지에 관한 것입니다.
gubby

3
instanceof와 cast의 성능은 상당히 좋습니다. Java7에서 문제에 대한 다양한 접근 방식에 대한 타이밍을 게시했습니다. stackoverflow.com/questions/16320014/…
Wheezil

답변:


268

최신 JVM / JIC 컴파일러는 instanceof, 예외 처리, 리플렉션 등을 포함하여 전통적으로 "느린"작업의 성능 저하를 제거했습니다.

도널드 크 누스 (Donald Knuth)는 "우리는 작은 효율성에 대해 잊어야합니다. 시간의 약 97 %를 말합니다. 조기 최적화는 모든 악의 근원입니다." instanceof의 성능은 문제가되지 않으므로 문제가 확실 할 때까지 이국적인 해결 방법을 찾는 데 시간을 낭비하지 마십시오.


13
최신 JVM / JIC ..COULD이 최적화가 어떤 Java 버전에서 다루어 졌는지 언급 해 주시겠습니까?
Ravisha

138
성능이 주제 일 때 Knuth를 인용하는 사람이 항상 있습니다 ... Knuth도 같은 기사에서 언급했습니다. 소프트웨어 엔지니어링 분야에서 우위를 선점해야합니다. "그의 거의 모든 작업은 알고리즘의 효율성에 관한 것이 었으며, 특히 더 나은 성능을 달성하기 위해 어셈블리 알고리즘을 작성했습니다. Meh ...
kgadek

4
여기를 제쳐두고 있지만 try { ObjT o = (ObjT)object } catch (e) { no not one of these }더 빠를 것 입니다 ??
peterk

35
"object"가 ObjT의 인스턴스 인 경우이를 캐스팅하는 것이 인스턴스를 수행하는 것보다 약간 빠르지 만 빠른 테스트에서 발견 한 차이는 10,000,000 회 반복에서 10-20ms입니다. 그러나 "object"가 ObjT가 아닌 경우 예외를 잡는 것은 3000 배 이상 느립니다. 인스턴스의 경우 31,000ms 이상 ~ 10ms 이상입니다.
Steve

19
"참조"가없는 그러한 강력한 주장은 단지 의견이 있기 때문에 완전히 쓸모가 없습니다.
marcorossi

279

접근하다

다양한 구현을 평가하기 위해 벤치 마크 프로그램 을 작성 했습니다 .

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

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

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

결과

| 운영 | 작업 당 나노초의 런타임 | instanceof와 관련 |
| ------------ | ------------------------------------ -| ------------------------ |
| INSTANCEOF | 39,598 ± 0,022 ns / op | 100,00 % |
| GETCLASS | 39,687 ± 0,021 ns / op | 100,22 % |
| 종류 | 46,295 ± 0,026 ns / op | 116,91 % |
| OO | 48,078 ± 0,026 ns / op | 121,42 % |

tl; dr

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


58
+0.(9)과학을 위해!

16
+ 나에게서 다른 0.1 : D
Tobias Reich

14
@TobiasReich 그래서 우리는 얻었다 +1.0(9). :)
Pavel

9
나는 이것이 의미있는 것을 전혀 측정하지 않는다고 생각합니다. 이 코드 System.currentTimeMillis()는 단일 메서드 호출을 넘지 않는 연산을 사용하여 측정 하므로 정밀도가 매우 낮습니다. 대신 JMH 와 같은 벤치 마크 프레임 워크를 사용하십시오 !
Lii

6
또는 통화 당이 아닌 수십억 통화의 타이밍을 수행하십시오.
LegendLength

74

instanceOf 성능이 문자가 하나 인 문자열 객체에 대한 간단한 s.equals () 호출과 비교되는 방법을보기 위해 간단한 테스트를 수행했습니다.

10.000.000 루프에서 instanceOf는 63-96ms를, 문자열 equals는 106-230ms를 주었다

Java jvm 6을 사용했습니다.

따라서 간단한 테스트에서 하나의 문자열 비교 대신 instanceOf를 수행하는 것이 더 빠릅니다.

문자열 대신 Integer의 .equals ()를 사용하면 == 사용했을 때만 동일한 결과를 얻었습니다.


4
여기에 코드를 게시 할 수 있습니까? 대단 할 것입니다!
연금술사

7
instanceOf는 다형성 함수 디스패치와 어떻게 비교 되었습니까?
Chris

21
왜 instanceof를 String.equals ()와 비교합니까? 유형을 확인하려면 object.getClass (). equals (SomeType.class)
marsbear

4
equals()서브 클래 싱 때문에 @marsbear 는 잘리지 않습니다. 당신은 필요합니다 isAssignableFrom().
David Moles

1
@marsbear 맞습니다. 그러나 OP가 요구하는 것을 더 잘 테스트하지는 않습니다.
David Moles 2014

20

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

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

나는 네 가지 다른 파견 방법에 대한 마이크로 벤치 마크를 만들었습니다 . Solaris의 결과는 다음과 같으며 숫자가 작을수록 빠릅니다.

InstanceOf 3156
class== 2925 
OO 3083 
Id 3067 

18

가장 마지막 질문에 답하기 : 프로파일 러가 말하지 않는 한, 인스턴스에서 우스꽝스러운 시간을 보낸다는 것입니다.

최적화 할 필요가없는 것을 최적화하는 것에 대해 궁금해하기 전에 : 알고리즘을 가장 읽기 쉬운 방식으로 작성하고 실행하십시오. jit-compiler가 자체 최적화 할 수있을 때까지 실행하십시오. 그런 다음이 코드 조각에 문제가있는 경우 프로파일 러를 사용하여 가장 많은 정보를 얻고 최적화 할 수있는 위치를 알려주십시오.

고도로 최적화 된 컴파일러의 경우 병목 현상에 대한 추측이 완전히 틀릴 수 있습니다.

그리고이 답변의 진정한 정신으로 (나는 진심으로 믿습니다) : 일단 jit 컴파일러가 그것을 최적화 할 수있는 기회를 얻은 후에 instanceof와 ==가 어떻게 관련되는지는 절대 모릅니다.

잊어 버렸습니다 : 첫 번째 달리기를 측정하지 마십시오.


1
그러나 원래 포스터에서 언급 한 성능은이 응용 프로그램에 매우 중요하므로 해당 상황에서 초기에 최적화하는 것은 부당하지 않습니다. 다시 말해, GWBasic에서 3D 게임을 작성하지 않고 결국에는 최적화를 시작하겠습니다. 첫 단계는 C ++로 포팅하는 것입니다.
LegendLength

적절한 라이브러리가 있다면 GWBasic은 3D 게임을 시작하기에 좋습니다. 그러나 인위적인 주장이므로 OP는 최적화로 완전한 재 작성을 요구하지 않습니다. 영향이 중요한지조차 알 수없는 단일 구조에 관한 것입니다 ( 현재 버전의 컴파일러에서 동일한 작업을 수행하는 더 나은 방법이 있더라도 ). 나는 c2.com/cgi/wiki?ProfileBeforeOptimizing 과 내 대답 뒤에 단단히 서 있습니다. 예비 최적화는 모든 악의 근원입니다! 그것은 어렵게 유지한다 - 및 유지 보수 가치가 최적화되는 양상이다
올라프 KOCK

15

나는 같은 질문을 가지고 있지만 내 것과 비슷한 유스 케이스에 대한 '성능 메트릭'을 찾지 못했기 때문에 샘플 코드를 더 많이 작성했습니다. 내 하드웨어와 Java 6 & 7에서 instanceof와 스위치의 10mln 반복의 차이점은

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에서 이것을 다시 방문 했습니까? 더 중요한 것은 instanceofs 인 경우 길이를 ints의 case 문에 필수적인 것과 비교하는 것입니다. int 스위치가 빠르게 밝아 질 것으로 예상합니다.
Azeroth2b

1
5 년 전에 무슨 일이 있었는지 정확히 기억할 수 없습니다 .Java 6과 Java 7 모두 비슷한 결과를 가지고 있다고 생각합니다. 따라서 단 하나의 결과 만 제공됩니다 (2 줄은 클래스 깊이에 따라 다릅니다). , 나는 Java 8과 비교하려고 시도하지 않았다. 전체 테스트 코드가 제공된다-당신은 그것을 복사 / 붙여 넣기하고 필요한 환경을 점검 할 수있다 (주-나는 이것을 위해 JMH 테스트를 사용할 것이다).
Xtra Coder

9

instanceof CPU 명령 몇 개만 사용하면 정말 빠릅니다.

X클래스에로드 된 서브 클래스 가없는 경우 (JVM은 알고 있음) 다음과 instanceof같이 최적화 할 수 있습니다.

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

주요 비용은 단지 읽기입니다!

X서브 클래스가로드 된 경우 몇 가지 추가 읽기가 필요합니다. 그것들은 같은 위치에있을 가능성이 높기 때문에 추가 비용도 매우 낮습니다.

모두 좋은 소식입니다!


2
를 최적화 할이나 되는 최적화? 출처?

@vaxquis jvm impl을 지정할 수 있습니다.
RecursiveExceptionException

@itzJanuary 한숨 여기 내 질문의 요점을 놓친 : 모두는 컴파일러가 알 수있는 최적화 foo-하지만 되어 foo실제로 현재 오라클의 javac의 / VM에 의해 최적화 - 또는 미래에 그렇게 할거야 바로 가능하다? 또한 응답자 에게 실제로 백업 소스 (문서, 소스 코드, 개발자 블로그 등)가 실제로 최적화되거나 최적화 될 수 있는지 문서화 했습니까? 그것이 없으면이 답변은 컴파일러가 할 수 있는 일 에 대해 무작위로 생각하는 것 입니다.

@vaxquis Hotspot VM을 언급 한 적이 없지만이 경우 "최적화"인지는 알 수 없습니다.
RecursiveExceptionException

1
최근 JIT (JVM 8)가 직접 호출을 통해 1 또는 2 유형에 대한 호출 사이트를 최적화하지만 실제 유형이 두 개 이상인 경우 vtable로 되돌아갑니다. 따라서 런타임시 콜 사이트를 통과하는 두 가지 구체적인 유형 만 있으면 성능 이점이 있습니다.
simon.watts

5

Instanceof는 매우 빠릅니다. 클래스 참조 비교에 사용되는 바이트 코드로 요약됩니다. 루프에서 몇 백만 개의 인스턴스를 사용 해보고 직접보십시오.


5

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

왜 그런 겁니까? 아마도 일어날 일이 있기 때문에 몇 가지 인터페이스가 있으며 일부 기능 (예 : 인터페이스 x, y 및 z)을 제공하고 해당 인터페이스 중 하나를 구현하거나 구현하지 않을 수있는 조작 할 객체가 있습니다 ... 직접하지 않습니다. 예를 들어, 나는 가지고있다 :

w는 x를 확장

구현은 w

B는 A를 확장

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

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

객체 d 인 D의 인스턴스를 처리한다고 가정합니다. 컴퓨팅 (d instanceof x)은 d.getClass ()를 가져 와서 구현 한 인터페이스를 반복하여 하나가 == ~ x인지 여부를 알기 위해 반복합니다. 해당 트리를 폭 넓게 탐색하면 y와 z가 아무 것도 확장하지 않는다고 가정 할 때 최소 8 번의 비교가 이루어집니다.

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

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

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


5

instanceof는 매우 효율적이므로 성능이 저하되지 않습니다. 그러나 많은 instanceof를 사용하면 디자인 문제가 발생합니다.

xClass == String.class를 사용할 수 있으면 더 빠릅니다. 참고 : 최종 클래스에는 instanceof가 필요하지 않습니다.


1
Btw "최종 수업에 instanceof가 필요하지 않다"는 것은 무엇을 의미합니까?
Pacerier

최종 클래스는 하위 클래스를 가질 수 없습니다. 이 경우 x.getClass() == Class.class와 동일합니다x instanceof Class
Peter Lawrey

x가 null이 아니라고 가정하면 어느 것이 좋습니까?
Pacerier

좋은 지적. 내가 생각할 것인지의 여부에 달려 x있습니다 null. (또는 둘 중 더 명확한 것)
Peter Lawrey

흠 방금 java.lang.class.isAssignableFrom을 사용할 수 있다는 것을 깨달았습니다. instanceof 키워드가 내부적으로 이와 같은 함수를 사용 하는지 알고 있습니까?
Pacerier

4

일반적으로 "instanceof"연산자가 이와 같은 경우 (이 인스턴스가이 기본 클래스의 서브 클래스를 확인하는 경우)에 찡그린 이유는 오퍼레이션을 메소드로 이동하고 해당 오퍼레이션을 대체하기 때문입니다. 서브 클래스. 예를 들어 다음과 같은 경우

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

그걸로 바꿀 수 있습니다.

o.doEverything();

클래스 1에서 "doThis ()"를 호출하고 클래스 2에서 "doThat ()"을 호출하는 등 "doEverything ()"을 구현하십시오.


11
그러나 때때로 당신은 할 수 없습니다. Object를 사용하는 인터페이스를 구현하고 어떤 유형인지 알려면 instanceof가 실제로 유일한 옵션입니다. 전송을 시도 할 수 있지만 instanceof는 일반적으로 더 깨끗합니다.
Herms

4

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

객체가 일부 서브 클래스의 인스턴스인지 테스트하는 스위치가있는 경우 설계를 재 작업해야 할 수도 있습니다. 서브 클래스 특정 동작을 서브 클래스 자체로 푸시 다운하는 것을 고려하십시오.


4

데미안과 바울은 좋은 지적을합니다. 하나 실행할 코드의 배치는 실제로 데이터를 사용하려는 방법에 따라 다릅니다.

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

여기에 패턴이 들어옵니다 ...

(방문자 패턴에서와 같이) 이중 디스패치를 ​​사용하여 각 객체에 "전화를 걸도록"요청할 수 있습니다. 그러면 객체 유형이 해결됩니다. 하나 (다시) 가능한 모든 하위 유형으로 "일을 할 수있는"클래스가 필요합니다.

처리하려는 각 하위 유형에 대한 전략을 등록 할 수있는 전략 패턴을 선호합니다. 다음과 같은 것. 이는 정확히 일치하는 유형에만 도움이되지만 확장 가능하다는 이점이 있습니다. 제 3 자 제공 업체는 자체 유형 및 처리기를 추가 할 수 있습니다. (새 번들을 추가 할 수있는 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();
}
}

내가 추측한다면 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 컴파일러가없고 코드 만 해석하는 사람조차도 거의 즉시 테스트 인스턴스를 만들 수 있어야합니다.


1
o가 java.lang.String에서 상속되는지 알아낼 필요가 없습니까?
WW.

1
그래서 나는 그것이 "빠르다"고 말했다. 실제로 루프를 수행하여 문제의 클래스에 대해 먼저 iAmInstanceOf를 확인한 다음 o의 상속 트리를 위로 올라가고 모든 수퍼 클래스에 대해이 검사를 반복합니다 (따라서이 루프를 두 번 실행해야 할 수도 있습니다)
Mecki

3

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


2

대신에 는 객체 지향 디자인이 열악하다는 경고입니다.

현재 JVM은 instanceOf 자체가 성능 문제가별로 없다는 것을 의미합니다 . 특히 핵심 기능을 위해 많은 것을 사용하고 있다면 디자인을 살펴볼 때입니다. 더 나은 디자인으로 리팩토링 할 때의 성능 (및 단순성 / 유지 보수성) 이점은 실제 instanceOf 호출 에 소비 된 실제 프로세서주기보다 훨씬 큽니다 .

아주 간단한 프로그래밍 예제를 제공합니다.

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

열악한 아키텍처가 더 나은 선택은 SomeObject를 두 개의 자식 클래스의 부모 클래스로 만드는 것입니다. 각 자식 클래스는 메서드 (doSomething)를 재정의하므로 코드는 다음과 같습니다.

Someobject.doSomething();

61
알고 있습니다. 그것은 내가 요구 한 것이 아닙니다.
Josh

이 점이 좋은 점인지에 대한 투표 여부는 확실하지 않지만 질문에 대답하지 않습니다 ...
jklp

7
코드 예제는 실제로 매우 나쁜 것으로 생각합니다. Double 클래스를 확장 할 수 없으며 다른 클래스에서 Double을 파생시킬 수도 없습니다. 예를 들어 다른 클래스를 사용한 경우에는 괜찮을 것입니다.
레나 Schimmel

6
또한 SomeObject의 자식 클래스가 값 개체 인 경우 논리를 넣지 않으려 고합니다. 예를 들어, 파이 및 로스트가 putInOven () 및 putInMouth () 논리에 올바른 위치가 아닐 수 있습니다.
sk.

자기 요리 파이와 로스트는 멋진 것입니다
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

프로젝트의 성능 문제인 경우 측정 / 프로필을 작성해야합니다. 그것이 가능하다면 재 설계를 권장합니다. 플랫폼의 기본 구현 (C로 작성)을 이길 수 없다고 확신합니다. 이 경우 다중 상속도 고려해야합니다.

구체적인 유형에만 관심이있는 경우 연관 저장소 (예 : Map <Class, Object>)를 사용할 수 있습니다.


0

Peter Lawrey의 참고 사항에 따르면 최종 수업에 인스턴스를 필요로하지 않고 참조 평등을 사용할 수 있다는 점에 유의하십시오! 최종 클래스는 확장 할 수 없지만 동일한 클래스 로더에 의해로드 될 수는 없습니다. 해당 코드 섹션에 대해 하나의 클래스 로더가 있다고 확신하는 경우 x.getClass () == SomeFinal.class 또는 ilk 만 사용하십시오.


4
클래스가 다른 클래스 로더에 의해로드되면 instanceof가 일치하지 않을 것이라고 생각합니다.
Peter Lawrey

0

나는 열거 형 접근법을 선호하지만 추상 기본 클래스를 사용하여 서브 클래스가 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();
}

여기서 SingleItem에서 head ()를 호출하면 값이 변경되지 않습니다. 에 의해 코드를 교체

seq = seq.head();

루프에서 발생하는 문자열-이중 변환과 같은 매우 무거운 일이 있음에도 불구하고 269ms에서 169ms로 속도가 향상됩니다. 물론 연산자 자체를 제거하는 것보다 조건부 분기를 제거함으로써 속도가 더 빨라질 수 있습니다. 하지만 언급 할 가치가 있다고 생각했습니다.


if그 자체 때문일 수 있습니다 . trues와 falses 의 분포가 짝수에 가까우면 투기 적 실행이 쓸모 없게되어 상당한 지연이 발생합니다.
Dmytro

-4

당신은 잘못된 것에 집중하고 있습니다. instanceof와 동일한 것을 확인하는 다른 방법의 차이점은 아마도 측정 할 수 없을 것입니다. 성능이 중요한 경우 Java는 잘못된 언어 일 수 있습니다. 가장 큰 이유는 VM이 ​​가비지 수집을 원할 때 제어 할 수 없기 때문에 큰 프로그램에서 CPU를 몇 초 동안 100 %까지 차지할 수 있기 때문입니다 (MagicDraw 10은 훌륭했습니다). 모든 컴퓨터를 제어 할 수 없다면이 프로그램이 실행될 것입니다. 어떤 버전의 JVM이 있는지 보장 할 수 없으며 많은 오래된 것들이 주요 속도 문제를 가지고있었습니다. 이 작은 응용 프로그램이 있다면 당신은 자바 괜찮을 수 있지만 끊임없이 읽고 데이터를 폐기하는 경우 당신은 때 GC 차기에서 확인할 수 있습니다.


7
예전보다 더 현대적인 Java 가비지 수집 알고리즘의 경우에는 훨씬 덜 사실입니다. 가장 간단한 알고리즘조차도 사용 직후에 버리는 메모리 양은 더 이상 신경 쓰지 않으며 젊은 세대 컬렉션에서 얼마나 많은 메모리를 유지해야하는지 신경 쓰게됩니다.
Bill Michell

3
가장 최근의 JVM을 사용하고 있으며 GC가 실행될 때 컴퓨터가 여전히 크롤링된다는 점을 제외하면 좋습니다. 듀얼 코어, 3GB 램 서버 성능이 실제로 중요한 경우 Java는 사용할 언어가 아닙니다.
tloach

@David : 앱이 일정 기간 동안 사라질 때 실시간으로 문제가 발생하지 않아도됩니다. 내가 만난 재미있는 것은 GC가 스트림을 먼저 닫지 않았고 돌아 왔을 때 네트워크 트래픽의 과부하를 처리 할 수 ​​없기 때문에 GC가 실행될 때 사망 한 TCP 스트림에 연결된 Java 응용 프로그램입니다. GC가 실행되는 루프로 이동합니다. 앱이 다시 시작되면 많은 데이터를 훔쳐서 메모리가 부족하여 GC를 트리거했습니다. Java는 많은 작업에 적합하지만 많은 작업에는 적합하지 않습니다. 강력한 성능이 요구됩니다.
tloach

6
@tloach는 나쁜 앱 디자인처럼 들립니다. 마치 "성능"에 대해 마치 마치 일차원적인 것입니다. 예를 들어, 매우 큰 데이터 세트의 빠른 대화식 통계 분석 및 시각화를 제공하거나 매우 큰 트랜잭션 볼륨을 매우 빠르게 처리하는 데 유용한 성능을 가진 많은 Java 앱을 사용하여 작업했습니다. "성능"은 단지 한 가지가 아니며 누군가가 메모리를 잘못 관리하고 GC가 자체 방식으로 가져올 수있는 응용 프로그램을 작성할 수 있다는 사실이 "성능"이 필요한 것을 다른 것으로 작성해야한다는 것을 의미하지는 않습니다.
David Moles
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.