¿Por qué GCC genera código 15-20% más rápido si optimizo el tamaño en lugar de la velocidad?
Noté por primera vez en 2009 que GCC (al menos en mis proyectos y en mis máquinas) tienen la tendencia a generar código notablemente más rápido si optimizo para tamaño (-Os
) en lugar de velocidad (-O2
o -O3
), y me he estado preguntando desde entonces por qué.
He logrado crear un código (bastante tonto) que muestra este comportamiento sorprendente y es lo suficientemente pequeño como para publicarlo aquí.
const int LOOP_BOUND = 200000000;
__attribute__((noinline))
static int add(const int& x, const int& y) {
return x + y;
}
__attribute__((noinline))
static int work(int xval, int yval) {
int sum(0);
for (int i=0; i<LOOP_BOUND; ++i) {
int x(xval+sum);
int y(yval+sum);
int z = add(x, y);
sum += z;
}
return sum;
}
int main(int , char* argv[]) {
int result = work(*argv[1], *argv[2]);
return result;
}
Si puedo compilar con -Os
, toma 0.38 s para ejecutar este programa, y 0.44 s si se compila con -O2
o -O3
. Estos tiempos se obtienen de forma consistente y prácticamente sin ruido (gcc 4.7.2, x86_64 GNU/Linux, Intel Core i5-3320M).
(Actualización: He movido todo el código ensamblador a GitHub : Hicieron el post hinchado y aparentemente agregan muy poco valor a las preguntas ya que las banderas fno-align-*
tienen el mismo efecto.)
Aquí está el ensamblado generado con -Os
y -O2
.
Desafortunadamente, mi la comprensión de la asamblea es muy limitada, por lo que no tengo idea de si lo que hice a continuación fue correcto: Agarré la asamblea para -O2
y fusioné todas sus diferencias en la asamblea para -Os
excepto las líneas .p2align
, resultan aquí. Este código todavía se ejecuta en 0.38 s y la única diferencia es la .p2align
cosas.
Si supongo correctamente, estos son rellenos para la alineación de la pila. Según ¿Por qué funciona GCC pad con NOPs? es hecho con la esperanza de que el código se ejecutará más rápido, pero aparentemente esta optimización fracasó en mi caso.
¿Es el relleno el culpable en este caso? Por qué y cómo?
El ruido que hace prácticamente hace que las micro-optimizaciones de sincronización sean imposibles.
¿Cómo puedo asegurarme de que tales alineaciones accidentales afortunadas / desafortunadas no interfieran cuando hago micro-optimizaciones (no relacionadas con la alineación de la pila) en el código fuente de C o C++ código?
ACTUALIZACIÓN:
Siguiendo la respuesta de Pascal Cuoq he retocado un poco con las alineaciones. Al pasar -O2 -fno-align-functions -fno-align-loops
a gcc, todos .p2align
desaparecen del ensamblado y el ejecutable generado se ejecuta en 0.38 s. De acuerdo con la documentación de gcc :
- Os habilita todas las optimizaciones-O2 [pero] - Os deshabilita los siguientes indicadores de optimización:
-falign-functions -falign-jumps -falign-loops <br/> -falign-labels -freorder-blocks -freorder-blocks-and-partition <br/> -fprefetch-loop-arrays <br/>
Por lo tanto, parece más o menos una (mala)alineación cuestión.
Todavía soy escéptico sobre -march=native
como se sugiere en La respuesta de Marat Dukhan . No estoy convencido de que no solo esté interfiriendo con este (mal)problema de alineación; no tiene absolutamente ningún efecto en mi máquina. (Sin embargo, voté su respuesta.)
ACTUALIZAR 2:
Podemos sacar -Os
de la imagen. Los siguientes tiempos se obtienen compilando con
-
-O2 -fno-omit-frame-pointer
0,37 s -O2 -fno-align-functions -fno-align-loops
0.37 s-
-S -O2
a continuación, moviendo manualmente el conjunto deadd()
después dework()
0.37 s -O2
0.44 s
Me parece que la distancia de add()
desde el sitio de la llamada importa mucho. He intentado perf
, pero la salida de perf stat
y perf report
tiene muy poco sentido para mí. Sin embargo, solo pude obtener un resultado consistente de ello:
-O2
:
602,312,864 stalled-cycles-frontend # 0.00% frontend cycles idle
3,318 cache-misses
0.432703993 seconds time elapsed
[...]
81.23% a.out a.out [.] work(int, int)
18.50% a.out a.out [.] add(int const&, int const&) [clone .isra.0]
[...]
¦ __attribute__((noinline))
¦ static int add(const int& x, const int& y) {
¦ return x + y;
100.00 ¦ lea (%rdi,%rsi,1),%eax
¦ }
¦ ? retq
[...]
¦ int z = add(x, y);
1.93 ¦ ? callq add(int const&, int const&) [clone .isra.0]
¦ sum += z;
79.79 ¦ add %eax,%ebx
Para fno-align-*
:
604,072,552 stalled-cycles-frontend # 0.00% frontend cycles idle
9,508 cache-misses
0.375681928 seconds time elapsed
[...]
82.58% a.out a.out [.] work(int, int)
16.83% a.out a.out [.] add(int const&, int const&) [clone .isra.0]
[...]
¦ __attribute__((noinline))
¦ static int add(const int& x, const int& y) {
¦ return x + y;
51.59 ¦ lea (%rdi,%rsi,1),%eax
¦ }
[...]
¦ __attribute__((noinline))
¦ static int work(int xval, int yval) {
¦ int sum(0);
¦ for (int i=0; i<LOOP_BOUND; ++i) {
¦ int x(xval+sum);
8.20 ¦ lea 0x0(%r13,%rbx,1),%edi
¦ int y(yval+sum);
¦ int z = add(x, y);
35.34 ¦ ? callq add(int const&, int const&) [clone .isra.0]
¦ sum += z;
39.48 ¦ add %eax,%ebx
¦ }
Para -fno-omit-frame-pointer
:
404,625,639 stalled-cycles-frontend # 0.00% frontend cycles idle
10,514 cache-misses
0.375445137 seconds time elapsed
[...]
75.35% a.out a.out [.] add(int const&, int const&) [clone .isra.0] ¦
24.46% a.out a.out [.] work(int, int)
[...]
¦ __attribute__((noinline))
¦ static int add(const int& x, const int& y) {
18.67 ¦ push %rbp
¦ return x + y;
18.49 ¦ lea (%rdi,%rsi,1),%eax
¦ const int LOOP_BOUND = 200000000;
¦
¦ __attribute__((noinline))
¦ static int add(const int& x, const int& y) {
¦ mov %rsp,%rbp
¦ return x + y;
¦ }
12.71 ¦ pop %rbp
¦ ? retq
[...]
¦ int z = add(x, y);
¦ ? callq add(int const&, int const&) [clone .isra.0]
¦ sum += z;
29.83 ¦ add %eax,%ebx
Parece que estamos estancando la llamada a add()
en el caso lento.
He examinado todo que perf -e
puede escupir en mi máquina; no solo las estadísticas que se dan arriba.
Para el mismo ejecutable, el stalled-cycles-frontend
muestra una correlación lineal con el tiempo de ejecución; no noté nada más que se correlacionara tan claramente. (Comparar stalled-cycles-frontend
para diferentes ejecutables no tiene sentido me.)
Incluí el cache misses como el primer comentario. Examiné todos los errores de caché que se pueden medir en mi máquina por perf
, no solo los indicados anteriormente. Los errores de caché son muy, muy ruidosos y muestran poca o ninguna correlación con los tiempos de ejecución.
6 answers
Por defecto los compiladores optimizan para el procesador "promedio". Dado que diferentes procesadores favorecen diferentes secuencias de instrucciones, las optimizaciones del compilador habilitadas por -O2
podrían beneficiar al procesador promedio, pero disminuir el rendimiento de su procesador en particular (y lo mismo se aplica a -Os
). Si intenta el mismo ejemplo en diferentes procesadores, encontrará que en algunos de ellos se benefician de -O2
mientras que otros son más favorables a -Os
optimizaciones.
Aquí están los resultados para time ./test 0 0
en varios procesadores (tiempo de usuario reportado):
Processor (System-on-Chip) Compiler Time (-O2) Time (-Os) Fastest
AMD Opteron 8350 gcc-4.8.1 0.704s 0.896s -O2
AMD FX-6300 gcc-4.8.1 0.392s 0.340s -Os
AMD E2-1800 gcc-4.7.2 0.740s 0.832s -O2
Intel Xeon E5405 gcc-4.8.1 0.603s 0.804s -O2
Intel Xeon E5-2603 gcc-4.4.7 1.121s 1.122s -
Intel Core i3-3217U gcc-4.6.4 0.709s 0.709s -
Intel Core i3-3217U gcc-4.7.3 0.708s 0.822s -O2
Intel Core i3-3217U gcc-4.8.1 0.708s 0.944s -O2
Intel Core i7-4770K gcc-4.8.1 0.296s 0.288s -Os
Intel Atom 330 gcc-4.8.1 2.003s 2.007s -O2
ARM 1176JZF-S (Broadcom BCM2835) gcc-4.6.3 3.470s 3.480s -O2
ARM Cortex-A8 (TI OMAP DM3730) gcc-4.6.3 2.727s 2.727s -
ARM Cortex-A9 (TI OMAP 4460) gcc-4.6.3 1.648s 1.648s -
ARM Cortex-A9 (Samsung Exynos 4412) gcc-4.6.3 1.250s 1.250s -
ARM Cortex-A15 (Samsung Exynos 5250) gcc-4.7.2 0.700s 0.700s -
Qualcomm Snapdragon APQ8060A gcc-4.8 1.53s 1.52s -Os
En algunos casos, puede aliviar el efecto de optimizaciones desventajosas pidiendo gcc
que optimice para su procesador en particular (usando opciones -mtune=native
o -march=native
):
Processor Compiler Time (-O2 -mtune=native) Time (-Os -mtune=native)
AMD FX-6300 gcc-4.8.1 0.340s 0.340s
AMD E2-1800 gcc-4.7.2 0.740s 0.832s
Intel Xeon E5405 gcc-4.8.1 0.603s 0.803s
Intel Core i7-4770K gcc-4.8.1 0.296s 0.288s
Actualización: en Ivy Bridge basado en Core i3 tres versiones de gcc
(4.6.4
, 4.7.3
, y 4.8.1
) producen binarios con un rendimiento significativamente diferente, pero el código ensamblador solo tiene variaciones sutiles. Hasta ahora, no tengo ninguna explicación de esto. hecho.
Ensamblado desde gcc-4.6.4 -Os
(se ejecuta en 0.709 segundos):
00000000004004d2 <_ZL3addRKiS0_.isra.0>:
4004d2: 8d 04 37 lea eax,[rdi+rsi*1]
4004d5: c3 ret
00000000004004d6 <_ZL4workii>:
4004d6: 41 55 push r13
4004d8: 41 89 fd mov r13d,edi
4004db: 41 54 push r12
4004dd: 41 89 f4 mov r12d,esi
4004e0: 55 push rbp
4004e1: bd 00 c2 eb 0b mov ebp,0xbebc200
4004e6: 53 push rbx
4004e7: 31 db xor ebx,ebx
4004e9: 41 8d 34 1c lea esi,[r12+rbx*1]
4004ed: 41 8d 7c 1d 00 lea edi,[r13+rbx*1+0x0]
4004f2: e8 db ff ff ff call 4004d2 <_ZL3addRKiS0_.isra.0>
4004f7: 01 c3 add ebx,eax
4004f9: ff cd dec ebp
4004fb: 75 ec jne 4004e9 <_ZL4workii+0x13>
4004fd: 89 d8 mov eax,ebx
4004ff: 5b pop rbx
400500: 5d pop rbp
400501: 41 5c pop r12
400503: 41 5d pop r13
400505: c3 ret
Ensamblado desde gcc-4.7.3 -Os
(se ejecuta en 0.822 segundos):
00000000004004fa <_ZL3addRKiS0_.isra.0>:
4004fa: 8d 04 37 lea eax,[rdi+rsi*1]
4004fd: c3 ret
00000000004004fe <_ZL4workii>:
4004fe: 41 55 push r13
400500: 41 89 f5 mov r13d,esi
400503: 41 54 push r12
400505: 41 89 fc mov r12d,edi
400508: 55 push rbp
400509: bd 00 c2 eb 0b mov ebp,0xbebc200
40050e: 53 push rbx
40050f: 31 db xor ebx,ebx
400511: 41 8d 74 1d 00 lea esi,[r13+rbx*1+0x0]
400516: 41 8d 3c 1c lea edi,[r12+rbx*1]
40051a: e8 db ff ff ff call 4004fa <_ZL3addRKiS0_.isra.0>
40051f: 01 c3 add ebx,eax
400521: ff cd dec ebp
400523: 75 ec jne 400511 <_ZL4workii+0x13>
400525: 89 d8 mov eax,ebx
400527: 5b pop rbx
400528: 5d pop rbp
400529: 41 5c pop r12
40052b: 41 5d pop r13
40052d: c3 ret
Ensamblado desde gcc-4.8.1 -Os
(se ejecuta en 0.994 segundos):
00000000004004fd <_ZL3addRKiS0_.isra.0>:
4004fd: 8d 04 37 lea eax,[rdi+rsi*1]
400500: c3 ret
0000000000400501 <_ZL4workii>:
400501: 41 55 push r13
400503: 41 89 f5 mov r13d,esi
400506: 41 54 push r12
400508: 41 89 fc mov r12d,edi
40050b: 55 push rbp
40050c: bd 00 c2 eb 0b mov ebp,0xbebc200
400511: 53 push rbx
400512: 31 db xor ebx,ebx
400514: 41 8d 74 1d 00 lea esi,[r13+rbx*1+0x0]
400519: 41 8d 3c 1c lea edi,[r12+rbx*1]
40051d: e8 db ff ff ff call 4004fd <_ZL3addRKiS0_.isra.0>
400522: 01 c3 add ebx,eax
400524: ff cd dec ebp
400526: 75 ec jne 400514 <_ZL4workii+0x13>
400528: 89 d8 mov eax,ebx
40052a: 5b pop rbx
40052b: 5d pop rbp
40052c: 41 5c pop r12
40052e: 41 5d pop r13
400530: c3 ret
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-07 21:46:18
Mi colega me ayudó a encontrar una respuesta plausible a mi pregunta. Notó la importancia del límite de 256 bytes. Él no está registrado aquí y me animó a publicar la respuesta yo mismo (y tomar toda la fama).
Respuesta Corta:
¿Es el relleno el culpable en este caso? Por qué y cómo?
Todo Se reduce a la alineación. Las alineaciones pueden tener un impacto significativo en el rendimiento, es por eso que tenemos las banderas -falign-*
en primer lugar.
He presentado un (falso?) informe de error a los desarrolladores de gcc. Resulta que el comportamiento predeterminado es "alineamos bucles a 8 bytes por defecto, pero tratamos de alinearlos a 16 bytes si no necesitamos rellenar más de 10 bytes." Aparentemente, este valor predeterminado no es la mejor opción en este caso particular y en mi máquina. Clang 3.4 (trunk) con -O3
hace la alineación apropiada y el código generado no muestra este extraño comportamiento.
Por supuesto, si se hace una alineación inapropiada, empeora las cosas. Una alineación innecesaria / incorrecta solo consume bytes sin ninguna razón y potencialmente aumenta los errores de caché, etc.
El ruido que hace prácticamente hace micro-optimizaciones de tiempo imposible.
¿Cómo puedo asegurarme de que tales alineaciones accidentales afortunadas / desafortunadas no están interfiriendo cuando hago micro-optimizaciones (no relacionadas con la pila alineación) en C o C++ ¿códigos fuente?
Simplemente diciéndole a gcc que haga la alineación correcta:
g++ -O2 -falign-functions=16 -falign-loops=16
Respuesta Larga:
El código se ejecutará más lento si:
Un límite de bytes
XX
cortaadd()
en el medio (XX
siendo dependiente de la máquina).Si la llamada a
add()
tiene que saltar sobre un límite de bytesXX
y el destino no está alineado.Si
add()
no es alinear.Si el bucle no está alineado.
Los primeros 2 son bellamente visibles en los códigos y resultados que Marat Dukhan amablemente publicó. En este caso, gcc-4.8.1 -Os
(se ejecuta en 0.994 segundos):
00000000004004fd <_ZL3addRKiS0_.isra.0>:
4004fd: 8d 04 37 lea eax,[rdi+rsi*1]
400500: c3
Un límite de 256 bytes corta add()
justo en el medio y ni add()
ni el bucle está alineado. Sorpresa, sorpresa, este es el caso más lento!
En el caso gcc-4.7.3 -Os
(se ejecuta en 0.822 segundos), el límite de 256 bytes solo corta en un frío sección (pero ni el bucle, ni add()
se corta):
00000000004004fa <_ZL3addRKiS0_.isra.0>:
4004fa: 8d 04 37 lea eax,[rdi+rsi*1]
4004fd: c3 ret
[...]
40051a: e8 db ff ff ff call 4004fa <_ZL3addRKiS0_.isra.0>
Nada está alineado, y la llamada a add()
tiene que saltar sobre el límite de 256 bytes. Este código es el segundo más lento.
En el caso gcc-4.6.4 -Os
(se ejecuta en 0.709 segundos), aunque nada está alineado, la llamada a add()
no tiene que saltar sobre el límite de 256 bytes y el objetivo está exactamente a 32 bytes de distancia:
4004f2: e8 db ff ff ff call 4004d2 <_ZL3addRKiS0_.isra.0>
4004f7: 01 c3 add ebx,eax
4004f9: ff cd dec ebp
4004fb: 75 ec jne 4004e9 <_ZL4workii+0x13>
Este es el más rápido de los tres. Por qué el límite de 256 bytes es speacial en su máquina, lo dejaré depende de él descubrirlo. No tengo tal procesador.
Ahora, en mi máquina no obtengo este efecto de límite de 256 bytes. Solo la función y la alineación del bucle se activan en mi máquina. Si paso g++ -O2 -falign-functions=16 -falign-loops=16
entonces todo vuelve a la normalidad: siempre obtengo el caso más rápido y el tiempo ya no es sensible a la bandera -fno-omit-frame-pointer
. Puedo pasar g++ -O2 -falign-functions=32 -falign-loops=32
o cualquier múltiplo de 16, el código tampoco es sensible a eso.
Me di cuenta por primera vez en 2009 que gcc (al menos en mi proyectos y en mi máquinas) tienen la tendencia a generar código notablemente más rápido si optimizar el tamaño (- Os) en lugar de la velocidad (-O2 o-O3) y he sido me pregunto desde entonces por qué.
Una explicación probable es que tenía puntos calientes que eran sensibles a la alineación, al igual que el de este ejemplo. Al jugar con las banderas (pasando -Os
en lugar de -O2
), esos puntos de acceso se alinearon de una manera afortunada por accidente y el código se hizo más rápido. No tenía nada que ver con la optimización para el tamaño: Estos fueron por pura casualidad que los puntos de acceso se alinearon mejor. A partir de ahora, comprobaré los efectos de la alineación en mis proyectos.
Ah, y una cosa más. ¿Cómo pueden surgir estos hotspots, como el que se muestra en el ejemplo? ¿Cómo puede fallar la inserción de una función tan pequeña como add()
?
Considere esto:
// add.cpp
int add(const int& x, const int& y) {
return x + y;
}
Y en un archivo separado:
// main.cpp
int add(const int& x, const int& y);
const int LOOP_BOUND = 200000000;
__attribute__((noinline))
static int work(int xval, int yval) {
int sum(0);
for (int i=0; i<LOOP_BOUND; ++i) {
int x(xval+sum);
int y(yval+sum);
int z = add(x, y);
sum += z;
}
return sum;
}
int main(int , char* argv[]) {
int result = work(*argv[1], *argv[2]);
return result;
}
Y compilado como: g++ -O2 add.cpp main.cpp
.
Gcc no está en línea add()
!
Eso es todo, es así de fácil crear sin querer hotspots como el de la OP. Por supuesto que en parte es culpa mía: gcc es un excelente compilador. Si compilo lo anterior como: g++ -O2 -flto add.cpp main.cpp
, es decir, si realizo la optimización del tiempo de enlace, ¡el código se ejecuta en 0.19 s!
(La inserción está desactivada artificialmente en el OP, por lo tanto, el código en el OP era 2 veces más lento).
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:18:02
Estoy agregando este post-accept para señalar que los efectos de la alineación en el rendimiento general de los programas, incluidos los grandes, se han estudiado. Por ejemplo, este artículo (y creo que una versión de esto también apareció en CACM) muestra cómo el orden de los enlaces y los cambios de tamaño del entorno del sistema operativo por sí solos fueron suficientes para cambiar el rendimiento significativamente. Atribuyen esto a la alineación de "hot loops".
Este artículo, titulado "Producir datos incorrectos sin hacer nada obviamente mal!"dice que el sesgo experimental inadvertido debido a las diferencias casi incontrolables en los entornos de ejecución de programas probablemente hace que muchos resultados de referencia no tengan sentido.
Creo que estás encontrando un ángulo diferente en la misma observación.
Para el código crítico de rendimiento, este es un argumento bastante bueno para los sistemas que evalúan el entorno en la instalación o en tiempo de ejecución y eligen la mejor versión local entre versiones optimizadas de rutinas clave.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-27 21:07:00
Creo que puedes obtener el mismo resultado que lo que hiciste:
Tomé el ensamblado para-O2 y fusioné todas sus diferencias en el ensamblado para-Os excepto el.p2líneas de alineación:
Using usando -O2 -falign-functions=1 -falign-jumps=1 -falign-loops=1 -falign-labels=1
. He estado compilando todo con estas opciones, que eran más rápidas que plain -O2
cada vez que me molestaba en medir, durante 15 años.
También, para un contexto completamente diferente (incluyendo un compilador diferente), noté que la situación es similar: la opción que se supone que "optimiza el tamaño del código en lugar de la velocidad" optimiza el tamaño y la velocidad del código.
Si supongo correctamente, estos son rellenos para la alineación de la pila.
No, esto no tiene nada que ver con la pila, los NOPs que se generan por defecto y que options-falign-*=1 prevent son para alineación de código.
Según Por qué funciona GCC pad con NOPs? se hace con la esperanza de que el código se ejecutará más rápido, pero al parecer esta optimización fracasó en mi caso.
¿Es el relleno el culpable en este caso? Por qué y cómo?
Es muy probable que el padding sea el culpable. La razón por la que el relleno se considera necesario y es útil en algunos casos es que el código se obtiene típicamente en líneas de 16 bytes (ver Recursos de optimización de Agner Fog para los detalles, que varían según el modelo del procesador). Alinear una función, bucle o etiqueta en un límite de 16 bytes significa que las posibilidades aumentan estadísticamente de que se necesite una línea menos para contener la función o el bucle. Obviamente, resulta contraproducente porque estos NOPs reducen la densidad del código y, por lo tanto, la eficiencia de la caché. En el caso de bucles y etiquetas, los NOPs pueden incluso necesitar ser ejecutados una vez (cuando la ejecución llega al bucle/etiqueta normalmente, en lugar de desde un salto).
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-19 21:59:11
Si su programa está limitado por el CÓDIGO de caché L1, entonces la optimización para el tamaño de repente comienza a pagar.
La última vez que revisé, el compilador no es lo suficientemente inteligente como para averiguar esto en todos los casos.
En su caso, -O3 probablemente genera código suficiente para dos líneas de caché, pero-Os encaja en una línea de caché.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-24 15:48:50
De ninguna manera soy un experto en esta área, pero me parece recordar que los procesadores modernos son bastante sensibles cuando se trata de predicción de ramas. Los algoritmos utilizados para predecir las ramas están (o al menos lo estaban en los días en que escribí el código ensamblador) basados en varias propiedades del código, incluida la distancia de un objetivo y la dirección.
El escenario que viene a la mente son pequeños bucles. Cuando la rama iba hacia atrás y la distancia no era demasiado lejos, el la predicición de ramas se optimizó para este caso, ya que todos los bucles pequeños se hacen de esta manera. Las mismas reglas pueden entrar en juego cuando se intercambia la ubicación de add
y work
en el código generado o cuando la posición de ambos cambia ligeramente.
Dicho esto, no tengo idea de cómo verificar eso y solo quería hacerle saber que esto podría ser algo que desea investigar.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 10:31:09