GCC 4.4 : GCC의 스위치 / 사례 문을 방지하십시오.
-
15-09-2020 - |
문제
strong> 이것은 4.4 이전의 GCC 버전의 문제 일뿐입니다. 이것은 GCC 4.5에서 수정되었습니다.
는 컴파일러에 스위치에서 사용 된 변수가 제공된 경우에 제공된 경우가 있으십니까? 특히 그것이 작은 범위이고 점프 테이블이 생성 된 경우.
extern int a;
main()
{
switch (a & 0x7) { // 0x7 == 111 values are 0-7
case 0: f0(); break;
case 1: f1(); break;
case 2: f2(); break;
case 3: f3(); break;
case 4: f4(); break;
case 5: f5(); break;
case 6: f6(); break;
case 7: f7(); break;
}
}
.
gcc_unreachable ()을 사용하여 gcc_unreachable ()을 사용하여 열거 형을 사용하여 낮은 비트 (예 참조)로 XOR'ing을 시도했습니다. 생성 된 코드는 변수가 범위 내에 있는지 항상 점검하고 무의미한 지점 조건부를 추가하고 점프 테이블 계산 코드를 멀리 이동합니다.
참고 : 이것은 디코더의 가장 안쪽 루프, 성능 문제가 크게 중요합니다.
기본 분기가 결코 취하지 않는다고 GCC에 알릴 방법이 없습니다. 그것이 그 증명할 수있는 경우 기본 분기를 생략하지만 가치는 초기 조건 검사를 기반으로 범위를 벗어나지 않습니다.
그래서 GCC가 변수를 증명할 수 있고 위의 예에서는 기본 분기가 없습니까? (조건부 분기를 추가하지 않고) 물론.)
업데이트
-
이것은 GCC 4.2가있는 Snow Leopard에있었습니다 (Xcode에서 기본값.) Linux에서 GCC 4.4 / 4.3 (Nathon 및 Jens Gustedt 가보고)
리> -
예제의 기능은 가독성이 있거나 인라인 또는 단지 진술을하는 것으로 생각합니다. x86에서 함수 호출을 만드는 것은 비싸다.
또한 메모에서 언급 한 것처럼 예제는 데이터 (BIG DATA) 루프 내부에 속합니다.
GCC 4.2 / OS X가있는 생성 된 코드는 다음과 같습니다.
.[...] andl $7, %eax cmpl $7, %eax ja L11 mov %eax, %eax leaq L20(%rip), %rdx movslq (%rdx,%rax,4),%rax addq %rdx, %rax jmp *%rax .align 2,0x90 L20: .long L12-L20 .long L13-L20 .long L14-L20 .long L15-L20 .long L16-L20 .long L17-L20 .long L18-L20 .long L19-L20 L19: [...]
문제점은
cmp $7, %eax;
ja L11;
에 있습니다.
-
OK, 추악한 솔루션을 사용하고 스위치없이 다른 버전을 사용하여 4.4 미만의 GCC 버전을 추가하고 GOTO 및 GCC의 && Label Extensions를 사용합니다.
.static void *jtb[] = { &&c_1, &&c_2, &&c_3, &&c_4, &&c_5, &&c_6, &&c_7, &&c_8 }; [...] goto *jtb[a & 0x7]; [...] while(0) { c_1: // something break; c_2: // something break; [...] }
레이블 배열은 정적이므로 모든 통화마다 계산되지 않습니다.
해결책
나는 -O5와 -FNO 인라인 (내 F0-F7 기능이 사소한)과 비교할 수있는 것을 편성하려고 시도했다.
8048420: 55 push %ebp ;; function preamble
8048421: 89 e5 mov %esp,%ebp ;; Yeah, yeah, it's a function.
8048423: 83 ec 04 sub $0x4,%esp ;; do stuff with the stack
8048426: 8b 45 08 mov 0x8(%ebp),%eax ;; x86 sucks, we get it
8048429: 83 e0 07 and $0x7,%eax ;; Do the (a & 0x7)
804842c: ff 24 85 a0 85 04 08 jmp *0x80485a0(,%eax,4) ;; Jump table!
8048433: 90 nop
8048434: 8d 74 26 00 lea 0x0(%esi,%eiz,1),%esi
8048438: 8d 45 08 lea 0x8(%ebp),%eax
804843b: 89 04 24 mov %eax,(%esp)
804843e: e8 bd ff ff ff call 8048400
8048443: 8b 45 08 mov 0x8(%ebp),%eax
8048446: c9 leave
.
최적화 수준을 가지고 노는 것을 시도 했습니까?
다른 팁
스위치 대신 함수 포인터 배열을 사용할 수 있습니까?
#include <stdio.h>
typedef void (*func)(void);
static void f0(void) { printf("%s\n", __FUNCTION__); }
static void f1(void) { printf("%s\n", __FUNCTION__); }
static void f2(void) { printf("%s\n", __FUNCTION__); }
static void f3(void) { printf("%s\n", __FUNCTION__); }
static void f4(void) { printf("%s\n", __FUNCTION__); }
static void f5(void) { printf("%s\n", __FUNCTION__); }
static void f6(void) { printf("%s\n", __FUNCTION__); }
static void f7(void) { printf("%s\n", __FUNCTION__); }
int main(void)
{
const func f[8] = { f0, f1, f2, f3, f4, f5, f6, f7 };
int i;
for (i = 0; i < 8; ++i)
{
f[i]();
}
return 0;
}
. switch
변수를 비트 필드로 선언 시도 했습니까?
struct Container {
uint16_t a:3;
uint16_t unused:13;
};
struct Container cont;
cont.a = 5; /* assign some value */
switch( cont.a ) {
...
}
.
이 작품을 희망합니다!
나는 시도하지 않았지만, gcc_unreachable
가 __builtin_unreachable
와 동일한 것을 잘 모르겠습니다.Googling in gcc_unreachable
는 GCC 자체의 개발을위한 어설 션 도구로 설계된 것으로 보입니다. 아마도 분기 예측 힌트가 포함되어있는 반면, __builtin_unreachable
는 기본 블록을 삭제하는 것과 같은 것처럼 보이는 프로그램을 즉시 정의하지 않습니다..
아마도 첫 번째 또는 마지막 경우에 default
레이블을 사용하는 것입니다.
이 질문은 확실히 우리에게 겉보기에 겉보기에 겉보기에 분명한 확실한 컴파일러 최적화의 관점에서 흥미롭게 흥미 롭습니다. 그리고 나는 똑바로 솔루션을 일으키기 위해 상당한 시간을 보냈습니다.
, 나는 를 인정해야한다. 나는이 추가적인 지시가 실제로, 특히 새로운 MAC에서 측정 가능한 성능 차이를 초래할 것이라는 점을 매우 회의적으로 인정해야한다.상당한 양의 데이터가있는 경우 I / O 바인딩이되며 단일 명령은 결코 병목 현상이 아닙니다.작은 양의 데이터가있는 경우, 단일 지시가 병목 현상이되기 전에 반복적으로 계산의 로트 로트 을 수행해야합니다.
실제로 성능 차이가 있음을 보여주기 위해 몇 가지 코드를 게시 하시겠습니까?또는 귀하의 작업 코드와 데이터를 설명하십시오.