답변:
당신의 예에서 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 사양의 결과입니다 ...
그러나 최상의 코딩 방법의 이름으로 가능한 가장 작은 범위에서 변수를 선언하는 것이 좋습니다 (이 예제에서는 변수가 사용되는 유일한 위치이므로 루프 내부에 있음).
final
연인 선언 str
으로 final
에서 inside
패키지의 경우 도 차이가 없습니다 =)
가장 작은 범위 에서 객체를 선언 하면 가독성이 향상 됩니다.
오늘날의 컴파일러에는 성능이 중요하지 않습니다. (이 시나리오에서는)
유지 관리 측면에서 두 번째 옵션이 더 좋습니다.
가능한 가장 좁은 범위에서 같은 장소에서 변수를 선언하고 초기화하십시오.
Donald Ervin Knuth 는 다음과 같이 말했습니다.
"우리는 시간의 97 % 정도의 작은 효율성을 잊어야합니다. 조기 최적화는 모든 악의 근원입니다"
즉, 프로그래머가 성능 고려 사항 이 코드 의 설계 에 영향을 미치는 상황 . 입니다 디자인이 발생할 수 있습니다 깨끗하지 가 있었을으로 또는 코드가 있기 때문에, 잘못된 코드 복잡 의해 최적화 및 프로그래머가 산만 최적화 .
업데이트 된 답변으로 건너 뛰십시오 ...
성능에 관심이있는 사람들은 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의 향상된 테스트 및 상세 결과
위 코드와 동일하지 않습니다. 더미 루프 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이 실제로이 작업을 수행해야합니다.
str
after while 루프 (범위 관련) 를 사용할 필요가 없으면 두 번째 조건 즉
while(condition){
String str = calculateStr();
.....
}
스택에 객체를 정의하면 condition
true 인 경우에만 더 좋습니다 . 즉, 필요한 경우 사용 하십시오
귀하의 질문에 대답하는 가장 좋은 자료는 다음 게시물입니다.
루프 전 또는 루프에서 변수 선언의 차이점은 무엇입니까?
내 이해에 따르면이 것은 언어에 달려 있습니다. IIRC Java는 이것을 최적화하므로 차이는 없지만 JavaScript (예 :)는 루프에서 매번 전체 메모리 할당을 수행합니다 .Java에서는 특히 프로파일 링이 완료되면 두 번째가 더 빨리 실행될 것이라고 생각합니다.
많은 사람들이 지적했듯이
String str;
while(condition){
str = calculateStr();
.....
}
이다 NOT 이보다 더 :
while(condition){
String str = calculateStr();
.....
}
따라서 재사용하지 않는 경우 변수를 범위 밖에서 선언하지 마십시오.
변수는 가능한 한 사용되는 곳에 가깝게 선언해야합니다.
RAII (Resource Acquisition Is Initialization)가 쉬워집니다.
변수의 범위를 엄격하게 유지합니다. 이를 통해 옵티마이 저가 더 잘 작동합니다.
이 str
변수는 코드 아래에서 실행 된 후에도 사용 가능하고 메모리의 일부 공간을 예약합니다.
String str;
while(condition){
str = calculateStr();
.....
}
str
변수는 사용할 수 없습니다 또한 메모리에 할당 된 발표 될 예정이다 str
코드 아래에 변수입니다.
while(condition){
String str = calculateStr();
.....
}
두 번째 것을 확실히 따르면 시스템 메모리가 줄어들고 성능이 향상됩니다.
사실, 위에서 언급 한 질문은 프로그래밍 문제입니다. 코드를 어떻게 프로그래밍 하시겠습니까? 어디에서 'STR'에 액세스해야합니까? 로컬 변수로 로컬로 사용되는 변수를 선언하지 않아도됩니다. 내가 믿는 프로그래밍의 기초
이 질문의 거의 모든 사람에 대한 경고 : 다음은 루프 내에서 Java 7을 사용하는 컴퓨터에서 쉽게 200 배 느릴 수있는 샘플 코드입니다 (메모리 소비도 약간 다릅니다). 그러나 그것은 범위뿐만 아니라 할당에 관한 것입니다.
public class Test
{
private final static int STUFF_SIZE = 512;
private final static long LOOP = 10000000l;
private static class Foo
{
private long[] bigStuff = new long[STUFF_SIZE];
public Foo(long value)
{
setValue(value);
}
public void setValue(long value)
{
// Putting value in a random place.
bigStuff[(int) (value % STUFF_SIZE)] = value;
}
public long getValue()
{
// Retrieving whatever value.
return bigStuff[STUFF_SIZE / 2];
}
}
public static long test1()
{
long total = 0;
for (long i = 0; i < LOOP; i++)
{
Foo foo = new Foo(i);
total += foo.getValue();
}
return total;
}
public static long test2()
{
long total = 0;
Foo foo = new Foo(0);
for (long i = 0; i < LOOP; i++)
{
foo.setValue(i);
total += foo.getValue();
}
return total;
}
public static void main(String[] args)
{
long start;
start = System.currentTimeMillis();
test1();
System.out.println(System.currentTimeMillis() - start);
start = System.currentTimeMillis();
test2();
System.out.println(System.currentTimeMillis() - start);
}
}
결론 : 지역 변수의 크기에 따라 큰 변수는 아니지만 차이가 클 수 있습니다.
때로는 외부 또는 내부 루프가 중요합니다.
bigStuff[(int) (value % STUFF_SIZE)] = value;
(2147483649L의 값을보십시오)
메소드가 널을 리턴 하면 str에서 메소드를 호출하려고 할 위험이 NullPointerException
있습니다 .calculateStr()
보다 일반적으로, 널값을 갖는 변수를 사용하지 마십시오 . 그건 그렇고, 클래스 속성에 강합니다.
NullPointerException.
에 시도이 코드 경우 return str;
는 컴파일 오류가 발생하는 것입니다.