왜 GCC 컴파일러가 일부 코드를 생략합니까?


9

왜 GCC 컴파일러가 이웃에서 동일한 코드를 유지하면서 내 코드의 일부를 자르는 이유를 이해할 수 없습니까?

C 코드 :

#define setb_SYNCO do{(PORTA|= (1<<0));} while(0);

ISR(INT0_vect){
    unsigned char i;

    i = 10;
    while(i>0)i--;   // first pause - omitted

    setb_SYNCO;
    setb_GATE;
    i=30;
    clrb_SYNCO;
    while(i>0)i--;  // second pause - preserved
    clrb_GATE;
}

LSS의 해당 부분 (컴파일러가 작성한 어셈블러 파일) ​​:

ISR(INT0_vect){
  a4:   1f 92           push    r1
  a6:   0f 92           push    r0
  a8:   0f b6           in  r0, 0x3f    ; 63
  aa:   0f 92           push    r0
  ac:   11 24           eor r1, r1
  ae:   8f 93           push    r24
    unsigned char i;

    i = 10;
    while(i>0)i--;

    setb_SYNCO;
  b0:   d8 9a           sbi 0x1b, 0 ; 27
    setb_GATE;
  b2:   d9 9a           sbi 0x1b, 1 ; 27
    i=30;
    clrb_SYNCO;
  b4:   d8 98           cbi 0x1b, 0 ; 27
  b6:   8e e1           ldi r24, 0x1E   ; 30
  b8:   81 50           subi    r24, 0x01   ; 1
    while(i>0)i--;
  ba:   f1 f7           brne    .-4         ; 0xb8 <__vector_1+0x14>
    clrb_GATE;
  bc:   d9 98           cbi 0x1b, 1 ; 27
}
  be:   8f 91           pop r24
  c0:   0f 90           pop r0
  c2:   0f be           out 0x3f, r0    ; 63
  c4:   0f 90           pop r0
  c6:   1f 90           pop r1
  c8:   18 95           reti

컴파일러가 그러한 코드가 더미이고 잘라내는 것을 알 수 있다고 가정 할 수 있지만 왜 코드 끝에 동일한 코드가 유지됩니까?

그러한 최적화를 막기위한 컴파일러 명령어가 있습니까?


1
컴파일러에게 단일 함수를 최적화하지 않도록 지시 할 수도 있습니다. 아마도 ISR로이 방법을 시도해 보는 것이 좋습니다. stackoverflow 에서이 질문을 참조하십시오 .
블라디미르 Cravero

2
로마, 귀하의 질문에 "c"태그를 추가하여 atmega를 제거했습니다. 한도 (5)가 있기 때문에 하나의 태그를 제거해야했으며 코드와 관련된 질문을 할 때 언어 이름을 태그로 추가하면 모든 코드 (Q & A)가 강조 표시되기 때문에 훌륭합니다.
Vladimir Cravero

5
일반적으로 C와 같은 고급 언어 는 결과 어셈블리와 1 : 1 관계 에 속하지 않도록 명시 적으로 설계되었습니다 . 타이밍을 올바르게 맞추기 위해 지시 사항을 세어야 할 경우 항상 답변에 따라 어셈블리에 의존해야합니다. 고급 언어의 요점은 컴파일러가 코드를 이전보다 더 빠르게 만들 수있는 자유가 있다는 것입니다. 레지스터 할당 및 분기 예측과 같은 세부 사항은 컴파일러가 훨씬 더 잘 남겨 둡니다. 프로그래머와 같은 경우를 제외하고는 프로그래머가 원하는 지침을 정확하게 알고 있습니다.
Cort Ammon

5
더 좋은 질문은 왜 GCC가 루프를 모두 최적화하지 않는 것 입니까?
Ilmari Karonen

5
Gcc는 먼저 루프를 풀고 나서 해당 코드가 쓸모가 없다는 것을 알게됩니다. 루프 크기가 ​​30이면 롤링이 어리 석고 gcc가 수행하지 않습니다. 더 높은 최적화 수준에서 둘 다 최적화됩니다.
Marc Glisse

답변:


9

한 의견에서는 "각 CPU 틱이 합당하다"고 말하고 있으므로 인라인 어셈블리를 사용하여 원하는대로 지연 루프를 만드는 것이 좋습니다. 이 솔루션은 다양한 것보다 우수 volatile하거나 의도가 분명-O0 하기 때문에 우수합니다 .

unsigned char i = 10;
__asm__ volatile ( "loop: subi    %0, 0x01\n\t"
                   "      brne    loop"
                   : "+rm" (i)
                   : /* no inputs */
                   : /* no dirty registers to decleare*/);

그 트릭을해야합니다. 휘발성은 컴파일러에게 "이것은 아무것도하지 않는다는 것을 알고, 그냥 지키고 믿어 라"는 것입니다. 세 asm "statement"는 매우 자명하다. r24 대신 어떤 레지스터라도 사용할 수있다. 컴파일러는 낮은 레지스터를 좋아하므로 높은 것을 사용하고 싶을 것이다. 첫 번째 후에는 :출력 (읽기 및 쓰기) c 변수 :를 나열해야 하며 아무것도 없습니다. 두 번째 후에는 입력 (일반적으로) c 변수를 다시 나열해야합니다. 없음은 없으며 세 번째 매개 변수는 쉼표로 구분 된 수정 된 레지스터 목록입니다 이 경우에는 r24입니다. ZERO물론 플래그가 변경되었으므로 상태 레지스터도 포함 해야하는지 잘 모르겠습니다 . 포함하지 않았습니다.

OP 요청에 따라 수정 된 답변을 수정합니다 . 몇 가지 메모.

"+rm"전에 (i)컴파일러를시키는 것을 수단에 난을 배치하기로 결정 m의 에 모리 또는에서 r에 egister. 컴파일러는 무료 인 경우 더 잘 최적화 할 수 있기 때문에 대부분의 경우에 좋습니다. 귀하의 경우에는 i를 레지스터로 강제하기 위해 r 제약 조건 만 유지하고 싶다고 생각합니다.


이것이 정말로 필요한 것 같습니다. 그러나 원래 답변에서 언급 한 c리터럴 대신 변수 를 허용하도록 답변을 수정할 수 10있습니까? asm 구성 의 올바른 사용에 관한 GCC 매뉴얼을 읽으려고 하지만 지금은 조금 모호합니다. 매우 감사하겠습니다!
Roman Matveev

1
@RomanMatveev 요청하신대로 편집
Vladimir Cravero

13

루프가 실제로 무언가를하도록 만들 수 있습니다. 컴파일러가 의미하는 바에 따르면 "이 루프는 아무 것도하지 않고 있습니다. 제거하겠습니다"라고합니다.

그래서 당신은 내가 자주 사용하는 구조를 시도 할 수 있습니다 :

int i;
for (i = 0; i < 10; i++) {
    asm volatile ("nop");
}

참고 : gcc 컴파일러의 모든 대상이 동일한 인라인 어셈블리 구문을 사용하는 것은 아닙니다. 대상에 맞게 조정해야 할 수도 있습니다.


귀하의 솔루션은 내 것보다 훨씬 더 우아해 보입니다 ... PRECISE 사이클 수가 필요할 때 내 것이 더 좋을까요? 내 말은, 물건의 전체가 특정 방식으로 컴파일되는 것이 보장되지는 않습니까?
Vladimir Cravero

8
C를 사용한다는 단순한 사실은 사이클을 보장 할 수 없음을 의미합니다. 정확한 사이클 계산이 필요한 경우 ASM이 유일한 방법입니다. -funroll-loops를 사용하지 않는 경우와 그렇지 않은 경우보다 C 루프를 사용하면 타이밍이 달라집니다.
Majenko

그래, 내 생각이야 충분히 높은 i 값 (100 이상)으로 HW 지연을 수행하면 솔루션이 가독성을 향상시키면서 거의 동일한 결과를 산출한다고 생각합니다.
Vladimir Cravero

6

그렇습니다. 변수 i를 휘발성으로 선언하면 컴파일러에게 i를 최적화하지 않도록 지시합니다.


1
내 의견으로는 이것이 사실이 아닙니다.
Vladimir Cravero

1
@VladimirCravero, 그 말은 무슨 뜻입니까? 좀 더 명확하게 말씀해 주시겠습니까?
Roman Matveev

2
내가 의미하는 바는 컴파일러가 무엇을하는지 잘 모르겠다는 것입니다. volatile 변수를 선언하면 컴파일러가 다른 곳에서 변경 될 수 있으므로 실제로 변경해야한다고 알려줍니다.
블라디미르 Cravero

1
@Roman Matveev register unsigned char volatile i __asm__("r1");어쩌면?
a3f

2
i휘발성으로 선언 하면 모든 것이 해결됩니다. 이것은 C 표준 5.1.2.3에 의해 보장됩니다. i휘발성 인 경우 적합한 컴파일러는 이러한 루프를 최적화하지 않아야합니다 . 다행히 GCC는 적합한 컴파일러입니다. 불행히도 표준을 준수하지 않는 많은 C 컴파일러가 있지만이 특정 질문과 관련이 없습니다. 인라인 어셈블러가 필요하지 않습니다.
Lundin

1

첫 번째 루프 이후는 i상수입니다. i루프 의 초기화 와 루프는 상수 값만 생성합니다. 표준의 어떤 것도이 루프를 그대로 컴파일해야한다고 지정하지 않습니다. 표준은 타이밍에 대해서도 언급하지 않습니다. 컴파일 된 코드는 루프가 존재하는 것처럼 동작해야합니다. 이 최적화가 표준에 따라 수행되었다고 확실하게 말할 수는 없습니다 (타이밍은 계산되지 않음).

두 번째 루프도 삭제해야합니다. 나는 그것이 버그 (또는 누락 된 최적화)라고 생각하지 않습니다. 루프 i가 일정한 제로 후 . 코드는 i0 으로 설정 하여 바꿔야 합니다.

나는 i(불투명 한) 포트 액세스를 수행하는 것이 영향을 줄 수 있기 때문에 GCC가 순전히 주변에 있다고 생각합니다 i.

사용하다

asm volatile ("nop");

루프가 무언가를한다고 믿도록 GCC를 속입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.