c++ - gcc compiler option optimize




정수 범위를 지정하여 최적화 프로그램에 힌트를 줄 수 있습니까? (3)

값을 저장하기 위해 int 유형을 사용하고 있습니다. 프로그램의 의미에 따라 값은 항상 매우 작은 범위 (0-36)로 변하며 int ( char 아님)는 CPU 효율성 때문에 만 사용됩니다.

이러한 작은 범위의 정수에서 많은 특수 산술 최적화가 수행되는 것처럼 보입니다. 이러한 정수에 대한 많은 함수 호출은 작은 "마법의"연산으로 최적화 될 수 있으며 일부 함수는 테이블 조회로 최적화 될 수도 있습니다.

그래서 컴파일러 에게이 int 가 항상 그 작은 범위에 있다고 말할 수 있습니까? 그리고 컴파일러가 그러한 최적화를 수행 할 수 있습니까?


네 가능합니다. 예를 들어 gcc 경우 __builtin_unreachable 을 사용하여 다음과 같이 불가능한 조건에 대해 컴파일러에 알릴 수 있습니다.

if (value < 0 || value > 36) __builtin_unreachable();

위의 조건을 매크로로 감쌀 수 있습니다.

#define assume(cond) do { if (!(cond)) __builtin_unreachable(); } while (0)

그리고 그렇게 사용하십시오 :

assume(x >= 0 && x <= 10);

보시다시피 gcc 는 다음 정보를 기반으로 최적화를 수행합니다.

#define assume(cond) do { if (!(cond)) __builtin_unreachable(); } while (0)

int func(int x){
    assume(x >=0 && x <= 10);

    if (x > 11){
        return 2;
    }
    else{
        return 17;
    }
}

생산 :

func(int):
    mov     eax, 17
    ret

그러나 한 가지 단점 은 코드에서 이러한 가정을 어기면 정의되지 않은 동작이 발생한다는 것 입니다.

디버그 빌드에서도 이러한 상황이 발생하면 알려주지 않습니다. 가정을 사용하여 버그를보다 쉽게 ​​디버그 / 테스트 / 캐치하려면 하이브리드 가정 / 어설 션 매크로 (@David Z에 대한 신용)를 다음과 같이 사용할 수 있습니다.

#if defined(NDEBUG)
#define assume(cond) do { if (!(cond)) __builtin_unreachable(); } while (0)
#else
#include <cassert>
#define assume(cond) assert(cond)
#endif

디버그 빌드 ( NDEBUG 정의 되지 않은 상태 )에서는 일반 assert , 인쇄 오류 메시지 및 abort 프로그램과 같이 작동하며 릴리스 빌드에서는 가정을 사용하여 최적화 된 코드를 생성합니다.

그러나 릴리스 빌드에서 일반 assert cond 대체 할 수는 없으므로 assume(VeryExpensiveComputation()) 과 같은 작업을 수행해서는 안됩니다.


이에 대한 표준 지원이 있습니다. stdint.h ( cstdint )를 포함 cstdint 유형을 사용하는 것이 uint_fast8_t .

이것은 컴파일러에게 0에서 255 사이의 숫자만을 사용하고 있지만 더 빠른 코드를 제공하면 더 큰 유형을 자유롭게 사용할 수 있음을 알려줍니다. 마찬가지로 컴파일러는 변수가 255보다 큰 값을 가지지 않는다고 가정 한 다음 그에 따라 최적화를 수행 할 수 있습니다.


현재 답변은 범위가 무엇인지 확실히 알고있는 경우에 유용하지만 값이 예상 범위를 벗어날 때 여전히 올바른 동작을 원하면 작동하지 않습니다.

이 경우이 기술이 효과가 있음을 알았습니다.

if (x == c)  // assume c is a constant
{
    foo(x);
}
else
{
    foo(x);
}

아이디어는 코드-데이터 트레이드 오프입니다. 1 비트의 데이터 ( x == c 관계없이)를 제어 로직으로 옮깁니다 .
이것은 x 가 실제로 알려진 상수 c 라는 옵티 마이저에게 암시하고 나머지와는 별도로 foo 의 첫 번째 호출을 인라인하고 최적화하도록 권장합니다.

그러나 실제로 코드를 단일 서브 루틴 foo 합니다. 코드를 복제하지 마십시오.

예:

이 기술이 작동하려면 약간 운이 좋을 필요가 있습니다. 컴파일러가 정적으로 평가하지 않기로 결정한 경우가 있으며 임의의 종류입니다. 그러나 작동하면 잘 작동합니다.

#include <math.h>
#include <stdio.h>

unsigned foo(unsigned x)
{
    return x * (x + 1);
}

unsigned bar(unsigned x) { return foo(x + 1) + foo(2 * x); }

int main()
{
    unsigned x;
    scanf("%u", &x);
    unsigned r;
    if (x == 1)
    {
        r = bar(bar(x));
    }
    else if (x == 0)
    {
        r = bar(bar(x));
    }
    else
    {
        r = bar(x + 1);
    }
    printf("%#x\n", r);
}

-O3 사용 하고 어셈블러 출력 에서 사전 평가 된 상수 0x200x30e 를 확인 하십시오 .





compiler-optimization