2011-03-22 3 views
3

ARM9 프로세서 용 로깅 C 코드를 작성하고 있습니다. 이 코드는 동적 모듈이있는 경우 일부 데이터를 기록합니다. 모듈은 일반적으로 프로덕션 빌드에는 존재하지 않지만 로깅 코드는 항상 컴파일됩니다. 고객이 버그를 발견하면이 모듈을로드 할 수 있고 로깅 코드는 디버깅 정보를 덤프합니다.RVCT4.0을 사용하는 Arm9의 정적 분기 예측

모듈이 없을 때 로깅 코드는 최소한의 영향을 주어야하므로 모든주기가 중요합니다. RVCT 4.0은 다음과 같습니다 코드 생성에 최적화를

__inline void log_some_stuff(Provider *pProvider, other args go here...) 
{ 
    if (NULL == pProvider) 
     return; 
    ... logging code goes here ... 
} 

: 일반적으로 로깅 코드는 다음과 같이 보입니다

ldr  r4,[r0,#0x2C]  ; pProvider,[r0,#44] 
cmp  r4,#0x0   ; pProvider,#0 
beq  0x23BB4BE (usually taken) 
... logging code goes here... 
... regular code starts at 0x23BB4BE 

이 프로세서는 더 분기 예측이 없습니다, 그리고 나의 이해는 점이다 분지가 채취 될 때마다 2 사이클의 벌칙이 부과됩니다 (분과회를하지 않으면 벌칙이 없습니다).

나는 일반적인 경우를 원합니다. 여기서 NULL == pProvider은 빠른 케이스가되고, 분기가 수행되지 않습니다. RVCT 4.0에서 이와 같은 코드를 생성하려면 어떻게합니까?

가 나는 다음과 같이 __builtin_expect를 사용하여 시도했다 :

if (__builtin_expect(NULL == pProvider, 1)) 
    return; 

불행하게도,이 생성 된 코드에 영향을주지 않습니다. __builtin_expect을 잘못 사용하고 있습니까? 다른 방법이 있습니까 (인라인 어셈블리가 없기를 바랍니다).

+0

이 코드는 의미가 없습니다. pProvider가 첫 번째 arg이고 역 참조를 거치지 않고 NULL에 대해 검사되는 경우 컴파일러는 pProvider가 이미 r0에 있으므로 ldr이 필요하지 않습니다. Provider 유형으로의 오프셋을보고있는 것처럼 보입니다. –

+0

@Variable Length Coder, 로그 함수는 인라인되어 있으므로 인수에 대해 걱정할 필요가 없습니다. ldr 명령은 일부 데이터 구조에서 pProvider를 가져옵니다. 함수가 인라인되지 않으면 호출 전에 발생합니다. –

+0

참고로 RVCT 3.0 설명서에서 [__builtin_expect] (http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0202h/Cjabddedbde.html)에 대한 지원을 나타냅니다. –

답변

1

따라서 분기 예측기가없고 분기를 수행 할 때 두 사이클의 패널티가 발생하면 그냥 프로그램을 다시 작성하지 않는 것이 좋을까요? 컴파일 "수"

__inline void log_some_stuff(Provider *pProvider, other args go here...) 
{ 
    if (pProvider) { 
     ... logging code goes here ... 
    } 
} 

것을 (물론 실제로는 위의 예는 이미 "올바른"코드가 발생할 것이라고 생각할 것입니다,하지만 우리는 시도 할 수 있습니다)에 : 당신이 만약

ldr  r4,[r0,#0x2C]  ; pProvider,[r0,#44] 
cmp  r4,#0x0   ; pProvider,#0 
bneq  logging_code (usually NOT taken) 
... regular code here 
logging_code: .. well anywhere 

' 행운이지만, 지금도 컴파일러가 변경 될 때마다 변경 될 수 있으며 사용하는 컴파일러에 관계없이 어셈블리 코드가 생성되는지 여부는 알 수 없습니다. 어쨌든 인라인 어셈블리로 작성 하시겠습니까? 그렇게 많은 코드와 gcc (VC뿐만 아니라 다른 사람들도 그렇게한다고 가정 함)를 아주 쉽게 만들 수 있습니다. 가장 쉬운 당신은 당신의 로깅 코드를 추가하는 방법을 정의하고 그 호출하지 거라고합니다 (ARM ABI에 대해 아무 생각, 그래서 당신은 작성해야 그 자신)

+0

코드를 로깅하지만 RVCT는 동일한 코드를 생성합니다. 인라인 어셈블리로 다시 작성하는 것은 길 일지 모르지만, 컴파일러가이를 존중할 지 모르겠다. 블록 레이아웃 문제이기 때문이다. 어쨌든 귀하의 제안에 감사드립니다. –

+0

그래, 컴파일러 최적화 문제가 될 수 있습니다. RVCT와 함께 사용하지는 않지만 gcc에서는 컴파일러가 코드를 혼자 남겨 둘지 확인하기 위해 어셈블리에 __ volatile (__)을 사용할 수 있습니다 (괄호는 없지만 굵게 표시됩니다). 키워드는 기본적으로 이동/삭제하지 않음을 의미합니다 코드). – Voo

1

당신은 다음과 같은 구조를 사용하는 경우 :

void log_some_stuff_implementation(Provider *pProvider, int x, int y, char const* str); 


__inline void log_some_stuff(Provider *pProvider, int x, int y, char const* str) 
{ 
    if (__builtin_expect(pProvider != NULL, 0)) { 
     log_some_stuff_implementation(pProvider, x, y, str); 
    } 

    return; 
} 
을 일반적인 경우에 따라서

// r0 already has the Provider* in it - r2 has a value that indicates whether 
//  r0 was loaded with a valid pointer or not 
cmp r2, #0 
ldrne r3, [r1, #0] 
addne r1, r2, #1 
ldrneb r2, [r3, #0] @ zero_extendqisi2 
blne log_some_stuff_implementation 

(공급자 *이 NULL), 4 개 지침은 다음과 같습니다 -O2

GCC 4.5.2 log_some_stuff()에 호출 (적어도 내 간단한 테스트를위한) 다음 코드를 생성 사용되었지만 조건부로 인해 실행되지 않았습니다. bu ARM의 파이프 라인은 플러시되지 않습니다. 아마도 로깅 코드를 실제로 실행하지 않는 일반적인 경우에 대해 얻을 수있는 것만 큼 좋을 것 같습니다.

필자는 실제로 로깅 작업을 수행하는 코드가 별도의 함수에서 비 인라인으로 수행되므로 컴파일러에서 해당 기능을 적절히 설정하고 호출 할 수 있도록 조건부로 실행되는 몇 가지 지침 . 실제 로깅 코드는 최적화 할 필요가 없기 때문에 인라인 될 이유가 없습니다. 일반적인 경우는 아니며 아마도 실제 작업을 수행하는 코드 일 것입니다. 따라서 함수 호출의 오버 헤드는 수용 가능해야합니다 (적어도 그것이 내 가정입니다).

덧붙여서, 단순한 테스트에서는, __builtin_expect()을 생략해도 동일한 코드 순서 (또는 본질적으로 같은 순서)가 생성되지만, 간단한 간단한 테스트보다 복잡한 순서에서는 내장 함수가 도움이 될 것이라고 상상합니다 컴파일러 아웃. 그래서 아마 그것을에두고 싶지만, 나는 또한 아마 리눅스 커널의 매크로처럼 더 읽기 버전을 사용하십시오 :

#define likely(x)  __builtin_expect((x),1) 
#define unlikely(x)  __builtin_expect((x),0) 
+0

슬프게도 우리는 GCC가 아니라 RVCT를 사용하고 있습니다. RVCT 설명서는 __builtin_expect가 작동해야 함을 나타내지 만 효과가 없습니다. 내가 가능성이/가능성은 매크로에 대한 조언을 취할 것입니다. –

+0

GCC에서도'__builtin_expect()'에 관계없이 동일한 코드가 생성되었지만 테스트는 간단했습니다. 어쨌든 그것을 떠나는 것에 대한 나의 의견은 더 복잡한 시나리오에서 컴파일러를 도울 수 있기를 바랍니다. 난 핵심 인라인'log_some_stuff()'함수를 컴파일러가 조건부 opcode를 사용하여 일반적인 경우에 단지 몇 개의 NOP 만 효과적으로 사용할 수있을만큼 짧게 유지하는 것이라고 생각한다. 인라인 된 함수가 너무 길면 NOP는 분기 후에 파이프 라인을 다시 채우는 것처럼 많은 사이클을 수행하므로 분기를 피할 수는 없습니다. –

0

지사 최적화는 당신에게 약간을 얻을 것입니다. 다음을 수행하면 더 많은 것을 얻을 수 있습니다.

#define log_some_stuff(pProvider, other_arg) \ 
     do {\ 
      if(pProvider != NULL) \ 
       real_log_some_stuff(pProvider, other_arg); \ 
     } \ 
     while(0) 

이 코드는 NULL 코드를 호출 할 때마다 인라인으로 표시됩니다. 그건 손실처럼 보일지 모르지만 컴파일러가 레지스터, 분기 자체를 푸시하고 r0-r3 및 lr이 간단한 NULL 검사로 무효화되는 것을 포함하여 함수 호출의 오버 헤드를 피할 수 있다는 것입니다. 어쨌든해야했다). 전반적으로, 이것은 한 명령을 일찍 끝내면 절약 할 수있는 단일 사이클보다 훨씬 많은 이득을 얻을 것이라고 확신합니다.

+1

함수를 인라인하는 대신 매크로를 사용하면 (이미 수행중인 작업) 동일한 결과를 얻고 매크로를 사용하여 실행하는 모든 끔찍한 문제를 해결할 수 있습니다. 장점이 보이지 않는다. – Voo

+0

이 함수가 이미 인라인되어 있음을 확인했다. 위에 게시 된 지침은 말 그대로 정상적인 경우 로깅 기능에 대해 실행되는 유일한 세 가지 지침입니다. –

0

당신은 goto를 사용할 수 있습니다

__builtin_expect를 사용
__inline void log_some_stuff(Provider *pProvider, other args go here...) 
{ 
    if (pProvider != NULL) 
     goto LOGGING; 
    return; 
LOGGING: 
    ... logging code goes here ... 
} 

은 쉽게하지만 난 RVCT 그것을 가지고 확실하지 않다.