커널에서 가능한 호출과 불가능한 호출의 차이점은 무엇입니까?

커널에서 가능한 호출과 불가능한 호출의 차이점은 무엇입니까?

커널에서 가능한 호출과 불가능한 호출의 차이점은 무엇입니까? 커널 소스 코드를 검색하는 동안 다음 문장을 발견했습니다.

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

누군가 이것을 설명할 수 있나요?

답변1

이는 GCC의 컴파일러 힌트입니다. 조건문에서 분기를 수행할 수 있는지 여부를 컴파일러에 알리는 데 사용됩니다. 이는 컴파일러가 가장 일반적인 결과에 가장 적합한 방식으로 코드를 작성하는 데 도움이 됩니다.

이들은 다음과 같이 사용됩니다:

if (likely(some_condition)) {
  // the compiler will try and make the code layout optimal for the case
  // where some_condition is true, i.e. where this block is run
  most_likely_action();
} else {
  // this block is less frequently used
  corner_case();
}

주의해서 사용해야 합니다(즉, 실제 분기 분석 결과를 기반으로 함). 잘못된 프롬프트는 성능을 저하시킵니다(분명히).

를 검색하면 코드를 최적화하는 방법에 대한 몇 가지 예를 쉽게 찾을 수 있습니다 GCC __builtin_expect. 이 블로그 게시물gcc 최적화: __builtin_expect예를 들어 분해가 자세히 설명되어 있습니다.

수행할 수 있는 최적화 유형은 프로세서별로 다릅니다. 일반적인 아이디어는 코드가 모든 곳에서 분기/점프하지 않으면 프로세서가 일반적으로 코드를 더 빠르게 실행한다는 것입니다. 선형성이 높고 분기의 예측 가능성이 높을수록 실행 속도가 빨라집니다. (예를 들어 깊은 파이프라인이 있는 프로세서의 경우 특히 그렇습니다.)

따라서 컴파일러는 가능성이 가장 높은 분기에 점프가 포함되지 않도록 코드를 내보냅니다(예: 대상 CPU가 점프를 선호하는 경우).

답변2

디컴파일하여 GCC 4.8이 무엇을 하는지 살펴보겠습니다.

예상치 못한

#include "stdio.h"
#include "time.h"

int main() {
    /* Use time to prevent it from being optimized away. */
    int i = !time(NULL);
    if (i)
        printf("%d\n", 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 14                   jne    24 <main+0x24>
  10:       ba 01 00 00 00          mov    $0x1,%edx
  15:       be 00 00 00 00          mov    $0x0,%esi
                    16: R_X86_64_32 .rodata.str1.1
  1a:       bf 01 00 00 00          mov    $0x1,%edi
  1f:       e8 00 00 00 00          callq  24 <main+0x24>
                    20: R_X86_64_PC32       __printf_chk-0x4
  24:       bf 00 00 00 00          mov    $0x0,%edi
                    25: R_X86_64_32 .rodata.str1.1+0x4
  29:       e8 00 00 00 00          callq  2e <main+0x2e>
                    2a: R_X86_64_PC32       puts-0x4
  2e:       31 c0                   xor    %eax,%eax
  30:       48 83 c4 08             add    $0x8,%rsp
  34:       c3                      retq

메모리의 명령어 순서는 변경되지 않습니다. 먼저 printf, 그 다음 putsreturn 입니다 retq.

기대를 가지고

이제 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 11                   je     21 <main+0x21>
  10:       bf 00 00 00 00          mov    $0x0,%edi
                    11: R_X86_64_32 .rodata.str1.1+0x4
  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
  21:       ba 01 00 00 00          mov    $0x1,%edx
  26:       be 00 00 00 00          mov    $0x0,%esi
                    27: R_X86_64_32 .rodata.str1.1
  2b:       bf 01 00 00 00          mov    $0x1,%edi
  30:       e8 00 00 00 00          callq  35 <main+0x35>
                    31: R_X86_64_PC32       __printf_chk-0x4
  35:       eb d9                   jmp    10 <main+0x10>

printf__printf_chk(로 컴파일됨)은 함수의 맨 끝 으로 이동한 다음 puts다른 답변에서 언급한 것처럼 분기 예측을 개선하기 위해 반환됩니다.

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

int i = !time(NULL);
if (i)
    goto printf;
puts:
puts("a");
return 0;
printf:
printf("%d\n", i);
goto puts;

이 최적화는 유용하지 않습니다 -O0.

__builtin_expect하지만 없는 것보다 더 빠르게 실행되는 예제를 작성하는 데 행운이 있기를 바랍니다 .그 당시 CPU는 정말 똑똑했어요.. 나는 순진하게 노력했다여기 있어요.

C++20 [[likely]][[unlikely]]

C++20은 다음과 같은 C++ 내장 함수를 표준화했습니다.https://stackoverflow.com/questions/51797959/how-to-use-c20s-likely-unlikely-attribute-in-if-else-statement그들은 아마도 (말장난 의도!) 같은 일을 할 것입니다.

관련 정보