나는 사용하고 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 프로그램 과 같이 작동 하며 릴리스 빌드에서는 가정을 사용하여 최적화 된 코드를 생성합니다.
참고하지만, 일반을 대신 할 수는 없습니다 것을 assert
– cond
릴리스의 유적이 빌드, 그래서 당신은 그런 짓을해서는 안됩니다 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]]
함수가 실제로 리턴 한다는 경고를 받는다는 것입니다.