[c++] 어떤 경우에 C ++ 컴파일러가 꼬리 재귀 최적화를 수행합니까?

C와 C ++에서 꼬리 재귀 최적화를 수행하는 것이 완벽하게 잘 작동하는 것처럼 보이지만 디버깅하는 동안이 최적화를 나타내는 프레임 스택을 보지 못하는 것 같습니다. 스택은 재귀의 깊이를 알려주기 때문에 좋은 것입니다. 그러나 최적화도 좋습니다.

C ++ 컴파일러가이 최적화를 수행합니까? 왜? 왜 안돼?

컴파일러에게 지시하는 방법은 무엇입니까?

  • MSVC의 경우 : /O2또는/Ox
  • GCC의 경우 : -O2또는-O3

컴파일러가 특정 경우 에이 작업을 수행했는지 확인하는 것은 어떻습니까?

  • MSVC의 경우 PDB 출력이 코드를 추적 할 수 있도록 설정 한 다음 코드를 검사하십시오.
  • GCC ..?

컴파일러가 특정 함수가 이와 같이 최적화되어 있는지 확인하는 방법에 대한 제안을 계속합니다 (Konrad가 나에게 그것을 가정한다고 확신하더라도)

컴파일러가 무한 재귀를 만들고 무한 루프 또는 스택 오버플로가 발생하는지 확인하여 컴파일러 가이 작업을 수행하는지 항상 확인할 수 있지만 (GCC 로이 작업을 수행하고 -O2충분 하다는 것을 알았습니다 ) 내가 아는 특정 기능을 확인하면 어쨌든 종료됩니다. 나는 이것을 확인하는 쉬운 방법을 갖고 싶다 🙂


몇 가지 테스트 후, 소멸자 가이 최적화를 할 가능성을 망치는 것을 발견했습니다. 리턴 문이 시작되기 전에 특정 변수 및 임시 범위의 범위를 변경하여 범위를 벗어나는 것이 때때로 가치가있을 수 있습니다.

테일 콜 이후에 소멸자를 실행해야하는 경우 테일 콜 최적화를 수행 할 수 없습니다.



답변

현재의 모든 주류 컴파일러 는 다음 과 같은 상호 재귀 호출에 대해서도 테일 콜 최적화를 상당히 잘 수행하며 10 년 이상 수행 했습니다.

int bar(int, int);

int foo(int n, int acc) {
    return (n == 0) ? acc : bar(n - 1, acc + 2);
}

int bar(int n, int acc) {
    return (n == 0) ? acc : foo(n - 1, acc + 1);
}

컴파일러가 최적화를 수행하도록하는 것은 간단합니다. 속도 최적화를 켜십시오.

  • MSVC의 경우 /O2또는을 사용하십시오 /Ox.
  • GCC, Clang 및 ICC의 경우 -O3

컴파일러가 최적화를 수행했는지 확인하는 쉬운 방법은 스택 오버플로가 발생하거나 어셈블리 출력을 보는 호출을 수행하는 것입니다.

흥미로운 역사적 메모 로 Mark Probst 의 졸업장 논문 과정에서 C에 대한 꼬리 호출 최적화가 GCC에 추가되었습니다 . 논문은 구현에있어 몇 가지 흥미로운 경고를 설명합니다. 읽을 가치가 있습니다.


답변

gcc 4.3.2는이 함수 (크 래피 / 사소한 atoi()구현)를로 완전히 인라인합니다 main(). 최적화 수준은 -O1입니다. 나는 그것을 가지고 놀면 ( 꼬리 에서 static로 변경하더라도 extern꼬리 재귀는 꽤 빨리 사라 지므로 프로그램 정확성에 의존하지 않을 것입니다.

#include <stdio.h>
static int atoi(const char *str, int n)
{
    if (str == 0 || *str == 0)
        return n;
    return atoi(str+1, n*10 + *str-'0');
}
int main(int argc, char **argv)
{
    for (int i = 1; i != argc; ++i)
        printf("%s -> %d\n", argv[i], atoi(argv[i], 0));
    return 0;
}


답변

명백한 것 외에도 (컴파일러는 요청하지 않는 한 이러한 종류의 최적화를 수행하지 않습니다) C ++의 테일 콜 최적화에는 소멸자가 복잡합니다.

다음과 같은 것이 주어진다 :

   int fn(int j, int i)
   {
      if (i <= 0) return j;
      Funky cls(j,i);
      return fn(j, i-1);
   }

컴파일러는 이후 의 소멸자를 호출해야하기 때문에 (일반적으로) 꼬리 호출을 최적화 할 수 없습니다.cls 재귀 호출이 반환 된 .

때때로 컴파일러는 소멸자가 외부에 보이는 부작용이 없다는 것을 알 수 있지만 (초기 수행 할 수 있음) 종종 그렇지 않습니다.

이것의 가장 일반적인 형태 Funky는 실제로 std::vector또는 유사한 곳 입니다 .


답변

대부분의 컴파일러는 디버그 빌드에서 어떤 종류의 최적화도 수행하지 않습니다.

VC를 사용하는 경우 PDB 정보가 켜진 상태에서 릴리스 빌드를 시도하십시오. 그러면 최적화 된 앱을 추적 할 수 있으며 원하는 것을 원하는대로 볼 수 있습니다. 그러나 최적화 된 빌드를 디버깅하고 추적하면 모든 곳을 돌아 다니며 레지스터에서 끝나거나 완전히 최적화되어 변수를 직접 검사 할 수없는 경우가 종종 있습니다. “흥미로운”경험입니다 …


답변

Greg가 언급했듯이 컴파일러는 디버그 모드에서 수행하지 않습니다. 디버그 빌드가 prod 빌드보다 느리면 괜찮지 만 더 자주 충돌해서는 안됩니다. 테일 콜 최적화에 의존하는 경우 정확히 그렇게 할 수 있습니다. 이 때문에 테일 호출을 일반 루프로 다시 작성하는 것이 가장 좋습니다. 🙁


답변