Was ist "loop unrolling"?
Das Herunter- oder Hochzählen des Schleifenzählers und der
Sprung kostet eine gewisse Anzahl von Taktzyklen.
Dies kann reduziert werden, wenn die Tätigkeit innerhalb
der Schleife z. B. 16-mal hintereinander steht, und so nur alle 16 Mal der
Zähler aktualisiert und gesprungen werden muss.
Zudem ist es möglicherweise leichter
auf verschiedene Register zu parallelisieren, da nicht
immer die gleichen Register genutzt werden müssen.
Eine ausgerollte Schleife muss jedoch doppelt
implementiert werden, da der Rest wieder einzeln
abgearbeitet werden muss, da sich z.b. 100 nicht
durch 16 teilen lässt, d. h. eine 16-fach ausgerollte
Schleife muss in der ersten Schleife (6*16)-mal
etwas machen, in der zweiten Schleife den Rest
(Modulo) von 4, da: 6*16+4=100.
Ist der Code etwas größer, kann es sein, dass
ein Ausrollen von nur 8 oder 4 mehr bringt.
Das Ausrollen von 2er-Potenzen habe ich als
praktisch empfunden, da der erste Zähler mit
einem Schiebebefehl und der zweite mit einer
UND-Verknüpfung ermittelt werden kann:
100 shr 4 = 6 und 100 and 15 = 4
Das nachfolgende Beispiel und ein Beispiel mit 2-Register-Zahlen
(64 Bit im 32-Bit-Modus) gibt es auch
als Delphi-Projekt
zum Herunterladen (ZIP-Datei 340 kB).
Lazarus (FreePascal)
uses SysUtils,StdCtrls,Forms,LResources,interfaces;
function RdTsc:int64;
asm
db $0f, $31
end;
function Fibonacci01(n:integer):integer;
asm
push ebx
push ecx
mov ecx, n
jecxz @@Fertig
mov eax, 0
mov ebx, 1
@@lp01:
add eax, ebx
xchg eax, ebx
dec ecx
jnz @@lp01
@@Fertig:
pop ecx
pop ebx
end;
function Fibonacci04(n:integer):integer;
asm
push ebx
push ecx
push edx
mov ecx, n
jecxz @@Fertig
mov edx, ecx // Zähler in EDX sichern
shr ecx, 2
mov eax, 0
mov ebx, 1
jecxz @@Rest
@@lp01:
add eax, ebx
xchg eax, ebx
add eax, ebx
xchg eax, ebx
add eax, ebx
xchg eax, ebx
add eax, ebx
xchg eax, ebx
dec ecx
jnz @@lp01
@@Rest:
and edx, 3
mov ecx, edx
jecxz @@Fertig
@@lp02:
add eax, ebx
xchg eax, ebx
dec ecx
jnz @@lp02
@@Fertig:
pop edx
pop ecx
pop ebx
end;
function Fibonacci08(n:integer):integer;
asm
push ebx
push ecx
push edx
mov ecx, n
jecxz @@Fertig
mov edx, ecx // Zähler in EDX sichern
shr ecx, 3
mov eax, 0
mov ebx, 1
jecxz @@Rest
@@lp01:
add eax, ebx
xchg eax, ebx
add eax, ebx
xchg eax, ebx
add eax, ebx
xchg eax, ebx
add eax, ebx
xchg eax, ebx
add eax, ebx
xchg eax, ebx
add eax, ebx
xchg eax, ebx
add eax, ebx
xchg eax, ebx
add eax, ebx
xchg eax, ebx
dec ecx
jnz @@lp01
@@Rest:
and edx, 7
mov ecx, edx
jecxz @@Fertig
@@lp02:
add eax, ebx
xchg eax, ebx
dec ecx
jnz @@lp02
@@Fertig:
pop edx
pop ecx
pop ebx
end;
function Fibonacci16(n:integer):integer;
asm
push ebx
push ecx
push edx
mov ecx, n
jecxz @@Fertig
mov edx, ecx // Zähler in EDX sichern
shr ecx, 4
mov eax, 0
mov ebx, 1
jecxz @@Rest
@@lp01:
add eax, ebx
xchg eax, ebx
add eax, ebx
xchg eax, ebx
add eax, ebx
xchg eax, ebx
add eax, ebx
xchg eax, ebx
add eax, ebx
xchg eax, ebx
add eax, ebx
xchg eax, ebx
add eax, ebx
xchg eax, ebx
add eax, ebx
xchg eax, ebx
add eax, ebx
xchg eax, ebx
add eax, ebx
xchg eax, ebx
add eax, ebx
xchg eax, ebx
add eax, ebx
xchg eax, ebx
add eax, ebx
xchg eax, ebx
add eax, ebx
xchg eax, ebx
add eax, ebx
xchg eax, ebx
add eax, ebx
xchg eax, ebx
dec ecx
jnz @@lp01
@@Rest:
and edx, 15
mov ecx, edx
jecxz @@Fertig
@@lp02:
add eax, ebx
xchg eax, ebx
dec ecx
jnz @@lp02
@@Fertig:
pop edx
pop ecx
pop ebx
end;
function fibmich(Tiefe:integer): String;
const
times=1000;
var
t1,t2,t3,t4,t5:int64;
ta,tb,tc,td:int64;
i,j,k,m,o:integer;
f1,f2,f3:integer;
s1 : String;
begin
s1 := 'Fibonacci-Zahlen:' + #13+#10;
f1 := 0;
f2 := 1;
for i:=0 to Tiefe do
begin
s1 := s1 + IntToStr(i) + ', ' + IntToStr(f1) + #13+#10;
f3 := f1 + f2;
f1 := f2;
f2 := f3;
end;
t1:=RdTsc;
for i:=1 to times do
begin
j:=Fibonacci01(Tiefe);
end;
t2:=RdTsc;
for i:=1 to times do
begin
k:=Fibonacci04(Tiefe);
end;
t3:=RdTsc;
for i:=1 to times do
begin
m:=Fibonacci08(Tiefe);
end;
t4:=RdTsc;
for i:=1 to times do
begin
o:=Fibonacci16(Tiefe);
end;
t5:=RdTsc;
ta:=(t2-t1) div times;
tb:=(t3-t2) div times;
tc:=(t4-t3) div times;
td:=(t5-t4) div times;
fibmich := s1 + 'Funktionsergebnisse: '
+ IntToStr(j) + ', ' + IntToStr(k) + ', ' + IntToStr(m) + ', ' + IntToStr(o)
+ #13 + #10
+ 'Taktverbrauch der einzelnen Funktionen:'
+ IntToStr(ta) + ', ' + IntToStr(tb) + ', ' + IntToStr(tc) + ', ' + IntToStr(td);
end;
type
TForm1 = class(TForm)
Button1: TButton;
Memo1: TMemo;
procedure Button1Click();
end;
var
Form1: TForm1;
procedure TForm1.Button1Click();
const
Tiefe=46;
begin
Memo1.Clear;
Memo1.Lines.Add(fibmich (Tiefe));
end;
begin
LazarusResources.Add('TForm1','FORMDATA',['TPF0'
+#6'TForm1'#5'Form1'
+#5'Width'#3#88#2
+#6'Height'#3#224#1
+#7'Caption'#6#23'Loop Enrolling Beispiel'
+#0
+#7'TButton'#7'Button1'
+#4'Left'#2#24
+#3'Top'#2#16
+#5'Width'#2#75
+#6'Height'#2#25
+#7'Caption'#6#10'Klick mich'
+#7'OnClick'#7#12'Button1Click'+#0
+#0
+#5'TMemo'#5'Memo1'
+#4'Left'#2#120
+#3'Top'#2#16
+#5'Width'#3#194#1
+#6'Height'#3#194#1
+#10'ScrollBars'#7#6'ssBoth'+#0
+#0#0
]);
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
Hubert Seidel Aug 2008 in d.c.l.a. (g8ur6c$sf2$03$1@news.t-online.com)