Loop Unrolling bezeichnet im einfachsten Fall folgende Umformung:
vorher nachher DO I= 1, 4 A(1) = B(1) + C(1) A(I) = B(I) + C(I) A(2) = B(2) + C(2) ENDDO A(3) = B(3) + C(3) A(4) = B(4) + C(4)
Der Vorteil dieser Transformation ist klar: Nicht nur verschwindet der Overhead für das Initialisieren und Abfragen der Schleifen-Variablen, es treten auch keine Sprünge mehr auf, die die schöne Pipeline durcheinanderbringen.
Leider ist die Arbeit für einen Compiler meistens schwerer als hier: Zum einen ist die Zahl der Iterationen meistens zu groß, um sie komplett ``aufzurollen''. Dann nimmt der Compiler ein ``partial unrolling'' vor, wie etwa im folgenden Fall:
vorher nachher DO I = 1, 4000 DO I = 1, 4000, 4 A(I) = B(I) + C(I) A(I ) = B(I) + C(I) ENDDO A(I+1) = B(I+1) + C(I+1) A(I+2) = B(I+2) + C(I+2) A(I+3) = B(I+3) + C(I+3) ENDDO
Den Unrolling-Faktor, im Beispiel 4, bestimmt der Compiler i.w. aus der Größe des Schleifeninneren.
Zum anderen sieht man dem Code die Zahl der Iterationen meistens nicht an. In diesem (Normal)Fall wird die Schleife teilweise ``abgerollt'', es wird aber auch noch ein Teil für den ``Rest'' eingefügt:
vorher nachher DO I = 1, N REST = MOD(N, 4) A(I) = B(I) + C(I) DO I=1,REST ENDDO A(I,J) = B(I,J) + C(I,J) ENDDO DO I = 1+REST, N, 4 A(I, J) = B(I ,J) + C(I ,J) A(I+1,J) = B(I+1,J) + C(I+1,J) A(I+2,J) = B(I+2,J) + C(I+2,J) A(I+3,J) = B(I+3,J) + C(I+3,J) ENDDO
Wie man sieht, ist die einfache Idee durch die harte Wirklichkeit ganz schön durcheinandergerüttelt worden. Das Beispiel zeigt auch ganz klar den Schwachpunkt an: Wenn N klein ist, wird der Optimierungs-Overhead den Gewinn mehr als zunichte machen. Ein schönes Beispiel dafür ist im vorstehenden Artikel zu sehen. In einem solchen Fall hilft nur, durch gezieltes Einfügen von Compiler-Direktiven den Unrolling-Faktor anzupassen oder Unrolling ganz zu verbieten.