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


150

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

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

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

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

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

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

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

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


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

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

답변:


129

현재의 모든 주류 컴파일러 는 다음 과 같은 상호 재귀 호출에 대해서도 테일 콜 최적화를 상당히 잘 수행하며 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에 추가되었습니다 . 논문은 구현에있어 몇 가지 흥미로운 경고를 설명합니다. 읽을 가치가 있습니다.


ICC는 그렇게 할 것이라고 믿습니다. 내가 아는 한, ICC는 시장에서 가장 빠른 코드를 생성합니다.
Paul Nathan

35
@Paul 문제는 테일 콜 최적화와 같은 알고리즘 최적화로 인한 ICC 코드 속도의 정도와 인텔 자체 프로세서에 대한 친밀한 지식을 갖춘 인텔 만이 할 수있는 캐시 및 마이크로 명령 최적화로 인해 발생하는 정도입니다.
Imagist

6
gcc-foptimize-sibling-calls"형제 및 꼬리 재귀 호출을 최적화하는" 더 좁은 옵션 이 있습니다. 이 옵션 ( gcc(1)다양한 플랫폼을 대상으로하는 버전 4.4, 4.7 및 4.8의 매뉴얼 페이지 에 따라 )은 ,, 레벨 -O2에서 활성화됩니다 . -O3-Os
FooF

또한 명시 적으로 최적화를 요청하지 않고 DEBUG 모드에서 실행하면 전혀 최적화되지 않습니다. 실제 릴리스 모드 EXE에 대해 PDB를 활성화하고 단계별로 시도해 볼 수 있지만 릴리스 모드에서 디버깅하면 보이지 않는 / 제거 된 변수, 병합 된 변수, 알 수없는 / 예기치 않은 범위의 범위를 벗어난 변수, 절대로 들어 가지 않는 변수 등의 복잡한 문제가 있습니다. 범위 및 스택 수준 주소를 가진 진정한 상수가되었으며 잘 합쳐 지거나 누락 된 스택 프레임. 일반적으로 병합 된 스택 프레임은 수신자가 인라인되고 누락 / 백 머지 된 프레임은 테일 콜을 의미합니다.
Петър Петров

21

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;
}

1
링크 타임 최적화를 활성화 할 수 있으며 extern메소드 조차도 인라인 될 수 있다고 생각합니다 .
Konrad Rudolph 2019

5
이상한. 난 그냥 GCC에게 4.2.3 (86, 슬랙웨어 12.1)과 GCC 4.6.2 (위지 AMD64, 데비안) 및 테스트 -O1 존재하지 아니 인라인없고 꼬리 재귀 최적화 . 당신은 그것을 위해 사용해야 -O2합니다 (물론, 4.2.x에서, 지금은 다소 오래되었지만 여전히 인라인되지는 않습니다). BTW gcc가 엄밀하게 꼬리가 아닌 경우에도 재귀를 최적화 할 수 있다는 점도 가치가 있습니다 (축적 기가없는 계승과 같은).
przemoc

16

명백한 것 외에도 (컴파일러는 요청하지 않는 한 이러한 종류의 최적화를 수행하지 않습니다) 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또는 유사한 곳 입니다 .


나를 위해 작동하지 않습니다. 시스템은 답변이 편집 될 때까지 투표가 잠 겼음을 알려줍니다.
hmuelner

방금 답변을 수정하고 (제거 된 parantheses) 이제 다운 보트를 취소 할 수 있습니다.
hmuelner

11

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

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


2
gcc why -g -O3을 시도하고 디버그 빌드에서 최적화를 얻으십시오. xlC의 동작은 동일합니다.
g24l

"대부분의 컴파일러"라고 말할 때 : 어떤 컴파일러 모음을 고려하십니까? 지적했듯이 디버그 빌드 중에 최적화를 수행하는 컴파일러가 적어도 두 개 있습니다. 내가 VC 가하는 한 알고 있습니다 (수정하고 계속 사용할 수있는 경우는 제외).
skyking

7

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.