0

가능한 중복 :

자바 예외는 얼마나 느린가요?

다음 두 프로그램의 실행 시간은 거의 같습니다.

public class Break {
    public static void main(String[] argv){
        long r = 0, s = 0, t = 0;
        for(long x = 10000000; x > 0; x--){
            long y = x;
            while(y != 1){
                if(y == 0)
                    throw new AssertionError();
                try2: {
                    try1: {
                        for(;;){
                            r++;
                            if(y%2 == 0)
                                break try1;
                            y = y*3 + 1;
                        }
                    }/*catch(Thr _1)*/{
                        for(;;){
                            s++;
                            if(y%2 == 1)
                                break try2;
                            y = y/2;
                        }
                    }
                }/*catch(Thr _2)*/{
                    t++;
                }
            }
        }
        System.out.println(r + ", " + s + ", " + t);
    }
}
public class Try {
    private static class Thr extends Throwable {}

    private static final Thr thrown = new Thr();

    public static void main(String[] argv){
        long r = 0, s = 0, t = 0;
        for(long x = 10000000; x > 0; x--){
            long y = x;
            while(y != 1){
                try{
                    if(y == 0)
                        throw new AssertionError();
                    try{
                        for(;;){
                            r++;
                            if(y%2 == 0)
                                throw thrown;
                            y = y*3 + 1;
                        }
                    }catch(Thr _1){
                        for(;;){
                            s++;
                            if(y%2 == 1)
                                throw thrown;
                            y = y/2;
                        }
                    }
                }catch(Thr _2){
                    t++;
                }
            }
        }
        System.out.println(r + ", " + s + ", " + t);
    }
}
$ for x in Break Try; do echo $x; time java $x; done
Break
1035892632, 1557724831, 520446316

real    0m10.733s
user    0m10.719s
sys     0m0.016s
Try
1035892632, 1557724831, 520446316

real    0m11.218s
user    0m11.204s
sys     0m0.017s

그러나 다음 두 프로그램에 소요되는 시간은 상대적으로 다릅니다.

public class Return {
    private static int tc = 0;

    public static long find(long value, long target, int depth){
        if(depth > 100)
            return -1;
        if(value%100 == target%100){
            tc++;
            return depth;
        }
        long r = find(target, value*29 + 4221673238171300827l, depth + 1);
        return r != -1? r : find(target, value*27 + 4494772161415826936l, depth + 1);
    }

    public static void main(String[] argv){
        long s = 0;
        for(int x = 0; x < 1000000; x++){
            long r = find(0, x, 0);
            if(r != -1)
                s += r;
        }
        System.out.println(s + ", " + tc);
    }
}
public class Throw {
    public static class Found extends Throwable {
        // note the static!
        public static int value = 0;
    }

    private static final Found found = new Found();

    private static int tc = 0;

    public static void find(long value, long target, int depth) throws Found {
        if(depth > 100)
            return;
        if(value%100 == target%100){
            Found.value = depth;
            tc++;
            throw found;
        }
        find(target, value*29 + 4221673238171300827l, depth + 1);
        find(target, value*27 + 4494772161415826936l, depth + 1);
    }

    public static void main(String[] argv){
        long s = 0;
        for(int x = 0; x < 1000000; x++)
            try{
                find(0, x, 0);
            }catch(Found _){
                s += found.value;
            }
        System.out.println(s + ", " + tc);
    }
}
$ for x in Return Throw; do echo $x; time java $x; done
Return
84227391, 1000000

real    0m2.437s
user    0m2.429s
sys     0m0.017s
Throw
84227391, 1000000

real    0m9.251s
user    0m9.215s
sys     0m0.014s

나는 간단한 try / throw / catch 메커니즘이 적어도 부분적으로 꼬리말에 최적화 된 리턴과 비슷할 것이라고 생각한다. (그래서 컨트롤이 가장 가까운 catch로 돌아 가야하는 곳을 직접 알 수있다). 물론, JRE 구현은 많은 최적화를 수행합니다.

후자에 큰 차이가있는 이유는 무엇입니까? 제어 흐름 분석이 전 두 프로그램이 거의 같고 실제 try / throw / catch가 특히 느리거나 또는 Return의find던지기 중 하나가 될 수없는 동안 메서드 호출을 피하는 뭔가로 어떤 수준으로 펼쳐져, 또는 ..? 감사.

편집 :이 질문은 나와 다른 것처럼 보입니다.자바 예외는 얼마나 느린가요?왜냐하면 왜 이와 비슷한 큰 차이가 있는지 묻지 않기 때문입니다. 또한 예외 객체를 만드는 데 시간이 소비된다는 사실을 무시합니다 (fillInStackTrace스택을 가로 지르고 배열을 만드는 것과 같은 것을 포함하여 오버라이드됩니다. 그것은 분명히 내 질문의 일부 대답 : "그것은 제어 흐름 분석을 꽤 많이 있기 때문에 과거의 두 프로그램을 결정하기 때문에 그것은 - 그것은 스택 추적 않습니다 답변에 언급 된 이상한 것 같지만 아마도 스택이 보이지 않는다고 판단하기위한 복잡한 분석을하지 않으면 어떤 스로우 / 캐치 연산도 왜곡 될 것입니다.


  • (i) 마이크로 벤치 마크에주의하십시오 -그들은 짓기가 어렵다.. (ii) 확실한가요?findReturn and Throw에서 같은 수의 시간이라고 불리우는가? - assylias
  • (i) 부분적으로 내가이 질문을 한 이유는 일부 구성 요소가 다른 구성 요소보다 더 빠르다고 선언하지 않았기 때문입니다. (ii) 다른 값을 반환하는 것이 좋습니다.-1안으로Return던지기 같아.Throw-이 결과가 직접적으로 끌어 올려 진 것을 쉽게 알 수 있습니다.main메소드의 마지막 두 줄 (다른 유일한 호출)을 보면됩니다. - sigynsurfeit
  • 예외는 예외없이 항상 느립니다. - Stu
  • @Stu 때때로 그들은 그다지 느리지는 않습니다. (BreakTry). Stephen이 아래의 답변에서 지적한 것처럼 JVM 개발자가이를 수행하는 데 드는 비용이이를 수행했을 때의 이점보다 클 수 있다고 판단했기 때문에 다른 코드만큼 사용하는 최적화 코드가 부족하기 때문일 수 있습니다. - sigynsurfeit
  • @Stu 또한 각 예외는 생성되지 않습니다.throw누군가가 "예외"를 제기 할 때 보통 행해지는 것처럼. 두 가지 테스트에서 나는 똑같은 것을 던지기 만했다.Throwable객체, 부분적으로 일종의 신호 - sigynsurfeit

1 답변


2

벤치 마크는 JVM 예열 효과를 고려하지 않습니다. 그러므로 실제 프로그램에서 try / break / return이 어떻게 수행되는지를 보여주는 결과가 실제로 있다는 것을 의심 할 여지가 있습니다.

(메소드에서 각 시간 지정 테스트를 선언하고 메소드를 여러 번 호출해야합니다. 그런 다음 처음 몇 번의 호출에서 출력을 버리십시오. 또는 숫자가 안정화 될 때까지 ... JIT의 일회성 비용을 없애기 위해 컴파일, 클래스 로딩 등).


진행중인 작업을 실제로 찾으려면 JIT 컴파일러를 통해 각 경우에 대해 생성하는 원시 코드를 덤프해야합니다.

첫 번째 경우 JIT 컴파일러가 throw / catch를 돌리고 있다는 것을 알게 될 것입니다.방법 내에서간단한 분기 명령으로 두 번째 경우에는 JIT 컴파일러가 더 복잡한 코드를 생성 할 가능성이 있습니다. 아마도이 코드는 분기와 동일한 것으로 인식하지 못하기 때문입니다.

차이점은 무엇입니까? JIT 옵티마이 저가 시도한 각 최적화에 대한 비용 / 이점 절충이 있습니다. JIT 컴파일러가 지원하는 각각의 새로운 최적화에는 구현 및 유지 관리 비용이 있습니다. 그리고 런타임에 컴파일러는 최적화를위한 전제 조건이 충족되는지 컴파일하기 위해 컴파일 할 코드를 검사해야합니다. 그들이 있다면 최적화를 수행 할 수 있습니다. 그렇지 않으면 JIT 컴파일러가 시간을 낭비하게됩니다.

이제이 예제에서 우리는 같은 메소드에서 throw되고 catch 된 (다른 경우에는) 메소드 호출 / 리턴 경계를 넘어서 전파되는 예외가 있습니다. 전자의 예에서 최적화 및 (가능한) 최적화 된 코드 시퀀스에 대한 충분한 전제 조건은 모두 매우 간단합니다. 후자의 예에서, 옵티마이 저는 예외를 던지고 잡는 메소드가 다른 컴파일 단위 (즉, 다시로드 될 수 있음), 재정의 가능성 등을 처리 할 수 있어야합니다. 또한 생성 코드 시퀀스는 훨씬 더 복잡합니다 ... 표준이 아닌 return-from-call 시퀀스와 분기가 이어집니다.

그래서 제 이론은 JIT 컴파일러 작성자들이 더 복잡한 최적화가 효과가 있다고 생각하지 않는다는 것입니다. 그리고 대부분의 사람들이그런 자바 코드를 쓰지 마라.아마 맞을 것이다.


  • 적어도 몇 초 동안은 워밍업 효과를 고려해야한다고 생각합니다. -XX : + PrintCompilation에 의해보고 된 마지막 컴파일은 전체 실행에 Throw.main ~ 195ms입니다. - sigynsurfeit
  • JIT 컴파일 코드가 실제로 실행되었다는 증거가 있습니까? 내가 제안한대로 벤치 마크를 작성했다면 의심의 여지가 없을 것입니다. - Stephen C

연결된 질문


관련된 질문

최근 질문