자바 캐스팅으로 오버 헤드가 발생합니까? 왜?


105

한 유형의 객체를 다른 유형으로 캐스트 할 때 오버 헤드가 있습니까? 아니면 컴파일러가 모든 것을 해결하고 런타임에 비용이 들지 않습니까?

이것은 일반적인 것입니까, 아니면 다른 경우가 있습니까?

예를 들어, 각 요소가 다른 유형을 가질 수있는 Object []의 배열이 있다고 가정하십시오. 그러나 우리는 항상 요소 0이 Double이고 요소 1이 문자열이라는 것을 알고 있습니다. (나는 이것이 잘못된 디자인이라는 것을 알고 있지만 이것을해야한다고 가정합시다.)

Java의 유형 정보가 런타임에 계속 유지됩니까? 아니면 컴파일 후 모든 것이 잊혀지고 (Double) elements [0]을 수행하면 포인터를 따라 8 바이트를 double로 해석합니다.

Java에서 유형이 수행되는 방식에 대해 매우 명확하지 않습니다. 책이나 기사에 대한 추천이 있으면 감사합니다.


instanceof 및 캐스팅의 성능은 꽤 좋습니다. 여기에 문제에 대한 다양한 접근 방식에 대한 Java7의 타이밍을 게시했습니다. stackoverflow.com/questions/16320014/…
Wheezil

이 다른 질문은 아주 좋은 답변이 stackoverflow.com/questions/16741323/...
user454322

답변:


78

캐스팅에는 두 가지 유형이 있습니다.

암시 적 캐스팅 : 유형에서 더 넓은 유형으로 캐스트 할 때 자동으로 수행되고 오버 헤드가 없습니다.

String s = "Cast";
Object o = s; // implicit casting

더 넓은 유형에서 더 좁은 유형으로 이동할 때 명시 적 캐스팅. 이 경우 다음과 같이 캐스팅을 명시 적으로 사용해야합니다.

Object o = someObject;
String s = (String) o; // explicit casting

두 번째 경우에는 두 가지 유형을 확인해야하고 캐스팅이 가능하지 않은 경우 JVM에서 ClassCastException을 throw해야하므로 런타임에 오버 헤드가 발생합니다.

JavaWorld 에서 가져옴 : 캐스팅 비용

캐스팅 은 유형 간, 특히 여기서 관심이있는 캐스팅 작업 유형에 대한 참조 유형 간 변환에 사용됩니다.

업 캐스트 작업 (Java 언어 사양에서 확장 변환이라고도 함)은 하위 클래스 참조를 상위 클래스 참조로 변환합니다. 이 캐스팅 작업은 항상 안전하고 컴파일러에서 직접 구현할 수 있기 때문에 일반적으로 자동입니다.

다운 캐스트 작업 (Java 언어 사양에서 축소 변환이라고도 함)은 상위 클래스 참조를 하위 클래스 참조로 변환합니다. 이 캐스팅 작업은 실행 오버 헤드를 발생시킵니다. Java는 캐스트가 유효한지 확인하기 위해 런타임에 확인해야하기 때문입니다. 참조 된 객체가 캐스트의 대상 유형 또는 해당 유형의 서브 클래스의 인스턴스가 아닌 경우 시도 된 캐스트는 허용되지 않으며 java.lang.ClassCastException을 발생시켜야합니다.


102
그 자바 월드 기사는 10 년이 넘었 기 때문에 성능에 대한 모든 진술을 여러분의 가장 훌륭한 소금으로 받아 들일 것입니다.
skaffman

1
@skaffman, 사실 나는 그것이 (성능에 관계없이) 소금 한 알로 만드는 모든 진술을 취할 것입니다.
Pacerier

캐스트 된 개체를 참조에 할당하지 않고 메서드를 호출하면 같은 경우가 될까요? 같은((String)o).someMethodOfCastedClass()
Parth Vishvajit

4
이제이 기사는 거의 20 년이되었습니다. 그리고 그 대답도 오래되었습니다. 이 질문에는 현대적인 답변이 필요합니다.
Raslanove

원시 유형은 어떻습니까? 예를 들어 int에서 short로 캐스팅하면 비슷한 오버 헤드가 발생합니까?
luke1985 19

44

합리적인 Java 구현을 위해 :

각 개체는 다른 것들 사이에 포함 된 헤더를 가지고, 실행시의 형태에 대한 포인터 (예를 들어 DoubleString있지만, 결코 수 CharSequence또는 AbstractList). 런타임 컴파일러 (일반적으로 Sun의 경우 HotSpot)가 유형을 정적으로 결정할 수 없다고 가정하면 생성 된 기계 코드에서 일부 검사를 수행해야합니다.

먼저 런타임 유형에 대한 포인터를 읽어야합니다. 어쨌든 비슷한 상황에서 가상 메서드를 호출하는 데 필요합니다.

클래스 유형으로 캐스팅하는 경우를 누를 때까지 정확히 몇 개의 수퍼 클래스가 있는지 알 java.lang.Object수 있으므로 유형 포인터에서 상수 오프셋 (실제로는 HotSpot의 처음 8 개)에서 유형을 읽을 수 있습니다. 다시 말하지만 이것은 가상 메서드에 대한 메서드 포인터를 읽는 것과 유사합니다.

그런 다음 읽기 값은 캐스트의 예상 정적 유형과의 비교 만 필요합니다. 명령어 세트 아키텍처에 따라 다른 명령어는 잘못된 분기에서 분기 (또는 오류)해야합니다. 32 비트 ARM과 같은 ISA에는 조건부 명령어가 있으며 슬픈 경로가 행복한 경로를 통과하도록 할 수 있습니다.

인터페이스의 다중 상속으로 인해 인터페이스가 더 어렵습니다. 일반적으로 인터페이스에 대한 마지막 두 개의 캐스트는 런타임 유형에 캐시됩니다. 초기 (10 년 전)에는 인터페이스가 약간 느 렸지만 더 이상 관련이 없습니다.

이런 종류의 것이 성능과 거의 관련이 없음을 알 수 있기를 바랍니다. 소스 코드가 더 중요합니다. 성능 측면에서 가장 큰 타격은 모든 곳에서 개체 포인터를 쫓아 캐시 미스가 발생하기 쉽습니다 (물론 유형 정보는 일반적입니다).


1
흥미 롭습니다. 이것은 비 인터페이스 클래스에 대해 Superclass sc = (Superclass) subclass를 작성하면 의미합니다. (jit ie : load time) 컴파일러가 "Class"헤더의 Superclass 및 Subclass 각각에있는 Object의 오프셋을 "정적으로"넣은 다음 간단한 추가 + 비교를 통해 문제를 해결할 수 있습니까? -그것은 멋지고 빠릅니다 :) 인터페이스의 경우 작은 해시 테이블이나 btree보다 나쁘지 않을 것이라고 생각합니까?
peterk

@peterk 클래스 간 캐스팅의 경우 객체 주소와 "vtbl"(메서드 포인터 테이블, 클래스 계층 구조 테이블, 인터페이스 캐시 등)은 모두 변경되지 않습니다. 따라서 [class] 캐스트는 유형을 확인하고 그것이 맞으면 다른 일이 일어나지 않아야합니다.
Tom Hawtin-tackline

8

예를 들어, 각 요소가 다른 유형을 가질 수있는 Object []의 배열이 있다고 가정하십시오. 그러나 우리는 항상 요소 0이 Double이고 요소 1이 문자열이라는 것을 알고 있습니다. (나는 이것이 잘못된 디자인이라는 것을 알고 있지만 이것을해야한다고 가정합시다.)

컴파일러는 배열의 개별 요소 유형을 기록하지 않습니다. 단순히 각 요소 표현식의 유형이 배열 요소 유형에 할당 가능한지 확인합니다.

Java의 유형 정보가 런타임에 계속 유지됩니까? 아니면 컴파일 후 모든 것이 잊혀지고 (Double) elements [0]을 수행하면 포인터를 따라 8 바이트를 double로 해석합니다.

일부 정보는 런타임에 보관되지만 개별 요소의 정적 유형은 보관되지 않습니다. 클래스 파일 형식을 보면 알 수 있습니다.

이론적으로 JIT 컴파일러는 일부 할당에서 불필요한 유형 검사를 제거하기 위해 "이스케이프 분석"을 사용할 수 있습니다. 그러나 제안하는 정도까지이 작업을 수행하는 것은 현실적인 최적화의 범위를 벗어납니다. 개별 요소의 유형을 분석하는 데 따른 보상은 너무 적습니다.

게다가 사람들은 어쨌든 그렇게 응용 프로그램 코드를 작성해서는 안됩니다.


1
프리미티브는 어떻습니까? (float) Math.toDegrees(theta)여기에도 상당한 오버 헤드가 있습니까?
SD

2
일부 원시 캐스트에는 오버 헤드가 있습니다. 중요한지 여부는 상황에 따라 다릅니다.
Stephen C

6

런타임에 캐스팅을 수행하기위한 바이트 코드 명령이 호출 checkcast됩니다. 를 사용하여 Java 코드를 디스 어셈블하여 javap생성되는 명령어를 확인할 수 있습니다 .

배열의 경우 Java는 런타임에 유형 정보를 유지합니다. 대부분의 경우 컴파일러는 유형 오류를 포착하지만 ArrayStoreException배열에 객체를 저장하려고 할 때 를 실행 하지만 유형이 일치하지 않는 경우가 있습니다 (컴파일러가이를 포착하지 못함). . Java 언어 스펙은 다음 예제를 제공합니다 :

class Point { int x, y; }
class ColoredPoint extends Point { int color; }
class Test {
    public static void main(String[] args) {
        ColoredPoint[] cpa = new ColoredPoint[10];
        Point[] pa = cpa;
        System.out.println(pa[1] == null);
        try {
            pa[0] = new Point();
        } catch (ArrayStoreException e) {
            System.out.println(e);
        }
    }
}

Point[] pa = cpaColoredPointPoint의 하위 클래스 이므로 유효 하지만 pa[0] = new Point()유효하지 않습니다.

이것은 런타임에 유형 정보가 유지되지 않는 제네릭 유형과 반대입니다. 컴파일러 checkcast는 필요한 곳에 명령어를 삽입 합니다.

제네릭 유형 및 배열에 대한 유형의 이러한 차이로 인해 종종 배열과 제네릭 유형을 혼합하는 것이 적합하지 않습니다.


2

이론적으로는 오버 헤드가 발생합니다. 그러나 최신 JVM은 똑똑합니다. 각 구현은 다르지만 충돌이 발생하지 않는다는 것을 보장 할 수있을 때 JIT가 어웨이 캐스팅 검사를 최적화 한 구현이있을 수 있다고 가정하는 것은 합리적이지 않습니다. 어떤 특정 JVM이 이것을 제공하는지에 대해서는 말할 수 없습니다. JIT 최적화의 세부 사항을 알고 싶습니다만, 이것은 JVM 엔지니어가 걱정해야 할 사항입니다.

이야기의 교훈은 먼저 이해할 수있는 코드를 작성하는 것입니다. 속도가 느려지는 경우 프로필을 작성하고 문제를 식별하십시오. 캐스팅 때문이 아닐 확률이 높습니다. 필요한 것을 알 때까지 최적화를 위해 깨끗하고 안전한 코드를 희생하지 마십시오.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.