2012-02-17 2 views
7

는 나는 그것이 for loop의 인덱스 변수를 수정하는 나쁜 습관이다 그 학교에 들었다JVM 옵션이

for(int i = 0 ; i < limit ; i++){ 
    if(something){ 
     i+=2;  //bad 
    } 
    if(something){ 
     limit+=2;  //bad 
    } 
} 

인수했다을 일부 컴파일러 그 최적화는 루프을 최적화 할 수 있으며 각 루프에서 인덱스와 바운드를 다시 계산하지 않습니다.

나는 java에서 약간의 테스트를했으며, 기본적으로 인덱스와 바운드가 매번 다시 계산되는 것으로 보입니다.

JVM HotSpot에서 이런 종류의 기능을 활성화 할 수 있는지 궁금합니다. 작성하지 않고도

for(int i = 0 ; i < foo.getLength() ; i++){ } 

: 예를 들어

루프 이런 종류의 최적화

int length = foo.getLength() 
for(int i = 0 ; i < length ; i++){ } 

그것은 내가 시도하고 improvments을보고 궁금해서 그냥 예입니다.

편집

피터 Lawrey에 따르면 왜이 간단한 예에서 JVM이 getLenght() 방법을 인라인하지 않는 대답? :

public static void main(String[] args) { 
    Too t = new Too(); 
    for(int j=0; j<t.getLength();j++){ 
    } 
} 


class Too { 

    int l = 10; 
    public Too() { 
    } 
    public int getLength(){ 
     //System.out.println("test"); 
     return l; 
    } 
} 

"test"출력은 10 회 인쇄됩니다.

나는 이런 종류의 실행을 최적화하는 것이 좋을 것이라고 생각한다.

편집 2 : 내가 오해를 만든 것 같은데 ...

은 내가 println를 제거해야하고 실제로 프로파일 러는 방법 getLength()은이 경우에도 한 번 호출되지 않는다는 것을 말해.

+2

* 인라인이 무엇인지 오해하는 것 같습니다. 모든 컴파일러 최적화의 101은 생성 된 코드가 JLS가 요구하는 동작과 기능적으로 동일 할 수 있다는 것입니다. 즉, 함수 호출을 인라인 할 수는 있지만'println()'호출을 제거 할 수는 없습니다. 또한 그러한 컴파일러 최적화에 대해 정말로 걱정하지 않아야합니다. 그렇지 않으면 적어도 이런 종류의 코드를 테스트하는 방법을 충분히 이해해야합니다. – Voo

+0

좋아, 나는 그걸 몰랐고, 나는 아주 새롭고 여전히 많은 것을 배웠다. 이런 종류의 "진보?" 지식은 (나의) 학교에서 unteach에 머물러 있기 때문에 혼자서 이해하려고 노력하며 종종 실수를합니다. : –

+0

무료 였고 실제로 코드를 자세하게 살펴 보았습니다 - 곧 귀하의 질문에 대한 답변을 드리겠습니다 : JIT는'getLength()'println() 문을 거기에 넣었는지 아닌지를 독립적으로 정의 할 수 있습니다. 세부 사항을 원한다면 아래에 짧은 요약을 게시;) – Voo

답변

11

나는 자바에서 몇 가지 테스트를했고 기본적으로 인덱스와 바운드가 매번 다시 계산되는 것으로 보입니다. Java 언어 사양에 따라

이 :

for(int i = 0 ; i < foo.getLength() ; i++){ } 

getLength() 각 루프 반복에서 호출되는 것을 의미한다. 자바 컴파일러는 허용getLength() 루프를 호출하면 루프를 호출 할 수 있습니다. 은 관찰 가능한 동작을 변경하지 않습니다. (예를 들어, getLength()이 매번 동일한 변수에서 동일한 값을 반환했다면 JIT 컴파일러가 호출을 인라인 할 수있는 좋은 기회가 있으며 일 경우은 호이스트 최적화를 수행 할 수 있다고 추측합니다. 그러나 getLength()에 동시 또는 동기화 된 콜렉션의 길이는 최적화가 허용 될 가능성이 희박합니다 ... 다른 스레드의 잠재적 인 작업으로 인해 가능합니다.)

이렇게 컴파일러는이 수행 할 수있는 것이므로 입니다.

JVM 핫스팟에서 이러한 종류의 기능을 활성화 할 수 있는지 궁금합니다.

간단한 대답은 당신이 알려주는 컴파일러 스위치를 제안 할 것으로 보인다 번호

입니다 /를 JLS 규칙을 무시하는 컴파일러를 할 수 있습니다. 그런 스위치는 없습니다. 이러한 스위치는 잘못된 IDEA입니다. 올바른/유효한/작동하는 프로그램을 중단시키는 것은 책임이 있습니다. 이것을 고려하십시오 :

컴파일러는 루프 밖으로 getLength(arg) 전화를 이동하는 것이 허용 된 경우
class Test { 
    int count; 

    int test(String[] arg) { 
     for (int i = 0; i < getLength(arg); i++) { 
      // ... 
     } 
     return count; 
    } 

    int getLength(String[] arg) { 
     count++; 
     return arg.length; 
    } 
} 

, 그것은 메서드가 호출 횟수를 변경, 따라서 test 메소드에 의해 반환되는 값을 변경합니다.

올바르게 작성된 Java 프로그램의 동작을 변경하는 Java 최적화가 올바르지 않은 최적화입니다. (멀티 스레딩은 물을 흐리게하는 경향이 있습니다 .JLS, 특히 메모리 모델 규칙은 컴파일러가 최적화를 수행하여 서로 다른 스레드가 응용 프로그램 상태의 일관되지 않은 버전을 볼 수있게합니다 ... 동기화하지 않으면 제대로 행동의 결과 것은 개발자의 관점에서 올바르지 않습니다.하지만 진짜 문제는 응용 프로그램이 아닌 컴파일러입니다.) 그런데


하는 더 설득력 이유는 당신이해야하지 루프 본문에서 루프 변수를 변경하면 코드를 이해하기가 어려워집니다.

+0

정보 주셔서 감사합니다. 당신의 대답의 두 번째 부분에 대해서는, 나는 그것이 가능하다면 작동하는 programm로이 기능을 가능하게하는 것은 나쁜 생각이라고 동의한다. 그러나이 규칙을 알고있는 프로젝트를 시작하면 성능이 향상 될 수 있다고 생각합니다. –

+0

Java의 조각이 다른 것보다 빠르게 실행되는지 여부를 실제로 알 수있는 유일한 방법은 Java 용으로 특별히 설계된 벤치마킹 도구를 사용하는 것입니다.이 도구는 JVM 및 모든 것을 예열합니다. –

+0

@LouisWasserman netbeans 프로파일 러를 사용했습니다. 당신은'println'에 대해 강경했습니다. 제 편집문을보십시오. –

13

foo.getLength()가 수행하는 작업에 따라 다릅니다. 그것이 인라인 될 수 있다면, 그것은 사실 똑같은 일이 될 수 있습니다. 인라인 될 수없는 경우 JVM은 결과가 같은지 여부를 판별 할 수 없습니다.

쓸모있는 라이너 하나만 쓸 수 있습니다.

for(int i = 0, length = foo.getLength(); i < length; i++){ } 

편집 : 아무 것도 가치가 없습니다.

  • 메서드 및 루프는 일반적으로 10,000 번 호출 될 때까지 최적화되지 않습니다.
  • 오버 헤드를 줄이기 위해 프로파일 러 서브 샘플 호출. 그들은 10 또는 100 또는 그 이상을 계산할 수 있으므로 사소한 예가 나타나지 않을 수 있습니다.
+0

팁 주셔서 감사합니다! 내 편집을 한번보세요. –

4

이렇게하지 않는 주된 이유는 코드를 이해하고 유지하는 것이 훨씬 어렵 기 때문입니다.

JVM이 최적화 되더라도 프로그램의 정확성을 저해하지 않습니다. 인덱스가 루프 내에서 수정되어 최적화를 수행 할 수없는 경우 인덱스를 최적화하지 않습니다. 나는 그러한 최적화가 있는지 없는지 자바 테스트가 어떻게 보여줄 수 있는지를 보지 못한다.

어쨌든 핫스팟은 많은 것들을 최적화합니다. 두 번째 예제는 핫스팟이 행복하게 할 수있는 일종의 명시 적 최적화입니다.

+0

HotSpot은 신비한 블랙 박스입니다.하지만 실제로 최적화를 할 수 있을까요? foo가 String이 아닌 것으로 가정하고 getLength()가 변경 될 수 있도록 getLength()가 변경되는 foo를 수정하는 다중 스레드는 어떨까요? 나는. 변경 가능한 콜렉션을 가정하고 대신 getSize()를 사용하십시오. –

+0

두 번째 예제에서 JVM 핫스팟이 최적화를 수행하는지 모르겠습니다. 'foo.getLength()'가 10을 리턴하면, getLength() 메소드가 10 번 실행된다고 가정 해 봅시다. –

+1

최적화로 인해 프로그램의 _behavior_에 실제 차이가 나타나지 않습니다. 'System.out.println ("test")'줄을 제거했다면 JVM이 최적화를 할 가능성이 있습니다. –

2

더 많은 추론을하기 전에 필드 액세스가 인라인되지 않습니다. 어쩌면 우리가 네가 찾고있는 것을 알고 있다면 네가 보여 주어야한다. (실제로 Java에서는 사소한 일이다.) 필드 액세스가 잘 인라인되어있다.

먼저 JIT 작동 방식에 대한 기본적인 이해가 필요합니다. 실제로 한 가지 대답으로는 그럴 수 없습니다. 함수가 충분히 자주 호출 된 후 JIT에만 작동 말을 충분 (> 일반적으로 10K)

그래서 우리는 실제 테스트 물건에 대한 다음 코드를 사용합니다

public class Test { 
    private int length; 

    public Test() { 
     length = 10000; 
    } 

    public static void main(String[] args) { 
     for (int i = 0; i < 14000; i++) { 
      foo(); 
     } 
    } 

    public static void foo() { 
     Test bar = new Test(); 
     int sum = 0; 
     for (int i = 0; i < bar.getLength(); i++) { 
      sum += i; 
     } 
     System.out.println(sum); 
    } 

    public int getLength() { 
     System.out.print("_"); 
     return length; 
    }  
} 

이제 우리는이 코드를 실행 컴파일을 신성 긴 출력 결과 java.exe -XX:+UnlockDiagnosticVMOptions -XX:CompileCommand=print,*Test.foo Test >Test.txt와 그것을하지만 흥미로운 부분은 다음과 같습니다

우리가 실제로 실행하고있는 내부 루프가
0x023de0e7: mov %esi,0x24(%esp) 
    0x023de0eb: mov %edi,0x28(%esp) 
    0x023de0ef: mov $0x38fba220,%edx ; {oop(a 'java/lang/Class' = 'java/lang/System')} 
    0x023de0f4: mov 0x6c(%edx),%ecx ;*getstatic out 
             ; - Test::[email protected] (line 24) 
             ; - Test::[email protected] (line 17) 
    0x023de0f7: cmp (%ecx),%eax  ;*invokevirtual print 
             ; - Test::[email protected] (line 24) 
             ; - Test::[email protected] (line 17) 
             ; implicit exception: dispatches to 0x023de29b 
    0x023de0f9: mov $0x3900e9d0,%edx ;*invokespecial write 
             ; - java.io.PrintStream::[email protected] 
             ; - Test::[email protected] (line 24) 
             ; - Test::[email protected] (line 17) 
             ; {oop("_")} 
    0x023de0fe: nop  
    0x023de0ff: call 0x0238d1c0   ; OopMap{[32]=Oop off=132} 
             ;*invokespecial write 
             ; - java.io.PrintStream::[email protected] 
             ; - Test::[email protected] (line 24) 
             ; - Test::[email protected] (line 17) 
             ; {optimized virtual_call} 
    0x023de104: mov 0x20(%esp),%eax 
    0x023de108: mov 0x8(%eax),%ecx  ;*getfield length 
             ; - Test::[email protected] (line 25) 
             ; - Test::[email protected] (line 17) 
    0x023de10b: mov 0x24(%esp),%esi 
    0x023de10f: cmp %ecx,%esi 
    0x023de111: jl  0x023de0d8   ;*if_icmpge 
             ; - Test::[email protected] (line 17) 

. 다음은 0x023de108: mov 0x8(%eax),%ecx이 레지스터의 길이 값을로드한다는 점에 유의하십시오. 위의 내용은 System.out 호출을위한 것입니다 (더 복잡하기 때문에 제거 했으므로 하나 이상의 사람이 인라인을 방해한다고 생각하기 때문에 거기에 남겨 뒀다). x86 어셈블리에 맞지 않더라도 분명히 알 수 있습니다. 네이티브 쓰기 호출을 제외한 어디에도 호출 명령어가 없습니다.

+0

우분투에 Unfortunnaletely 나는 "_"가득 파일을 얻을. 나는 어떤 mov 줄도 보지 않는다 ... –

+0

@alain 필요한 플러그인이 설치되어 있는지 확인하고, 테스트 문구를 놓치지 않거나 시스템에 null 스트림을 설치하지 않도록 테스트 문을 더 잘 제거한다. out) – Voo

+0

관심있는 사람이 있으면 해결책을 찾았습니다! -XX 사용 방법 : + UnlockDiagnosticVMOptions -XX : JVM 핫스팟으로 CompileCommand = print 옵션 –