등가 정적 및 비 정적 방법의 속도 차이가 큼


86

이 코드에서 main메서드 에서 Object를 생성 한 다음 해당 객체 method : ff.twentyDivCount(i)(runs in 16010 ms)를 호출하면이 주석을 사용하여 호출하는 것보다 훨씬 빠르게 twentyDivCount(i)실행됩니다 . (runs in 59516 ms). 물론 객체를 생성하지 않고 실행하면 메서드를 정적으로 만들어서 메인에서 호출 할 수 있습니다.

public class ProblemFive {

    // Counts the number of numbers that the entry is evenly divisible by, as max is 20
    int twentyDivCount(int a) {    // Change to static int.... when using it directly
        int count = 0;
        for (int i = 1; i<21; i++) {

            if (a % i == 0) {
                count++;
            }
        }
        return count;
    }

    public static void main(String[] args) {
        long startT = System.currentTimeMillis();;
        int start = 500000000;
        int result = start;

        ProblemFive ff = new ProblemFive();

        for (int i = start; i > 0; i--) {

            int temp = ff.twentyDivCount(i); // Faster way
                       // twentyDivCount(i) - slower

            if (temp == 20) {
                result = i;
                System.out.println(result);
            }
        }

        System.out.println(result);

        long end = System.currentTimeMillis();;
        System.out.println((end - startT) + " ms");
    }
}

편집 : 지금까지 다른 컴퓨터가 다른 결과를 생성하는 것처럼 보이지만 JRE 1.8. *를 사용하면 원래 결과가 일관되게 재현되는 것처럼 보입니다.


4
벤치 마크를 어떻게 실행하고 있습니까? 이것은 코드를 최적화하기에 충분한 시간이없는 JVM의 아티팩트라고 장담합니다.
Patrick Collins

2
JVM에 대한 그것의 충분한 시간이 컴파일하고 주요 방법에 대한 OSR 수행 할 것으로 보인다 +PrintCompilation +PrintInlining
Tagir Valeev 보낸

1
나는 코드 스 니펫을 시도했지만 Stabbz가 말한 것과 같은 시차를 얻지 못했습니다. 그들은 56282ms (인스턴스 사용) 54551ms (정적 방법으로).
Don Chakkappan 2015 년

1
@PatrickCollins 5 초면 충분합니다. 나는 그것을 조금 다시 썼다 둘 다 (JVM은 변형에 따라 시작됩니다 하나)을 측정 할 수 있도록. 벤치 마크로서 여전히 결함이 있음을 알고 있지만 충분히 설득력이 있습니다. 1457ms STATIC 대 5312ms NON_STATIC.
maaartinus

1
아직 질문에 대해 자세히 조사하지는 않았지만 관련 이 있을 수 있습니다. shipilev.net/blog/2015/black-magic-method-dispatch (어쩌면 Aleksey Shipilëv가 여기에서 우리를 계몽 할 수 있습니다)
Marco13

답변:


72

JRE 1.8.0_45를 사용하면 비슷한 결과를 얻습니다.

조사:

  1. -XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation -XX:+PrintInliningVM 옵션으로 자바를 실행 하면 두 메서드가 모두 컴파일되고 인라인됨을 보여줍니다.
  2. 메서드 자체에 대해 생성 된 어셈블리를 보면 큰 차이가 없습니다.
  3. 그러나 일단 인라인되면 생성 된 어셈블리 main는 매우 다릅니다. 인스턴스 메서드는 특히 루프 언 롤링 측면에서보다 적극적으로 최적화됩니다.

그런 다음 위의 의심을 확인하기 위해 다른 루프 풀기 설정으로 테스트를 다시 실행했습니다. 다음과 같이 코드를 실행했습니다.

  • -XX:LoopUnrollLimit=0 두 방법 모두 느리게 실행됩니다 (기본 옵션이있는 정적 방법과 유사).
  • -XX:LoopUnrollLimit=100 두 방법 모두 빠르게 실행됩니다 (기본 옵션을 사용하는 인스턴스 방법과 유사).

결론적으로 기본 설정을 사용하면 핫스팟 1.8.0_45 의 JIT 메서드가 정적 일 때 루프를 풀 수없는 것 같습니다 (그런 방식으로 작동하는 이유는 모르겠지만). 다른 JVM은 다른 결과를 생성 할 수 있습니다.


52에서 71 사이에 원래 동작이 복원됩니다 (적어도 내 컴퓨터에서는 내 대답). 정적 버전이 20 단위 더 큰 것 같지만 그 이유는 무엇입니까? 이건 이상해.
maaartinus

3
@maaartinus 그 숫자가 정확히 무엇을 나타내는 지 확신 할 수 없습니다. 문서는 상당히 회피 적입니다. " 서버 컴파일러 중간 표현 노드 수가이 값보다 작은 언롤 루프 본문입니다. 서버 컴파일러에서 사용하는 제한 은이 값의 함수입니다. 실제 값이 아닙니다 . 기본값은 JVM이 실행되는 플랫폼에 따라 다릅니다. "...
assylias

나도 모르지만 첫 번째 추측은 정적 메서드가 어떤 단위에서든 약간 더 커지고 중요한 지점에 도달했다는 것입니다. 그러나 그 차이는 꽤 크므로 현재 내 추측은 정적 버전이 약간 더 크게 만드는 최적화를 얻는다는 것입니다. 생성 된 asm을 보지 않았습니다.
maaartinus

33

assylias의 답변에 근거한 입증되지 않은 추측입니다.

JVM은 70과 같은 루프 언 롤링에 임계 값을 사용합니다. 어떤 이유로 든 정적 호출은 약간 더 크고 언 롤링되지 않습니다.

결과 업데이트

  • LoopUnrollLimit(52) 아래에서, 두 버전은 느리다.
  • 52에서 71 사이에는 정적 버전 만 느립니다.
  • 71 이상에서는 두 버전 모두 빠릅니다.

내 생각에 정적 호출이 내부 표현에서 약간 더 크고 OP가 이상한 경우에 부딪 혔기 때문에 이것은 이상합니다. 그러나 그 차이는 약 20 인 것 같습니다.

 

-XX:LoopUnrollLimit=51
5400 ms NON_STATIC
5310 ms STATIC
-XX:LoopUnrollLimit=52
1456 ms NON_STATIC
5305 ms STATIC
-XX:LoopUnrollLimit=71
1459 ms NON_STATIC
5309 ms STATIC
-XX:LoopUnrollLimit=72
1457 ms NON_STATIC
1488 ms STATIC

실험하려는 사람들에게는 내 버전 이 유용 할 수 있습니다.


'1456ms'시간입니까? 그렇다면 정적이 느리다고 말하는 이유는 무엇입니까?
Tony

@Tony 나는 혼동 NON_STATIC하고 STATIC, 그러나 나의 결론은 옳았다. 지금 해결되었습니다. 감사합니다.
maaartinus

0

이것이 디버그 모드에서 실행될 때, 숫자는 인스턴스와 정적 케이스에서 동일합니다. 이는 또한 JIT가 인스턴스 메소드의 경우와 동일한 방식으로 정적 케이스에서 코드를 원시 코드로 컴파일하는 것을 주저함을 의미합니다.

왜 그렇게합니까? 말하기 어렵습니다. 이것이 더 큰 응용 프로그램이라면 아마도 올바른 일을 할 것입니다 ...


"그렇게하는 이유는 무엇입니까? 말하기 어렵습니다.이 앱이 더 큰 앱이라면 옳은 일을 할 것입니다." 아니면 실제로 디버깅하기에는 너무 큰 이상한 성능 문제가있을 것입니다. (말하기 어렵지 않습니다. assylias처럼 JVM이 뱉어 낸 어셈블리를 볼 수 있습니다.)
tmyklebu

@tmyklebu 또는 완전히 디버깅하는 데 불필요하고 비용이 많이 드는 이상한 성능 문제가 있으며 쉬운 해결 방법이 있습니다. 마지막으로 여기에서 JIT에 대해 이야기하고 있습니다. 작성자는 JIT가 모든 상황에서 정확히 어떻게 작동하는지 알지 못합니다. :) 다른 답변을보세요. 문제를 설명하기에 매우 훌륭하고 매우 가깝습니다.하지만 아직까지 정확히 왜 이런 일이 발생하는지 아무도 모릅니다.
Dragan Bozanovic 2015 년

@DraganBozanovic : 실제 코드에서 실제 문제가 발생하면 "완전히 디버깅 할 필요가 없습니다."
tmyklebu 2015 년

0

테스트를 약간 수정 한 결과 다음과 같은 결과가 나왔습니다.

산출:

Dynamic Test:
465585120
232792560
232792560
51350 ms
Static Test:
465585120
232792560
232792560
52062 ms

노트

개별적으로 테스트하는 동안 동적의 경우 ~ 52 초, 정적의 경우 ~ 200 초를 얻었습니다.

이것은 프로그램입니다 :

public class ProblemFive {

    // Counts the number of numbers that the entry is evenly divisible by, as max is 20
    int twentyDivCount(int a) {  // Change to static int.... when using it directly
        int count = 0;
        for (int i = 1; i<21; i++) {

            if (a % i == 0) {
                count++;
            }
        }
        return count;
    }

    static int twentyDivCount2(int a) {
         int count = 0;
         for (int i = 1; i<21; i++) {

             if (a % i == 0) {
                 count++;
             }
         }
         return count;
    }

    public static void main(String[] args) {
        System.out.println("Dynamic Test: " );
        dynamicTest();
        System.out.println("Static Test: " );
        staticTest();
    }

    private static void staticTest() {
        long startT = System.currentTimeMillis();;
        int start = 500000000;
        int result = start;

        for (int i = start; i > 0; i--) {

            int temp = twentyDivCount2(i);

            if (temp == 20) {
                result = i;
                System.out.println(result);
            }
        }

        System.out.println(result);

        long end = System.currentTimeMillis();;
        System.out.println((end - startT) + " ms");
    }

    private static void dynamicTest() {
        long startT = System.currentTimeMillis();;
        int start = 500000000;
        int result = start;

        ProblemFive ff = new ProblemFive();

        for (int i = start; i > 0; i--) {

            int temp = ff.twentyDivCount(i); // Faster way

            if (temp == 20) {
                result = i;
                System.out.println(result);
            }
        }

        System.out.println(result);

        long end = System.currentTimeMillis();;
        System.out.println((end - startT) + " ms");
    }
}

또한 테스트 순서를 다음과 같이 변경했습니다.

public static void main(String[] args) {
    System.out.println("Static Test: " );
    staticTest();
    System.out.println("Dynamic Test: " );
    dynamicTest();
}

그리고 나는 이것을 얻었다 :

Static Test:
465585120
232792560
232792560
188945 ms
Dynamic Test:
465585120
232792560
232792560
50106 ms

보시다시피 정적보다 동적이 호출되면 정적 속도가 크게 감소합니다.

이 벤치 마크를 기반으로 :

나는 모든 것이 JVM 최적화에 달려 있다고 가정 합니다. 따라서 정적 및 동적 방법을 사용하기 위해 경험 법칙을 따르는 것이 좋습니다.

엄지 손가락 규칙 :

자바 : 정적 메서드를 사용하는 경우


"정적 및 동적 방법을 사용하려면 경험 법칙을 따르십시오." 이 경험 법칙은 무엇입니까? 그리고 누구 / 무엇을 인용하고 있습니까?
weston

@weston 미안합니다 내가 생각했던 링크를 추가하지 않았습니다 :). thx
nafas

0

시도하십시오 :

public class ProblemFive {
    public static ProblemFive PROBLEM_FIVE = new ProblemFive();

    public static void main(String[] args) {
        long startT = System.currentTimeMillis();
        int start = 500000000;
        int result = start;


        for (int i = start; i > 0; i--) {
            int temp = PROBLEM_FIVE.twentyDivCount(i); // faster way
            // twentyDivCount(i) - slower

            if (temp == 20) {
                result = i;
                System.out.println(result);
                System.out.println((System.currentTimeMillis() - startT) + " ms");
            }
        }

        System.out.println(result);

        long end = System.currentTimeMillis();
        System.out.println((end - startT) + " ms");
    }

    int twentyDivCount(int a) {  // change to static int.... when using it directly
        int count = 0;
        for (int i = 1; i < 21; i++) {

            if (a % i == 0) {
                count++;
            }
        }
        return count;
    }
}

20273 ms ~ 23000+ ms, 각 실행마다 다름
Stabbz
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.