3dnow tests (tutorial scherfgen)



  • hallo alle,

    ich hab nach dem 3dnow! tutorial von der scherfgen seite mal das folgende kleine programm geschrieben.

    #include <iostream> 
    
    int main() { 
    
      float f[4]; 
      float g[4]; 
    
      int i = 0; 
    
      for(i = 0; i < 4; i++) { 
    
        f[i] = i; 
        g[i] = i; 
    
      } 
    
      for(i = 0; i < 4; i++) std::cout << f[i] << "\t"; 
    
      std::cout << "\n"; 
    
      asm( 
          "femms\n\t" 
          "mov %1, (%%eax)\n\t" 
          "mov %2, (%%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" 
          "mov %%eax, %0\n\t" 
          "femms\n\t" 
          :"=m" (f) 
          :"a" (f), "b" (g) 
          ); 
    
      std::cout << f << "\n"; 
    
      for(i = 0; i < 4; i++) std::cout << f[i] << "\t"; 
    
      std::cout << "\n"; 
    
    }
    


    befehl quelle, ziel anordnung statt: befehl ziel, quelle
    \n\t ist nur für den assembler damit er aufeinanderfolgende befehle erkennt \n\t umbruch+tab)

    nun liefert mein programm aber statt dem erwarteten 0 2 4 6, -1,99bla 2 4 6 als ergebnis.

    ich kann mir auf die schnelle nicht erklären was da passiert.. kann mir da jemand weiterhelfen?

    vielen dank im voraus

    eviluser

    [edit]In Zukunft zur besseren Lesbarkeit bitte Codetags benutzen => sfds[/edit]



  • hallo nochmal,

    ich will hier nicht zu penetrant anfragen zumal ich ein unregistrierter benutzer bin. aber leider komme ich mit meinem fehler einfach nicht weiter. ich kann es drehen und wenden wie ich es will. die ersten zahlen meiner operation wollen einfach keine richtigen werte annehmen. ein gedanke war, es sind vieleicht die constraints, die ich unten angeben muss (bei den 2 doppelpunkten unten im asm block). aber die sollten mein ergebnis nicht verändern.. muss ich vieleicht den eax-register mit "0" überschreiben bei der rückgabe? als ich das probiert habe hat das auch nicht funktioniert.. ich schätze es hat wirklich mit eax/ebx zutun. aber ich hab keinen blassen dunst was..
    wie sieht es eigentlich mit "normalen arrayoperationen" aus? hat da jemand erfahrungen damit und könnte mir einpaar tips geben? was ich beim tutorial nicht ganz verstanden habe:

    movq %%mm0, (%%eax)
    

    wieso funktioniert das.. was wird da übergeben?

    ich würde micht freuen wenn mir jemand helfen könnte,
    vielen dank im voraus

    eviluser



  • Ich kenne 3dnow nicht, und ich gebe zu, dass das mit den Constraints manchmal kompliziert sein kann, aber der von dir angesprochene Befehl schreibt einfach den Inhalt von mm0 an die durch eax angegebene Speicheradresse.



  • 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.


Anmelden zum Antworten