3dnow tests (tutorial scherfgen)



  • wow
    das war mir beispielsweise nicht klar 🙂 danke
    das heisst wenn ich eax auf 0 zurückgesetzt hätte (und auch habe 🙂 ) wäre das ergebnis an die speicheradresse 0 gegangen ..
    ich schätze mein problem hat nicht direkt mit 3dnow/mmx zutun sondern eher mit dem zugriff auf das array und die "typen" mit denen ich diesen zugriff veranlasse.. (hat das was mit den constraints zutun.. oder sehe ich das falsch?)
    vielen dank nochmal

    gruss

    eviluser



  • hallo nochmal..

    ich glaub ich flippe noch aus 🙂 ich hab nun 2 wochen damit totgeschlagen und weiss immernoch den fehler nicht..

    es hat zumindest mal nix mit den mmx/3dnow befehlen zutun.. weil wenn ich auf die addition verzichte und einfach nur das array kopiere, kommt der selbe mist raus (also im ersten element). wenn ich einen einfachen "ringtausch" (oder noch einfacher array kopieren) mache mit 3 registern eax, ebx, ecx auch. ich bin verwirrt..

    es wird irgendwas am ersten element verpfuscht.. aber ist das mein fehler? bin ich blind? könnte mir jemand eine funktionierende version vorführen?

    verzweifelte grüsse

    eviluser

    in dem code ist immernoch der selbe fehler.. obwohl ich nun die vom compiler erzeugte operation:

    mov %eax, (%eax)

    vermieden habe indem ich auf ecx/edx umgestiegen bin (obwohl das eigentlich egal sein sollte)...

    "mov %1, (%%ecx)\n\t"
          "mov %2, (%%edx)\n\t"
    /*      "mov (%%ebx), %%ecx\n\t"
          "mov %%ecx, (%%eax)\n\t"
          "mov 4(%%ebx), %%ecx\n\t"
          "mov %%ecx, 4(%%eax)\n\t"
          "mov 8(%%ebx), %%ecx\n\t"
          "mov %%ecx, 8(%%eax)\n\t"
          "mov 12(%%ebx), %%ecx\n\t"
          "mov %%ecx, 12(%%eax)\n\t"*/
          "femms\n\t"
          "movq (%%edx), %%mm0\n\t"
          "movq 8(%%edx), %%mm1\n\t"
          "movq %%mm0, (%%ecx)\n\t"
          "movq %%mm1, 8(%%ecx)\n\t"
          "femms\n\t"
          "mov %%ecx, %0\n\t"
          :"=m" (f)
          :"c" (f), "d" (g)
    

  • Mod

    Ich begreife die Klammersetzung nicht ganz (bin allerdings auch nicht mit dieser syntax vertraut); wenn ich es recht verstehe steht %%eax für das register und (%%eax) für den speicher auf den eax zeigt, obwohl es ja so ungefähr zu funktionieren scheint;
    aber welchen zweck erfüllt "mov %%eax, %0\n\t"; wenn ich es richtigt verstehe schreibt es eax nach %0, also f - womit klar wäre warum das ergebnis falsch ist
    mit masm müsste es wohl so aussehen

    femms
         mov      eax, offset f
         mov      ebx, offset g
         movq     mm0, [ eax ]
         movq     mm1, [ eax + 8 ]
         pfadd    mm0, [ ebx ]
         pfadd    mm1, [ ebx + 8 ]
         movq     [ eax ], mm0
         movq     [ eax + 8 ], mm1
         femms
    

    und mit gnu-assembler

    :"a" (f), "b" (g) 
          "femms\n\t" 
          "mov a, (%%eax)\n\t"
          "mov b, (%%ebx)\n\t" 
          "movq (%%eax), %%mm0\n\t" 
          "movq 8(%%eax), %%mm1\n\t" 
          "pfadd (%%ebx), %%mm0\n\t" 
          "pfadd 8(%%ebx), %%mm1\n\t" 
          "movq %%mm0, (%%eax)\n\t" 
          "movq %%mm1, 8(%%eax)\n\t" 
          "femms\n\t"
    


  • hallo,

    danke erstmal für deine antwort. ich habe beim copy/paste anscheinend gepfuscht..

    mov %%eax, %0

    schreibt den wert von eax in f.

    asm("" //assembler block
        :  //ausgabe
        :  //eingabe
        :  //clobberlist (was immer das auch sein mag).
        );
    

    ein/ausgabe werden von %0 bis %9 gezählt (glaube ich). deswegen brauche ich arrays.. um viele variablen bearbeiten zu können.. aber wenn ich arrays verwende tritt immer dieser fehler auf.

    deine empfehlung lautet (soweit ich das verstanden habe) den output wegzulassen.. doch wie wird dann das ergebnis übertragen?
    oder meintest du was anderes? würde ich reinen assembler benutzen hätte ich wahrscheinlich kein problem.. aber ich muss eine c++ bibliothek schreiben (mit assembler auszügen).

    gruss

    eviluser


  • Mod

    das ergebnis wird bereits durch

    "movq %%mm0, (%%eax)\n\t' 
         "movq %%mm1, 8(%%eax)\n\t'
    

    übertragen

    vom gnu-assembler ist generell abzuraten, erstens ist die syntax sehr gewöhnungsbedürftig (und nicht bugfrei); ausserdem werden nur ungenügend fehlermeldungen produziert



  • hehe.. du hast vollkommen recht.. 🙂

    vieleicht blick ich ja diese speicheroperationen irgendwann..

    das ändert aber leider nichts an der situation.. die erste zahl ist:

    3.2198...e-039

    in deinem code hast du offset f und später [f] verwendet.. kann es vieleicht daran liegen?!? ist die startadresse um irgendwas verschoben?

    ein freund meinte wenn c/c++ irgendeine zahl nahe null liefert ist die variable nicht definiert worden. aber das ist sie ja angeblich.. in meinem beispiel..

    danke nochmal für deine hilfe.

    gruss

    eviluser



  • AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

    ich habs gelöst...

    *heul* 2 monate und alle meine nerven später...

    ich bin so dumm.....

    was übertrage ich wenn ich f übergebe? die adresse! also brauche ich doch nicht extra mov %0, (%%eax) machen (um die adresse zu bekommen...). ich vermute sogar das ist mal so richtig falsch (die eine dicke zeile da)..

    wenn ich die klammern einfach weglasse stimmt alles wieder..

    ich könnte mir selbst in den hintern beissen..

    danke für eure geduld.. und camper.. deine erklärungen haben mir echt weitergeholfen.. ich geh mich betrinken...

    gruss

    eviluser


  • Mod

    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


  • Mod

    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


  • Mod

    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.


  • Mod

    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


Anmelden zum Antworten