ByteOrder umkehren



  • BSWAP r32
    


  • Für 64 Bit Wert geht folgendes:

    bswap %eax
    bswap %ebx
    xchg %eax, %ebx // oder wie auch immer du sie am liebsten tauschst
    


  • Rock Lobster schrieb:

    b = ((a & 0xFF000000) >> 24)
      + ((a & 0x00FF0000) >> 8)
      + ((a & 0x0000FF00) << 8)
      + ((a & 0x000000FF) << 24);
    

    ...Mir persönlich fällt nix ein, aber möglicherweise gibt's ja auch noch was schnelleres.

    versuch's mal so:

    b = (a >> 24)
      | ((a & 0x00FF0000) >> 8)
      | ((a & 0x0000FF00) << 8)
      | (a << 24);
    

    vielleicht ist das ein klitzekleines bischen schneller.
    natürlich kein vergleich mit 'ner asm-instruction (bswap). 😉
    dieses std::reverse wird wohl um ein vielfaches langsamer sein.
    🙂



  • Bitte unterlasst das posten von x86 Assembler Code. Das ist hier im ISO C++ Forum vollkommen irrelevant. Danke.

    @Rock Lobster
    Schau mal hier, ob du da eventuell etwas effizientes findest. Ansonsten halt den üblichen Weg.



  • Okay vielen Dank, ich werd wohl mal austesten, was die schnellsten Varianten sind. Ist auch gut zu wissen, daß es direkt Assembler-Befehle dafür gibt.

    @ groovemaster: Ich hab in meinem Eingangsposting ausdrücklich darauf hingewiesen, daß ich auch im Falle eines direkten Assembler-Befehls gern informiert werden möchte, von daher finde ich es schon gut, wenn das hier erwähnt wird 😉



  • Für alle die es noch interessiert, ich hab einfach mal 'nen kleinen Vergleich gemacht:

    #include <stdio.h>
    #include <time.h>
    
    #define HOW_MUCH_SWAPS      0x06FFFFFF
    
    int swap1(int i)
    {
        int a;
    
        a = ((i & 0xFF000000) >> 24) 
          + ((i & 0x00FF0000) >> 8) 
          + ((i & 0x0000FF00) << 8) 
          + ((i & 0x000000FF) << 24);
    
        return a;
    }
    
    int swap2(int i)
    {
        int a;
    
        __asm
        {
            push eax
            mov eax, i
            bswap eax
            mov a, eax
            pop eax
        }
    
        return a;
    }
    
    clock_t test(int (*func)(int), int how)
    {
        clock_t start, end;
        int a;
    
        start = clock();
        for(int i=0; i<how; i++)
            a = func(how);
    
        end = clock();
        return (end - start);
    }
    
    int main()
    {
        clock_t time;
    
        time = test(&swap1, HOW_MUCH_SWAPS);
        printf("Swap 1: %i\n", time);
    
        time = test(&swap2, HOW_MUCH_SWAPS);
        printf("Swap 2: %i\n", time);
    
        return 0;
    }
    

    Der erste Test läuft bei mir 1015 ms, der zweite 719 ms. Grob gesagt braucht die optimierte Version also nur etwa dreiviertel der Zeit, somit sicherlich auch sinnvoll. Ob die beiden "push" und "pop" nötig sind, weiß ich zwar nicht, aber die haben an der Laufzeit nix geändert

    Es ändert sich auch nix beim ersten Test, wenn man das & 0xFF000000 und & 0x000000FF wegläßt (so wie von Undertaker beschrieben), vielleicht wirkt sich das minimalst aus, wenn man mehr Schleifendurchläufe macht.

    Naja wie auch immer, ich denke für meinen Zweck ist es schon ganz gut, wenn ich die Assembler-Version nehme. Falls es jemals auf nicht-x86-Plattformen portiert werden sollte, kann ich die "normale" Version ja weiterhin benutzen (über ein paar kluge #ifdefs).

    Die 64-Bit-Version hab ich noch nicht getestet, aber ich denke mal, da wird es nicht großartig anders sein 😉

    EDIT: Ich habe gerade mal mit Digital Mars kompiliert, und da sieht das Ergebnis völlig anders aus. Hier braucht die erste Variante 984 ms und die zweite 1625 ms. Sehr seltsam, ich werd mir mal den Output genauer anschauen... das erste mal habe ich mit Visual C++ 2005 kompiliert.



  • Rock Lobster schrieb:

    EDIT: Ich habe gerade mal mit Digital Mars kompiliert, und da sieht das Ergebnis völlig anders aus. Hier braucht die erste Variante 984 ms und die zweite 1625 ms. Sehr seltsam, ich werd mir mal den Output genauer anschauen... das erste mal habe ich mit Visual C++ 2005 kompiliert.

    ja, wirklich seltsam.
    vielleicht könntest du auch mal std::reverse testen? würde mich echt interessieren.
    🙂



  • Rock Lobster schrieb:

    EDIT: Ich habe gerade mal mit Digital Mars kompiliert, und da sieht das Ergebnis völlig anders aus. Hier braucht die erste Variante 984 ms und die zweite 1625 ms. Sehr seltsam, ich werd mir mal den Output genauer anschauen... das erste mal habe ich mit Visual C++ 2005 kompiliert.

    Im Release-Mode?

    Heutzutage ist es oftmals naiv anzunehmen, man könne manuell besser optimieren als der Compiler es im Releasemode sowieso tut, deshalb überrascht mich das Ergebnis (Digital Mars) zunächst nicht. Falls Du mit VC++ auch im Releasemode gemessen hast, wundert mich eher, dass der da nicht besser optimiert.


  • Mod

    Traurig, das sowas kein Einzelfall ist.
    Falls du unbedingt ein "schnelles swap" brauchst, solltest du dich erst einmal auf die Suche nach entsprechenden Primitiven des Compilers machen - inline assembler ist hier alles andere als optimal - und das nicht wegen der fehlenden Portabilität.
    Visual C++ etwa bietet die _byteswap_* Funktionen an, die auch eine intrinsische Form haben.



  • camper schrieb:

    Visual C++ etwa bietet die _byteswap_* Funktionen an, die auch eine intrinsische Form haben.

    bswap_* in byteswap.h bei der GNU libc.



  • push eax und pop eax kannste dir sparen. der compiler schert sich bei der funktion eh ned drum. und a landet bei funktionsende sowieso in eax.

    int swap2(int i)
    {
        __asm
        {
            mov eax, i
            bswap eax
            mov i, eax
        }
    
        return i;
    }
    

    für maximale performance schreib dir noch passende pro- und epilog und mach die funktion 'naked' mit

    __declspec(naked) int swap2(int i)
    

    thread sollte vielleicht nach rudp verschoben werden?


  • Mod

    cpt. obvious schrieb:

    push eax und pop eax kannste dir sparen. der compiler schert sich bei der funktion eh ned drum. und a landet bei funktionsende sowieso in eax.

    int swap2(int i)
    {
        __asm
        {
            mov eax, i
            bswap eax
            mov i, eax
        }
       
        return i;
    }
    

    dann gleich richtig

    inline unsigned swap2(unsigned i)
    {
        __asm mov eax, i
        __asm bswap eax
    }
    

    cpt. obvious schrieb:

    für maximale performance schreib dir noch passende pro- und epilog und mach die funktion 'naked' mit

    __declspec(naked) int swap2(int i)
    

    Was in der Praxis den gegenteiligen Effekt haben dürfte, weil der Compiler nicht mehr inline-Expandieren kann. Schließlich ruft man so ein Funktion in der Regel nicht indirekt über einen Pointer auf.



  • camper schrieb:

    dann gleich richtig

    inline unsigned swap2(unsigned i)
    {
        __asm mov eax, i
        __asm bswap eax
    }
    

    ok, ich dachte das return wäre unbedingt notwendig. als ich das letzte mal inline-asm geschrieben hab ging das so noch nicht, afair.

    Was in der Praxis den gegenteiligen Effekt haben dürfte, weil der Compiler nicht mehr inline-Expandieren kann. Schließlich ruft man so ein Funktion in der Regel nicht indirekt über einen Pointer auf.

    hm, könnte was dran sein. einen versuch wärs trotzdem mal wert, insbesondere wenn er mit komischen compilern hantiert.



  • Naja rein vom Aufruf waren beide Versionen ja gleichberechtigt, und nur was intern ablief, war anders. Der Output sieht bei VC und bei DC ebenfalls recht ähnlich aus, also erstmal vom Umfang her, aber auch die eigentlichen Befehle unterscheiden sich kaum. Ich kann nachher aber gerne mal den tatsächlichen Assembler-Output posten. Eigentlich hab ich beides auch im Release-Modus kompiliert, da ich von Hand über die Console kompiliert habe, ohne /DEBUG oder so etwas zu setzen.

    Werde aber nachher nochmal ein wenig rumfrickeln.

    Das mit "naked" kapier ich nicht so ganz (bin auch kein C++-Guru), was genau bewirkt das? Und noch eine schnelle Frage, geht "inline" nur in Klassen oder auch in normalen C-Funktionen? Ich habe neulich den Fall gehabt, daß ich eine Funktion nicht inlinen konnte, aber das bisher nicht näher verfolgt. Es war eine normale C-Funktion in einem extern ("C")-Block.

    EDIT: Das push/pop hatte ich drin, weil ich zunächst davon ausging, daß vielleicht vor dem Call etwas wichtiges im eax stehen könnte - beim Call müßte aber sowieso vorher alles gepusht werden, kann das sein?



  • Rock Lobster schrieb:

    Und noch eine schnelle Frage, geht "inline" nur in Klassen oder auch in normalen C-Funktionen? Ich habe neulich den Fall gehabt, daß ich eine Funktion nicht inlinen konnte, aber das bisher nicht näher verfolgt. Es war eine normale C-Funktion in einem extern ("C")-Block.

    Jede Funktion kann (sowohl auf Anforderung als auch aus eigener Kraft) inline gesetzt werden, solange einige Randbedingungen an die Komplexität (Schleifen und Rekursionen sind ungünstig) erfüllt werden - und der Compiler den Quellcode der Funktion sehen kann.

    (d.h. wenn du eine Funktion im CPP inline definierst, ist sie außerhalb dieser Übersetzungseinheit nicht erreichbar)



  • Ahh gut dann weiß ich woran es lag. Ich hatte eine tools.cpp und eine tools.h, und in der .h wurden die Funktionen einfach nur in einem extern ("C")-Block definiert, und dann in der .cpp implementiert. In dem Fall wird mir wohl nichts anderes übrig bleiben, als die Funktionen direkt in der .h zu implementieren, liege ich da richtig (kann's grad hier nicht testen)?



  • Rock Lobster schrieb:

    Das mit "naked" kapier ich nicht so ganz (bin auch kein C++-Guru), was genau bewirkt das?

    der compiler generiert keinen ein- und austrittscode für diese funktion, sondern verlässt sich darauf das du das tust. du bist also selbst dafür verantwortlich, argumente vom stack zu holen und was sonst noch so zu tun ist.
    http://msdn2.microsoft.com/en-us/library/5f7adz6y(VS.80).aspx
    das ist auch der grund, warum der compiler dann nicht mehr inlinen kann - er hat keine kontrolle mehr über den ein- und austrittscode (der beim inlinen entfallen muss)

    EDIT: Das push/pop hatte ich drin, weil ich zunächst davon ausging, daß vielleicht vor dem Call etwas wichtiges im eax stehen könnte - beim Call müßte aber sowieso vorher alles gepusht werden, kann das sein?

    siehe oben. in deinem fall steht halt nix wichtiges in eax, die funktion tut ja quasi nix. du solltest die registerbelegungsregeln deines compiler mal nachschlagen, wenn es die gibt und es dich interessiert.


Anmelden zum Antworten