GNURZ - Arithmetik-Unit für große Zahlen
-
- Beiträge: 134
- Registriert: So 30. Nov 2008, 21:53
Re: GNURZ - Arithmetik-Unit für große Zahlen
Normalerweise wird beim Mehrwort-Subtrahieren immer SBB (= subtract with borrow) verwendet. Bist Du sicher, daß zB
sub esi, [ecx+edi]
adc eax, 0
richtig ist? Auf jeden Fall wäre
sbb esi, [ecx+edi]
wohl etwas schneller.
Frank
sub esi, [ecx+edi]
adc eax, 0
richtig ist? Auf jeden Fall wäre
sbb esi, [ecx+edi]
wohl etwas schneller.
Frank
-
- Beiträge: 3444
- Registriert: Mo 11. Sep 2006, 10:24
- OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
- CPU-Target: X32 / X64 / ARMv5
- Wohnort: Krefeld
Re: GNURZ - Arithmetik-Unit für große Zahlen
Da bin ich allerdings ziemlich sicher. Beim Doppel-Subtrahieren musste ich das Carry-Wort (nicht Bit, hier muss ja ein Übertrag zur nächsten "Stelle" von bis zu 2 gehalten werden, der passt nicht in ein Bit !) positiv halten und dann subtrahieren. (Negativ halten geht nicht, dann muss man bei der nächsten Stelle "add" machen und das gibt den falschen Carry.)
"sbb esi, [ecx+edi]" wäre falsch. Der Carry muss in der nächsten "Stelle" verwendet werden nicht in derselben. Deshalb ist der Code ja so lang.
Porbier's bitte aus
!
Ich habe eine Idee, dass man ein Carry bit im Carry-Flag und nur das andere in einem Register halten kann. Das könnte einige Befehle sparen.
-Michael
"sbb esi, [ecx+edi]" wäre falsch. Der Carry muss in der nächsten "Stelle" verwendet werden nicht in derselben. Deshalb ist der Code ja so lang.
Porbier's bitte aus

Ich habe eine Idee, dass man ein Carry bit im Carry-Flag und nur das andere in einem Register halten kann. Das könnte einige Befehle sparen.
-Michael
Zuletzt geändert von mschnell am Mo 8. Dez 2008, 18:44, insgesamt 2-mal geändert.
-
- Lazarusforum e. V.
- Beiträge: 2808
- Registriert: Fr 22. Sep 2006, 10:38
- OS, Lazarus, FPC: Lazarus v2.0.10, FPC 3.2.0
- Wohnort: Hessen
- Kontaktdaten:
Re: GNURZ - Arithmetik-Unit für große Zahlen
Die Bilanz der Assembler-Optimierungen ist beeindruckend:mschnell hat geschrieben: Hier die Variante mit vierfachem Loop-Enrolling.
Code: Alles auswählen
Laufzeitmessung von: 10.000 Additionen, 50.000-stellige Zahlen
Bisherige Routine: 2,196 sec
Assembler-Routine: 0,973 sec (alt)
Assembler-Routine: 0,858 sec (4-fach enrolling)
Die folgenden Tage werde ich meine Zeit u.a. den folgenden Dingen widmen:
1. Feste Integration der Assembler-Routinen AddInternal in alle Bereichen der GNURZ.
2. High-Level-Optimierung des bisherigen Multiplikations-Algorithmus. (dauert ggf auch länger)
3. Prüfung der mit dem GGZTyp verbundenen Routinen für negative Zahlen.
4. Testen der SubInternal-Routinen sowie deren Integration
Wir haben dann voraussichtlich Mitte kommender Woche eine neue GNURZ-Unit, die nicht unerhebliche Geschwindigkeitsvorteile mit sich bringt und zudem negative Zahlen unterstützt.
Viele Grüße, Euklid
-
- Beiträge: 3444
- Registriert: Mo 11. Sep 2006, 10:24
- OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
- CPU-Target: X32 / X64 / ARMv5
- Wohnort: Krefeld
Re: GNURZ - Arithmetik-Unit für große Zahlen
Die Subtraktion ist ja klar. Ich kann den Code dafür morgen 'mal reinstellen (mit 4-ach enrolling)
Die SubSub-Routine ist hier noch in der Diskussion.
Ich bastele gerade an der primitiv-Multiplikation: in ASM mache ich nur Langzahl * Basistyp, der Überbau wird Pascal)
Freue mich schon auf 64 Bit
-Michael
Die SubSub-Routine ist hier noch in der Diskussion.
Ich bastele gerade an der primitiv-Multiplikation: in ASM mache ich nur Langzahl * Basistyp, der Überbau wird Pascal)
Freue mich schon auf 64 Bit

-Michael
-
- Beiträge: 3444
- Registriert: Mo 11. Sep 2006, 10:24
- OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
- CPU-Target: X32 / X64 / ARMv5
- Wohnort: Krefeld
Re: GNURZ - Arithmetik-Unit für große Zahlen
Hier die Subtraktion (tivial
)
-Michael

Code: Alles auswählen
procedure GNZSubInternal(var s, s1, s2: GNZBaseType; length: Integer); assembler;
asm
push ebx // s, s1 and s2 already are eax, edx and ecx
push esi
push edi
mov ebx, length
mov edi, ebx
shr ebx, 2
and edi, 3 // clear carry (finally edi=0).
jz @loop
inc ebx
dec edi
jz @l1
dec edi
jz @l2
dec edi
jz @l3
@loop:
mov esi, [edx+edi]
sbb esi, [ecx+edi]
mov [eax+edi], esi
lea edi, [edi+4] //ebp = ebp + 4 without affecting carry
@l3:
mov esi, [edx+edi]
sbb esi, [ecx+edi]
mov [eax+edi], esi
lea edi, [edi+4] //ebp = ebp + 4 without affecting carry
@l2:
mov esi, [edx+edi]
sbb esi, [ecx+edi]
mov [eax+edi], esi
lea edi, [edi+4] //ebp = ebp + 4 without affecting carry
@l1:
mov esi, [edx+edi]
sbb esi, [ecx+edi]
mov [eax+edi], esi
lea edi, [edi+4] //ebp = ebp + 4 without affecting carry
dec ebx
jnz @loop
pop edi
pop esi
pop ebx
end;
function GNZSub(s1, s2: GNZType): GNZType;
begin
setlength(Result, length(s1));
GNZSubInternal(Result[0], s1[0], s2[0], length(s1));
end;
-
- Beiträge: 3444
- Registriert: Mo 11. Sep 2006, 10:24
- OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
- CPU-Target: X32 / X64 / ARMv5
- Wohnort: Krefeld
Re: GNURZ - Arithmetik-Unit für große Zahlen
Hier noch eine Variante der ausgerollten Addition, die sich nur in der Reihenfolge der Befehle von einem vorigen Versuch mit ein-Register-Adressierung unterscheidet.
Hintergrund: Ein beliebtes C-Konstrukt is die autoincrement-Adressierung "*x++". Hierfür haben viele Prozessoren spezielle Befehle mit eingebauter inkrementierung der Adresse. Der X86 hat das nicht (außer bei den unflexiblen und auf neuen Prozessoren vermutlich nicht sehr schnellen "String"-Befehlen). Da die Befehle aber intern in RISC übersetzt werden, könnte es sein dass der Übersetzer in der Lage ist den Speicherzugriff mit anschließendem Increment in einen einzigen RISC befehl übersetzen kann.
Es könnte sich lohnen, die Geschwindigkeit dieser Variante zu testen.
-Michael
Hintergrund: Ein beliebtes C-Konstrukt is die autoincrement-Adressierung "*x++". Hierfür haben viele Prozessoren spezielle Befehle mit eingebauter inkrementierung der Adresse. Der X86 hat das nicht (außer bei den unflexiblen und auf neuen Prozessoren vermutlich nicht sehr schnellen "String"-Befehlen). Da die Befehle aber intern in RISC übersetzt werden, könnte es sein dass der Übersetzer in der Lage ist den Speicherzugriff mit anschließendem Increment in einen einzigen RISC befehl übersetzen kann.
Es könnte sich lohnen, die Geschwindigkeit dieser Variante zu testen.
-Michael
Code: Alles auswählen
procedure GNZAddInternal(var s, s1, s2: GNZBaseType; length: Integer); assembler;
asm
push ebx // s, s1 and s2 already are eax, edx and ecx
push esi
push edi
mov ebx, length
mov edi, ebx
shr ebx, 2
and edi, 3 // clear carry (finally edi=0).
jz @loop
inc ebx
dec edi
jz @l1
dec edi
jz @l2
dec edi
jz @l3
@loop:
mov esi, [edx]
lea edx, [edx+4] //edx = edx + 4 without affecting carry
adc esi, [ecx]
lea ecx, [ecx+4] //ecx = ecx + 4 without affecting carry
mov [eax], esi
lea eax, [eax+4] //eax = eax + 4 without affecting carry
@l3:
mov esi, [edx]
lea edx, [edx+4] //edx = edx + 4 without affecting carry
adc esi, [ecx]
lea ecx, [ecx+4] //ecx = ecx + 4 without affecting carry
mov [eax], esi
lea eax, [eax+4] //eax = eax + 4 without affecting carry
@l2:
mov esi, [edx]
lea edx, [edx+4] //edx = edx + 4 without affecting carry
adc esi, [ecx]
lea ecx, [ecx+4] //ecx = ecx + 4 without affecting carry
mov [eax], esi
lea eax, [eax+4] //eax = eax + 4 without affecting carry
@l1:
mov esi, [edx]
lea edx, [edx+4] //edx = edx + 4 without affecting carry
adc esi, [ecx]
lea ecx, [ecx+4] //ecx = ecx + 4 without affecting carry
mov [eax], esi
lea eax, [eax+4] //eax = eax + 4 without affecting carry
dec ebx
jnz @loop
pop edi
pop esi
pop ebx
end;
Zuletzt geändert von mschnell am Di 9. Dez 2008, 19:55, insgesamt 2-mal geändert.
-
- Beiträge: 3444
- Registriert: Mo 11. Sep 2006, 10:24
- OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
- CPU-Target: X32 / X64 / ARMv5
- Wohnort: Krefeld
Re: GNURZ - Arithmetik-Unit für große Zahlen
Hier die ASM-Versionen der AddAdd und SubSub Funktionen, die für die Karazuba-Multiplikation sinnvoll sein können.
Euklid könnte testen, ob die schneller sind als zweimal hintereinander Add bzw Sub aufzurufen.
Bei 64 Bit wird das interessanter, da wir keine temporären Speicherzellen brauchen, sondern genügend Register für die Pointer haben. Da kann man auch wieder die ein-Register Adressierung probieren und möglicherweise bringt Loop-Enrolling noch was.
-Michael
Euklid könnte testen, ob die schneller sind als zweimal hintereinander Add bzw Sub aufzurufen.
Bei 64 Bit wird das interessanter, da wir keine temporären Speicherzellen brauchen, sondern genügend Register für die Pointer haben. Da kann man auch wieder die ein-Register Adressierung probieren und möglicherweise bringt Loop-Enrolling noch was.
-Michael
Code: Alles auswählen
procedure GNZAddAddInternal(var s, s1, s2, s3: GNZBaseType; length: Integer); assembler;
var
stemp, s1temp: Pointer;
asm // s, s1 and s2 are eax, edx and ecx.
push ebx
push esi
push edi
mov stemp, eax // s -> stemp.
mov s1temp, edx // s1 -> s1temp.
mov ebx, length
xor edi, edi // rdi = 0 .
xor eax, eax // carry eax = 0.
@loop: // eax = 0 or 1 or 2 or 3.
mov edx, s1temp
mov esi, [edx+edi] // s1.
adc esi, eax // add in all carries.
mov eax, 0
adc eax, eax // save first carry.
add esi, [ecx+edi] // s2.
adc eax, 0 // save first and second carry.
mov edx, s3
add esi, [edx+edi] // 3nd carry !.
mov edx, stemp
mov [edx+edi], esi
lea edi, [edi+4] //ebp = ebp + 4 without affecting carry
dec ebx
jnz @loop
pop edi
pop esi
pop ebx
end;
procedure GNZSubSubInternal(var s, s1, s2, s3: GNZBaseType; length: Integer); assembler;
var
stemp, s1temp: Pointer;
asm // s, s1 and s2 are eax, edx and ecx.
push ebx
push esi
push edi
mov stemp, eax // s -> stemp.
mov s1temp, edx // s1 -> s1temp.
mov ebx, length
xor edi, edi // rdi = 0 .
xor eax, eax // carry eax = 0.
@loop: // eax = 0 or 1 or 2 or 3.
mov edx, s1temp
mov esi, [edx+edi] // s1.
sbb esi, eax // sub in all carries.
mov eax, 0
adc eax, eax // save first carry.
sub esi, [ecx+edi] // s2.
adc eax, 0 // save first and second carry.
mov edx, s3
sub esi, [edx+edi] // 3nd carry !.
mov edx, stemp
mov [edx+edi], esi
lea edi, [edi+4] //ebp = ebp + 4 without affecting carry
dec ebx
jnz @loop
pop edi
pop esi
pop ebx
end;
function GNZAddAdd(s1, s2, s3: GNZType): GNZType;
begin
setlength(Result, length(s1));
GNZAddAddInternal(Result[0], s1[0], s2[0], s3[0], length(s1));
end;
function GNZSubSub(s1, s2, s3: GNZType): GNZType;
begin
setlength(Result, length(s1));
GNZSubSubInternal(Result[0], s1[0], s2[0], s3[0], length(s1));
end;
Zuletzt geändert von mschnell am Di 9. Dez 2008, 23:23, insgesamt 1-mal geändert.
-
- Beiträge: 440
- Registriert: So 10. Dez 2006, 14:59
- OS, Lazarus, FPC: MacOSX Lion 10.7 (L 0.9.31 FPC 2.7.1)
- CPU-Target: 64Bit
- Kontaktdaten:
Re: GNURZ - Arithmetik-Unit für große Zahlen
Habe mich mal bissel ans testen des GGZTyps gesetzt, nach nen paar einfachen rechnungen +-/* glaube auch mod, verlief es richtig, sobald ich mehr zeit habe mache ich noch nen paar andere tests 
Btw: soviel Assembler in diesem Thread und ich verstehe null davon^^

Btw: soviel Assembler in diesem Thread und ich verstehe null davon^^
-
- Lazarusforum e. V.
- Beiträge: 2808
- Registriert: Fr 22. Sep 2006, 10:38
- OS, Lazarus, FPC: Lazarus v2.0.10, FPC 3.2.0
- Wohnort: Hessen
- Kontaktdaten:
Re: GNURZ - Arithmetik-Unit für große Zahlen
Werde ich machen! Eine Antibiotika-Allergie hatte mich die vergangenen Tage ein wenig außer Gefecht gesetzt und meine Zeiteinteilung etwas durcheinander geworfen. Dennoch sollte ich den Zeitplan einhalten können und Mitte der kommenden Woche eine erste assembleroptimierte GNURZ-Arithmetikunit supported by michael rausgeben.mschnell hat geschrieben:Hier die ASM-Versionen der AddAdd und SubSub Funktionen, die für die Karazuba-Multiplikation sinnvoll sein können.
Euklid könnte testen, ob die schneller sind als zweimal hintereinander Add bzw Sub aufzurufen.

Schön, danke. Werde auch ein paar Tests durchführen. Wenn wir bis Mitte kommender Woche keine Unregelmäßigkeiten feststellen, wird die Unterstützung negativer Zahlen Teil der nächsten Version sein. Geht ja ordentlich voran mit der Gnurz.EugenE hat geschrieben:Habe mich mal bissel ans testen des GGZTyps gesetzt, nach nen paar einfachen rechnungen +-/* glaube auch mod, verlief es richtig, sobald ich mehr zeit habe mache ich noch nen paar andere tests
Viele Grüße, Euklid
- af0815
- Lazarusforum e. V.
- Beiträge: 6859
- Registriert: So 7. Jan 2007, 10:20
- OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
- CPU-Target: 32Bit (64Bit)
- Wohnort: Burgenland
- Kontaktdaten:
Re: GNURZ - Arithmetik-Unit für große Zahlen
Lohnt sich nicht die Errichtung einer Testumgebung, damit Änderungen immer gegengeprüft werden können. Sprich das man Probleme = falsche Resulate sofort erkennen kann ?Euklid hat geschrieben:Schön, danke. Werde auch ein paar Tests durchführen. Wenn wir bis Mitte kommender Woche keine Unregelmäßigkeiten feststellen, ....
Vorschlag: Einlesen von definierten Zahlen, verarbeiten in der Testumgebung (GNURZ) , Ausgabe in Files und anschliessend mittels diff nachsehen ob die Ergebenisse das sind was man erwartet hat.
Ist zwar einmal ein grösserer Aufwand lohnt sich aber sicherlich. Vor allen können dann schneller alle Änderungen auf den verschiedenen Plattformen auch geprüft werden.
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).
-
- Beiträge: 3444
- Registriert: Mo 11. Sep 2006, 10:24
- OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
- CPU-Target: X32 / X64 / ARMv5
- Wohnort: Krefeld
Re: GNURZ - Arithmetik-Unit für große Zahlen
Das finde ich eine sehr gute idee !
Zudem wäre es natürlich sehr schön, auch auf anderen Plattformen als X86 PC zu testen. (Hierzu müssten dann Compiler-Optionen für die Endian-Behandling eingebaut werden.)
Die Pascal-Versionen der Unterprogramme sollten natürlich immer im Quellcode bleiben und die ASM-arianten nur mit Compiler-Optionen (und natürlich Plattform-spezifisch eingeschaltet werden.
-Michael
Zudem wäre es natürlich sehr schön, auch auf anderen Plattformen als X86 PC zu testen. (Hierzu müssten dann Compiler-Optionen für die Endian-Behandling eingebaut werden.)
Die Pascal-Versionen der Unterprogramme sollten natürlich immer im Quellcode bleiben und die ASM-arianten nur mit Compiler-Optionen (und natürlich Plattform-spezifisch eingeschaltet werden.
-Michael
Zuletzt geändert von mschnell am Mi 10. Dez 2008, 20:56, insgesamt 1-mal geändert.
- af0815
- Lazarusforum e. V.
- Beiträge: 6859
- Registriert: So 7. Jan 2007, 10:20
- OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
- CPU-Target: 32Bit (64Bit)
- Wohnort: Burgenland
- Kontaktdaten:
Re: GNURZ - Arithmetik-Unit für große Zahlen
Für die Tests ein Einsprung Test your Lazarus/FPC code with FPCUnit. Werde mir das mal ansehen.
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).
-
- Lazarusforum e. V.
- Beiträge: 2808
- Registriert: Fr 22. Sep 2006, 10:38
- OS, Lazarus, FPC: Lazarus v2.0.10, FPC 3.2.0
- Wohnort: Hessen
- Kontaktdaten:
Re: GNURZ - Arithmetik-Unit für große Zahlen
Für die Unit in Version 0.99.3 hatte ich einen Testzyklus geschrieben, der die Grundoperationen (Add, Sub, Mul, Div) an jeweils 10000 Zufallszahlen überprüfte. (Daher bin ich mir bei der 0.99.3 auch so sicher, dass die Grundoperationen weitgehend fehlerfrei sind).
Diesen Test auf sämtliche Funktionen der GNURZ auszuweiten ist keine schlechte Idee; sie wird umgesetzt. Werde mich daran begeben, wenn kommende Woche die neue Version der Gnurz veröffentlicht ist.
Wenn die GNURZ anschließend einen umfangreichen Testzyklus, welcher sämtliche Fälle behandelt, besteht, kann man sie dann aus dem Betastadium rausnehmen und als stabile Version bezeichnen.
Viele Grüße, Euklid
Diesen Test auf sämtliche Funktionen der GNURZ auszuweiten ist keine schlechte Idee; sie wird umgesetzt. Werde mich daran begeben, wenn kommende Woche die neue Version der Gnurz veröffentlicht ist.
Wenn die GNURZ anschließend einen umfangreichen Testzyklus, welcher sämtliche Fälle behandelt, besteht, kann man sie dann aus dem Betastadium rausnehmen und als stabile Version bezeichnen.
Viele Grüße, Euklid
-
- Beiträge: 3444
- Registriert: Mo 11. Sep 2006, 10:24
- OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
- CPU-Target: X32 / X64 / ARMv5
- Wohnort: Krefeld
Re: GNURZ - Arithmetik-Unit für große Zahlen
Hier ein Vorschlag für die Primitiv-Multiplikation. Bitte testen !
-Michael
-Michael
Code: Alles auswählen
procedure GNZMulInternal(var p, p1: GNZBaseType; p2: GNZBaseType; length: Integer); assembler;
var
ptemp, p1temp: Pointer;
p2temp: Cardinal;
asm // p, p1 and p2 are eax, edx and ecx.
push ebx
push esi
push edi
mov ptemp, eax // p -> ptemp.
mov p1temp, edx // p1 -> p1temp.
mov p2temp, ecx // p2 -> p2temp.
mov ebx, length
xor edi, edi // edi = 0 .
xor esi, esi // carry esi = 0.
@loop:
mov ecx, p1temp
mov eax, [ecx+edi] // p1.
mul [p2temp] // EAX * [p2temp] -> EDX:EAX.
// EDX <= $FFFFFFFE.
mov ecx, ptemp
add eax, [ecx+edi] // p = p + low(p1*p2).
// This digit | next digit | second next digit.
adc edx, 0 // | high(p1*p2) + new carry bit | Carry not possible.
add eax, esi // + old carry word. | Carry bit possible
mov ecx, ptemp
mov [ecx+edi], eax // -> p.
mov esi, edx // | next carry word.
adc esi, 0 // | + carry | Carry not possble
lea edi, [edi+4] //ebp = ebp + 4 without affecting carry.
dec ebx
jnz @loop
mov ecx, ptemp
// add esi, [ecx+edi] // digit n+1 is still = 0 !!! .
mov [ecx+edi], esi
pop edi
pop esi
pop ebx
end;
function GNZPrimitivMul(p1, p2: GNZType): GNZType;
var
l, i: Integer;
begin
l := length(p1);
setlength(Result, l*2);
for i := 0 to l*2 - 1 do begin
Result[i] := 0;
end;
for i := 0 to l - 1 do begin
GNZMulInternal(Result[i], p1[0], p2[i], l);
end;
end;
Zuletzt geändert von mschnell am Do 11. Dez 2008, 15:57, insgesamt 2-mal geändert.
-
- Beiträge: 440
- Registriert: So 10. Dez 2006, 14:59
- OS, Lazarus, FPC: MacOSX Lion 10.7 (L 0.9.31 FPC 2.7.1)
- CPU-Target: 64Bit
- Kontaktdaten:
Re: GNURZ - Arithmetik-Unit für große Zahlen
Wie wäre es wenn man die größte Länge von beiden: p1, p2 nimmt und nicht nur von p1
Dann hätte ich noch ne frage kommt mir das nurso vor oder wird es da falsch gerechnet?
(den Assembler code nicht mit berücksichtigt)
aber da wird dann ja die zahl : p1[0] = 4; p2[0] = 5; nach Result[0] geschrieben, also ist ja dann Result[0] = 0 und die 2( 20 ) wo kommt die hin?
Dann hätte ich noch ne frage kommt mir das nurso vor oder wird es da falsch gerechnet?
(den Assembler code nicht mit berücksichtigt)
aber da wird dann ja die zahl : p1[0] = 4; p2[0] = 5; nach Result[0] geschrieben, also ist ja dann Result[0] = 0 und die 2( 20 ) wo kommt die hin?