3dnow tests (tutorial scherfgen)
-
ja, das waren die klammern, die ich nicht ganz begriffen hatte
[eax] bzw. (%%eax) bezieht sich auf den inhalt des speichers an der addresse, auf die eax zeigt
und in diesem fall ist es nat. unnötig, diese adresse erst in ein register zu laden
es wäre also:
femms movq mm0, [ f ] movq mm1, [ f + 8 ] pfadd mm0, [ g ] pfadd mm1, [ g + 8 ] movq [ f ], mm0 movq [ f + 8 ], mm1 femms
die klammern für [ f ] können auch entfallen; in manchen fällen erhöhen sie aber die lesbarkeit
mit SSE gehts noch besser
movdqu xmm0, f movdqu xmm1, g addps xmm0, xmm1 movdqu f, xmm0 bzw. movdqa xmm0, f addps xmm0, g movdqa f, xmm0 dann musst du aber sicherstellen, dass f und g auf 16byte ausgerichtet sind
da alle wesentlichen 3dnow befehle in sse enthalten sind (genauer: es gibt für alles, was man braucht ein equivalent, teilweise sind auch die opcodes identisch) (und 3dnow von intel nicht unterstützt wird), ist es besser gleich sse zu schreiben wenn möglich
(zumal viele sachen eleganter werden, schon allein auf grund der größeren register)
-
ich muss für dieses projekt sowieso alle varianten durchgehen. ich dachte.. fang erstmal klein an.. 3dnow wird sicher nicht so schwer sein. danach kommen sse und dann sse2. vieleicht muss ich auch noch irgendwelche operationen verwenden die von speziellen grafikkarten zur verfügung gestellt werden.. mal sehen.
gruss
eviluser
-
hallo,
ich hab jetzt mal einbischen im "assembler ge-packt" geschmökert.. da steht auch bei jedem 2. befehl dass die übergebenen variablen an einer 16 byte grenze ausgerichtet sein sollen.. damit ist nicht zufällig die mindestgrösse des übergebenen datensatzes gemeint.. oder?
gruss
eviluser
-
Damit ist gemeint, dass die letzte Stelle der Adresse in Hex geschrieben 0 ist.
-
... autsch...
ich trau mich fast garnicht zu fragen.. aber wie bekomme ich meine adresse so hin?!? wenn ich nen array initialisiere liegt der doch irgendwo im speicher.. und nicht unbedingt bei einer die durch 16 teilbar ist..
meine idee wäre ein array zu initialisieren und solange durchzulaufen bis ich bei einer adresse mir 0 am ende ankomme ( und dann a = &a[fundstelle])...
fraglich ob das funktioniert..gruss
eviluser
-
ich bin nicht sicher, ob die syntax bei jedem compiler so ist, aber generell sollte jeder bessere c++ compiler über entspr. funktionen verfügen:
vc++ :
mittels __declspec kannst du derartige attribute hinzufügen:__declspec( align( 16 ) ) sorgt für ausrichtung an 16byte grenzen, z.B.
__declspec( align( 16 ) ) float xmmFeld[4]
wenn das datenfeld teil einer klasse (oder struct/union) ist, erhält auch die umschliessende struktur dieses attribut, ausserdem wird ggf. sizeof angepasst; so dass z.B. in
class A { __declspec( align( 16 ) ) float irgendwas; };
es gefahrlos möglich ist, ein array A _a[...] zu benutzen, die enhaltenen datenfelder sind dann alle ausgerichtet. allerdings kannst du diese struktur (zumindest in vc) nicht local verwenden, da der stack i.allg. nicht ausgerichtet ist. auch malloc oder new liefern nicht notwendigerweise ausgerichtete blöcke - für diesen zweck ist _aligned_malloc notwendig
übrigens könntest du auch einen normalen nichtausgerichteten pointer verwenden, z.b. so:
int *p,*p_aligned; p = malloc( 1000 * sizeof( int ) - 1 + ALIGNMENT ); p_aligned = (int*)( ( (unsigned)p - 1 + ALIGNMENT ) & -ALIGNMENT ); // irgendwas mit p_aligned tun free( p );
-
hallo,
nachdem ich nun einpaar tips mehr bekommen habe (danke nochmal camper), habe ich aus dem tutorial eine c++ methode gebastelt, die ein vektorarray bearbeitet und zurückliefert:
inline void multwithvector(const float* input, const float* output, const int vectorcount) { //.. eine etwas schludrige lösung.. //aber der skalierungsfaktor kann nur 2,4 oder 8 sein //da ich aber 16 brauche... int count = (vectorcount-1)*2; asm( "mov %0, %%eax\n\t" "mov %1, %%ebx\n\t" "mov %2, %%ecx\n\t" "mov %3, %%edx\n\t" "femms\n\t" "loop:\n\t" "movq (%%ebx,%%edx,8), %%mm7\n\t" //für alle nicht at&t assembler: "movq 8(%%ebx,%%edx,8), %%mm6\n\t" //(%%ebx,%%edx,8) == (%ebx+%edx*8) "movq (%%eax), %%mm0\n\t" "movq 8(%%eax), %%mm1\n\t" //nachdem camper mir mit der pipelinefrage geholfen hat hab ich //beschlossen die reihenfolge aus dem tut nicht zu verändern //da eh auf den speicher gewartet wird.. "pfmul %%mm7, %%mm0\n\t" "pfmul %%mm6, %%mm1\n\t" "movq 16(%%eax), %%mm2\n\t" "movq 24(%%eax), %%mm3\n\t" "pfmul %%mm7, %%mm2\n\t" "pfmul %%mm6, %%mm3\n\t" "movq 32(%%eax), %%mm4\n\t" "movq 40(%%eax), %%mm5\n\t" "pfmul %%mm7, %%mm4\n\t" "pfmul %%mm6, %%mm5\n\t" "movq 48(%%eax), %%mm6\n\t" "pfmul %%mm7, %%mm6\n\t" "movq 56(%%eax), %%mm7\n\t" "pfmul 8(%%ebx,%%edx,8), %%mm7\n\t" "pfacc %%mm1, %%mm0\n\t" "pfacc %%mm3, %%mm2\n\t" "pfacc %%mm2, %%mm0\n\t" "pfacc %%mm5, %%mm4\n\t" "pfacc %%mm7, %%mm6\n\t" "pfacc %%mm6, %%mm4\n\t" "movq %%mm0, (%%ecx,%%edx,8)\n\t" "movq %%mm4, 8(%%ecx,%%edx,8)\n\t" "jz ende\n\t" //nach assembler-ge-packt erzeugt dec 1 byte, sub 3 byte maschinencode "dec %%edx\n\t" "dec %%edx\n\t" "jmp loop\n\t" "ende:\n\t" "femms\n\t" ://kein output //Ah=matrix .. ich berechne A*x nicht x*A wie im tutorial :"a" (Ah), "b" (input), "c" (output), "d" (count) ); }
die frage ist nun nurnoch.. ist das wirklich schneller als compilercode?!? ich kann mir eigentlich keine c++ variante des codes vorstellen (zusehr auf die assembler version eingeschossen
), sodass ich das nicht probieren möchte. ich würde wahrscheinlich vorsätzlich langsame methoden/berechnungen basteln
da ich den code mit eurer hilfe geschrieben habe poste ich ihn hier. kritik ist erwünscht solange sie konstruktiv ist
gruss
eviluser
-
Ich glaube, es gibt keinen Compiler, der 3dnow-Code erzeugt. Aber mit SSE könnte man den Vergleich mit dem Intel-Compiler mal wagen. Dein Code schaut nicht unbedingt schlecht aus, aber der Compiler wird's vermutlich doch noch etwas schneller hinkriegen.
-
Ringding schrieb:
Ich glaube, es gibt keinen Compiler, der 3dnow-Code erzeugt.
gcc
-
ein paar kleinigkeiten müssten es noch etwas beschleunigen: (auf jeden fall solltest du einfach mal nen benchmark laufen lassen, um zu sehen, welche änderungen tatsächlich etwas bringen)
ich würde die pfacc befehle unmittelbar hinter die entsprechenden pfmul befehle bringen, ansonsten ist der adder eine weile lang nicht beschäftigt während er später überstunden schieben muss
"pfacc %%mm2, %%mm0\n\t" kann ja auch nicht unmittelbar nach "pfacc %%mm3, %%mm2\n\t" ohne zwangspause ausgeführt werden. (der compiler macht sowas ganz auomatisch, weil ja bei a*b+c*d+e*f... die token bereits ganz natürlich abwechselnd vorkommen: mul, mul, add, mul, add...)
sub durch zwei dec befehle zu ersetzen ist nicht notwendigerweise günstig, da das zweite vom ersten dec abhängig ist, wenn du stattdessen sub benutzt könntest du auch das carry flag abfragen (das ja von dec nicht gesetzt wird) und so den unbedingten sprung vermeiden (der bedingte sprung wird ja fast nie - bis auf den letzten durchlauf) ausgeführt, und er nimmt ja auch code weg):
"sub 2,%%edx\n\t" "jnc loop\n\t"
jetzt könntest du nat. auch gleich um 16 erniedrigen, das macht bei sub ja keinen unterschied.
noch günstiger - wobei in diesem fall wahrscheinlich ohne auswirkungen auf die laufzeit - ist es, den vektor von vorne nach hinten und nicht umgekehrt zu durchlaufen:"mov %4, %%edx\n\t" //vectorcount "shl 4, %%edx\n\t" // vectocount*16 "mov %0, %%eax\n\t" "mov %1, %%ebx\n\t" "mov %2, %%ecx\n\t" "add %%edx, %%ebx\n\t" "add %%edx, %%ecx\n\t" "neg %%edx"
jetzt kannst du ganz normal über ebx+edx bzw ecx+edx addressieren, statt sub muss dann nat. add am ende der schleifen stehen, und es wird auf 0 und nicht carry geprüft.
-
mein vorschlag wäre das hier:
inline void multwithvector(const float* input, const float* output, const int vectorcount) { asm ( "mov %3, %%edx\n\t" //vectorcount "shl 4, %%edx\n\t" //vectocount*16 "mov %0, %%eax\n\t" "mov %1, %%ebx\n\t" "mov %2, %%ecx\n\t" "add %%edx, %%ebx\n\t" "add %%edx, %%ecx\n\t" "neg %%edx" "femms\n\t" "loop:\n\t" "movq (%%ebx,%%edx), %%mm0\n\t" //für alle nicht at&t assembler: "movq 8(%%ebx,%%edx), %%mm1\n\t" //(%%ebx,%%edx,8) == (%ebx+%edx*8) "add 16, %%edx\n\t" //flags frühzeitig setzen, um sprungvorhersage zu optimieren "movq %%mm0, %%mm2\n\t" "pfmul (%%eax), %%mm0\n\t" "movq %%mm1, %%mm3\n\t" "pfmul 8(%%eax), %%mm1\n\t" "pfacc %%mm1, %%mm0\n\t" "movq %%mm2, %%mm4\n\t" "pfmul 16(%%eax), %%mm2\n\t" "pfacc %%mm2, %%mm0\n\t" "movq %%mm3, %%mm5\n\t" "pfmul 24(%%eax), %%mm3\n\t" "pfacc %%mm3, %%mm0\n\t" "movq %%mm4, %%mm6\n\t" "pfmul 32(%%eax), %%mm4\n\t" "movq %%mm5, %%mm7\n\t" "pfmul 40(%%eax), %%mm5\n\t" "pfacc %%mm5, %%mm4\n\t" "pfmul 48(%%eax), %%mm6\n\t" "pfacc %%mm6, %%mm4\n\t" "pfmul 56(%%eax), %%mm7\n\t" "pfacc %%mm7, %%mm4\n\t" "movq %%mm0, -16(%%ecx,%%edx)\n\t" //edx wurde schon erhöht "movq %%mm4, -8(%%ecx,%%edx)\n\t" "jnz loop\n\t" "femms\n\t" ://kein output //Ah=matrix .. ich berechne A*x nicht x*A wie im tutorial :"a" (Ah), "b" (input), "c" (output), "d" (vectorcount) ); }
-
huuuuuuuuuuu schrieb:
Ringding schrieb:
Ich glaube, es gibt keinen Compiler, der 3dnow-Code erzeugt.
gcc
Hmm, stimmt. Der kann aber nicht vektorisieren, das ist bei solchen Sachen auch oft nützlich/notwendig.
-
schön
funktioniert aber leider nicht.. gibt nen seg. fault. ich versuche gerade herauszufinden woran das liegt.. aber das selbe problem hatte ich auch mal (mein erster war auch ein logischer "von vorne nach hinten durchlaufen" ansatz) und konnte es nicht lösen.
gruss
eviluser
-
die pfacc befehle sind nat. falsch gewesen:
"movq (%%ebx,%%edx), %%mm0\n\t" "movq 8(%%ebx,%%edx), %%mm1\n\t" "add 16, %%edx\n\t" "movq %%mm0, %%mm2\n\t" "pfmul (%%eax), %%mm0\n\t" "movq %%mm1, %%mm3\n\t" "pfmul 8(%%eax), %%mm1\n\t" "pfacc %%mm1, %%mm0\n\t" "movq %%mm2, %%mm4\n\t" "pfmul 16(%%eax), %%mm2\n\t" "movq %%mm3, %%mm5\n\t" "pfmul 24(%%eax), %%mm3\n\t" "pfacc %%mm3, %%mm2\n\t" "movq %%mm4, %%mm6\n\t" "pfmul 32(%%eax), %%mm4\n\t" "pfacc %%mm2, %%mm0\n\t" "movq %%mm5, %%mm7\n\t" "pfmul 40(%%eax), %%mm5\n\t" "pfacc %%mm5, %%mm4\n\t" "pfmul 48(%%eax), %%mm6\n\t" "pfmul 56(%%eax), %%mm7\n\t" "pfacc %%mm7, %%mm6\n\t" "pfacc %%mm6, %%mm4\n\t" "movq %%mm0, -16(%%ecx,%%edx)\n\t" "movq %%mm4, -8(%%ecx,%%edx)\n\t"
warum hier ein segfault auftritt ist mir allerdings nicht klar
-
okok.. ich bestehe ja darauf at&t assembler zu verwenden.. da bin ich selbst daran schuld..
die lösung ist.. ich kann nicht einfach so ne zahl in nen operator schreiben.. ich muss sie kennzeichnen.. add $16, %%edxdann klappts.. bis auf den letzten schleifendurchlauf..
den kopiere ich einfach runter..
so.. perfekt
-
ich weiss nicht inwiefern das representativ ist.. deshalb nenne ich das ding einfach mal pseudobench..
ich brauche für 10000000 vektoren angeblich 1.1 sekunden prozessorzeit.
mit der anderen (umgekehrten version) genau das selbe.. ich suche jetzt erstmal nach diesem amd-tool.. vieleicht bekomme ich da brauchbarere werte raus
gruss
eviluser
-
ich probiere gerade einbischen mit der ausrichtung von adressen herum und wenn ich mir den code ansehe den du mir vorher gepostet hattest..
int *p,*p_aligned; p = malloc( 1000 * sizeof( int ) - 1 + ALIGNMENT ); p_aligned = (int*)( ( (unsigned)p - 1 + ALIGNMENT ) & -ALIGNMENT ); // irgendwas mit p_aligned tun free( p );
muss ich feststellen dass das ein trick ist den ich nciht verstehe
du erzeugst 2 pointer vom typ int. einem davon reservierst du speicher .. so etwa 4015 gross. und dann machst du etwas krasses mit p_aligned .. vergleichst es bitweise mit -Alignment ?? im endeffekt haben dan beide (p&p_aligned) die selbe adresse.. und die endet mit 0.. und irgendwie beschleicht mich das gefühl dass das kein zufall war..gruss
eviluser