문제

저는 늘 궁금해하는 경우,일반적으로 선언 버리기 전에 변수 루프 반대로 반복적으로 루프 안에서 어떠(performance)차이가 있나요?A (아주 무의미) 예제에서 Java:

a) 선언을 하기 전에 반복:

double intermediateResult;
for(int i=0; i < 1000; i++){
    intermediateResult = i;
    System.out.println(intermediateResult);
}

b) 선언(반복적으로)의 내부 반복:

for(int i=0; i < 1000; i++){
    double intermediateResult = i;
    System.out.println(intermediateResult);
}

어느 것이 더 낫다는 것을, ab?

내가 의심되는 반복되는 변수 선언(예제 b)성상 오버헤드 이론, 지만,컴파일러는 충분히 똑똑하도록 그것은 중요하지 않습니다.예 b 는 장점이 있고 제한 범위의 가변이 어디에 사용됩니다.여전히,나는 경향이 있 코드에 따라 예제 a.

편집: 나는에서 특히 관심 Java 경우입니다.

도움이 되었습니까?

해결책

어떤게 더 좋아, 또는 ?

성능 관점에서 볼 때 측정해야합니다. (내 생각에, 차이를 측정 할 수 있다면 컴파일러가 그리 좋지 않습니다).

유지 보수 관점에서 더 나은. 가능한 가장 좁은 범위에서 같은 장소에서 변수를 선언하고 초기화하십시오. 선언과 초기화 사이에 틈새 구멍을 남기지 말고 필요하지 않은 네임 스페이스를 오염시키지 마십시오.

다른 팁

글쎄, 나는 당신의 A와 B 예제를 각각 20 배, 1 억 번 반복했습니다. (JVM -1.5.0)

A : 평균 실행 시간 : .074 초

B : 평균 실행 시간 : .067 초

놀랍게도 B는 약간 더 빨랐습니다. 컴퓨터가 이제 정확하게 측정 할 수 있는지 말하기는 어렵습니다. 나는 그것을 길로 코딩 할 것이지만 실제로는 중요하지 않다고 말할 것입니다.

언어와 정확한 용도에 따라 다릅니다. 예를 들어, C# 1에서는 아무런 차이가 없었습니다. C# 2에서, 로컬 변수가 익명 방법 (또는 C# 3의 Lambda 표현)으로 캡처되면 매우 중요한 차이를 만들 수 있습니다.

예시:

using System;
using System.Collections.Generic;

class Test
{
    static void Main()
    {
        List<Action> actions = new List<Action>();

        int outer;
        for (int i=0; i < 10; i++)
        {
            outer = i;
            int inner = i;
            actions.Add(() => Console.WriteLine("Inner={0}, Outer={1}", inner, outer));
        }

        foreach (Action action in actions)
        {
            action();
        }
    }
}

산출:

Inner=0, Outer=9
Inner=1, Outer=9
Inner=2, Outer=9
Inner=3, Outer=9
Inner=4, Outer=9
Inner=5, Outer=9
Inner=6, Outer=9
Inner=7, Outer=9
Inner=8, Outer=9
Inner=9, Outer=9

차이점은 모든 동작이 동일하게 캡처한다는 것입니다. outer 변수이지만 각각 자체 별도가 있습니다 inner 변하기 쉬운.

다음은 내가 .NET에서 작성하고 편집 한 것입니다.

double r0;
for (int i = 0; i < 1000; i++) {
    r0 = i*i;
    Console.WriteLine(r0);
}

for (int j = 0; j < 1000; j++) {
    double r1 = j*j;
    Console.WriteLine(r1);
}

이것이 내가 얻는 것입니다 .NET 리플렉터 언제 코드로 다시 렌더링됩니다.

for (int i = 0; i < 0x3e8; i++)
{
    double r0 = i * i;
    Console.WriteLine(r0);
}
for (int j = 0; j < 0x3e8; j++)
{
    double r1 = j * j;
    Console.WriteLine(r1);
}

따라서 컴파일 후 둘 다 정확히 동일하게 보입니다. 관리 언어에서 코드는 CL/바이트 코드로 변환되며 실행시 이점은 기계 언어로 변환됩니다. 따라서 머신 언어에서는 스택에 더블이 생성되지 않을 수 있습니다. 코드가 임시 변수라는 것을 반영함에 따라 레지스터 일 수 있습니다. WriteLine 기능. 루프에 대한 전체 세트 최적화 규칙이 있습니다. 따라서 평범한 사람은 특히 관리되는 언어에서 그것에 대해 걱정해서는 안됩니다. 예를 들어 코드 관리를 최적화 할 수있는 경우가 있습니다. 예를 들어, 그냥 사용하여 많은 문자열을 연결 해야하는 경우 string a; a+=anotherstring[i] 사용 대 사용 StringBuilder. 둘 사이의 성능에는 큰 차이가 있습니다. 컴파일러가 코드를 최적화 할 수없는 경우가 많이 있습니다. 더 큰 범위에서 의도 된 내용을 알 수 없기 때문입니다. 그러나 그것은 당신을 위해 기본적인 것들을 거의 최적화 할 수 있습니다.

이것은 vb.net의 gotcha입니다. 시각적 기본 결과는이 예에서 변수를 다시 이용하지 않습니다.

For i as Integer = 1 to 100
    Dim j as Integer
    Console.WriteLine(j)
    j = i
Next

' Output: 0 1 2 3 4...

이것은 처음으로 0 인쇄됩니다 (시각적 기본 변수는 선언 될 때 기본값이 있습니다!). i 그 후마다.

추가하면 a = 0, 그러나 기대할 수있는 것을 얻을 수 있습니다.

For i as Integer = 1 to 100
    Dim j as Integer = 0
    Console.WriteLine(j)
    j = i
Next

'Output: 0 0 0 0 0...

간단한 테스트를했습니다.

int b;
for (int i = 0; i < 10; i++) {
    b = i;
}

vs

for (int i = 0; i < 10; i++) {
    int b = i;
}

이 코드를 GCC -5.2.0으로 컴파일했습니다. 그런 다음이 두 코드의 Main ()을 분해했는데 결과입니다.

1º:

   0x00000000004004b6 <+0>:     push   rbp
   0x00000000004004b7 <+1>:     mov    rbp,rsp
   0x00000000004004ba <+4>:     mov    DWORD PTR [rbp-0x4],0x0
   0x00000000004004c1 <+11>:    jmp    0x4004cd <main+23>
   0x00000000004004c3 <+13>:    mov    eax,DWORD PTR [rbp-0x4]
   0x00000000004004c6 <+16>:    mov    DWORD PTR [rbp-0x8],eax
   0x00000000004004c9 <+19>:    add    DWORD PTR [rbp-0x4],0x1
   0x00000000004004cd <+23>:    cmp    DWORD PTR [rbp-0x4],0x9
   0x00000000004004d1 <+27>:    jle    0x4004c3 <main+13>
   0x00000000004004d3 <+29>:    mov    eax,0x0
   0x00000000004004d8 <+34>:    pop    rbp
   0x00000000004004d9 <+35>:    ret

vs

   0x00000000004004b6 <+0>: push   rbp
   0x00000000004004b7 <+1>: mov    rbp,rsp
   0x00000000004004ba <+4>: mov    DWORD PTR [rbp-0x4],0x0
   0x00000000004004c1 <+11>:    jmp    0x4004cd <main+23>
   0x00000000004004c3 <+13>:    mov    eax,DWORD PTR [rbp-0x4]
   0x00000000004004c6 <+16>:    mov    DWORD PTR [rbp-0x8],eax
   0x00000000004004c9 <+19>:    add    DWORD PTR [rbp-0x4],0x1
   0x00000000004004cd <+23>:    cmp    DWORD PTR [rbp-0x4],0x9
   0x00000000004004d1 <+27>:    jle    0x4004c3 <main+13>
   0x00000000004004d3 <+29>:    mov    eax,0x0
   0x00000000004004d8 <+34>:    pop    rbp
   0x00000000004004d9 <+35>:    ret 

ASM 결과와 동일합니다. 두 코드가 같은 것을 생산한다는 증거가 아닙니까?

난 항상 사용하여(에 의존하기보다는 컴파일러)및 수 있습도 다시 작성하기:

for(int i=0, double intermediateResult=0; i<1000; i++){
    intermediateResult = i;
    System.out.println(intermediateResult);
}

이것은 여전히 제한 intermediateResult 루프의 범위,그러나지 않도록 다시 동안 각 반복.

언어 의존적입니다 - IIRC C#이 이것을 최적화하므로 차이가 없지만 JavaScript (예 : JavaScript)는 매번 전체 메모리 할당을 수행합니다.

제 생각에는 B가 더 나은 구조입니다. A에서는 루프가 완료된 후 중개자의 마지막 값이 주변에 있습니다.

편집 : 이것은 값 유형과 많은 차이를 만들지 않지만 참조 유형은 다소 무게가 될 수 있습니다. 개인적으로, 나는 정리를 위해 가능한 한 빨리 변수를 해석하는 것을 좋아하고 B는 당신을 위해 그렇게합니다.

몇몇 컴파일러가 동일한 코드가되도록 최적화 할 수 있지만 확실히 전부는 아닙니다. 그래서 나는 당신이 전자와 더 나은 말을한다고 말하고 싶습니다. 후자의 유일한 이유는 선언 된 변수가 사용되도록하려는 경우 루프 내에서.

일반적으로 변수를 가장 내면의 가능한 범위로 선언합니다. 따라서 루프 외부에서 중개자를 사용하지 않는다면 B와 함께 갈 것입니다.

동료는 첫 번째 양식을 선호하여 선언을 재사용하는 것을 선호하는 최적화라고 말합니다.

나는 두 번째를 선호합니다 (그리고 내 동료를 설득하려고 노력하십시오! ;-)).

  • 변수의 범위를 필요한 위치로 줄입니다. 이는 좋은 일입니다.
  • Java는 성능에 큰 차이를 만들 수 없을 정도로 최적화합니다. IIRC, 아마도 두 번째 형태는 훨씬 빠릅니다.

어쨌든, 그것은 컴파일러 및/또는 JVM의 품질에 의존하는 조기 최적화 범주에 속합니다.

Lambda 등의 변수를 사용하는 경우 C#에는 차이가 있지만 일반적으로 컴파일러는 기본적으로 동일한 작업을 수행합니다. 변수가 루프 내에서만 사용된다고 가정합니다.

기본적으로 동일하다는 점을 감안할 때 : 버전 B는 리더에게 변수가 루프 후에 사용할 수없고 할 수 없다는 것을 독자에게 훨씬 더 분명하게 만듭니다. 또한, 버전 B는 훨씬 더 쉽게 리팩토링됩니다. 버전 a에서 루프 본체를 자체 방법으로 추출하는 것이 더 어렵습니다. 또한 버전 B는 그러한 리팩토링에 부작용이 없음을 보증합니다.

따라서 버전 A는 이점이없고 코드에 대해 추론하기가 훨씬 더 어려워지기 때문에 나를 끝내지 않습니다.

글쎄, 당신은 항상 그 범위를 만들 수 있습니다.

{ //Or if(true) if the language doesn't support making scopes like this
    double intermediateResult;
    for (int i=0; i<1000; i++) {
        intermediateResult = i;
        System.out.println(intermediateResult);
    }
}

이렇게하면 변수를 한 번만 선언하면 루프를 떠날 때 죽습니다.

나는 항상 당신이 당신의 루프 내부에서 당신의 변수를 선언한다면, 당신은 메모리를 낭비하고 있다고 생각했습니다. 다음과 같은 것이 있다면 :

for(;;) {
  Object o = new Object();
}

그런 다음 각 반복마다 개체를 만들어야 할뿐만 아니라 각 객체에 대해 새 참조가 할당되어야합니다. 쓰레기 수집가가 느리면 청소해야 할 수많은 매달려있는 참조가있을 것 같습니다.

그러나, 당신이 이것을 가지고 있다면 :

Object o;
for(;;) {
  o = new Object();
}

그런 다음 단일 참조 만 만들고 매번 새 개체를 할당합니다. 물론, 범위를 벗어나려면 시간이 조금 더 걸릴 수 있지만, 다루는 참조는 단 하나뿐입니다.

나는 그것이 컴파일러에 달려 있다고 생각하며 일반적인 대답을하기가 어렵다고 생각합니다.

내 연습은 다음과 같습니다.

  • 변수 유형이 간단한 경우 (int, double, ...) 나는 변형을 선호합니다 (내부에).
    이유: 변수의 범위 감소.

  • 변수 유형이 간단하지 않은 경우 (어떤 종류의 class 또는 struct) 나는 변형을 선호합니다 (밖의).
    이유: CTOR-DTOR 통화 수를 줄입니다.

성능의 관점에서 외부는 훨씬 더 좋습니다.

public static void outside() {
    double intermediateResult;
    for(int i=0; i < Integer.MAX_VALUE; i++){
        intermediateResult = i;
    }
}

public static void inside() {
    for(int i=0; i < Integer.MAX_VALUE; i++){
        double intermediateResult = i;
    }
}

나는 각각 10 억 번의 기능을 실행했다. 외부 ()는 65 밀리 초를 가져 갔다. 내부 ()는 1.5 초가 걸렸습니다.

a) B보다 안전한 내기입니다.

처럼

typedef struct loop_example{

JXTZ hi; // where JXTZ could be another type...say closed source lib 
         // you include in Makefile

}loop_example_struct;

//then....

int j = 0; // declare here or face c99 error if in loop - depends on compiler setting

for ( ;j++; )
{
   loop_example loop_object; // guess the result in memory heap?
}

당신은 확실히 메모리 누출 문제에 직면해야합니다!. 따라서 'B'는 메모리 축적에 취약한 반면 'A'는 더 안전하다고 생각합니다.

흥미로운 질문입니다. 내 경험에 따르면이 문제를 코드에 대해 토론 할 때 고려해야 할 궁극적 인 질문이 있습니다.

변수가 글로벌이어야하는 이유가 있습니까?

코드를 구성하는 것이 더 좋고 코드 줄이 적기 때문에 로컬로 여러 번 변수를 한 번만 선언하는 것이 합리적입니다. 그러나 하나의 메소드 내에서 로컬로만 선언 해야하는 경우 해당 메소드에서 초기화하여 변수가 해당 메소드와 독점적으로 관련되어 있음이 분명합니다. 후자의 옵션을 선택하면 초기화 된 방법 외부 에서이 변수를 호출하지 않도록주의하십시오. 코드는 귀하가 말하는 내용을 알지 못하고 오류를보고합니다.

또한, 참고로, 목적이 거의 동일하지 않더라도 다른 방법들 사이에 로컬 변수 이름을 복제하지 마십시오. 혼란스러워집니다.

관심이 있으시면 Node 4.0.0으로 JS를 테스트했습니다. 루프 외부를 선언하면 시험 당 1 억 루프 반복으로 평균 1000 개 이상의 시험에서 성능이 향상되었습니다. 그래서 나는 계속해서 B, IMO 인 가장 읽기 쉬운 / 유지 가능한 방식으로 글을 쓸 것입니다. 코드를 바이올린에 넣었지만 Performance-Now 노드 모듈을 사용했습니다. 코드는 다음과 같습니다.

var now = require("../node_modules/performance-now")

// declare vars inside loop
function varInside(){
    for(var i = 0; i < 100000000; i++){
        var temp = i;
        var temp2 = i + 1;
        var temp3 = i + 2;
    }
}

// declare vars outside loop
function varOutside(){
    var temp;
    var temp2;
    var temp3;
    for(var i = 0; i < 100000000; i++){
        temp = i
        temp2 = i + 1
        temp3 = i + 2
    }
}

// for computing average execution times
var insideAvg = 0;
var outsideAvg = 0;

// run varInside a million times and average execution times
for(var i = 0; i < 1000; i++){
    var start = now()
    varInside()
    var end = now()
    insideAvg = (insideAvg + (end-start)) / 2
}

// run varOutside a million times and average execution times
for(var i = 0; i < 1000; i++){
    var start = now()
    varOutside()
    var end = now()
    outsideAvg = (outsideAvg + (end-start)) / 2
}

console.log('declared inside loop', insideAvg)
console.log('declared outside loop', outsideAvg)

이것은 더 나은 형태입니다

double intermediateResult;
int i = byte.MinValue;

for(; i < 1000; i++)
{
intermediateResult = i;
System.out.println(intermediateResult);
}

1) 이런 식으로 한 번 변수는 한 번 변수를 한 번 선언했으며 각각은 사이클에 대한 것이 아닙니다. 2) 과제는 다른 모든 옵션이 뚱뚱한 thean입니다. 3) 따라서 최고의 실습 규칙은 반복 외부의 선언입니다.

이동 중에 동일한 것을 시도하고 사용하는 컴파일러 출력을 비교했습니다. go tool compile -S Go 1.9.4

어셈블러 출력에 따라 제로 차이.

나는 오랫동안이 같은 질문을 가졌다. 그래서 더 간단한 코드를 테스트했습니다.

결론: 을 위한 그러한 경우 거기 있습니다 아니 성능 차이.

외부 루프 케이스

int intermediateResult;
for(int i=0; i < 1000; i++){
    intermediateResult = i+2;
    System.out.println(intermediateResult);
}

내부 루프 케이스

for(int i=0; i < 1000; i++){
    int intermediateResult = i+2;
    System.out.println(intermediateResult);
}

Intellij의 디 컴파일러에서 컴파일 된 파일을 확인했는데 두 경우 모두 같은 Test.class

for(int i = 0; i < 1000; ++i) {
    int intermediateResult = i + 2;
    System.out.println(intermediateResult);
}

나는 또한 이것에 주어진 방법을 사용하여 두 사례에 대한 코드를 분해했습니다. 대답. 답과 관련된 부분 만 보여 드리겠습니다

외부 루프 케이스

Code:
  stack=2, locals=3, args_size=1
     0: iconst_0
     1: istore_2
     2: iload_2
     3: sipush        1000
     6: if_icmpge     26
     9: iload_2
    10: iconst_2
    11: iadd
    12: istore_1
    13: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
    16: iload_1
    17: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
    20: iinc          2, 1
    23: goto          2
    26: return
LocalVariableTable:
        Start  Length  Slot  Name   Signature
           13      13     1 intermediateResult   I
            2      24     2     i   I
            0      27     0  args   [Ljava/lang/String;

내부 루프 케이스

Code:
      stack=2, locals=3, args_size=1
         0: iconst_0
         1: istore_1
         2: iload_1
         3: sipush        1000
         6: if_icmpge     26
         9: iload_1
        10: iconst_2
        11: iadd
        12: istore_2
        13: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        16: iload_2
        17: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        20: iinc          1, 1
        23: goto          2
        26: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           13       7     2 intermediateResult   I
            2      24     1     i   I
            0      27     0  args   [Ljava/lang/String;

당신이 세심한주의를 기울이면 Slot 할당 i 그리고 intermediateResult 안에 LocalVariableTable 외모 순서의 산물로 교체됩니다. 슬롯의 동일한 차이는 다른 코드 라인에 반영됩니다.

  • 추가 작업이 수행되지 않습니다
  • intermediateResult 두 경우 모두 로컬 변수이므로 액세스 시간 차이가 없습니다.

보너스

컴파일러는 많은 최적화를 수행 하고이 경우 발생하는 일을 살펴 봅니다.

제로 작업 케이스

for(int i=0; i < 1000; i++){
    int intermediateResult = i;
    System.out.println(intermediateResult);
}

제로 작업이 소화되었습니다

for(int i = 0; i < 1000; ++i) {
    System.out.println(i);
}

내 컴파일러가 충분히 똑똑하다는 것을 알고 있더라도, 나는 그것에 의존하는 것을 좋아하지 않으며, a) 변형을 사용할 것입니다.

b) 변형은 당신이 필사적으로 당신이 중개자 루프 본체 후에는 사용할 수 없습니다. 하지만 어쨌든 그런 절박한 상황을 상상할 수 없습니다 ....

편집하다: 존 스키트 루프 내부의 변수 선언이 실제 의미 론적 차이를 만들 수 있음을 보여 주면서 아주 좋은 지적을했습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top