[java] 루프 내부 또는 외부에서 변수 선언

다음은 왜 잘 작동합니까?

String str;
while (condition) {
    str = calculateStr();
    .....
}

그러나 이것은 위험하거나 부정확하다고합니다.

while (condition) {
    String str = calculateStr();
    .....
}

루프 외부에서 변수를 선언해야합니까?



답변

지역 변수의 범위는 항상 가장 작아야합니다.

당신의 예에서 I의 가정이 str되어 하지 의 외부 사용 while내부를 선언하기 때문에, 그렇지 않으면 당신은 질문을 할 수없는 것, 루프를 while반복하는 것은 옵션이 될 수없는 것입니다은 컴파일되지 것이기 때문이다.

따라서, 이후는 str되어 있지 가장 작은 가능한 범위는 루프 밖에 사용 str이다 내에 While 루프.

그래서, 대답은 단호 것을 str절대적으로 while 루프 내에서 선언되어야한다. if, no ands, no buts는 없습니다.

이 규칙을 위반할 수있는 유일한 경우는 어떤 이유로 든 모든 클럭 사이클을 코드에서 짜 내야하는 것이 매우 중요한 경우입니다. 내부 범위의 모든 반복에서 다시 인스턴스화합니다. 그러나 이것은 자바에서 문자열의 불변성으로 인해 예제에 적용되지 않습니다 : str의 새로운 인스턴스는 항상 루프의 시작 부분에 생성되며 마지막에 버려 져야합니다. 거기에서 최적화 할 가능성이 없습니다.

편집 : (아래 답변에 내 의견을 주입)

어쨌든 올바른 작업을 수행하는 모든 방법은 모든 코드를 올바르게 작성하고, 제품의 성능 요구 사항을 설정하고,이 요구 사항에 대해 최종 제품을 측정 한 후 만족하지 않으면 작업을 최적화하는 것입니다. 그리고 일반적으로 발생하는 것은 전체 코드 기반을 다룰 필요없이 프로그램의 성능 요구 사항을 충족시키는 몇 곳에서 멋지고 공식적인 알고리즘 최적화를 제공하는 방법을 찾는 것입니다. 여기저기서 클럭 사이클을 짜기 위해.


답변

그 두 가지 (유사한) 예제의 바이트 코드를 비교했습니다.

1을 보자 . 예제 :

package inside;

public class Test {
    public static void main(String[] args) {
        while(true){
            String str = String.valueOf(System.currentTimeMillis());
            System.out.println(str);
        }
    }
}

javac Test.java, javap -c Test당신은 얻을 것이다 :

public class inside.Test extends java.lang.Object{
public inside.Test();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   invokestatic    #2; //Method java/lang/System.currentTimeMillis:()J
   3:   invokestatic    #3; //Method java/lang/String.valueOf:(J)Ljava/lang/String;
   6:   astore_1
   7:   getstatic       #4; //Field java/lang/System.out:Ljava/io/PrintStream;
   10:  aload_1
   11:  invokevirtual   #5; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   14:  goto    0

}

2를 보자 . 예제 :

package outside;

public class Test {
    public static void main(String[] args) {
        String str;
        while(true){
            str =  String.valueOf(System.currentTimeMillis());
            System.out.println(str);
        }
    }
}

javac Test.java, javap -c Test당신은 얻을 것이다 :

public class outside.Test extends java.lang.Object{
public outside.Test();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   invokestatic    #2; //Method java/lang/System.currentTimeMillis:()J
   3:   invokestatic    #3; //Method java/lang/String.valueOf:(J)Ljava/lang/String;
   6:   astore_1
   7:   getstatic       #4; //Field java/lang/System.out:Ljava/io/PrintStream;
   10:  aload_1
   11:  invokevirtual   #5; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   14:  goto    0

}

관찰 결과는이 두 예 사이에 차이없음을 보여줍니다 . JVM 사양의 결과입니다 …

그러나 최상의 코딩 방법의 이름으로 가능한 가장 작은 범위에서 변수를 선언하는 것이 좋습니다 (이 예제에서는 변수가 사용되는 유일한 위치이므로 루프 내부에 있음).


답변

가장 작은 범위 에서 객체를 선언 하면 가독성이 향상 됩니다.

오늘날의 컴파일러에는 성능이 중요하지 않습니다. (이 시나리오에서는)
유지 관리 측면에서 두 번째 옵션이 더 좋습니다.
가능한 가장 좁은 범위에서 같은 장소에서 변수를 선언하고 초기화하십시오.

Donald Ervin Knuth 는 다음과 같이 말했습니다.

“우리는 시간의 97 % 정도의 작은 효율성을 잊어야합니다. 조기 최적화는 모든 악의 근원입니다”

즉, 프로그래머가 성능 고려 사항 이 코드 의 설계 에 영향을 미치는 상황 . 입니다 디자인이 발생할 수 있습니다 깨끗하지 가 있었을으로 또는 코드가 있기 때문에, 잘못된 코드 복잡 의해 최적화 및 프로그래머가 산만 최적화 .


답변

str외부 루프도 사용하려면 ; 밖에 선언하십시오. 그렇지 않으면 두 번째 버전이 좋습니다.


답변

업데이트 된 답변으로 건너 뛰십시오 …

성능에 관심이있는 사람들은 System.out을 꺼내고 루프를 1 바이트로 제한하십시오. 이중 (테스트 1/2) 및 문자열 (3/4)을 사용하여 경과 시간 (밀리 초)은 Windows 7 Professional 64 비트 및 JDK-1.7.0_21에서 아래에 제공됩니다. 바이트 코드 (test1 및 test2에 대해 아래에 제공됨)는 동일하지 않습니다. 나는 가변적이고 비교적 복잡한 객체로 테스트하기에는 너무 게으르다.

더블

테스트 1 소요 : 2710 밀리 초

Test2 소요 : 2790 msecs

문자열 (테스트에서 double을 문자열로 대체하십시오)

Test3 소요 : 1200msec

Test4 소요 : 3000msec

바이트 코드 컴파일 및 가져 오기

javac.exe LocalTest1.java

javap.exe -c LocalTest1 > LocalTest1.bc


public class LocalTest1 {

    public static void main(String[] args) throws Exception {
        long start = System.currentTimeMillis();
        double test;
        for (double i = 0; i < 1000000000; i++) {
            test = i;
        }
        long finish = System.currentTimeMillis();
        System.out.println("Test1 Took: " + (finish - start) + " msecs");
    }

}

public class LocalTest2 {

    public static void main(String[] args) throws Exception {
        long start = System.currentTimeMillis();
        for (double i = 0; i < 1000000000; i++) {
            double test = i;
        }
        long finish = System.currentTimeMillis();
        System.out.println("Test1 Took: " + (finish - start) + " msecs");
    }
}


Compiled from "LocalTest1.java"
public class LocalTest1 {
  public LocalTest1();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]) throws java.lang.Exception;
    Code:
       0: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J
       3: lstore_1
       4: dconst_0
       5: dstore        5
       7: dload         5
       9: ldc2_w        #3                  // double 1.0E9d
      12: dcmpg
      13: ifge          28
      16: dload         5
      18: dstore_3
      19: dload         5
      21: dconst_1
      22: dadd
      23: dstore        5
      25: goto          7
      28: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J
      31: lstore        5
      33: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      36: new           #6                  // class java/lang/StringBuilder
      39: dup
      40: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V
      43: ldc           #8                  // String Test1 Took:
      45: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      48: lload         5
      50: lload_1
      51: lsub
      52: invokevirtual #10                 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
      55: ldc           #11                 // String  msecs
      57: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      60: invokevirtual #12                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      63: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      66: return
}


Compiled from "LocalTest2.java"
public class LocalTest2 {
  public LocalTest2();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]) throws java.lang.Exception;
    Code:
       0: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J
       3: lstore_1
       4: dconst_0
       5: dstore_3
       6: dload_3
       7: ldc2_w        #3                  // double 1.0E9d
      10: dcmpg
      11: ifge          24
      14: dload_3
      15: dstore        5
      17: dload_3
      18: dconst_1
      19: dadd
      20: dstore_3
      21: goto          6
      24: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J
      27: lstore_3
      28: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      31: new           #6                  // class java/lang/StringBuilder
      34: dup
      35: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V
      38: ldc           #8                  // String Test1 Took:
      40: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      43: lload_3
      44: lload_1
      45: lsub
      46: invokevirtual #10                 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
      49: ldc           #11                 // String  msecs
      51: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      54: invokevirtual #12                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      57: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      60: return
}

업데이트 된 답변

모든 JVM 최적화와 성능을 비교하는 것은 쉽지 않습니다. 그러나 다소 가능합니다. Google Caliper의 향상된 테스트 및 상세 결과

  1. 블로그에 대한 일부 세부 정보 : 루프 내부 또는 루프 전에 변수를 선언해야합니까?
  2. GitHub 리포지토리 : https://github.com/gunduru/jvdt
  3. 이중 케이스 및 100M 루프 (및 모든 JVM 세부 사항 예)에 대한 테스트 결과 : https://microbenchmarks.appspot.com/runs/b1cef8d1-0e2c-4120-be61-a99faff625b4

1,759.209 전에 선언 내부 2,242.308에 선언

  • 1,759.209ns 이전에 선언 됨
  • 2,242.308 ns 이내 선언

이중 선언을위한 부분 테스트 코드

위 코드와 동일하지 않습니다. 더미 루프 JVM 만 코딩하면 JVM이 건너 뛰므로 적어도 무언가를 할당하고 반환해야합니다. 이는 Caliper 설명서에서도 권장됩니다.

@Param int size; // Set automatically by framework, provided in the Main
/**
* Variable is declared inside the loop.
*
* @param reps
* @return
*/
public double timeDeclaredInside(int reps) {
    /* Dummy variable needed to workaround smart JVM */
    double dummy = 0;

    /* Test loop */
    for (double i = 0; i <= size; i++) {

        /* Declaration and assignment */
        double test = i;

        /* Dummy assignment to fake JVM */
        if(i == size) {
            dummy = test;
        }
    }
    return dummy;
}

/**
* Variable is declared before the loop.
*
* @param reps
* @return
*/
public double timeDeclaredBefore(int reps) {

    /* Dummy variable needed to workaround smart JVM */
    double dummy = 0;

    /* Actual test variable */
    double test = 0;

    /* Test loop */
    for (double i = 0; i <= size; i++) {

        /* Assignment */
        test = i;

        /* Not actually needed here, but we need consistent performance results */
        if(i == size) {
            dummy = test;
        }
    }
    return dummy;
}

요약 : 선언 된 BeforeBefore는 더 작은 성능을 나타내며 가장 작은 범위 원칙에 위배됩니다. JVM이 실제로이 작업을 수행해야합니다.


답변

이 문제에 대한 한 가지 해결책은 while 루프를 캡슐화하는 변수 범위를 제공하는 것입니다.

{
  // all tmp loop variables here ....
  // ....
  String str;
  while(condition){
      str = calculateStr();
      .....
  }
}

외부 범위가 끝나면 자동으로 참조 해제됩니다.


답변

내부에서는 변수의 범위가 적을수록 더 좋습니다.