정수 범위를 지정하여 최적화 프로그램에 힌트를 줄 수 있습니까? 특수 산술 최적화가 수행되는

나는 사용하고 int값을 저장하는 유형입니다. 프로그램의 의미에 따라 값은 항상 매우 작은 범위 (0-36)로 변하며 int(a 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‘ing 프로그램 과 같이 작동 하며 릴리스 빌드에서는 가정을 사용하여 최적화 된 코드를 생성합니다.

참고하지만, 일반을 대신 할 수는 없습니다 것을 assertcond릴리스의 유적이 빌드, 그래서 당신은 그런 짓을해서는 안됩니다 assume(VeryExpensiveComputation()).


답변

이에 대한 표준 지원이 있습니다. 해야 할 일은 stdint.h( 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및 사전 평가 상수를 발견 0x20하고 0x30e에서 어셈블러 출력 .


답변

좀 더 표준 C ++ 인 솔루션을 원할 경우 [[noreturn]]속성을 사용 하여 자신 만의을 작성할 수 있다고 말하는 것입니다 unreachable.

그래서 나는 deniss의 훌륭한 예제 를 다시 사용 하여 보여줄 것이다 :

namespace detail {
    [[noreturn]] void unreachable(){}
}

#define assume(cond) do { if (!(cond)) detail::unreachable(); } while (0)

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

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

어떤 당신이 볼 수 있듯이 , 거의 동일한 코드의 결과 :

detail::unreachable():
        rep ret
func(int):
        movl    $17, %eax
        ret

물론 단점은 [[noreturn]]함수가 실제로 리턴 한다는 경고를 받는다는 것입니다.