정수 범위를 지정하여 최적화 프로그램에 힌트를 줄 수 있습니까?
나는 사용하고 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에 포함시켜야합니다. 코드를 복제하지 마십시오.
예:
For this technique to work you need to be a little lucky -- there are cases where the compiler decides not to evaluate things statically, and they're kind of arbitrary. But when it works, it works well:
#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);
}
Just use -O3 and notice the pre-evaluated constants 0x20 and 0x30e in the assembler output.
I am just pitching in to say that if you may want a solution that is more standard C++, you can use the [[noreturn]] attribute to write your own unreachable.
So I'll re-purpose deniss' excellent example to demonstrate:
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;
}
}
Which as you can see, results in nearly identical code:
detail::unreachable():
rep ret
func(int):
movl $17, %eax
ret
The downside is of course, that you get a warning that a [[noreturn]] function does, indeed, return.
'Programming' 카테고리의 다른 글
| SQL 절“GROUP BY 1”은 무엇을 의미합니까? (0) | 2020.05.24 |
|---|---|
| Node.js 로깅 (0) | 2020.05.24 |
| 요소의 id를 얻다 (0) | 2020.05.24 |
| 뷰 또는 부분 뷰에서 레이아웃 헤드에 CSS 또는 JavaScript 파일 추가 (0) | 2020.05.24 |
| IList 사용시기 및 List 사용시기 (0) | 2020.05.24 |