El desenrollado de bucle , también conocido como desenrollado de bucle , es una técnica de transformación de bucle que intenta optimizar la velocidad de ejecución de un programa a expensas de su tamaño binario , que es un enfoque conocido como compensación espacio-tiempo . La transformación puede ser realizada manualmente por el programador o por un compilador de optimización . En los procesadores modernos, el desenrollado de bucles suele ser contraproducente, ya que el aumento del tamaño del código puede provocar más pérdidas de caché; cf. Dispositivo de Duff . [1]
El objetivo de desenrollar el ciclo es aumentar la velocidad de un programa reduciendo o eliminando las instrucciones que controlan el ciclo, como la aritmética del puntero y las pruebas de "fin de ciclo" en cada iteración; [2] reducir las sanciones a las sucursales; así como ocultar latencias, incluido el retraso en la lectura de datos de la memoria. [3] Para eliminar esta sobrecarga computacional , los bucles se pueden reescribir como una secuencia repetida de declaraciones independientes similares. [4]
El desenrollado de bucles también forma parte de ciertas técnicas formales de verificación , en particular , la verificación del modelo acotado . [5]
Ventajas
La sobrecarga en bucles "ajustados" a menudo consiste en instrucciones para incrementar un puntero o índice al siguiente elemento en una matriz ( aritmética de puntero ), así como pruebas de "fin de ciclo". Si un compilador o ensamblador optimizador es capaz de precalcular compensaciones para cada variable de matriz referenciada individualmente , estas pueden integrarse directamente en las instrucciones del código de máquina , por lo que no requieren operaciones aritméticas adicionales en tiempo de ejecución.
- Se pueden obtener ganancias significativas si la reducción en las instrucciones ejecutadas compensa cualquier reducción del rendimiento causada por cualquier aumento en el tamaño del programa.
- Se minimiza la penalización de rama . [6]
- Si las declaraciones en el ciclo son independientes entre sí (es decir, cuando las declaraciones que ocurren antes en el ciclo no afectan las declaraciones que las siguen), las declaraciones pueden potencialmente ejecutarse en paralelo .
- Se puede implementar dinámicamente si se desconoce el número de elementos de la matriz en el momento de la compilación (como en el dispositivo de Duff ).
Los compiladores optimizados a veces realizarán el desenrollado automáticamente o bajo pedido.
Desventajas
- Aumento del tamaño del código del programa, que puede ser indeseable, particularmente para aplicaciones integradas. También puede provocar un aumento en las pérdidas de caché de instrucciones, lo que puede afectar negativamente al rendimiento.
- A menos que un compilador optimizador lo realice de forma transparente, el código puede volverse menos legible .
- Si el código del cuerpo del bucle implica llamadas a funciones, es posible que no sea posible combinar el desenrollado con la inserción , ya que el aumento en el tamaño del código puede ser excesivo. Por tanto, puede haber una compensación entre las dos optimizaciones.
- Posible aumento del uso de registros en una sola iteración para almacenar variables temporales [ dudoso ] , lo que puede reducir el rendimiento, aunque mucho dependerá de posibles optimizaciones. [7]
- Aparte del código muy pequeño y simple, los bucles desenrollados que contienen ramas son incluso más lentos que las recursiones. [8]
Desenrollamiento de bucle estático / manual
El desenrollado de bucle manual (o estático) implica que el programador analice el bucle e interprete las iteraciones en una secuencia de instrucciones que reducirán la sobrecarga del bucle. Esto contrasta con el desenrollado dinámico que realiza el compilador.
Ejemplo manual simple en C
Un procedimiento en un programa de computadora es eliminar 100 elementos de una colección. Esto normalmente se logra mediante un for
-loop que llama a la función delete (item_number) . Si esta parte del programa se va a optimizar, y la sobrecarga del bucle requiere recursos significativos en comparación con los de la función delete (x) , se puede utilizar el desenrollado para acelerarlo.
Bucle normal | Después de desenrollar el bucle |
---|---|
int x ; para ( x = 0 ; x < 100 ; x ++ ) { eliminar ( x ); } | int x ; para ( x = 0 ; x < 100 ; x + = 5 ) { eliminar ( x ); eliminar ( x + 1 ); eliminar ( x + 2 ); eliminar ( x + 3 ); eliminar ( x + 4 ); } |
Como resultado de esta modificación, el nuevo programa tiene que realizar solo 20 iteraciones, en lugar de 100. Luego, solo es necesario tomar el 20% de los saltos y ramas condicionales, y representa, a lo largo de muchas iteraciones, una disminución potencialmente significativa en el gastos generales de administración de bucle. Para producir el beneficio óptimo, no se deben especificar variables en el código desenrollado que requieran aritmética de punteros . Esto normalmente requiere direccionamiento " base más desplazamiento", en lugar de referencias indexadas.
Por otro lado, este desenrollado de bucle manual expande el tamaño del código fuente de 3 líneas a 7, que deben producirse, verificarse y depurarse, y el compilador puede tener que asignar más registros para almacenar variables en la iteración de bucle expandido [ dudoso ] . Además, las variables de control del bucle y el número de operaciones dentro de la estructura del bucle desenrollado deben elegirse con cuidado para que el resultado sea el mismo que en el código original (asumiendo que se trata de una optimización posterior en el código que ya funciona). Por ejemplo, considere las implicaciones si el recuento de iteraciones no fuera divisible por 5. Las modificaciones manuales requeridas también se vuelven algo más complicadas si las condiciones de prueba son variables. Consulte también el dispositivo de Duff .
Complejidad temprana
En el caso simple, el control de bucle es simplemente una sobrecarga administrativa que organiza las declaraciones productivas. El bucle en sí no contribuye en nada a los resultados deseados, simplemente le ahorra al programador el tedio de replicar el código cien veces, lo que podría haberlo hecho un preprocesador que genera las replicaciones o un editor de texto. De manera similar, las if
declaraciones -y otras declaraciones de control de flujo podrían ser reemplazadas por la replicación de código, excepto que el resultado puede ser un exceso de código . Los programas de computadora rastrean fácilmente las combinaciones, pero los programadores encuentran esta repetición aburrida y cometen errores. Considerar:
Bucle normal | Después de desenrollar el bucle |
---|---|
para yo: = 1: 8 si i mod 2 = 0 entonces do_even_stuff (i) else do_odd_stuff (i); siguiente yo; | do_odd_stuff (1); do_even_stuff (2);do_odd_stuff (3); do_even_stuff (4);do_odd_stuff (5); do_even_stuff (6);do_odd_stuff (7); do_even_stuff (8); |
Pero, por supuesto, el código realizado no necesita ser la invocación de un procedimiento, y este siguiente ejemplo involucra la variable de índice en el cálculo:
Bucle normal | Después de desenrollar el bucle |
---|---|
x (1): = 1;Para i: = 2: 9 hago x (i): = x (i - 1) * i; imprimir i, x (i); siguiente yo; | x (1): = 1;x (2): = x (1) * 2; imprimir 2, x (2);x (3): = x (2) * 3; imprimir 3, x (3);x (4): = x (3) * 4; imprimir 4, x (4);... etc. |
que, si se compila, podría producir una gran cantidad de código ( las declaraciones de impresión son notorias) pero es posible una mayor optimización. Este ejemplo hace referencia solo a x (i) y x (i - 1) en el ciclo (este último solo para desarrollar el nuevo valor x (i) ) por lo tanto, dado que no hay una referencia posterior a la matriz x desarrollada aquí, sus usos podrían reemplazarse por una variable simple. Sin embargo, tal cambio significaría una variable simple cuyo valor cambia, mientras que si permanece con la matriz, el análisis del compilador podría notar que los valores de la matriz son constantes, cada uno derivado de una constante anterior y, por lo tanto, traslada los valores constantes para que el código se convierte en
imprimir 2, 2;imprimir 3, 6;impresión 4, 24;... etc.
En general, el contenido de un bucle puede ser grande, lo que implica una intrincada indexación de matrices. Probablemente sea mejor dejar estos casos para optimizar los compiladores para desenrollarlos. La replicación de los bucles más internos podría permitir muchas optimizaciones posibles y, sin embargo, producir solo una pequeña ganancia a menos que n sea grande.
Desenrollar bucles WHILE
Considere un bucle de pseudocódigo WHILE similar al siguiente:
Bucle normal | Después de desenrollar el bucle | Bucle desenrollado y "modificado" |
---|---|---|
MIENTRAS (condición) HACER acciónFINALIZAR...... | MIENTRAS (condición) HACER acción SI NO (condición) ENTONCES GOTO romper acción SI NO (condición) ENTONCES GOTO romper acciónFINALIZARRotura de ETIQUETA:. | SI (condición) ENTONCES REPETIR acción SI NO (condición) ENTONCES GOTO romper acción SI NO (condición) ENTONCES GOTO romper acción MIENTRAS (condición)Rotura de ETIQUETA: |
En este caso, el desenrollado es más rápido porque ENDWHILE (un salto al inicio del ciclo) se ejecutará con un 66% menos de frecuencia.
Aún mejor, el ejemplo de pseudocódigo "modificado", que puede ser realizado automáticamente por algunos compiladores de optimización, eliminando los saltos incondicionales por completo.
Desenrollamiento dinámico
Dado que los beneficios del desenrollado de bucles dependen con frecuencia del tamaño de una matriz, que a menudo no se conoce hasta el tiempo de ejecución, los compiladores JIT (por ejemplo) pueden determinar si invocar una secuencia de bucle "estándar" o generar una (relativamente corta ) secuencia de instrucciones individuales para cada elemento. Esta flexibilidad es una de las ventajas de las técnicas justo a tiempo frente a la optimización estática o manual en el contexto del desenrollado de bucles. En esta situación, es a menudo con valores relativamente pequeños de n donde los ahorros siguen siendo útiles, requiriendo un aumento general bastante pequeño (si lo hay) en el tamaño del programa (que podría incluirse solo una vez, como parte de una biblioteca estándar).
Los programadores en lenguaje ensamblador (incluida la optimización de los escritores de compiladores) también pueden beneficiarse de la técnica de desenrollado de bucles dinámicos, utilizando un método similar al utilizado para las tablas de ramas eficientes . Aquí, la ventaja es mayor cuando el desplazamiento máximo de cualquier campo referenciado en una matriz en particular es menor que el desplazamiento máximo que se puede especificar en una instrucción de máquina (que será marcado por el ensamblador si se excede).
Ejemplo de ensamblador (IBM / 360 o Z / Architecture)
Este ejemplo es para ensambladores IBM / 360 o Z / Architecture y asume que un campo de 100 bytes (en el desplazamiento cero) se copiará de la matriz FROM a la matriz TO, ambos con 50 entradas con longitudes de elemento de 256 bytes cada una.
* La dirección de devolución está en R14.* Inicializar los registros R15, R0, R1 y R2 a partir de los datos definidos al final de * el programa que comienza con la etiqueta INIT / MAXM1. LM R15, R2, INIT Establecer R15 = número máximo de MVC* instrucciones (MAXM1 = 16), * R0 = número de entradas de la matriz,* R1 = dirección de la matriz 'FROM', y* R2 = dirección de la matriz 'TO'.** El ciclo comienza aquí.LOOP EQU * Definir etiqueta LOOP.* En este punto, R15 siempre contendrá el número 16 (MAXM1). SR R15, R0 Reste el número restante de * entradas en la matriz (R0) de R15. BNP TODOS Si R15 no es positivo, lo que significa que* tiene más de 16 entradas restantes* en la matriz, salte para hacer todo* Secuencia MVC y luego repetir.** Calcule un desplazamiento (desde el inicio de la secuencia MVC) para la rama incondicional a * el bucle MVC 'desenrollado' a continuación.* Si el número de entradas restantes en las matrices es cero, R15 será 16, por lo que * Se omitirán todas las instrucciones MVC. MH R15, = AL2 (ILEN) Multiplicar R15 por la longitud de uno* Instrucción MVC. B ALL (R15) Salta a ALL + R15, la dirección del* instrucción MVC específica calculada * con drop through al resto de ellos.** Instrucción MVC 'tabla'. * La primera entrada tiene un desplazamiento máximo permitido con un solo registro = hexadecimal F00* (15 * 256) en este ejemplo.* Las 16 instrucciones siguientes de MVC ('mover carácter') usan base-plus-offset * el direccionamiento y cada desplazamiento hacia / desde disminuye en la longitud de un elemento de la matriz* (256). Esto evita que se requiera aritmética de punteros para cada elemento hasta un * desplazamiento máximo permitido dentro de la instrucción de FFF hexadecimal * (15 * 256 + 255). Las instrucciones están en orden decreciente de compensación, por lo que el último * El elemento del conjunto se mueve primero.TODOS MVC 15 * 256 (100, R2), 15 * 256 (R1) Mover 100 bytes de la entrada 16 de * matriz 1 a matriz 2 (con * caer a través).ILEN EQU * -ALL Establece ILEN a la longitud de la anterior.* Instrucción MVC. MVC 14 * 256 (100, R2), 14 * 256 (R1) Mueve 100 bytes de la entrada número 15. MVC 13 * 256 (100, R2), 13 * 256 (R1) Mueve 100 bytes de la entrada número 14. MVC 12 * 256 (100, R2), 12 * 256 (R1) Mueve 100 bytes de la entrada número 13. MVC 11 * 256 (100, R2), 11 * 256 (R1) Mueve 100 bytes de la duodécima entrada. MVC 10 * 256 (100, R2), 10 * 256 (R1) Mueve 100 bytes de la undécima entrada. MVC 09 * 256 (100, R2), 09 * 256 (R1) Mueve 100 bytes de la décima entrada. MVC 08 * 256 (100, R2), 08 * 256 (R1) Mueve 100 bytes de la novena entrada. MVC 07 * 256 (100, R2), 07 * 256 (R1) Mueve 100 bytes de la octava entrada. MVC 06 * 256 (100, R2), 06 * 256 (R1) Mueve 100 bytes de la séptima entrada. MVC 05 * 256 (100, R2), 05 * 256 (R1) Mueve 100 bytes de la sexta entrada. MVC 04 * 256 (100, R2), 04 * 256 (R1) Mueve 100 bytes de la quinta entrada. MVC 03 * 256 (100, R2), 03 * 256 (R1) Mueve 100 bytes de la cuarta entrada. MVC 02 * 256 (100, R2), 02 * 256 (R1) Mueve 100 bytes de la tercera entrada. MVC 01 * 256 (100, R2), 01 * 256 (R1) Mueve 100 bytes de la segunda entrada. MVC 00 * 256 (100, R2), 00 * 256 (R1) Mueve 100 bytes de la primera entrada.* S R0, MAXM1 Reducir el número de entradas restantes* procesar. BNPR R14 Si no hay más entradas para procesar, regrese* a la dirección en R14. AH R1, = AL2 (16 * 256) Incrementar el puntero de matriz 'FROM' más allá* primer set. AH R2, = AL2 (16 * 256) Incrementar el puntero de matriz 'TO' más allá* primer set. L R15, MAXM1 Recargue el número máximo de MVC * instrucciones por lote en R15* (destruido por el cálculo en el * primera instrucción del bucle). B LOOP Ejecuta el bucle de nuevo.** Constantes y variables estáticas (estas pueden pasarse como parámetros, excepto * MAXM1).INIT DS 0A 4 direcciones (punteros) a ser * precargado con la instrucción 'LM'* al comienzo del programa.MAXM1 DC A (16) Número máximo de instrucciones MVC* ejecutado por lote.N DC A (50) Número de entradas reales en la matriz (a * variable, establecida en otro lugar). DC A (DESDE) Dirección de inicio de la matriz 1 * ("puntero"). DC A (TO) Dirección de inicio de matriz 2 * ("puntero").** Matrices estáticas (pueden adquirirse dinámicamente).FROM DS 50CL256 Matriz de 50 entradas de 256 bytes cada una.TO DS 50CL256 Matriz de 50 entradas de 256 bytes cada una.
En este ejemplo, se requerirían aproximadamente 202 instrucciones con un bucle "convencional" (50 iteraciones), mientras que el código dinámico anterior solo requeriría aproximadamente 89 instrucciones (o un ahorro de aproximadamente el 56%). Si la matriz hubiera consistido en solo dos entradas, aún se ejecutaría aproximadamente al mismo tiempo que el bucle desenrollado original. El aumento en el tamaño del código es de solo 108 bytes, incluso si hay miles de entradas en la matriz.
Por supuesto, se pueden utilizar técnicas similares cuando se involucran varias instrucciones, siempre que la duración de la instrucción combinada se ajuste en consecuencia. Por ejemplo, en este mismo ejemplo, si se requiere borrar el resto de cada entrada de la matriz a nulos inmediatamente después de que se copió el campo de 100 bytes XC xx*256+100(156,R1),xx*256+100(R2)
, se puede agregar una instrucción de borrado adicional inmediatamente después de cada MVC en la secuencia (donde xx
coincide con el valor en el MVC encima de él).
Por supuesto, es perfectamente posible generar el código anterior "en línea" usando una sola instrucción de macro de ensamblador , especificando solo cuatro o cinco operandos (o alternativamente, convertirlo en una subrutina de biblioteca, a la que se accede mediante una simple llamada, pasando una lista de parámetros), haciendo que la optimización sea fácilmente accesible.
C ejemplo
El siguiente ejemplo demuestra desenroscado de bucles dinámico para un sencillo programa escrito en C . A diferencia del ejemplo de ensamblador anterior, la aritmética de puntero / índice todavía es generada por el compilador en este ejemplo porque una variable (i) todavía se usa para direccionar el elemento de la matriz. La optimización completa solo es posible si se utilizan índices absolutos en las declaraciones de reemplazo.
#include / * El número de entradas procesadas por iteración de bucle. * / / * Tenga en cuenta que este número es una 'constante constante' que refleja el código siguiente. * / #define TAMAÑO DEL MANOJO (8)int main ( vacío ) { int i = 0 ; / * contador * / entradas int = 50 ; / * número total a procesar * / int repeat ; / * número de repeticiones while * / int left = 0 ; / * resto (procesar más tarde) * / / * Si el número de elementos no es divisible por BUNCHSIZE, * / / * obtiene los tiempos de repetición necesarios para realizar la mayor parte del procesamiento en el ciclo while * / repetir = ( entradas / BUNCHSIZE ); / * número de veces a repetir * / left = ( entradas % BUNCHSIZE ); / * calcular el resto * / / * Desenrolla el ciclo en 'grupos' de 8 * / while ( repeat - ) { printf ( "process (% d) \ n " , i ); printf ( "proceso (% d) \ n " , i + 1 ); printf ( "proceso (% d) \ n " , i + 2 ); printf ( "proceso (% d) \ n " , i + 3 ); printf ( "proceso (% d) \ n " , i + 4 ); printf ( "proceso (% d) \ n " , i + 5 ); printf ( "proceso (% d) \ n " , i + 6 ); printf ( "proceso (% d) \ n " , i + 7 ); / * actualiza el índice por cantidad procesada de una vez * / i + = BUNCHSIZE ; } / * Use una instrucción de cambio para procesar el resto saltando a la etiqueta del caso * / / * en la etiqueta que luego aparecerá para completar el conjunto * / cambiar ( izquierda ) { caso 7 : printf ( "proceso (% d) \ n " , i + 6 ); / * proceso y confía en drop through * / caso 6 : printf ( "proceso (% d) \ n " , i + 5 ); caso 5 : printf ( "proceso (% d) \ n " , i + 4 ); caso 4 : printf ( "proceso (% d) \ n " , i + 3 ); caso 3 : printf ( "proceso (% d) \ n " , i + 2 ); caso 2 : printf ( "proceso (% d) \ n " , i + 1 ); / * dos a la izquierda * / caso 1 : printf ( "proceso (% d) \ n " , i ); / * Sólo una izquierda al proceso * / caso 0 : ; / * no queda nada * / } }
La duplicación de código podría evitarse escribiendo las dos partes juntas como en el dispositivo de Duff .
Ejemplo de desenrollado de bucle de lenguaje ensamblador de C a MIPS [9]
El siguiente ejemplo calculará un producto escalar de dos vectores de tipo A y B de 100 entradas double
. Aquí está el código en C:
doble dotProduct = 0 ;para ( int i = 0 ; i < 100 ; i ++ ) { dotProduct + = A [ i ] * B [ i ];}
Conversión a lenguaje ensamblador MIPS
El siguiente es un código ensamblador MIPS que calculará el producto escalar de dos vectores de 100 entradas, A y B, antes de implementar el desenrollado de bucle. El siguiente código omite las inicializaciones del ciclo:
- Inicializar el recuento de bucles ($ 7) a 100.
- Inicialice el producto escalar ($ f10) a 0.
- Inicialice el
A[i]
puntero ($ 5) a la dirección base deA
. - Inicialice el
B[i]
puntero ($ 6) a la dirección base deB
.
Tenga en cuenta que el tamaño de un elemento de las matrices (a double
) es de 8 bytes.
loop3: ld $ f10 , 0 ( $ 5 ) ; $ f10 ← A [i] ld $ f12 , 0 ( $ 6 ) ; $ f12 ← B [i] mul.d $ f10 , $ f10 , $ f12 ; $ f10 ← A [i] * B [i] add.d $ f8 , $ f8 , $ f10 ; $ f8 ← $ f8 + A [i] * B [i] addi $ 5 , $ 5 , 8 ; incrementar el puntero para A [i] por el tamaño ; de un doble. addi $ 6 , $ 6 , 8 ; incrementar el puntero para B [i] por el tamaño ; de un doble. addi $ 7 , $ 7 , -1 ; disminución del recuento de bucles prueba: bgtz $ 7 , loop3 ; Continuar si el recuento de bucles> 0
Desenrollando el bucle en MIPS
Lo siguiente es el mismo que el anterior, pero con el desenrollado de bucle implementado en un factor de 4. Note nuevamente que el tamaño de un elemento de las matrices (a double
) es de 8 bytes; de ahí los 0, 8, 16, 24 desplazamientos y los 32 desplazamientos en cada bucle.
loop3: ld $ f10 , 0 ( $ 5 ) ; iteración con desplazamiento 0 ld $ f12 , 0 ( $ 6 ) mul.d $ f10 , $ f10 , $ f12 add.d $ f8 , $ f8 , $ f10 ld $ f10 , 8 ( $ 5 ) ; iteración con desplazamiento 8 ld $ f12 , 8 ( $ 6 ) mul.d $ f10 , $ f10 , $ f12 add.d $ f8 , $ f8 , $ f10 ld $ f10 , 16 ( $ 5 ) ; iteración con desplazamiento 16 ld $ f12 , 16 ( $ 6 ) mul.d $ f10 , $ f10 , $ f12 add.d $ f8 , $ f8 , $ f10 ld $ f10 , 24 ( $ 5 ) ; iteración con desplazamiento 24 ld $ f12 , 24 ( $ 6 ) mul.d $ f10 , $ f10 , $ f12 add.d $ f8 , $ f8 , $ f10 addi $ 5 , $ 5 , 32 addi $ 6 , $ 6 , 32 addi $ 7 , $ 7 , -4 prueba: bgtz $ 7 , loop3 ; Continuar bucle si $ 7> 0
Ver también
Referencias
- ^ Tso, Ted (22 de agosto de 2000). "Re: [PATCH] Re: Movimiento de controladores de entrada, se necesita alguna palabra de usted" . lkml.indiana.edu . Lista de correo del kernel de Linux . Consultado el 22 de agosto de 2014 .
Jim Gettys tiene una maravillosa explicación de este efecto en el servidor X. Resulta que con las predicciones de bifurcaciones y la velocidad relativa del cambio de la CPU frente a la memoria durante la última década, el desenrollado de bucles es prácticamente inútil. De hecho, al eliminar todas las instancias del dispositivo de Duff del servidor XFree86 4.0, el servidor se redujo en tamaño _half_ _a_ _megabyte_ (!!!), y fue más rápido de arrancar, porque la eliminación de todo ese código sobrante significó que el servidor X no estaba destrozando tanto las líneas de caché.
- ^ Ullman, Jeffrey D .; Aho, Alfred V. (1977). Principios de diseño de compiladores . Reading, Mass: Addison-Wesley Pub. Co. págs. 471-2 . ISBN 0-201-10073-8.
- ^ Petersen, WP, Arbenz, P. (2004). Introducción a la Computación Paralela . Prensa de la Universidad de Oxford. pag. 10 .CS1 maint: varios nombres: lista de autores ( enlace )
- ^ Nicolau, Alexandru (1985). "Cuantificación de bucle: desenrollado para la explotación del paralelismo de grano fino". Dpto. De Informática Informe Técnico. Ithaca, Nueva York: Universidad de Cornell. OCLC 14638257 . Cite journal requiere
|journal=
( ayuda ) - ^ Verificación de modelos mediante SMT y teoría de listas
- ^ Niebla, Agner (29 de febrero de 2012). "Optimización de subrutinas en lenguaje ensamblador" (PDF) . Facultad de Ingeniería de la Universidad de Copenhague. pag. 100 . Consultado el 22 de septiembre de 2012 .
12.11 Desenrollado de bucle
- ^ Sarkar, Vivek (2001). "Desenrollado optimizado de bucles anidados". Revista Internacional de Programación Paralela . 29 (5): 545–581. doi : 10.1023 / A: 1012246031671 . S2CID 3353104 .
- ^ Adam Horvath "Desenrollado del código: el rendimiento está muy lejos"
- ^ "Desenrollado de bucle" . Universidad de Minessota .
Otras lecturas
- Kennedy, Ken; Allen, Randy (2001). Optimización de compiladores para arquitecturas modernas: un enfoque basado en la dependencia . Morgan Kaufmann. ISBN 1-55860-286-0.
enlaces externos
- Capítulo 7, páginas 8 a 10 , de Michael Abrash 's gráfico de programación Libro Negro es desenrollado sobre el bucle, con un ejemplo en x86 montaje.
- Desenrollado de bucle generalizado , ofrece una introducción concisa.
- Optimización de subrutinas en lenguaje ensamblador Manual de optimizaciones de Agner Fog con la técnica de desenrollado de bucles (2012).