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


173

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

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

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


4
값 범위 최적화는 많은 컴파일러에 존재합니다 (예 : llvm 그러나 선언 할 언어 힌트를 알지 못합니다.
레무스 루사 누

2
음수가없는 경우 unsigned컴파일러가 추론하기가 더 쉬운 유형 을 사용하면 약간의 이득을 얻을 수 있습니다 .
user694733

4
@RemusRusanu : Pascal을 사용하면 하위 범위 유형 을 정의 할 수 있습니다 (예 :) var value: 0..36;.
Edgar Bonet

7
" int (char가 아님)는 CPU 효율성 때문에 만 사용됩니다. "기존의이 오래된 지식은 일반적으로 그리 사실이 아닙니다. 좁은 유형은 때로는 전체 레지스터 너비, 예를 들어 0으로 확장되거나 부호 확장되어야합니다. 배열 인덱스로 사용될 때 가끔 무료로 발생합니다. 이 유형의 어레이가있는 경우 일반적으로 캐시 풋 프린트 감소가 다른 것보다 중요합니다.
Peter Cordes

1
: 말을 잊어 버렸 intunsigned int필요 기호 - 또는 64 비트 포인터로 대부분의 시스템에서, 너무, 64 비트 32에서 제로 - 확장 될 수 있습니다. x86-64 에서 32 비트 레지스터의 연산은 무료로 64 비트로 0으로 확장됩니다 (부호 확장은 아니지만 부호있는 오버플로는 정의되지 않은 동작이므로 컴파일러는 원하는 경우 64 비트 부호있는 수학을 사용할 수 있음). 따라서 계산 결과가 아닌 32 비트 함수 인수를 제로 확장하는 추가 명령 만 볼 수 있습니다. 더 좁은 부호없는 유형에 적합합니다.
Peter Cordes

답변:


230

네 가능합니다. 예를 들어, 컴파일러에게 불가능한 조건에 대해 다음과 같이 알리는 데 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 프로그램 과 같이 작동 하며 릴리스 빌드에서는 가정을 사용하여 최적화 된 코드를 생성합니다.

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


5
내 예제에서 @Xofo는 그것을 얻지 못했습니다. return 2분기가 컴파일러에 의해 코드에서 제거 되었으므로 이미 발생했습니다 .

6
그러나 gcc OP가 기대하는 것처럼 마술 연산이나 테이블 조회에 함수를 최적화 할 수없는 것 같습니다 .
jingyu9575

19
@ user3528438 __builtin_expect은 엄격하지 않은 힌트입니다. __builtin_expect(e, c)" e로 평가 될 가능성이 가장 높음 " 으로 읽어야하며 c분기 예측을 최적화하는 데 유용 할 수 있지만 ealways로 제한 c되지는 않으므로 옵티마이 저가 다른 경우를 버릴 수 없습니다. 가지가 어떻게 조립되어 있는지 살펴보십시오 .

6
이론적으로 정의되지 않은 동작을 유발하는 모든 코드가 대신 사용될 수 있습니다 __builtin_unreachable().
코드 InChaos

14
즉,이 나쁜 생각을하게 대해 내가 모르는 어떤 특질이 아니라면, 그것은 이것을 결합하는 의미 할 수도 assert정의, 예를 들어 assume같은 assert경우에 NDEBUG정의되지 않은, 그리고 __builtin_unreachable()NDEBUG정의됩니다. 이렇게하면 프로덕션 코드에서 가정의 이점을 얻을 수 있지만 디버그 빌드에서는 여전히 명시 적으로 확인해야합니다. 물론 당신은 가정 야생에서 만족 될 것이라고 확신하기에 충분한 테스트를해야합니다 .
David Z

61

이에 대한 표준 지원이 있습니다. 해야 할 일은 stdint.h( cstdint) 를 포함 시킨 다음 유형을 사용하는 것입니다 uint_fast8_t.

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


2
이러한 유형은 거의 필요한만큼 사용되지 않습니다 (개인적으로는 존재하지 않는 경향이 있습니다). 그들은 빠르고 휴대 성이 뛰어나고 코드가 훌륭합니다. 그리고 그들은 1999 년부터 주변에있었습니다.
Lundin

이것은 일반적인 경우에 대한 좋은 제안입니다. deniss의 답변은 특정 시나리오에 적합한 가단성 솔루션을 보여줍니다.
궤도에서 가벼움 경주

1
컴파일러 uint_fast8_t는 실제로 8 비트 유형 (예 : unsigned charx86 / ARM / MIPS / PPC ( godbolt.org/g/KNyc31 ) 에있는 시스템)에서만 0-255 범위 정보를 얻습니다 . 에 21164A 전에 초기 알파 프로세서 제정신 구현을 사용합니다, 그래서 바이트로드 / 저장이 지원되지 않았다 typedef uint32_t uint_fast8_t. AFAIK에는 유형에 대한 대부분의 컴파일러 (gcc와 같은)에 추가 범위 제한이있는 메커니즘이 없으므로 해당 경우 uint_fast8_t와 정확히 동일하게 작동합니다 unsigned int.
Peter Cordes

( bool특별하고 범위가 0 또는 1로 제한되어 있지만 chargcc / clang 에서 헤더 파일로 정의되지 않은 내장 유형 입니다. 앞에서 말했듯이 대부분의 컴파일러에는 메커니즘이 있다고 생각하지 않습니다
Peter Cordes

1
어쨌든, uint_fast8_t그것은 효율적인 플랫폼에서 8 비트 유형을 사용하기 때문에 좋은 권장 사항 unsigned int입니다. (I하지 있는지 무엇을 실제로 해요 fast유형이 빨리 해야하는 위해 , 그리고 여부 캐시 공간 트레이드 오프는 그것의 일부가 될 예정이다.). x86은 메모리 소스를 사용하여 바이트 추가를 수행하는 경우에도 바이트 작업을 광범위하게 지원하므로 별도의 제로 확장로드 (매우 저렴한)를 수행 할 필요가 없습니다. gcc는 uint_fast16_tx86에서 64 비트 유형을 만듭니다 . 이는 대부분의 용도에 적합하지 않습니다 (vs. 32 비트). godbolt.org/g/Rmq5bv .
Peter Cordes

8

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

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

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에서 어셈블러 출력 .


원하지 if (x==c) foo(c) else foo(x)않습니까? 의 constexpr구현 을 잡기 위해서만 foo?
MSalters

@MSalters : 누군가가 그런 질문을 할 줄 알았습니다 !! 나는 전에이 기술을 constexpr생각해 냈으며 나중에 "업데이트"하려고하지 않았지만 (실제로는 걱정 constexpr하지 않아도되지만) 처음에하지 않은 이유는 컴파일러가 공통 코드로 쉽게 추출하고 분기를 일반적인 메소드 호출로 남겨두고 최적화하지 않기로 결정한 경우 분기를 제거하는 것이 더 쉬워집니다. c컴파일러가 c를 (유감스럽고 나쁜 농담으로) 두 개가 동일한 코드로 만드는 것이 실제로 어렵다는 것을 예상 했지만, 나는 이것을 결코 확인하지 못했습니다.
user541686

4

좀 더 표준 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]]함수가 실제로 리턴 한다는 경고를 받는다는 것입니다.


그것은 작동 clang, 내 원래의 솔루션을하지 않는 경우에 , 멋진 트릭과 +1 있도록. 그러나 모든 것은 컴파일러에 의존적입니다 (Peter Cordes가 우리에게 보여 주었 icc듯이 성능 을 악화시킬 수 있음). 그러나 여전히 보편적으로 적용 할 수는 없습니다. 또한 사소한 참고 사항 : 최적화가 가능하고unreachable 정의 가 가능하도록 정의 가 가능해야합니다 .
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.