programing

변수 선언은 비용이 많이 드나요?

firstcheck 2022. 7. 23. 11:21
반응형

변수 선언은 비용이 많이 드나요?

C로 코딩하던 중 아래와 같은 상황을 접하게 되었습니다.

int function ()
{
  if (!somecondition) return false;

  internalStructure  *str1;
  internalStructure *str2;
  char *dataPointer;
  float xyz;

  /* do something here with the above local variables */    
}

을 고려하다if위의 코드의 스테이트먼트는 함수에서 반환할 수 있으며, 두 곳에서 변수를 선언할 수 있습니다.

  1. 그 전에if진술.
  2. 그 후if진술.

프로그래머로서 변수 선언은 다음에도 계속 유지할 수 있을 것 같습니다.if진술.

신고장소에 돈이 좀 드나요?아니면 다른 방법보다 한 가지 방법을 더 선호하는 다른 이유가 있나요?

C99 이후(또는 C89에 대한 공통 적합 확장을 사용하는 경우)에서는 문장과 선언을 자유롭게 혼재시킬 수 있습니다.

이전 버전과 마찬가지로(컴파일러가 보다 스마트해지고 공격적이 되었을 때에만) 컴파일러는 레지스터와 스택을 할당하거나 as-if 규칙에 따라 다른 최적화를 수행하는 방법을 결정합니다.
즉, 성능 면에서는 어떠한 차이도 기대할 수 없습니다.

어쨌든, 그것이 허락된 이유는 아니었다.

이는 범위를 제한하고, 따라서 인간이 코드를 해석하고 검증할 때 염두에 두어야 할 문맥을 줄이기 위한 것입니다.

이치에 맞는 작업을 수행하지만, 현재 코딩 스타일은 변수 선언을 가능한 한 사용법에 가깝게 배치하는 것이 좋습니다.

실제로 변수 선언은 첫 번째 컴파일러 이후 거의 모든 컴파일러에서 자유롭다.이는 거의 모든 프로세서가 스택포인터(및 프레임포인터)를 사용하여 스택을 관리하기 때문입니다.예를 들어, 다음 두 가지 기능을 고려합니다.

int foo() {
    int x;
    return 5; // aren't we a silly little function now
}

int bar() {
    int x;
    int y;
    return 5; // still wasting our time...
}

최신 컴파일러로 컴파일(스마트하지 않고 사용하지 않는 로컬 변수를 최적화하지 않도록 지시)하면 다음과 같습니다(x64 어셈블리의 예).다른 것도 비슷합니다.

foo:
push ebp
mov  ebp, esp
sub  esp, 8    ; 1. this is the first line which is different between the two
mov  eax, 5    ; this is how we return the value
add  esp, 8    ; 2. this is the second line which is different between the two
ret

bar:
push ebp
mov  ebp, esp
sub  esp, 16    ; 1. this is the first line which is different between the two
mov  eax, 5     ; this is how we return the value
add  esp, 16    ; 2. this is the second line which is different between the two
ret

참고: 두 기능의 연산 코드 수는 동일합니다!

이는 거의 모든 컴파일러가 필요한 모든 공간을 사전에 할당하기 때문입니다(예:alloca(별도로 취급됩니다.실제로 x64에서는 이러한 효율적인 방법으로 작업을 수행해야 합니다.

(편집: Forss가 지적한 바와 같이 컴파일러는 일부 로컬 변수를 레지스터로 최적화할 수 있습니다.좀 더 엄밀히 말하면, 스택에 「스필오버」하는 최초의 변수에는 2개의 opcode가 필요하고, 나머지는 무료입니다.)

같은 이유로 컴파일러는 모든 로컬 변수 선언을 수집하여 바로 앞에 공간을 할당합니다.C89는 1패스 컴파일러가 되도록 설계되었기 때문에 모든 선언을 사전에 해야 합니다.C89 컴파일러가 할당해야 할 공간의 양을 알기 위해서는 나머지 코드를 내보내기 전에 모든 변수를 알아야 합니다.C99나 C++와 같은 현대 언어에서는 컴파일러가 1972년보다 훨씬 더 스마트해질 것으로 예상되기 때문에 개발자의 편의를 위해 이 제한이 완화됩니다.

최신 코딩 관행에서는 변수를 사용법에 가깝게 사용할 것을 제안합니다.

이것은 컴파일러와는 관계가 없습니다(어느 쪽이든 상관없습니다).대부분의 인간 프로그래머들은 변수를 사용하는 곳에 가까이 두면 코드를 더 잘 읽는 것으로 밝혀졌다.이것은 스타일 가이드일 뿐이므로, 사양하지 말아 주세요만, 개발자들 사이에서는 「올바른 방법」이라고 하는 공감대가 두드러지고 있습니다.

이제 몇 가지 코너 케이스에 대해 설명하겠습니다.

  • 컨스트럭터와 함께 C++를 사용하는 경우 컴파일러는 공간을 전면에 할당합니다(이렇게 하는 것이 빠르고 문제없기 때문입니다).단, 변수는 코드 흐름의 올바른 위치가 될 때까지 해당 공간에 구축되지 않습니다.경우에 따라서는 변수를 사용법에 가깝게 배치하는 것이 변수를 앞에 배치하는 것보다 더 빠를 수 있습니다.흐름 제어는 변수 선언을 안내할 수 있으며, 이 경우 생성자를 호출할 필요도 없습니다.
  • alloca이 위의 레이어에서 처리됩니다.궁금하신 분들을 위해서alloca구현은 스택 포인터를 임의의 양 아래로 이동시키는 효과가 있습니다.사용하는 기능alloca는 어떤 방법으로든 이 공간을 추적하고 스택포인터가 위쪽으로 다시 조정된 것을 확인하고 나서 나갈 필요가 있습니다.
  • 보통 16바이트의 스택공간이 필요한 경우가 있습니다만, 한 가지 조건에서는 50kB의 로컬어레이를 할당해야 합니다.코드의 어디에 변수를 넣든 함수가 호출될 때마다 사실상 모든 컴파일러가 50kB+16B의 스택 공간을 할당합니다.이는 거의 문제가 되지 않지만 강박적으로 재귀적인 코드에서는 스택에 오버플로가 발생할 수 있습니다.50kB 어레이로 동작하는 코드를 독자적인 기능으로 이동하거나alloca.
  • 일부 플랫폼(Windows 등)에서는 페이지 수 이상의 스택스페이스를 할당할 경우 프롤로그에 특별한 함수 호출이 필요합니다.이것은 분석을 크게 바꾸지 않을 것입니다(실장에서는 페이지당 1단어만 호출하는 매우 빠른 리프 함수입니다).

가장 좋은 방법은 게으른 접근 방식을 적용하는 것입니다. 즉, 정말로 필요할 때만 선언하는 것입니다.그 결과 다음과 같은 이점이 있습니다.

이러한 변수가 가능한 한 사용처에 가까운 것으로 선언되어 있는 경우, 코드는 보다 읽기 쉬워집니다.

네, 명확성이 필요할 수 있습니다.어떤 조건에서는 함수가 아무것도 할 필요가 없는 경우(전역적인 false를 검출했을 경우 등), 위에 나타내는 체크의 맨 위에 배치하는 것이 확실히 이해하기 쉽습니다.이 체크는 디버깅이나 문서 작성에 필수적인 것입니다.

로컬 변수(함수 등)를 C 스코프에 할당할 때마다 기본 초기화 코드(C++ 컨스트럭터 등)는 없습니다.또한 동적으로 할당되지 않으므로(초기화되지 않은 포인터일 뿐) 추가(및 잠재적으로 비용이 많이 드는) 함수를 호출할 필요가 없습니다(예:malloc)을 준비/준비하기 위해 사용합니다.

스택의 동작방식으로 인해 스택 변수를 할당하는 것은 단순히 스택 포인터를 위한 공간을 확보하기 위해 스택포인터를 줄이는 것을 의미합니다(즉, 대부분의 아키텍처에서는 스택이 아래로 커지기 때문에 스택사이즈를 늘리는 것).CPU의 관점에서 이것은 단순한 SUB 명령을 실행하는 것을 의미합니다.SUB rsp, 4(4월 4일 (32일)

의 큰 인 '하다'로 그룹화할 수 있을 정도로 스마트합니다.SUB rsp, XX여기서 ", ")XX는 스코프의 로컬 변수의 총 크기입니다.이론상으로는. 실제로, 조금 다른 일이 일어납니다.

이러한 상황에서는 컴파일러의 '후드'에서 일어나는 일을 (매우 쉽게) 알아낼 수 있는 GCC 탐색기가 매우 유용한 도구라는 것을 알게 되었습니다.

GCC 탐색기 링크와 같은 함수를 실제로 작성하면 어떻게 되는지 살펴보겠습니다.

C코드

int function(int a, int b) {
  int x, y, z, t;

  if(a == 2) { return 15; }

  x = 1;
  y = 2;
  z = 3;
  t = 4;

  return x + y + z + t + a + b;
}

결과 어셈블리

function(int, int):
    push    rbp
    mov rbp, rsp
    mov DWORD PTR [rbp-20], edi
    mov DWORD PTR [rbp-24], esi
    cmp DWORD PTR [rbp-20], 2
    jne .L2
    mov eax, 15
    jmp .L3
.L2:
    -- snip --
.L3:
    pop rbp
    ret

GCC를 사용하다로컬 변수를 할당하기 위한 SUB 명령도 전혀 수행하지 않습니다. 뿐,포인터를 되지 않습니다(를 들면, 「」( 「」)).SUB rsp, XX는 최신 이 스택포인터는 최신 상태로 이 경우 스택포인터는 최신 상태가 되지 않습니다.PUSH가 실행된다 no 「no」(no 「no」)rsp스택 영역을 사용한 후에는 문제가 없습니다.

다음으로 추가 변수가 선언되지 않은 예를 제시하겠습니다.http://goo.gl/3TV4hE

C코드

int function(int a, int b) {
  if(a == 2) { return 15; }
  return a + b;
}

결과 어셈블리

function(int, int):
    push    rbp
    mov rbp, rsp
    mov DWORD PTR [rbp-4], edi
    mov DWORD PTR [rbp-8], esi
    cmp DWORD PTR [rbp-4], 2
    jne .L2
    mov eax, 15
    jmp .L3
.L2:
    mov edx, DWORD PTR [rbp-4]
    mov eax, DWORD PTR [rbp-8]
    add eax, edx
.L3:
    pop rbp
    ret

전의 ( 「 」 「 」 「 」 「 」 「 」 。jmp .L3을 참조) 스택 변수를 "삭제"하기 위한 추가 명령은 호출되지 않습니다.b가 a와 b에 입니다.edi ★★★★★★★★★★★★★★★★★」esi는 첫 됩니다(레지스터의 경우).[rbp-4] ★★★★★★★★★★★★★★★★★」[rbp - 8]이는 첫 번째 예시와 같이 로컬 변수에 대해 추가 공간이 "할당"되지 않았기 때문입니다.따라서 이러한 로컬 변수를 추가하기 위한 유일한 "오버헤드"는 감산 항의 변경(추가 감산 연산도 추가하지 않음)입니다.

따라서 스택 변수를 선언하는 데 드는 비용은 거의 없습니다.

C에서는 모든 변수 선언이 함수 선언의 맨 위에 있는 것처럼 적용된다고 생각합니다만, 블록으로 선언하면 스코핑이라고 생각합니다(C++에서는 동일하지 않다고 생각합니다).컴파일러는 변수에 대한 모든 최적화를 수행하며, 일부는 더 높은 최적화에서 기계 코드에서 효과적으로 사라질 수 있습니다.컴파일러는 변수에 필요한 공간의 양을 결정하고 나중에 실행 중에 변수가 존재하는 스택으로 알려진 공간을 만듭니다.

함수를 호출하면 함수에 의해 사용되는 모든 변수가 호출된 함수에 대한 정보(반환 주소, 파라미터 등)와 함께 스택에 배치됩니다.변수가 선언된 위치는 중요하지 않습니다. 선언된 변수일 뿐이며, 변수는 스택에 할당됩니다.

변수를 선언하는 것 자체는 "비용"이 들지 않습니다.변수로 사용하지 않을 정도로 쉬운 경우 컴파일러는 변수를 제거할 수 있습니다.

이것 좀 봐.

Da stack

Wikipedia가 콜스택에 있고, 다른 장소가 스택있습니다.

물론 이 모든 것은 구현과 시스템에 의존합니다.

최종적으로 컴파일러에 따라 다르지만 일반적으로 모든 로컬이 함수의 시작 부분에 할당됩니다.

그러나 로컬 변수를 스택에 배치(또는 최적화 후 레지스터에 배치)하기 때문에 로컬 변수를 할당하는 비용은 매우 작습니다.

선언문은 사용처에 가능한 한 가깝게 보관합니다.중첩된 블록 내부에 이상적입니다. 이 .if★★★★★★ 。

이게 있으면

int function ()
{
   {
       sometype foo;
       bool somecondition;
       /* do something with foo and compute somecondition */
       if (!somecondition) return false;
   }
   internalStructure  *str1;
   internalStructure *str2;
   char *dataPointer;
   float xyz;

   /* do something here with the above local variables */    
}

.foo ★★★★★★★★★★★★★★★★★」somecondition 재사용할 수 str1, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, ,, , 뒤에 선언함으로써,if스택 공간을 절약할 수 있습니다.컴파일러의 최적화 기능에 따라서는, 안쪽의 브레이스 페어를 떼어내 기능을 평평하게 하거나, 또는 선언을 했을 경우에도, 스택스페이스의 절약이 실현될 가능성이 있습니다.str1 before before before 、 [ before ]if단, 이 경우 컴파일러/컴파일러는 스코프가 "실제로" 중복되지 않음을 알아채야 합니다.다음 선언을 제시함으로써if코드의 가독성이 향상되는 것은 말할 것도 없고, 최적화를 실시하지 않아도, 이 동작을 용이하게 합니다.

저는 "얼리 아웃" 조건을 기능의 최상위에 두고, 그 이유를 문서화하는 것을 선호합니다.여러 개의 변수 선언 뒤에 두면 코드를 잘 모르는 사람이 쉽게 놓칠 수 있습니다. 코드를 찾아야 한다는 것을 알지 못하는 한 말이죠.

"얼리 아웃" 조건을 문서화하는 것만으로는 충분하지 않습니다.코드에도 명확하게 하는 것이 좋습니다.또한 얼리아웃 조건을 맨 위에 두면 문서를 코드와 동기화하는 것이 쉬워집니다.예를 들어 나중에 얼리아웃 조건을 삭제하거나 추가 조건을 추가하는 경우 등입니다.

실제로 중요한 경우 변수 할당을 피할 수 있는 유일한 방법은 다음과 같습니다.

int function_unchecked();

int function ()
{
  if (!someGlobalValue) return false;
  return function_unchecked();
}

int function_unchecked() {
  internalStructure  *str1;
  internalStructure *str2;
  char *dataPointer;
  float xyz;

  /* do something here with the above local variables */    
}

하지만 실제로는 성능상의 이점을 찾을 수 없다고 생각합니다.약간의 오버헤드가 있다면.

물론 C++를 코딩할 때 이러한 로컬 변수 중 일부에 중요하지 않은 생성자가 있는 경우 검사 후에 해당 생성자를 배치해야 합니다.하지만 그렇다고 해서 기능을 분할하는 것은 도움이 되지 않을 것 같습니다.

if 스테이트먼트 뒤에 변수를 선언하고 함수에서 즉시 반환하면 컴파일러는 스택 내의 메모리를 커밋하지 않습니다.

언급URL : https://stackoverflow.com/questions/27729930/is-declaration-of-variables-expensive

반응형