다른 문장에서 GCC의 __builtin_expect의 장점은 무엇입니까?
만나다#define
그 안에서__builtin_expect
.
문서에는 다음과 같이 기재되어 있습니다.
기능 : :장능기 :
long __builtin_expect (long exp, long c)
하면 .
__builtin_expect
컴파일러에 분기 예측 정보를 제공합니다. 피드백)-fprofile-arcs
프로그래머는 프로그램이 실제로 어떻게 동작하는지를 예측하는 데 서투르기로 악명이 높기 때문입니다.그러나 이 데이터를 수집하기 어려운 애플리케이션도 있습니다.입니다.
exp
필수 표현식이어야 합니다.로는 '빌트인'은 '빌트인'이 될 입니다.exp == c
. : :if (__builtin_expect (x, 0)) foo ();
에서는, 「이러다」라고 부르는 하고 있지 .
foo
예상대로라면x
0 이 0 。
그렇다면 직접 사용해 보는 것은 어떨까요?
if (x)
foo ();
""를 사용합니다.__builtin_expect
다음에서 생성되는 어셈블리 코드를 상상해 보십시오.
if (__builtin_expect(x, 0)) {
foo();
...
} else {
bar();
...
}
이런 식으로 해야 할 것 같아요.
cmp $x, 0
jne _foo
_bar:
call bar
...
jmp after_if
_foo:
call foo
...
after_if:
되어 있는 을 볼 수 .bar
에 케이스가 붙다foo
(CC'R'MONT CHANGE: (C'RONT CHANGE: C'MONT CHANGE: C)점프는 이미 가져온 명령을 thrash하기 때문에 CPU 파이프라인을 더 잘 활용할 수 있습니다.
에 아래의)을 합니다.bar
케이스)가 파이프라인에 푸시됩니다. ★★★★★★★★★★★★★★★★★.foo
케이스가 거의 없고, 투신도 거의 없기 때문에 파이프라인을 파괴할 가능성은 거의 없습니다.
그럼 GCC 4.8이 이 컴파일을 통해 무엇을 하는지 봅시다.
Blagovest는 파이프라인을 개선하기 위해 브랜치 인버전을 언급했지만, 현재의 컴파일러는 실제로 그렇게 하고 있습니까?알아보자!
__builtin_expect
#include "stdio.h"
#include "time.h"
int main() {
/* Use time to prevent it from being optimized away. */
int i = !time(NULL);
if (i)
puts("a");
return 0;
}
GCC 4.8.2 x86_64 Linux를 사용하여 컴파일 및 디컴파일:
gcc -c -O3 -std=gnu11 main.c
objdump -dr main.o
출력:
0000000000000000 <main>:
0: 48 83 ec 08 sub $0x8,%rsp
4: 31 ff xor %edi,%edi
6: e8 00 00 00 00 callq b <main+0xb>
7: R_X86_64_PC32 time-0x4
b: 48 85 c0 test %rax,%rax
e: 75 0a jne 1a <main+0x1a>
10: bf 00 00 00 00 mov $0x0,%edi
11: R_X86_64_32 .rodata.str1.1
15: e8 00 00 00 00 callq 1a <main+0x1a>
16: R_X86_64_PC32 puts-0x4
1a: 31 c0 xor %eax,%eax
1c: 48 83 c4 08 add $0x8,%rsp
20: c3 retq
되지 않았습니다., 이 순서는 변경되지 않았습니다.처음에는puts
다음에 또 한 번.retq
아가가다
★★★★★★★★★★★★★★★★ __builtin_expect
「」를 합니다.if (i)
라이선스:
if (__builtin_expect(i, 0))
그 결과, 다음과 같이 됩니다.
0000000000000000 <main>:
0: 48 83 ec 08 sub $0x8,%rsp
4: 31 ff xor %edi,%edi
6: e8 00 00 00 00 callq b <main+0xb>
7: R_X86_64_PC32 time-0x4
b: 48 85 c0 test %rax,%rax
e: 74 07 je 17 <main+0x17>
10: 31 c0 xor %eax,%eax
12: 48 83 c4 08 add $0x8,%rsp
16: c3 retq
17: bf 00 00 00 00 mov $0x0,%edi
18: R_X86_64_32 .rodata.str1.1
1c: e8 00 00 00 00 callq 21 <main+0x21>
1d: R_X86_64_PC32 puts-0x4
21: eb ed jmp 10 <main+0x10>
puts
수의 was was was was was was was was was was was was was was was was was was was 로 이동했습니다.retq
★★★★★★★★★★★★★★★★★★!
새로운 코드는 기본적으로 다음과 같습니다.
int i = !time(NULL);
if (i)
goto puts;
ret:
return 0;
puts:
puts("a");
goto ret;
이 최적화는 다음과 같이 수행되지 않았습니다.-O0
.
보다 를 잘 바랍니다.__builtin_expect
요즘은 CPU가 정말 스마트해요.나의 순진한 시도가 여기 있다.
C++20[[likely]]
★★★★★★★★★★★★★★★★★」[[unlikely]]
C++20은 C++의 빌트인을 표준화하고 있습니다.if-else 스테이트먼트에서 C++20의 가능/비슷한 속성을 사용하는 방법 그들은 같은 일을 할 것입니다(말장난!).
★★★★★★★★★★★★★★★★★★의 아이디어__builtin_expect
컴파일러에게 보통 표현식이 c로 평가된다는 것을 알려주고 컴파일러가 그 경우에 맞게 최적화할 수 있도록 하는 것입니다.
누군가 그들이 영리하다고 생각하고 이 일을 함으로써 일을 가속화하고 있다고 생각했을 것이다.
불행하게도, 상황이 잘 이해되지 않는 한 (그들이 그런 짓을 하지 않았을 가능성이 높다) 상황을 더 악화시킬 수도 있다.문서에는 다음과 같은 내용이 기재되어 있습니다.
피드백)
-fprofile-arcs
프로그래머는 프로그램이 실제로 어떻게 동작하는지를 예측하는 데 서투르기로 악명이 높기 때문입니다.그러나 이 데이터를 수집하기 어려운 애플리케이션도 있습니다.
사용하지 않는 것이 __builtin_expect
다음과 같이 합니다.
- 성능 문제가 매우 심각합니다.
- 이미 시스템의 알고리즘을 적절하게 최적화했습니다.
- 특정 사례가 가장 가능성이 높다는 주장을 뒷받침하는 성능 데이터가 있습니다.
있는 첫 번째 를 컴파일러에게 요소를 합니다.x == 0
브런치가 유력합니다.즉, 브런치가 프로그램에 의해 자주 사용되는 브런치입니다.
이를 염두에 두고 컴파일러는 예상 조건이 유지되었을 때 최소한의 작업량을 요구하도록 조건을 최적화할 수 있으며, 예기치 않은 상황이 발생했을 경우 더 많은 작업을 수행해야 할 수도 있습니다.
컴파일 단계 및 그 결과 어셈블리에서 조건부 구현 방법을 살펴보고 한 지점이 다른 지점보다 작업량이 얼마나 적은지 확인합니다.
다만, 이 최적화는, 결과 코드의 차이가 비교적 작기 때문에, 문제의 조건이, 많이 호출되는 타이트한 내부 루프의 일부인 경우에만 현저한 효과를 기대할 수 있습니다.그리고 반대로 최적화하면 성능이 저하될 수 있습니다.
저는 당신이 질문하고 있다고 생각되는 질문에 대한 답변은 전혀 찾을 수 없습니다.
컴파일러에 분기 예측을 암시하는 더 편리한 방법이 있습니까?
질문의 제목으로 인해 다음과 같은 방법을 생각해 보았습니다.
if ( !x ) {} else foo();
될 'true'를 호출하지 할 수 .foo()
.
여기서의 문제는 컴파일러가 일반적으로 무엇을 상정할지를 모른다는 것입니다.따라서 이러한 기술을 사용하는 모든 코드는 신중하게 측정되어야 합니다(또한 컨텍스트가 변경되면 시간이 지남에 따라 감시될 수도 있습니다).
@Blagovest Buyukliev와 @Ciro에 따라 Mac에서 테스트합니다.어셈블리가 선명해 보여서 코멘트를 덧붙입니다.
는 " " " 입니다.gcc -c -O3 -std=gnu11 testOpt.c; otool -tVI testOpt.o
O3를 사용하면 __builtin_expect(i, 0)의 유무에 관계없이 동일하게 표시됩니다.
testOpt.o:
(__TEXT,__text) section
_main:
0000000000000000 pushq %rbp
0000000000000001 movq %rsp, %rbp // open function stack
0000000000000004 xorl %edi, %edi // set time args 0 (NULL)
0000000000000006 callq _time // call time(NULL)
000000000000000b testq %rax, %rax // check time(NULL) result
000000000000000e je 0x14 // jump 0x14 if testq result = 0, namely jump to puts
0000000000000010 xorl %eax, %eax // return 0 , return appear first
0000000000000012 popq %rbp // return 0
0000000000000013 retq // return 0
0000000000000014 leaq 0x9(%rip), %rdi ## literal pool for: "a" // puts part, afterwards
000000000000001b callq _puts
0000000000000020 xorl %eax, %eax
0000000000000022 popq %rbp
0000000000000023 retq
O2로 컴파일 할 때 __builtin_expect(i, 0)가 있는 경우와 없는 경우 모두 다릅니다.
첫 번째 제외
testOpt.o:
(__TEXT,__text) section
_main:
0000000000000000 pushq %rbp
0000000000000001 movq %rsp, %rbp
0000000000000004 xorl %edi, %edi
0000000000000006 callq _time
000000000000000b testq %rax, %rax
000000000000000e jne 0x1c // jump to 0x1c if not zero, then return
0000000000000010 leaq 0x9(%rip), %rdi ## literal pool for: "a" // put part appear first , following jne 0x1c
0000000000000017 callq _puts
000000000000001c xorl %eax, %eax // return part appear afterwards
000000000000001e popq %rbp
000000000000001f retq
이제 __builtin_expect(i, 0)를 사용합니다.
testOpt.o:
(__TEXT,__text) section
_main:
0000000000000000 pushq %rbp
0000000000000001 movq %rsp, %rbp
0000000000000004 xorl %edi, %edi
0000000000000006 callq _time
000000000000000b testq %rax, %rax
000000000000000e je 0x14 // jump to 0x14 if zero then put. otherwise return
0000000000000010 xorl %eax, %eax // return appear first
0000000000000012 popq %rbp
0000000000000013 retq
0000000000000014 leaq 0x7(%rip), %rdi ## literal pool for: "a"
000000000000001b callq _puts
0000000000000020 jmp 0x10
요약하면 __builtin_expect는 마지막 케이스에서 작동합니다.
대부분의 경우 지점 예측을 그대로 두고 걱정할 필요가 없습니다.
브런치가 많은 CPU 집약적인 알고리즘이 도움이 되는 경우가 있습니다.경우에 따라서는 점프로 인해가 현재의 CPU 프로그램캐시를 초과하여 CPU가 소프트웨어의 다음 부분을 실행할 때까지 대기할 수 있습니다.마지막에 있을 것 같지 않은 브랜치를 밀어넣으면 메모리를 닫아 두고 있을 것 같지 않은 경우에만 점프합니다.
언급URL : https://stackoverflow.com/questions/7346929/what-is-the-advantage-of-gccs-builtin-expect-in-if-else-statements
'programing' 카테고리의 다른 글
Vue.js.$set이 함수가 아닙니다. (0) | 2022.07.10 |
---|---|
'java.lang'을 어떻게 풀어요?"No Class Def Found Error?" (0) | 2022.07.10 |
동적 ID를 v-for loop + 문자열의 필드와 연결하는 방법은 무엇입니까? (0) | 2022.07.10 |
wildfly의 힙 메모리를 늘리는 방법 (0) | 2021.01.17 |
AngularJS-UI 라우터-프로그래밍 방식으로 상태 추가 (0) | 2021.01.17 |