GNU/Linux >> Tutoriales Linux >  >> Linux

¿Cuál es la diferencia entre llamadas probables e improbables en Kernel?

Son sugerencias del compilador para GCC. Se usan en condicionales para decirle al compilador si es probable que se tome una rama o no. Puede ayudar al compilador a establecer el código de tal manera que sea óptimo para el resultado más frecuente.

Se usan así:

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();
}

Debe usarse con mucho cuidado (es decir, en función de los resultados reales del perfilado de sucursales). Una pista incorrecta puede degradar el rendimiento (obviamente).

Algunos ejemplos de cómo se puede optimizar el código se encuentran fácilmente buscando GCC __builtin_expect . Esta publicación de blog optimización de gcc:__builtin_expect, por ejemplo, detalla un desmontaje con él.

El tipo de optimizaciones que se pueden hacer es muy específico del procesador. La idea general es que, a menudo, los procesadores ejecutarán el código más rápido si no se bifurca o salta por todas partes. Cuanto más lineal sea y más predecibles sean las ramas, más rápido se ejecutará. (Esto es especialmente cierto para los procesadores con canalizaciones profundas, por ejemplo).

Entonces, el compilador emitirá el código de manera que la rama más probable no implique un salto si eso es lo que prefiere la CPU de destino, por ejemplo.


Vamos a descompilar para ver qué hace GCC 4.8 con él

Sin esperar

#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;
}

Compilar y descompilar con GCC 4.8.2 x86_64 Linux:

gcc -c -O3 -std=gnu11 main.c
objdump -dr main.o

Salida:

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

El orden de las instrucciones en la memoria no cambió:primero el printf y luego puts y el retq volver.

Con expectativas

Ahora reemplaza if (i) con:

if (__builtin_expect(i, 0))

y obtenemos:

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>

Los printf (compilado a __printf_chk ) se movió al final de la función, después de puts y el retorno para mejorar la predicción de sucursales como se menciona en otras respuestas.

Entonces es básicamente lo mismo que:

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

Esta optimización no se realizó con -O0 .

Pero buena suerte al escribir un ejemplo que se ejecute más rápido con __builtin_expect que sin, las CPU son realmente inteligentes en estos días. Mis intentos ingenuos están aquí.

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

C++20 ha estandarizado esos elementos integrados de C++:https://stackoverflow.com/questions/51797959/how-to-use-c20s-likely-unlikely-attribute-in-if-else-statement Probablemente (un ¡juego de palabras!) hacer lo mismo.


Linux
  1. ¿Cuál es la diferencia entre InnoDB y MyISAM?

  2. ¿Cómo funcionan las macros probables/improbables en el kernel de Linux y cuál es su beneficio?

  3. ¿Cuál es la diferencia entre strtok_r y strtok_s en C?

  4. ¿Cuál es la diferencia entre adduser y useradd?

  5. ¿Cuál es la diferencia entre ls y l?

¿Cuál es la diferencia entre los núcleos de macOS y Linux?

¿Cuál es la diferencia entre Linux y Unix?

¿Qué es un Hipervisor? ¿Cuál es la diferencia entre el tipo 1 y 2?

¿Cuál es la diferencia entre curl y Wget?

¿Cuál es la diferencia entre $(CC) y $CC?

¿Cuál es la diferencia entre ruta y ruta ip?