2012-01-19 4 views
12

일부 Java 코드를 계산하는 데 필요한 시간을 계산하는 일부 코드를 사용하여 일부 Java 기능의 효율성 또는 비효율을 느낄 수 있습니다. 그렇게해서 나는 정말로 이상하게 생긴 약간의 효과로 지금 막 붙잡 혔고, 나는 단지 자신을 설명 할 수 없다. 어쩌면 당신의 누군가가 나를 이해하도록 도와 줄 수 있습니다. 이 상대적으로 긴 계산 시간을 초래 시작Java 효율

public class PerformanceCheck { 

public static void main(String[] args) { 
    List<PerformanceCheck> removeList = new LinkedList<PerformanceCheck>(); 

    int maxTimes = 1000000000; 

    for (int i=0;i<10;i++) { 
     long time = System.currentTimeMillis(); 

     for (int times=0;times<maxTimes;times++) { 
      // PERFORMANCE CHECK BLOCK START 

      if (removeList.size() > 0) { 
       testFunc(3); 
      } 

      // PERFORMANCE CHECK BLOCK END 
     } 

     long timeNow = System.currentTimeMillis(); 
     System.out.println("time: " + (timeNow - time)); 
    } 
} 

private static boolean testFunc(int test) { 
    return 5 > test; 
} 

} 

(testFunc 심지어 호출되지 않도록 removeList가 비어 기억) :

time: 2328 
time: 2223 
... 

을 removeList.size의 조합의 무엇이든을 교체하는 동안()> 0과 testFunc (3)을 사용하면 더 좋은 결과를 얻을 수 있습니다. 예를 들면 다음에

... 
if (removeList.size() == 0) { 
    testFunc(3); 
} 
... 

결과 (testFunc는 매번 불린다)

... 
if (removeList.size() == 0); 
    testFunc(3); 
... 

:

time: 8 
time: 7 
time: 0 
time: 0 

심지어 낮은 계산 시간 서로 결과 독립된 두 함수 호출 결과 :

time: 6 
time: 5 
time: 0 
time: 0 
... 

초기 예제의이 특정 조합 만 오래 걸립니다. 이것은 나를 자극하고 나는 그것을 정말로 이해하고 싶습니다. 뭐가 그렇게 특별한가요?

감사합니다.

추가 :

private static int testFunc2(int test) { 
    return 5*test; 
} 

처럼, 첫 번째 예제 뭔가 다른

if (removeList.size() > 0) { 
       testFunc(times); 
} 

에 testFunc()를 변경하면 빠른 다시 당할 것이다.

+0

그리고이 테스트를 여러 번 실행했는데 다른 주문이 맞았습니까? 순서는 중요하지 않아야합니다. 다만 확인하십시오. 또한 아래 제안 된대로 nanoTime을 사용하여 동일한 결과를 얻고 있습니까? – prelic

+5

이런 종류의 마이크로 벤치 마크는 꽤 나쁜 생각입니다. 또한 JIT가 임의의 시간에 다양한 결정을 내릴 수 있고 실제로 아무 것도 실현되지 않으면 코드를 완벽하게 최적화 할 수 있음을 알 수 있습니다. –

+1

Java에서 코드 실행을 측정하려면 [System.nanoTime()] (http://docs.oracle.com/javase/6/docs/api/java/lang/System.html#nanoTime%28%29)을 사용해야합니다 . 더 정확합니다. 더 많은 토론 [이 질문에] (예 : http://stackoverflow.com/questions/351565/system-currenttimemillis-vs-system-nanotime) – paislee

답변

0

컴파일러가 매우 똑똑하기 때문에 이러한 벤치 마크는 어렵습니다. 하나의 추측 : testFunc()의 결과는 무시되므로 컴파일러가이를 완전히 최적화 할 수 있습니다. 마지막에 System.out.println(counter)을, 단지 철저를 들어,

if (testFunc(3)) 
    counter++; 

처럼 뭔가를 카운터를 추가합니다.

+0

하지만 그렇게하면 첫 번째 버전이 빨라지지만 그렇지 않습니다. –

+2

@prelic에서 제안한대로 주문할 수 있습니다. 웬일인지 JIT는 처음에는 전화를 최적화하지 않았지만 2 번째로 알아 냈습니다. – user949300

+0

나는 그것을 로컬에서 두 가지 방식으로 실행했지만 상황은 바뀌지 않았습니다. –

3

정말 놀랍습니다. 생성 된 바이트 코드는 ifleifne 인 조건부를 제외하고는 동일합니다.

-Xint으로 JIT를 끄면 결과가 훨씬 더 현명합니다. 두 번째 버전은 2 배 느립니다. 따라서 JIT 최적화와 관련이 있습니다.

나는 두 번째 케이스에서 수표를 최적화 할 수 있다고 가정하지만 어떤 이유에서 건 첫 번째 케이스에서는 그렇지 않습니다. 함수의 작동을 의미한다고해도 조건을 빠뜨리면 훨씬 빠르게 처리됩니다. 그것은 파이프 라인 노점 및 모든 것을 피합니다.

+0

것은 testFunc()에 의존합니다. 예를 들어, 첫 번째 버전의 testFunc()를 다음과 같이 교환하십시오. \t private static int testFunc2 (int test) { \t \t return 5 * test; \t} 이렇게하면 계산 시간이 단축됩니다. –

1

글쎄, Java 성능 최적화를 다룰 필요가 없기 때문에 기쁩니다. Java JDK 7 64 비트를 사용하여 직접 시도했습니다. 결과는 임의적입니다.그것은 내가 사용하고있는리스트 나 루프에 들어가기 전에 size()의 결과를 캐시한다면 아무런 차이가 없다. 또한 테스트 함수를 완전히 제거하면 거의 차이가 없습니다 (따라서 분기 예측 히트가 될 수 없습니다). 최적화 플래그는 성능을 향상 시키지만 임의적입니다.

여기서 유일하게 논리적 인 결과는 JIT 컴파일러가 때로는 진술을 최적화 할 수 있다는 것입니다 (실제로는 그렇게 어렵지는 않지만). C++과 같은 언어를 선호하는 많은 이유 중 하나는 동작이 적어도 결정적 일 때도 있습니다. 항상 Windows에서처럼 최신의 BTW

이클립스는 IDE "실행"(디버그)를 통해이 코드를 실행하는 그것에 대해 너무 많이, 콘솔에서 그것을 실행하는 것보다 10 배 느린 ...

+0

결정 론적이지만 임의의 경우 +1 +1 –

1

런타임 컴파일러가 testFunc이 상수로 계산되는 것을 알 수있을 때, 나는 그것이 속도 향상을 설명하는 루프를 평가하지 않는다고 생각한다.

조건이 removeList.size() == 0 인 경우 함수 testFunc(3)이 상수로 계산됩니다. 조건이 removeList.size() != 0 인 경우 내부 코드가 평가되지 않으므로 속도를 높일 수 없습니다.

testFunc()가 처음 호출되지 않습니다
for (int times = 0; times < maxTimes; times++) { 
      testFunc(); // Removing this call makes the code slow again! 
      if (removeList.size() != 0) { 
       testFunc(); 
      } 
     } 

private static boolean testFunc() { 
    return testFunc(3); 
} 

, 런타임 컴파일러 testFunc()는 상수로 평가 실현하지 않는, 그래서 루프를 최적화 할 수 없습니다 다음과 같이 코드를 수정할 수 있습니다.

private static int testFunc2(int test) { 
    return 5*test; 
} 

특정 기능 컴파일러 가능성 (실행 전에) - 최적화 미리하려고하지만 명백하게 파라미터의 경우는 정수로 전달 조건에서 평가하지 않는.

벤치 마크는 최적화 완료 런타임 컴파일러 외부 루프의이 반복됩니다 것을 건의

time: 107 
time: 106 
time: 0 
time: 0 
... 

같은 시간을 반환합니다. -server 플래그로 컴파일하면 아마도 모든 0이 벤치 마크에서 반환됩니다.

2

이 질문과 직접적인 관련이있는 것은 아니지만 Caliper를 사용하여 정확하게 마이크로 벤치 마크 방법입니다. 아래는 Caliper로 실행되도록 수정 된 버전의 코드입니다. 내부 루프는 VM이 ​​최적화하지 않도록 일부를 수정해야했습니다. 놀랍게도 아무 일도 일어나지 않는다는 것을 깨닫는 것이 현명합니다.

Java 코드를 벤치마킹 할 때 많은 뉘앙스가 있습니다. 과거 기록이 현재 결과에 어떤 영향을 미칠 수 있는지 등 Java Matrix Benchmark에 쓴 몇 가지 쟁점에 관해 썼습니다. Caliper를 사용하면 이러한 문제를 피할 수 있습니다.

public class PerformanceCheck extends SimpleBenchmark { 

public int timeFirstCase(int reps) { 
    List<PerformanceCheck> removeList = new LinkedList<PerformanceCheck>(); 
    removeList.add(new PerformanceCheck()); 
    int ret = 0; 

    for(int i = 0; i < reps; i++) { 
     if (removeList.size() > 0) { 
      if(testFunc(i)) 
       ret++; 
     } 
    } 

    return ret; 
} 

public int timeSecondCase(int reps) { 
    List<PerformanceCheck> removeList = new LinkedList<PerformanceCheck>(); 
    removeList.add(new PerformanceCheck()); 
    int ret = 0; 

    for(int i = 0; i < reps; i++) { 
     if (removeList.size() == 0) { 
      if(testFunc(i)) 
       ret++; 
     } 
    } 

    return ret; 
} 

private static boolean testFunc(int test) { 
    return 5 > test; 
} 

public static void main(String[] args) { 
    Runner.main(PerformanceCheck.class, args); 
} 
} 

Benchmarking issues with Java Matrix Benchmark

http://code.google.com/p/caliper/
    1. 는 OUTPUT :

      0% Scenario{vm=java, trial=0, benchmark=FirstCase} 0.60 ns; σ=0.00 ns @ 3 trials 
      50% Scenario{vm=java, trial=0, benchmark=SecondCase} 1.92 ns; σ=0.22 ns @ 10 trials 
      
      benchmark ns linear runtime 
      FirstCase 0.598 ========= 
      SecondCase 1.925 ============================== 
      
      vm: java 
      trial: 0 
      
  • +0

    나는 마이크로 벤치 마크하는 방법이 아니라 관찰 된 행동을 설명하는 방법이라고 생각합니다. 그러나 당신은 길을 따라 그것을 쳤다. 예기치 않은 방식으로 JIT를 최적화합니다. –

    +0

    예, 동의합니다. 그 코드가 설명 된 후에도 벤치 마크가 어떻게 설정 되었는가에 따라 여전히 이상한 변동이 있기 때문에 올바른 방법을 지적 할 수 있다고 생각했습니다. –

    +0

    캘리퍼스 팁을 주셔서 감사합니다. 나는 이것을 장래에 사용할 것이다. 여전히 Java가 이와 같이 작동하는 이유는 여전히 의문입니다.귀하의 예에서는 목록에 Object를 추가하여 동작을 전환했습니다. 이제'timeSecondCase'의 if 절은 false를 반환합니다. 그리고 여전히 느린 testFunc()가 호출되지 않는 버전입니다. –

    1

    시간은 반복 당 비현실적으로 빠르다.이것은 JIT가 귀하의 코드가 아무 것도하지 않고 그것을 제거했다는 것을 감지했다는 것을 의미합니다. 미묘한 변화는 JIT를 혼란스럽게 할 수 있고 코드가 아무 것도하지 않으며 그것이 시간이 걸리는 것을 결정할 수 없다.

    테스트를 약간 유용하게 수행하도록 변경하면 차이가 사라집니다.