|
Autor |
Header einer C-Routine |
|
zippy
Senior  Dabei seit: 24.10.2018 Mitteilungen: 5147
 | Beitrag No.40, eingetragen 2023-08-20
|
Nur am Rande: Wozu soll das gut sein?
\quoteon(2023-08-20 23:22 - hyperG in Beitrag No. 38)
g++ -std=c++0x -m64 -march=native -Ofast -Wall -Wextra -Wpedantic -O3 -s mul256inclAusgabe.cpp
\quoteoff
Die O3-Option setzt doch die weitergehende Ofast-Option wieder außer Kraft. (Zitat Doku: "If you use multiple ‘-O’ options, with or without level numbers, the last such option is the one that is effective.")
--zippy
[Die Antwort wurde nach Beitrag No.38 begonnen.]
|
Profil
|
hyperG
Senior  Dabei seit: 03.02.2017 Mitteilungen: 2159
 | Beitrag No.41, eingetragen 2023-08-21
|
\quoteon(2023-08-20 22:54 - polygamma in Beitrag No. 36)
...
Was meinst du damit?
Schneller als was?
...
\quoteoff
na dass Dein Algorithmus "ohne Wandlung von String nach Zieltyp":
mpz_mul + mpz_add_ui
Schneller als mein Algorithmus "mit Konvertierung von String zum Zieltyp":
sprintf + mpz_set_str + mpz_mul
ist (egal ob nun mit der Sprache GMP Zieltyp mpz
oder Linux c mit Hilfe __uint128_t zum Zieltyp "struct u256b").
Noch deutlicher wird es, wenn man eine Quelldatei mit 1 Mrd Strings
hat, und 1 Mrd. Ergebnisse in einer anderen Datei herauskommen soll.
(da kommt dann noch gmp_printf dazu)
Ist aber schon interessant, dass der reine u256b_multiply mit gutem Linux-Compiler etwa 10 mal schneller als mpz_mul ist.
[Die Antwort wurde nach Beitrag No.38 begonnen.]
|
Profil
|
hyperG
Senior  Dabei seit: 03.02.2017 Mitteilungen: 2159
 | Beitrag No.42, eingetragen 2023-08-21
|
\quoteon(2023-08-20 23:36 - zippy in Beitrag No. 40)
Nur am Rande: Wozu soll das gut sein?
\quoteon(2023-08-20 23:22 - hyperG in Beitrag No. 38)
g++ -std=c++0x -m64 -march=native -Ofast -Wall -Wextra -Wpedantic -O3 -s mul256inclAusgabe.cpp
\quoteoff
Die O3-Option setzt doch die weitergehende Ofast-Option wieder außer Kraft. (Zitat Doku: "If you use multiple ‘-O’ options, with or without level numbers, the last such option is the one that is effective.")
--zippy
[Die Antwort wurde nach Beitrag No.38 begonnen.]
\quoteoff
d:\mingw64\bin\g++ -std=c++0x -m64 -march=native -Ofast -Wall -Wextra -Wpedantic -s mul256inclAusgabe.cpp -o mul256inclAusgabeNarr.exe
Erzeugt zwar eine exe, aber die stürzt nach dem Start sofort ab.
d:\mingw64\bin\gcc -Ofast -s mul256inclAusgabe.cpp -o mul256inclAusgabe.exe
ändert kaum was: 32 s
Alles noch meilenweit entfernt vom Online Compiler mit etwa 12 s
|
Profil
|
Ixx
Aktiv  Dabei seit: 05.04.2020 Mitteilungen: 382
 | Beitrag No.43, eingetragen 2023-08-21
|
\quoteon(2023-08-21 00:09 - hyperG in Beitrag No. 41)
Ist aber schon interessant, dass der reine u256b_multiply mit gutem Linux-Compiler etwa 10 mal schneller als mpz_mul ist.
\quoteoff
Eigentlich nicht. Dass ein Algorithmus, der für einen Spezialfall eines Problems optimiert wurde, bei diesem Spezialfall schneller ist als einer, der ein viel allgemeineres Problem löst, sollte jetzt nicht wirklich verwundern.
BTW: Wenn ich das richtig verstanden habe, dann will Jürgen ein paar Collatz-Iterationen berechnen. Entweder, das ist eine Spielerei — dann tut es auch eine naive Implementation eines 256-Bit-Datentyps — oder, es sollen *sehr* viele Startzahlen betrachtet werden. Aber auch dann ist eher eine schnelle 128-Bit-Arithmetik notwendig und auch hier reicht eine rudimentäre 256-Bit-Betrachtung aus, da der deutlich, deutlich, deutlich überwiegende Anteil der Rechnungen sich unterhalb von 2^128 abspielt.
Hier optimiert man m.E. nicht an der geeigneten Stelle. (Und ich sage das, weil ich selbst mal wieder seit Monaten immer mal weiter an meinem Collatz-Projekt arbeite, um David Barina mit seiner Implementation ( https://pcbarina.fit.vutbr.cz/ ) zu schlagen. Aber der macht sehr viel richtig; und meine algorithmischen Ideen wollen auch sauber umgesetzt werden…)
[Die Antwort wurde vor Beitrag No.42 begonnen.]
|
Profil
|
zippy
Senior  Dabei seit: 24.10.2018 Mitteilungen: 5147
 | Beitrag No.44, eingetragen 2023-08-21
|
\quoteon(2023-08-21 00:21 - hyperG in Beitrag No. 42)
Alles noch meilenweit entfernt vom Online Compiler mit etwa 12 s
\quoteoff
Es ging mir nicht um die Geschwindigkeit, sondern nur darum, dass ein gcc-Aufruf, der beide Optionen enthält, keinen Sinn ergibt.
|
Profil
|
juergenX
Aktiv  Dabei seit: 08.07.2019 Mitteilungen: 942
 | Beitrag No.45, vom Themenstarter, eingetragen 2023-08-23
|
Erstmal vielen Dank für den gewaltigen Response!
Die Routine quasi Karatsuba procedure dürfte auf einem guten Intel wohl die besten Resultate ergeben.
Ganz ohne C bekommenn wir das Problem der richtigen Initialisierung.
Wir hätten als Vorgabe zu berechnen
156327963765257101755948112982892178397 * 656327963765257101755948112982892178393
Ergebnis ist wie aus Beitrag #39 zu ersehen : 102602414137620088241174671551465753558236494655099839643647545871860456424623
Ich denke mal das stimmt;) Mich interessiert wie übergeben wie die 2* 4 * 64 Bit Blöcke an die reine Assembler Routine wie in #39?
rsi und rdi und rdx sind wohl Zeiger auf Zwischenspeicher rax und r8, r9?
Und wenn ich den BP dann um 0x8 kleiner mache, zeigen die auf den andere Faktor?
Dann mov 0x18(%rdx), %rcx verstehe ich schon nicht, ist das der Zeiger uuf das Ergebnis, der kommt nach rcx?
Weiter zu folgen gelingt mir nicht so recht..
Vor allem müssen ja
156327963765257101755948112982892178397 und 656327963765257101755948112982892178393
erstmal in je 4 Blocke gewandelt werden.
so dass wir 8 Register 64 bit als 2 Faktoren haben.
Also die Doku des progs mul256brute ist schlecht bzw. nicht vorhanden.
Und wie die Wandlung Dec to Hec und der Aufruf in Assembler (masm)?
ml256brute ( usb256 *x1 , usb256 *x2)
Evtl. über die binary to Dec Methode weiter oben?!
Und am Ende alles Rückwärts. Als Stack Pointer Referenz wird immer rbp genommen, siehe
mov -0x10(%rsp), %r12
Am Ende alles Rückwärts, Wo genaus steht das 4 Block Ergebnis?
Und es muss ja noch in dec gewandelt werde, wir wissen ja jetzt wie.(#33)
\sourceon ASM
mul256brute:
mov (%rsi), %rax
mov (%rdx), %r8
mov 0x8(%rdx), %r9
mov %rbp, -0x8(%rsp)
mov %rax, %rbp
mov 0x18(%rdx), %rcx
imul %rbp, %rcx
mov 0x10(%rdx), %r10
mul %r8
mov %rax, (%rdi)
mov %rdx, %r11
mov %rbp, %rax
mul %r10
mov %r12, -0x10(%rsp)
mov %rax, %r12
add %rdx, %rcx
mov %rbp, %rax
mul %r9
add %rax, %r11
mov 0x8(%rsi), %rbp
adc %rdx, %r12
adc $0, %rcx
imul %rbp, %r10
mov %rbp, %rax
mul %r8
add %rax, %r11
mov %rbp, %rax
adc %rdx, %r12
adc %r10, %rcx
mov 0x10(%rsi), %rbp
mul %r9
mov %r11, 0x8(%rdi)
imul %rbp, %r9
add %rax, %r12
mov %rbp, %rax
adc %rdx, %rcx
mov 0x18(%rsi), %rbp
imul %r8, %rbp
add %rbp, %rcx
mov -0x8(%rsp), %rbp
mul %r8
add %rax, %r12
mov %r12, 0x10(%rdi)
mov -0x10(%rsp), %r12
adc %r9, %rcx
add %rdx, %rcx
mov %rcx, 0x18(%rdi)
retq
\sourceoff
Ich erwarte nicht eine vollständige Doku. Aber so rätselt man doch sehr herum.
Am liebsten hätte ich ein fertiges in MASM Compilierbares Programm. Multiplikation 2er Dezimal-Zahlen mit Dezimal-Ergebnis.
Das kann ich hier aber von keinem verlangen. 😄🤔🙄🤒😁
|
Profil
|
hyperG
Senior  Dabei seit: 03.02.2017 Mitteilungen: 2159
 | Beitrag No.46, eingetragen 2023-08-24
|
Hallo juergenX,
mit Microsoft® Macro Assembler (x64) habe ich herumgespielt und konnte den Syntax auch anpassen. Natürlich zunächst mit addASM, um die Fehlersuche klein zu halten.
Jedoch hat die Microsoft/VC Welt ganz andere Einsprungs-Register, andere Reihenfolge & es gibt zig Arten der Übergabe und Rückgabe...
(viel Zeit & kaum Erfolge)
Dafür habe ich das hier gefunden mit AVX2 Befehlen:
\sourceon cpp
__m256i add256AVX(uint32_t& carry, __m256i A, __m256i B) {
A = _mm256_xor_si256(A, _mm256_set1_epi64x(0x8000000000000000));
__m256i s = _mm256_add_epi64(A, B);
__m256i cv = _mm256_cmpgt_epi64(A, s);
__m256i mv = _mm256_cmpeq_epi64(s, _mm256_set1_epi64x(0x7fffffffffffffff));
uint32_t c = _mm256_movemask_pd(_mm256_castsi256_pd(cv));
uint32_t m = _mm256_movemask_pd(_mm256_castsi256_pd(mv));
{
c = m + 2 * c; // lea
carry += c;
m ^= carry;
carry >>= 4;
m &= 0x0f;
}
return _mm256_add_epi64(s, BROADCAST_MASK[m]);
}
/* __m256i _mm256_add_epi64 (__m256i a, __m256i b) macht mit 1 Maschinenbefehl das hier:
FOR j := 0 to 3
i := j*64
dst[i+63:i] := a[i+63:i] + b[i+63:i]
ENDFOR
dst[MAX:256] := 0
*/
\sourceoff
Mit Struct & Union + Anpassung (fast) aller Funktionen für den bei VC fehlenden Typ __uint128_t hat das superschnell funktioniert.
(dürfte sogar schneller als addASM sein...
Bei der Multiplikation habe ich noch nichts für mul256AVX gefunden
(nur Arrays oder mit Vorzeichen...).
Dafür fand ich was, an dem ich noch arbeite. Falls es funktioniert,
wäre es ein Quantensprung -> ich melde mich ...
Grüße Gerd
|
Profil
|
juergenX
Aktiv  Dabei seit: 08.07.2019 Mitteilungen: 942
 | Beitrag No.47, vom Themenstarter, eingetragen 2023-08-26
|
Nochmal danke für die vielen Anregungen!
ich hab den richtigen Aufruf meiner ursprünglichen Routine leider noch nicht verstanden..
Diese pointerstruktur ist unklar sry...
\sourceon c
typedef unsigned long long u64b;
typedef __uint128_t u128b;
typedef struct u256b u256b;
struct u256b
{
u64b lo;
u64b mid;
u128b hi;
};
mul128b:
imul %rdi,%rcx
mov %rdx,%rax
imul %rdx,%rsi
mul %rdi
add %rsi,%rcx
lea (%rcx,%rdx,1),%rdi
mov %rdi,%rdx
retq
// Der aufruf muesste dann wohl richtig so heissen?
x:u256b = (2,1,1)// als Beispiel = 2+2^64+2^128
y:u256b = (3,0,2) // als Beispiel = 3+2*2^128
z= mul128b(*x,*y)
\sourceoff
Kann das jemand berichtigen?
Multiplizieren wir pointer?
|
Profil
|
hyperG
Senior  Dabei seit: 03.02.2017 Mitteilungen: 2159
 | Beitrag No.48, eingetragen 2023-08-26
|
Quantensprung hat sich erledigt, da meine CPU zwar AVX512 hat,
aber ich viel zu spät bemerkte, dass es noch viele Unterbefehlsgruppen gibt,
die meine CPU aber nicht alle unterstützt! (AVX512IFMA fehlt!; viele Abstürze & viel Zeit vergeudet, da es manchmal nicht abstürzte -> und ich an der falschen Stelle suchte)
Dann habe ich endlich mul128Bit hinbekommen (was unter VC nicht gibt & beim mingw-w64 viel zu langsam war: 32 s):
- ASM -> ml64.exe -> obj -> 73333 mul256inclSprintf dann 17.1 s
- AVX für den mul128 Teil -> 73333 mul256inclSprintf dann 17.4 s
Immerhin fast eine Verdopplung der Geschwindigkeit (und mit Optimierung der String-Nach-u256 könnte man bis 3 s herunterkommen), aber immer noch meilenweit von GMP entfernt.
analog Beitrag No.37 (1.1 s & 9,9 GMP), bei mir:
\sourceon nameDerSprache
656327963765257101755948112982892178393 * 156327963765257101755948112982111111111 =
102602414137620088241174671551465753558236494655099839643647545871860456424623 in 9.729 s GMP
656327963765257101755948112982892178393 * 156327963765257101755948112982111111111 =
102602414137620088241174671551465753558236494655099839643647545871860456424623 in 46.441 s u256b_multiplyGL
\sourceoff
Dann fand ich zwar eine 1024 Bit mul mit AVX512:
https://github.com/vkrasnov/vpmadd/blob/master/avx512_mul1024.s
aber leider nur in ASM Syntax der Linux-Welt und mein
gcc für win10 meckert nach
g++ -c avx512mul1024.s -o avx512mul1024.o
in der Zeile
\sourceon asm
.type mul1024_avx512, @function ----> Error 1
; ... hier code der Funktion
vmovdqu64 ACC3, 64*3(res)
ret
.size mul1024_avx512, .-mul1024_avx512 --->Error 2
;avx512_mul1024.s:177: Warning: .type pseudo-op used outside of .def/.endef: ignored.
;avx512_mul1024.s:177: Error: junk at end of line, first unrecognized character is `m'
;avx512_mul1024.s:630: Warning: .size pseudo-op used outside of .def/.endef: ignored.
;avx512_mul1024.s:630: Error: junk at end of line, first unrecognized character is `m'
\sourceoff
Schade...
[Die Antwort wurde nach Beitrag No.46 begonnen.]
|
Profil
|
hyperG
Senior  Dabei seit: 03.02.2017 Mitteilungen: 2159
 | Beitrag No.49, eingetragen 2023-08-26
|
\quoteon(2023-08-26 19:34 - juergenX in Beitrag No. 47)
...
Kann das jemand berichtigen?
Multiplizieren wir pointer?
\quoteoff
Nein, rcx und rax sind normale 64 Bit Register, die bei Deinem Beispiel sofort an imul 1:1 übergeben werden.
Allerdings hast Du viele Welten vermischt:
a) Linux-Welt um gcc Compiler:
- entweder den internen
\sourceon c
uint16_t MeineVariable;
asm volatile ("imul %h0, %b0" : "+a" (MeineVariable) );
\sourceoff
siehe https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#Output-Operands
oder https://www.felixcloutier.com/documents/gcc-asm.html
hier zum Spielen: https://godbolt.org/z/2LlfoL
-oder den externen mit Umweg der .o Datei. (so wie bei Deinem Beispiel, jedoch mehr drumherum, wo ich mich aber nicht auskenne).
Wie hier die Variablen an die Register weitergeleitet werden, ist mir auch nicht bekannt! Sind aber meist andere als in der Windows Welt!
(Schlagwort "Aufrufkonventionen")
Mein Mingw emuliert den gcc unter Windows, bringt aber oft Fehler und verhält sich anders als das Original unter Linux.
b) Windows Welt z.B. VC:
- Bei 64 Bit gibt es den internen ASM-Compiler nicht mehr.
(beim teuren Intel-Compiler mag das noch gehen)
- Also Extern (das geht immer, allerdings anderer Syntax!):
\sourceon asm
mulASM128 proc
mov rax, rcx
mul rdx ; Put the low digit in place (hi is already there)
mov qword ptr [r8], rdx ; hier wird der Hi Teil auf den vorhandenen Ptr gelegt
ret ; Return rax = Lo-Anteil
mulASM128 endp
\sourceoff
Dann ml64.exe -> obj -> diese dann in das cpp Projekt einbinden und mit
\sourceon cpp
extern "C" unsigned __int64 mulASM128(unsigned __int64 a, unsigned __int64 b, unsigned __int64 *mulHi);
//da die Register getrennt kommen, kann man sie z.B. so in eine Struktur schieben (Profis können das schon in ASM, aber kompliziert):
u128b Mul64x64_128(unsigned __int64 a, unsigned __int64 b)
{
u128b u128ret;//Strukturtyp, den man sich definieren muss
u128ret.lo = mulASM128(a,b, &u128ret.hi);
return u128ret;
}
//Aufruf:
u128b Meine128BitVariable=Mul64x64_128(2^63, 2^62);
\sourceoff
|
Profil
|
hyperG
Senior  Dabei seit: 03.02.2017 Mitteilungen: 2159
 | Beitrag No.50, eingetragen 2023-08-26
|
Verwirrend:
während gcc
aus
\sourceon c
unsigned long long mul128(unsigned long long a, unsigned long long b, unsigned long long *c) {
*c=7;
return a*b;
}
\sourceoff
das hier macht:
\sourceon asm
;mul128(unsigned long long, unsigned long long, unsigned long long*):
mov rax, rdi
mov QWORD PTR [rdx], 7
imul rax, rsi
ret
\sourceoff
also die Übergabeparameter in rdi, rsi und rdx kommen,
Sind das bei VC: RCX, RDX , r8
Ptr-Syntax ist normalerweise: QWORD PTR [rdx + 7h]
bei gcc extern: 0x7(%rdx)
|
Profil
|
zippy
Senior  Dabei seit: 24.10.2018 Mitteilungen: 5147
 | Beitrag No.51, eingetragen 2023-08-26
|
\quoteon(2023-08-26 22:10 - hyperG in Beitrag No. 50)
Verwirrend
\quoteoff
Sind das nicht einfach die Unterschiede zwischen der System V amd64 ABI (siehe z.B. hier) und der Windows x64 ABI (siehe z.B. hier)?
|
Profil
|
hyperG
Senior  Dabei seit: 03.02.2017 Mitteilungen: 2159
 | Beitrag No.52, eingetragen 2023-08-30
|
Profil
|
juergenX
Aktiv  Dabei seit: 08.07.2019 Mitteilungen: 942
 | Beitrag No.53, vom Themenstarter, eingetragen 2023-08-31
|
\quoteon(2023-08-16 21:45 - polygamma in Beitrag No. 15)
Hallo, Jürgen :)
Ich habe eine Funktion geschrieben, die eine beliebige Binärfolge entgegennimmt und die Dezimaldarstellung berechnet.
Wir sind hier zwar bis 256 bit unterwegs, aber ich dachte mir, dass es "allgemein" ein bisschen netter ist :)
Liebe Grüße
\quoteoff
OK, ich woltte noch anmerken zu oben zitierten Beitrag #15 :
Man koennete ja erst die die gewünschten DezimalFaktoren im Binaerstrings wandelt etwa
$x = 2^{256} = "1 0000000 0000000 0000000 0000000 0000000 0000000 0000000 0000000 0000000 0000000 0000000 0000000 0000000 0000000 0000000 0000000b"$.
und
$x-1 = 2^{256}-1 = "0 1111111 1111111 1111111 1111111 1111111 1111111 1111111 1111111 1111111 1111111 1111111 1111111 1111111 1111111 1111111 1111111b"$.
und
$y = 2^{128} = "1 0000000 0000000 0000000 0000000 0000000 0000000 0000000 0000000 0000000 0000000 0000000 0000000 0000000 0000000 0000000 0000000b"$,
und
$y-1 = 2^{128} -1 = "0 1111111 1111111 1111111 1111111 1111111 1111111 1111111 1111111 1111111 1111111 1111111 1111111 1111111 1111111 1111111 111111b"$.
$y-2 = 2^{128} -2 = "0 1111111 1111111 1111111 1111111 1111111 1111111 1111111 1111111 1111111 1111111 1111111 1111111 1111111 1111111 1111111 111110b"$,
Dann die Binärdarstellungvon y-1 und y-2 Multiplizieren nach der Kastubra Methode z = (y-1,y-2) berechnen und zurückwandeln in Decimal Zahlen.
Ich habe das extra wg. der Lesbarkei in 8er Blöcke unterteilt.
Das Wandeln in Binaerstrngs dürfte recht flott gehen, die Multiplikation nicht.
Ich muesste erst noch in cygwin die gmp_library und C++ importieren.
Wenn x recht klein < 1024 = 2^10 ist, werden Multiplikationen leichter, auch wenn y gegen 2^245 geht fallen vielen einzel Aktionen bei z =(x * y)
weg.
EDIT 01.09 Die FFFFFFFFF blöcke sind ja nicht in (0,1)
|
Profil
|
gonz
Senior  Dabei seit: 16.02.2013 Mitteilungen: 4952
Wohnort: Harz
 | Beitrag No.54, eingetragen 2023-09-01
|
Als Anmerkung hierzu noch... Wenn es "nur" um die Berechnung von Collatz Folgen geht, reicht die Multiplikation mit 3. Und diese läßt sich wahrscheinlich am einfachsten darstellen, indem man eine Kopie der Ausgangszahl durch Shiften mit 2 multipliziert und dann die Ausgangszahl addiert.
|
Profil
|
juergenX
Aktiv  Dabei seit: 08.07.2019 Mitteilungen: 942
 | Beitrag No.55, vom Themenstarter, eingetragen 2023-09-02
|
Ja schön wäre das alles, hätte man ein shl (256) bit mit carry im Intel..
|
Profil
|
juergenX
Aktiv  Dabei seit: 08.07.2019 Mitteilungen: 942
 | Beitrag No.56, vom Themenstarter, eingetragen 2023-09-02
|
ich komme noch mal auf die routine aus meinem Erstbeitrag zurück:
\sourceon C
typedef unsigned long long u64b;
typedef __uint128_t u128b;
typedef struct u256b u256b;
struct u256b
{
u64b lo;
u64b mid;
u128b hi;
};
u256b mul256b(u256b *x, u256b *y)
{
u128b t1 = (u128b) x->lo * y->lo;
u128b t2 = (u128b) x->lo * y->mid;
u128b t3 = x->lo * y->hi;
u128b t4 = (u128b) x->mid * y->lo;
u128b t5 = (u128b) x->mid * y->mid;
u64b t6 = x->mid * y->hi;
u128b t7 = x->hi * y->lo;
u64b t8 = x->hi * y->mid;
u64b lo = t1;
u128b m1 = (t1 >> 64) + (u64b)t2;
u64b m2 = m1;
u128b mid = (u128b) m2 + (u64b)t4;
u128b hi = (t2 >> 64) + t3 + (t4 >> 64) + t5 + ((u128b) t6 << 64) + t7
+ ((u128b) t8 << 64) + (m1 >> 64) + (mid >> 64);
u256b result = {lo, mid, hi};
return result;
}
\sourceoff
Ich habe die pointer Aufrufe wenn nötig nicht verstanden.
Wie sieht der main Programm aufruf aus?
Was ist mul128b (usb1, usb2)?
oder müssen da irgendwo sterne hin?
Sei
\sourceon C
usb1.hi= 3;
usb1.mid= 5;
usb1.lo= 2^10;
usb2.hi= 7;
usb2.mid= 6;
usb2.lo= 2^11;
\sourceoff
Ich kenne das von Pascal so etwa..
\sourceon pascal
r=record
x:integer;
y:integer;
end;
var r1: r:
var r2: r:
var p1 : ^r1;
var p2 : ^r2;
r4:´= r1^ + r2^;
\sourceoff
ich verstehe das pointerkonzept in C oder C++ nicht.
|
Profil
|
hyperG
Senior  Dabei seit: 03.02.2017 Mitteilungen: 2159
 | Beitrag No.57, eingetragen 2023-09-02
|
\quoteon(2023-09-02 20:12 - juergenX in Beitrag No. 56)
ich komme noch mal auf die routine aus meinem Erstbeitrag zurück:
\sourceon C
typedef unsigned long long u64b;
typedef __uint128_t u128b;
typedef struct u256b u256b;
struct u256b
{
u64b lo;
u64b mid;
u128b hi;
};
u256b mul256b(u256b *x, u256b *y)
{
u128b t1 = (u128b) x->lo * y->lo;
u128b t2 = (u128b) x->lo * y->mid;
u128b t3 = x->lo * y->hi;
u128b t4 = (u128b) x->mid * y->lo;
u128b t5 = (u128b) x->mid * y->mid;
u64b t6 = x->mid * y->hi;
u128b t7 = x->hi * y->lo;
u64b t8 = x->hi * y->mid;
u64b lo = t1;
u128b m1 = (t1 >> 64) + (u64b)t2;
u64b m2 = m1;
u128b mid = (u128b) m2 + (u64b)t4;
u128b hi = (t2 >> 64) + t3 + (t4 >> 64) + t5 + ((u128b) t6 << 64) + t7
+ ((u128b) t8 << 64) + (m1 >> 64) + (mid >> 64);
u256b result = {lo, mid, hi};
return result;
}
\sourceoff
Ich habe die pointer Aufrufe wenn nötig nicht verstanden.
Wie sieht der main Programm aufruf aus?
...
\quoteoff
Das hatten wir doch schon im #8 unter
https://matheplanet.com/matheplanet/nuke/html/viewtopic.php?rd2&topic=263288&start=0#p1913347
dass Ptr einer Variablen mit &Variablenname übergeben werden.
und in #22 sieht man, dass man sich nicht mehr mit u256 als Zwischenvariable herumschlagen muss,
da decimal_to_u256b bereits den nötigen Ptr liefert...
|
Profil
|
juergenX
Aktiv  Dabei seit: 08.07.2019 Mitteilungen: 942
 | Beitrag No.58, vom Themenstarter, eingetragen 2023-09-23
|
Ich nochmal
Mir ging es darum den code aus https://locklessinc.com/articles 256bit_arithmetic/ in C einzubinden.
Wie aber genau?
\sourceon asm
mul256brute:
mov (%rsi), %rax
mov (%rdx), %r8
mov 0x8(%rdx), %r9
mov %rbp, -0x8(%rsp)
mov %rax, %rbp
mov 0x18(%rdx), %rcx
imul %rbp, %rcx
mov 0x10(%rdx), %r10
mul %r8
mov %rax, (%rdi)
mov %rdx, %r11
mov %rbp, %rax
mul %r10
mov %r12, -0x10(%rsp)
mov %rax, %r12
add %rdx, %rcx
mov %rbp, %rax
mul %r9
add %rax, %r11
mov 0x8(%rsi), %rbp
adc %rdx, %r12
adc $0, %rcx
imul %rbp, %r10
mov %rbp, %rax
mul %r8
add %rax, %r11
mov %rbp, %rax
adc %rdx, %r12
adc %r10, %rcx
mov 0x10(%rsi), %rbp
mul %r9
mov %r11, 0x8(%rdi)
imul %rbp, %r9
add %rax, %r12
mov %rbp, %rax
adc %rdx, %rcx
mov 0x18(%rsi), %rbp
imul %r8, %rbp
add %rbp, %rcx
mov -0x8(%rsp), %rbp
mul %r8
add %rax, %r12
mov %r12, 0x10(%rdi)
mov -0x10(%rsp), %r12
adc %r9, %rcx
add %rdx, %rcx
mov %rcx, 0x18(%rdi)
retq
\sourceoff
Korrekt aufzurufen aus C, nicht C++ , gmp, oder C#, weil ich dieses inline assembler doof finde.
Wie ist das main prog?
\sourceon C
a = 123456789012345678906372467832647878594678954768954769;
b = 987654321098765432106372467823467823647863726478324678;
// mainprog
decimal Mul2Integer
{
call mul256brute(a,b)
return result
}
\sourceoff
Das Ergebinis in dezimal Ziffern wäre am besten. Oder Binärstring oder Hexstring ist auch recht.
Habe immer Probleme opder zu wenig Erfahrung mit der aufruf Syntax in C. Das call ist nicht so richtig ich weiß es eben nicht..
Ausgabe von result waere etwa dieser Art
\sourceon C
__int256 path lieget als dezimalZahl vor.
void printf_256(__int256 path)
{
// Ausgabe int256 im Dezimalformat
// jeweils 6 Ziffern durch Kommata getrennt
int ziffer[52];
int cnt = 0;
int loop;
while (path>0)
{
ziffer[cnt]=path%10;
path=path/10;
cnt++;
}
for (loop = cnt-1;loop >= 0;loop--)
{
printf("%i",ziffer[loop]);
if ( (loop%6 == 0) && (loop > 0) )
printf(",");
}
}
\sourceoff
Was genau also macht:
\sourceon C
mov (%rsi), %rax
mov (%rdx), %r8
mov 0x8(%rdx), %r9
mov %rbp, -0x8(%rsp)
mov %rax, %rbp
mov 0x18(%rdx), %rcx
\sourceoff
Ich versuchee die Parameter übergabe (call by reference) im einzelnen nachzuvollziehen.
Das 64 bit register %rax wir dahin gefüllt worauf %rsi zeigt?
Das 64 bit register %r8 wird mit dem gefüllt worauf %rdx zeigt?
Der Speicher auf den (%rdx+8) zeigt wird nach %r9 kopiert.
Das waere dann der high 128 bit teil von was a oder b.
Wir habe 3 pointer in rsi,rdx,rdx+8. Nach rax, r8,r9.
diese (rsi,rdx) muessen ja irgendwo vorbereitet sein?
So dass vom bp aus von unten gesehen pointer (%rdx+8), %r8, pointer(%rsi). (LIFO stack) benutzt werden
Der andere faktor landet in sp+8,rbp,%rcx oder?
Das minus bei mov %rbp, -0x8(%rsp) wundert mich. muss das nicht plus sein?
Prinzipiell geht es um parameter übergabe by valuie or reference
Wenn ich den ganzen mul256brute in ASM schreiben wollte, was ich am liebsten täte,
Müsste ich 8 64 bit register pushen oder eben so vorgehn wie der Autor von "mul256brute" vorgeht
Am ende steht das Ergebnis a*b auf dem Stack oder?
\sourceon C
add %rbp, %rcx
mov -0x8(%rsp), %rbp
mul %r8
add %rax, %r12
mov %r12, 0x10(%rdi)
mov -0x10(%rsp), %r12
adc %r9, %rcx
add %rdx, %rcx // das kommt mit falsch vor 2 Wete nach %rcx?
mov %rcx, 0x18(%rdi)
\sourceoff
in 4 64 bit Blöcken auf dem Stack. So hofft man;) Die eigentlie Rechnerei nach der Karatsuba Mehode ist recht kurz.
Es müssen Eingangs nur die pointer %rsi,%rdx,%rbp stimmen, die müssen auf was richtiges zeigen.
Vielleich kann da mal jemand drüber schauen :)
Ich Find die Verwending von $rbp und %rsp verwirrend..
Meine Frage ist,wie macht der C compiler das? offenbar richtig. Und könnte man an sich alles in Intel ASM schreiben!?
Mir geht um das volle verstehen der parameter übergabe. und evt eine reine Intel 8086 ASM version.
In dem anderen Threead find ich viele anregungen danke auch dafür.
Wie einfach wäre es wenn x nur 1 Byte groß wäre!
y = 123478875544545*3.
Man muss mit call by pointer arbeiten um, auf 256 bit Werte zu verweisen.
Thepretisch kann man so auch mit 512 bit oder längeren Faktoren arbeiten.
Thx
|
Profil
|
hyperG
Senior  Dabei seit: 03.02.2017 Mitteilungen: 2159
 | Beitrag No.59, eingetragen 2023-09-24
|
Da Du viele bereits beantwortete Dinge immer wieder neu fragst, und
"Das Ergebinis in dezimal Ziffern wäre am besten...."
(wir uns also im Kreise drehen ohne dabei vorwärts zu kommen)
empfehle ich den bereits empfohlenen Code unter
fast Karatsuba multiplication.
Dort ist alles fertig und in beiden Welten (LINUX & Windows) sofort mit Dezimalzahlen lauffähig.
Außerdem hatte ich bereits auf 2 neue Beiträge von mir hingewiesen, die auch zig Fragen beantworteten.
Letzte Hilfe zu:
Die Vermischung der LINUX-Welt & Windows Welt...
und die Vermischung der ASM-Welt mit der C-Welt...
Es gibt nicht den einen ASM-Compiler und c -Compiler,
sondern zig in beiden Welten! Wenn Du alles 1:1 sofort lauffähig auf dem Tablet geliefert haben möchtest, brauchen wir von Dir:
Betriebssystem:
C-Compiler
ASM-Compiler
Bereits unter hier
hatte ich die Register benannt, die das jeweilige Betriebssystem bei Funktionen übergeben werden.
https://matheplanet.com/matheplanet/nuke/html/uploads/c/47407_ASM_Register_Tabelle.png
Da wir 256Bit Strukturen an mehrere 64 Bit Register übergeben wollen, muss man also mit Ptr. arbeiten.
Mit https://godbolt.org/ solltest Du Dir klar machen, was welcher Compiler in welcher Welt für einen Maschinencode erzeugt:
a) Dein Ausgangs-LINK bezieht sich auf die LINUX-Welt mit gcc Compiler
https://matheplanet.com/matheplanet/nuke/html/uploads/c/47407_ASM_Linux.png
b) wenn Du aber in der Windows Welt bist, wo Intel-ASM gebräuchlicher ist, sieht ASM komplett anders aus:
https://matheplanet.com/matheplanet/nuke/html/uploads/c/47407_ASM_Windows.png
Dort sind die Übergabeparameter (also die Ptr., die auf die Strukturvariablen zeigen) also rcx, rdx, r8!
Hinweis: da die beiden Compiler unterschiedlich gut optimieren,
hat der obere gcc die beiden
64 Bit Teilbefehle
mov qword ptr [r8], rdx
mov qword ptr [r8 + 8], rax
zu einem AVX-Befehl optimiert
vmovdqu , denn dieser kann 128 Bit (oder 256 {oder sogar 512 bei meinem i9})
schnell von 128Bit Registern nach 128 Bit Variablen kopieren.
Grüße
|
Profil
|
hyperG
Senior  Dabei seit: 03.02.2017 Mitteilungen: 2159
 | Beitrag No.60, eingetragen 2023-09-24
|
Der ältere gcc erzeugt noch den einfachen ASM-Code ohne AVX Befehle:
https://matheplanet.com/matheplanet/nuke/html/uploads/c/47407_ASM_Linux2.png
Wenn Du jetzt Rückwärts ASM in einen C-Compiler mit hineinbringen willst,
gibt es je nach Betriebssystem & Compiler mehrere Möglichkeiten:
Linux:
a) gcc & Mingcc haben inline-ASM
b) externer ASM-Compiler erzeugt *.o Datei -> die kann gcc einbinden
Win
a) alter 32Bit VC konnte noch inline ASM
b) 64 Bit Compiler können das meist nicht mehr -> externer ASM -> *.obj
-> bei den Linker-Einstellungen diese obj angeben und im C-Code als
extern"c" ... die Funktion bekannt machen. Der Linker sucht dann in der obj nach dem Code den er einbinden soll.
|
Profil
|
juergenX
Aktiv  Dabei seit: 08.07.2019 Mitteilungen: 942
 | Beitrag No.61, vom Themenstarter, eingetragen 2023-09-25
|
\quoteon(2023-09-24 16:46 - hyperG in Beitrag No. 60)
.
.
.
Linux:
a) gcc & Mingcc haben inline-ASM
b) externer ASM-Compiler erzeugt *.o Datei -> die kann gcc einbinden
Win
a) alter 32Bit VC konnte noch inline ASM
b) 64 Bit Compiler können das meist nicht mehr -> externer ASM -> *.obj
-> bei den Linker-Einstellungen diese obj angeben und im C-Code als
extern"c" ... die Funktion bekannt machen. Der Linker sucht dann in der obj nach dem Code den er einbinden soll.
\quoteoff
Extra vielen Dank nochmal!!
Ich will noch nicht viel dazu sagen aber weil ich noch nicht alles voll gesichtet habe und nicht wieder doof fragen will.
Methode b :
mit externem w10 assembler, wie heiß der?, .Obj File aus den mul256brute s.o. erzeugen.
Die Rahmen Procedur in gcc schreiben und dort im C-Code als
extern"c" ... die Funktion bekannt machen.
Dazu muss ich genau wissen wie die 8 (!) 64bit qwords bzw.3 Pointer (stimmt das?) (besser) vom Rahmenprogramm in gcc in das kompilat übergeben werden. Hast du ja auch viel zu gesagt :)
Ein/ ausgabe ist in C leichter, wobei ich immer mit cygwin arbeite..
Also win 10, cygwin, externer assembler.
Wie gesagt du hat im Grunde alles lang und breit erklärt:)
Ich habe dies .obj einbinden aus turbo pascal 7.01 gut gekonnt sogar mit der 8087 FPU.
asm = tasm
Linker Tlink
Debugger Tdebug
Leider geht turbo pascal 7.01 nich mehr auf 64 bit prozessoren, dass ichs wuesste
Oder man nimmt Lazarus.
Aber C, C++, c sharp ist wohl unter win wie Linus heutiger standard.
Also win 10, cygwin, externer assembler.
Kein inline oder so nötich.
Leider kann man kein mul xmm1,Xmm2,Xmm3 nutzen.
Wenn man genau die Übergabe modalitäten vom Hauptsprche zu assember kennt, kann man vieles machen.
Die Verknüpfung Main Prog in C, die aufwendige Rechnerei in ASM und rückgabe ins Main.
Wie gesagt ich muss nochmal dein letztes verinnerlichen..
Melde mich dann.
yours
Jürgen
Anm.: ist doch wahr das bei
\sourceon C
mul256brute:
mov (%rsi), %rax
mov (%rdx), %r8
mov 0x8(%rdx), %r9
\sourceoff
immer move souce,destination gilt in Standard Intel oder umgekehrt ?
|
Profil
|
juergenX
Aktiv  Dabei seit: 08.07.2019 Mitteilungen: 942
 | Beitrag No.62, vom Themenstarter, eingetragen 2023-09-28
|
\quoteon(2023-09-25 21:28 - juergenX in Beitrag No. 61)
\sourceon C
mul256brute:
mov (%rsi), %rax
mov (%rdx), %r8
mov 0x8(%rdx), %r9
\sourceoff
immer move souce,destination gilt in Standard Intel oder umgekehrt ?
\quoteoff
OOOps natürlich ist es umgekehrt:
in Standard Intel immer move destination, source!
Deswegen obiger Beitrag #58 geändert.
Wunsch meinerseits mit der Version rechte obere Grafik.
https://matheplanet.com/matheplanet/nuke/html/uploads/c/47407_ASM_Linux.png
hätte ich gerne mal ein "mundfertiges" C Prog gesehen, das
x = 2361 183241 434822 606847 // = 2^71 -1
y = 9 223 372 036 854 775 811 // = 2^63 +3
x*y als Dezimal Zahl ausgibt ...
|
Profil
|
hyperG
Senior  Dabei seit: 03.02.2017 Mitteilungen: 2159
 | Beitrag No.63, eingetragen 2023-09-28
|
Da Du immer noch am alten ASM-Code festhältst, der:
- weder für Win
- noch für TASM
- noch für die meisten c-Compiler
kompatibel ist...
(Dann auch noch veraltet, da mit mehr als nur 4 nötige mul)
... und die bereits von polygamma vorgegebenen Wandlungsfunktionen...
oder die GMP-Befehle ignorierst...
...bringt uns das kein mm vorwärts!
(ich bezweifle sogar, dass TASM kompatible obj für cygwin erstellen kann;
ASM ist nur dann nötig, wenn der Compiler hoch spezialisierte Befehle nicht versteht!
Das ist etwa so, als wenn Du einen Raketenmotor in ein Spielzeugauto einbauen willst, aber keinerlei von Raketentechnik verstehst...)
Also hier das "mundfertige" C Prog, was jeder c-Compiler sofort verstehen sollte:
\showon
\sourceon c
#include
#include
#include
#include
//aus https://www.cs.cmu.edu/~cburch/251/karat/karat.txt
#define MAX_DIGITS 1024
#define KARAT_CUTOFF 4
void gradeSchool(int *a, int *b, int *ret, int d) {
int i, j;
for (i = 0; i < 2 * d; i++) ret[i] = 0;
for (i = 0; i < d; i++) {
for (j = 0; j < d; j++) ret[i + j] += a[i] * b[j];
}
}
void karatsuba(int *a, int *b, int *ret, int d) {
int i;
int *ar = &a[0]; // low-order half of a
int *al = &a[d / 2]; // high-order half of a
int *br = &b[0]; // low-order half of b
int *bl = &b[d / 2]; // high-order half of b
int *asum = &ret[d * 5]; // sum of a's halves
int *bsum = &ret[d * 5 + d / 2]; // sum of b's halves
int *x1 = &ret[d * 0]; // ar*br's location
int *x2 = &ret[d * 1]; // al*bl's location
int *x3 = &ret[d * 2]; // asum*bsum's location
if (d <= KARAT_CUTOFF) {
gradeSchool(a, b, ret, d);
return;
}
for (i = 0; i < d / 2; i++) {
asum[i] = al[i] + ar[i];
bsum[i] = bl[i] + br[i];
}
karatsuba(ar, br, x1, d / 2);
karatsuba(al, bl, x2, d / 2);
karatsuba(asum, bsum, x3, d / 2); // combine recursive steps
for (i = 0; i < d; i++) x3[i] = x3[i] - x1[i] - x2[i];
for (i = 0; i < d; i++) ret[i + d / 2] += x3[i];
}
void doCarry(int *a, int d) {
int c;
int i;
c = 0;
for (i = 0; i < d; i++) {
a[i] += c;
if (a[i] < 0) {
c = -(-(a[i] + 1) / 10 + 1);
}
else {
c = a[i] / 10;
}
a[i] -= c * 10;
}
if (c != 0) fprintf(stderr, "Overflow %d\n", c);
}
void
getNum(int *a, int *d_a, char *strIn) {
int c;
int i;
*d_a = 0;
int iLen = strlen(strIn);
while (*d_a < iLen) {//true
c = strIn[*d_a];//getchar();
if (c == '\n' || c == EOF) break;
if (*d_a >= MAX_DIGITS) {
fprintf(stderr, "using only first %d digits\n", MAX_DIGITS);
while (c != '\n' && c != EOF) c = getchar();
}
a[*d_a] = c - '0';
++(*d_a);
}
for (i = 0; i * 2 < *d_a - 1; i++) {
c = a[i], a[i] = a[*d_a - i - 1], a[*d_a - i - 1] = c;
}
}
void printNum(int *a, int d, char *strErg) {
int i;
char strH[22];
strErg[0] = 0;
for (i = d - 1; i > 0; i--) if (a[i] != 0) break;
for (; i >= 0; i--)
{
sprintf(strH,"%d", a[i]); strcat(strErg, strH);
}
}
void Test_KaratsubaDezi(char *strA, char *strB, char *strErg)
{
int a[MAX_DIGITS+4]; // first multiplicand
int b[MAX_DIGITS+4]; // second multiplicand
int r[6 * MAX_DIGITS+4]; // result goes here
int d_a; // length of a
int d_b; // length of b
int d; // maximum length
int i; // counter
getNum(a, &d_a, strA);
getNum(b, &d_b, strB);
if (d_a < 0 || d_b < 0) {//nur positive Zahlen
printf("0\n"); exit(0); return;// 0;
}
i = (d_a > d_b) ? d_a : d_b;
for (d = 1; d < i; d *= 2);
for (i = d_a; i < d; i++) a[i] = 0;
for (i = d_b; i < d; i++) b[i] = 0;
karatsuba(a, b, r, d);
doCarry(r, 2 * d); // now do any carrying
printNum(r, 2 * d, strErg);
}
//main je nach Compiler:
int main(int argc, char *argv[])
{
char strErgebnis[3000];
char x_decimal[] = "2361183241434822606847";//
char y_Dez[] = "9223372036854775811";
Test_KaratsubaDezi(x_decimal, y_Dez, strErgebnis);
printf("Ergebnis= %s \n ", strErgebnis);
//getchar(); warten auf Button-Klick, wenn man exe ohne Console startet
}
\sourceoff
\showoff
Grüße
|
Profil
|
juergenX
Aktiv  Dabei seit: 08.07.2019 Mitteilungen: 942
 | Beitrag No.64, vom Themenstarter, eingetragen 2023-09-29
|
OK vielen Dank !
hier nutzt du gar kein asm weil du es überfllüssig "mächtig" hälts?
Ich werde dein reines C mal step by step verstehen..
Meine Idee war die bruteforce method aus #58 in ein void main einzubinden in C Linux. die angeblich besser ist als das reine C compilat.
Darum ging es ja in der Referenz: https://locklessinc.com/articles/256bit_arithmetic/
Über benschmarks wurde ja in den anderem thread lang und breit diskutiert.
Tasm erwähnte ich nur weil man die .obj files leicht als external in Pascal Mains einbinden kann, womit ich früher gut und gerne arbeitete.
OK erstmal :)
|
Profil
|
hyperG
Senior  Dabei seit: 03.02.2017 Mitteilungen: 2159
 | Beitrag No.65, eingetragen 2023-10-02
|
juergenX,
da selbst zu meinem "mundfertigen" C Programm wieder abweichende Fragen kommen (Wieder andere Compiler, wieder Gedankensprünge zu anderen Sprachen... wieder Sprünge zw. Win & LINUX...wieder der Code aus dem Internet statt der mundfertige...;)
wird mir auch die private Antworterei zu viel.
Was ist daran so schwer den vorgegebenen Code zu kopieren
und unter
https://onlinegdb.com/SwTwV3DzH
eingeben und RUN klicken
fertig:
https://matheplanet.com/matheplanet/nuke/html/uploads/c/47407_Mundfertiges_c.PNG
Zu den Teilfragen:
- "welche Eingabe": genau deshalb hatte ich das Original für Dich umgeschrieben, damit die langen Zahlen nicht zur Laufzeit per Hand eingegeben werden müssen & alles kürzer und verständlicher wird
- "gradeschool ": wenn die Argumente der Multiplikation klein genug sind, kann man die primitive Multiplikation mit der herkömmlichen Art (Schulmathematik) berechnen und die (Karat...)Rekursion somit beenden
- "warum kein asm": weil asm nur für Befehle nötig wird, die der Compiler nicht unterstützt
Bitte "den roten Faden" entlang hangeln und die Gedankengänge geradlinig halten. Solange nicht mal dieses primitive Programm (mit Dezimalziffern) verstanden wurde,
sind Sprünge hin zu Hex-Arrays oder gar ASM absolut kontraproduktiv.
Und lese https://de.wikipedia.org/wiki/Karazuba-Algorithmus
dort steht beschrieben, mit welchem Trick man die Multiplikationsobergrenze der Argumente vergrößern kann
(und so ein ÜBERLAUF verhindert, denn die CPU hat nur 64 Bit Register;
selbst AVX nennt sich zwar 128, 256 & 512 Bit, aber bezüglich der Multiplikation sind alles nur parallele Teil-Register bis 64 Bit).
|
Profil
|
juergenX
Aktiv  Dabei seit: 08.07.2019 Mitteilungen: 942
 | Beitrag No.66, vom Themenstarter, eingetragen 2023-10-02
|
Danke nochmal habe jetzt den sinn von Gradschool und getnum verstanden..
Thema Kollatz test:
\sourceon C++
int main(int argc, char *argv[])
{
char strErgebnis[3000];
char x_decimal[] = "2361183241434822606847";// 2^67-1
char y_Dez[] = "3";
Test_KaratsubaDezi(x_decimal, y_Dez, strErgebnis);
printf("Ergebnis= %s \n ", strErgebnis);
}
\sourceoff
\sourceon C++
3*2361183241434822606847 (char x_decimal) = 7083549724304467820541
\sourceoff
ist mit obiger aufwendiger Routine zu langsam...was klar war. (Kanonen nach Spatzen)
Da ist wie gonz schon sagte shl und adc rax,1 sinnvoller, will das hier aber beschliessen.
Tx hyper-g.man ;) for patience
Jürgen
|
Profil
|
juergenX hat die Antworten auf ihre/seine Frage gesehen. juergenX hat selbst das Ok-Häkchen gesetzt. | Seite 2 | Gehe zur Seite: 1 | 2 |
|
All logos and trademarks in this site are property of their respective owner. The comments are property of their posters, all the rest © 2001-2023 by Matroids Matheplanet
This web site was originally made with PHP-Nuke, a former web portal system written in PHP that seems no longer to be maintained nor supported. PHP-Nuke is Free Software released under the GNU/GPL license.
Ich distanziere mich von rechtswidrigen oder anstößigen Inhalten, die sich trotz aufmerksamer Prüfung hinter hier verwendeten Links verbergen mögen. Lesen Sie die
Nutzungsbedingungen,
die Distanzierung,
die Datenschutzerklärung und das Impressum.
[Seitenanfang]
|