IT이야기

GCC가 비지 대기 루프를 최적화하지 않도록 하려면 어떻게 해야 합니까?

cyworld 2022. 6. 4. 08:13
반응형

GCC가 비지 대기 루프를 최적화하지 않도록 하려면 어떻게 해야 합니까?

Atmel AVR 마이크로컨트롤러용 C코드 펌웨어를 작성하고 싶습니다.GCC의 컴파일러의 「 」, 「 」)를 하게 하고 .-Os ★★★★★★★★★★★★★★★★★」-O2를 유효하게 하지 않을 이유가 없습니다.또한 수동으로 어셈블리를 쓰는 것보다 훨씬 빠른 속도로 어셈블리를 생성할 수 있습니다.

하지만 최적화되지 않은 작은 코드를 원합니다.기능 실행을 조금 늦추고 싶기 때문에 do-nothing 루프를 작성하여 시간을 낭비하고 싶었습니다.정확할 필요 없이 조금만 기다려 주세요.

/* How to NOT optimize this, while optimizing other code? */
unsigned char i, j;
j = 0;
while(--j) {
    i = 0;
    while(--i);
}

느리기 에, AVR 를 .i ★★★★★★★★★★★★★★★★★」jCPU ★★★★★★★★★★★★★★★★★★★★★★★★★★★」


업데이트: AVR Libc에서 util/delay.h 및 util/delay_basic.h를 찾았습니다.대부분의 경우 이러한 기능을 사용하는 것이 더 나을 수 있지만, 이 질문은 여전히 유효하고 흥미롭습니다.


관련 질문:

dmckee의 답변 링크에 따라 이 답변을 개발했지만, 답변과는 다른 접근 방식을 취합니다.

GCC의 Function Attributes 문서에는 다음 사항이 기재되어 있습니다.

noinline이 함수 Atribut은 함수를 인라인용으로 고려하지 않도록 합니다.함수에 부작용이 없는 경우, 함수 호출이 라이브인 경우에도 함수 호출이 최적화되는 인라인 이외의 최적화가 있습니다. , 「」를 붙입니다.asm ("");

이것은 나에게 흥미로운 아이디어를 주었다.「 」를 추가하는 에, 「 」를 추가해 주세요.nop내부 루프의 명령어를 입력하기 위해 빈 어셈블리 코드를 추가해 보았습니다.

unsigned char i, j;
j = 0;
while(--j) {
    i = 0;
    while(--i)
        asm("");
}

리고고 그과효효 !었!!!! 않으며, 의 루프는 .nop이치노

게다가, 만약 당신이volatile하고 gcc의 RAM 합니다.ldd ★★★★★★★★★★★★★★★★★」std임시 레지스터에 복사할 수 있습니다. 이 어프로치에서는, 「」는 사용하지 .volatile이치노


업데이트: 코드를 컴파일하는 경우-ansi ★★★★★★★★★★★★★★★★★」-std 경우, 을 치환해야 합니다.asm( 「」)__asm__를 참조해 주세요.

에도 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 이런 것도 쓸 수 있어요.__asm__ __volatile__("")조립문을 우리가 배치한 위치에서 실행해야 하는 경우(즉, 최적화로 루프에서 이동해서는 안 됩니다).

i ★★★★★★★★★★★★★★★★★」j(예: 서 variables변 variables variables variables variables variables variables ) 。volatile이렇게 하면 컴파일러가 이러한 변수와 관련된 코드를 최적화할 수 없습니다.

unsigned volatile char i, j;

»__asm__하다: data dependencies: "데이터 "

다음과 같이 합니다.

메인

int main(void) {
    unsigned i;
    for (i = 0; i < 10; i++) {
        __asm__ volatile("" : "+g" (i) : :);

    }
}

컴파일 및 분해:

gcc -O3 -ggdb3 -o main.out main.c
gdb -batch -ex 'disas main' main.out

출력:

   0x0000000000001040 <+0>:     xor    %eax,%eax
   0x0000000000001042 <+2>:     nopw   0x0(%rax,%rax,1)
   0x0000000000001048 <+8>:     add    $0x1,%eax
   0x000000000000104b <+11>:    cmp    $0x9,%eax
   0x000000000000104e <+14>:    jbe    0x1048 <main+8>
   0x0000000000001050 <+16>:    xor    %eax,%eax
   0x0000000000001052 <+18>:    retq 

은, 변수 「Loop variable」에 있기 합니다.i에서 제시된 바와 같이 C++에서 스테이트먼트 순서를 적용하여 원하는 루프를 생성합니다.

마크는 ★★★i인라인 어셈블리의 입력 및 출력으로 사용됩니다.로서 GCC의 을 알 수.i그래서 최적화가 안 될 것 같아요.

으로 __asm__

불량.c

int main(void) {
    unsigned i;
    for (i = 0; i < 10; i++) {
        __asm__ volatile("");
    }
}

루프가 완전히 제거되어 다음과 같이 출력됩니다.

   0x0000000000001040 <+0>:     xor    %eax,%eax
   0x0000000000001042 <+2>:     retq

, 「 」, 「 」라고 하는 도 주의해 주세요.__asm__("") ★★★★★★★★★★★★★★★★★」__asm__ volatile("")출력 오퍼랜드가 없기 때문에 다음과 같아야 합니다.asm, asm volatile 및 clobbering 메모리의 차이

다음과 같이 대체하면 무슨 일이 일어나고 있는지 알 수 있습니다.

__asm__ volatile("nop");

그 결과, 다음과 같이 됩니다.

   0x0000000000001040 <+0>:     nop
   0x0000000000001041 <+1>:     nop
   0x0000000000001042 <+2>:     nop
   0x0000000000001043 <+3>:     nop
   0x0000000000001044 <+4>:     nop
   0x0000000000001045 <+5>:     nop
   0x0000000000001046 <+6>:     nop
   0x0000000000001047 <+7>:     nop
   0x0000000000001048 <+8>:     nop
   0x0000000000001049 <+9>:     nop
   0x000000000000104a <+10>:    xor    %eax,%eax
   0x000000000000104c <+12>:    retq

즉, GCC가 루프로 전개된 것을 알 수 있습니다.nop이 경우 루프가 충분히 작았기 때문에 루프가 발생합니다.

빈칸에 하면 빈칸에 의존하게 됩니다.__asm__트레이드오프를 빈가 항상 됩니다.__asm__ volatile("");0으로 하다

noinline 루프

컴파일 시에 루프 사이즈를 알 수 없는 경우 풀 롤링은 할 수 없지만, GCC는 여전히 청크로 롤을 풀기로 결정할 수 있기 때문에 지연이 일관되지 않게 됩니다.

Denilson의 답변과 함께 비지 루프 함수는 다음과 같이 기술할 수 있습니다.

void __attribute__ ((noinline)) busy_loop(unsigned max) {
    for (unsigned i = 0; i < max; i++) {
        __asm__ volatile("" : "+g" (i) : :);
    }
}

int main(void) {
    busy_loop(10);
}

분해 위치:

Dump of assembler code for function busy_loop:
   0x0000000000001140 <+0>:     test   %edi,%edi
   0x0000000000001142 <+2>:     je     0x1157 <busy_loop+23>
   0x0000000000001144 <+4>:     xor    %eax,%eax
   0x0000000000001146 <+6>:     nopw   %cs:0x0(%rax,%rax,1)
   0x0000000000001150 <+16>:    add    $0x1,%eax
   0x0000000000001153 <+19>:    cmp    %eax,%edi
   0x0000000000001155 <+21>:    ja     0x1150 <busy_loop+16>
   0x0000000000001157 <+23>:    retq   
End of assembler dump.
Dump of assembler code for function main:
   0x0000000000001040 <+0>:     mov    $0xa,%edi
   0x0000000000001045 <+5>:     callq  0x1140 <busy_loop>
   0x000000000000104a <+10>:    xor    %eax,%eax
   0x000000000000104c <+12>:    retq   
End of assembler dump.

★★★★★★★★★★★★★★★★★★.volatile이 경우 출력 변수가 있기 때문에 어셈블리에 부작용이 있을 수 있음을 표시하기 위해 필요했습니다.

이중 루프 버전은 다음과 같습니다.

void __attribute__ ((noinline)) busy_loop(unsigned max, unsigned max2) {
    for (unsigned i = 0; i < max2; i++) {
        for (unsigned j = 0; j < max; j++) {
            __asm__ volatile ("" : "+g" (i), "+g" (j) : :);
        }
    }
}

int main(void) {
    busy_loop(10, 10);
}

GitHub 업스트림

관련 스레드:

Ubuntu 19.04, GCC 8.3.0에서 테스트 완료.

컴파일러 업그레이드 등에 의해 이 접근법이 완전히 잘못되어 쉽게 고장난다는 것은 왜 아직 언급되지 않았는지 모르겠습니다.대기할 시간 값을 결정하고 원하는 값을 초과할 때까지 현재 시간을 폴링하는 것이 훨씬 더 합리적입니다.에서는 x86을 사용할 수 .rdtsc위해서라면, 이 좋은 은, 「아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 네.clock_gettime(POSIX OS).에서는 "x86_64 Linux"의 됩니다.clock_gettime를 사용합니다.rdtsc할 수 있는 시스콜clock_nanosleep★★★★★★★★★★★…

avr 버전의 컴파일러가 풀셋을 지원하는지(링크에 있는 흥미로운 것은 모두 gcc 버전 4.4부터) 즉석에서 알 수 없지만 보통 여기서부터 시작합니다.

GCC 4.7.0에서는 빈 ASM이 O3(-O2에서는 시도하지 않음)로 최적화되어 레지스터 또는 휘발성으로 i++를 사용하면 (내 경우) 퍼포먼스가 크게 저하됩니다.

내가 한 것은 컴파일러가 "메인 프로그램"을 컴파일할 때 볼 수 없었던 다른 빈 함수와 링크하는 것이었다.

기본적으로 다음과 같습니다.

이 함수가 선언된 "helper.c"가 생성되었습니다(빈 함수).

void donotoptimize(){}

후 컴파일 되었습니다.gcc helper.c -c -o helper.o에.

while (...) { donotoptimize();}

연결하면 .gcc my_benchmark.cc helper.o.

이것은 나에게 최고의 결과를 가져다 주었다(그리고 내 생각에 오버헤드는 전혀 없지만, 프로그램이 없으면 작동하지 않기 때문에 테스트할 수 없다:).

나는 그것이 icc와도 작동해야 한다고 생각한다.링크 최적화를 이노블로 하면 안 될 수도 있지만 gcc를 사용하면 그렇게 됩니다.

휘발성 ASM을 넣으면 도움이 될 거예요.자세한 내용은 이쪽에서 보실 수 있습니다.

http://www.nongnu.org/avr-libc/user-manual/optimization.html

Windows 로 작업하고 있는 경우는, 이하에 자세하게 설명하듯이, 코드를 플러그마로 설정해 볼 수도 있습니다.

https://www.securecoding.cert.org/confluence/display/cplusplus/MSC06-CPP.+Be+aware+of+compiler+optimization+when+dealing+with+sensitive+data

이게 도움이 됐으면 좋겠다.

이 루프를 별도의 .c 파일에 저장하고 하나의 파일을 최적화하지 마십시오.게다가 그 루틴을 어셈블러에 기입해, 어느 쪽이든 옵티마이저가 관여하지 않는 C 로부터 호출하는 것이 좋습니다.

가끔 휘발성 있는 일을 하지만 보통 asm 함수를 만듭니다.이 함수는 최적기가 for/while 루프를 조여주는 함수에 대한 호출을 되돌립니다.다만, 더미 함수에 대한 모든 호출을 실시할 필요가 있기 때문에 최적화되지 않습니다.Denilson Sa의 nop 답변도 마찬가지지만 더 빡빡하게...

register 키워드를 사용할 수도 있습니다.레지스터로 선언된 변수는 CPU 레지스터에 저장됩니다.

고객님의 경우:

register unsigned char i, j;
j = 0;
while(--j) {
    i = 0;
    while(--i);
}

언급URL : https://stackoverflow.com/questions/7083482/how-to-prevent-gcc-from-optimizing-out-a-busy-wait-loop

반응형