sse2, integer to float conversion. Format bei Ausgabe?



  • Hallo

    Ich bin grad dabei, mir Wissen über SIMD-Instructions anzueignen und bin dabei auf die verschiedenen Conversion-Instructions gestoßen. Hab mir den folgenden Code zusammengeschrieben (Achtung GNU-Assembler, AT&T Syntax):

    .section .data
    .align 16
    //floating-point array
    fArr:
    	.float 3.4,5.2,-3.4,9.99
    //integer array
    iArr:
    	.int	4,-234,58,2
    output:
    	.asciz	"The values: %f,%f,%f,%f\n"
    output2:
    	.asciz	"The Values: %d,%d,%d,%d\n"
    .section .bss
    .align 16
    	.lcomm Buffer 16
    .section .text
    .globl main
    main:
    	//for gdb
    	nop
    
    	// Convert packed singles to doubleword integers
    	cvtps2dq fArr, %xmm0
    	// Convert doubleword integers to packed singles
    	cvtdq2ps iArr, %xmm1
    
    	// output integers
    	movaps %xmm0, Buffer
    	movl $Buffer, %esi
    	pushl 12(%esi)
    	pushl 8(%esi)
    	pushl 4(%esi)
    	pushl (%esi)
    	pushl $output2
    	call printf
    	addl $20, %esp
    
    	// output singles
    	movaps %xmm1, Buffer
    	movl $Buffer, %esi
    	pushl 12(%esi)
    	pushl 8(%esi)
    	pushl 4(%esi)
    	pushl (%esi)
    	pushl $output
    	call printf
    	addl $20, %esp
    
    	// done
    	pushl $0
    	call exit
    

    Nun habe ich das Programm laufen lassen und die folgende Ausgabe erhalten:

    The Values: 3,5,-3,10
    The values: -58546803812859904.000000,2.000000,0.000000,-0.176593
    

    Mit Integern hat's ja wunderbar geklappt wie man sieht, mit den Fließkommazahlen allerdings nicht. Ich hab daraufhin das Progrämmchen debugt und überprüft, ob der Inhalt stimmt. Nach dem cvtdq2ps-Befehl hab ich mir das xmm1-Register angeschaut:

    (gdb) print/f $xmm1
    $1 = {v4_float = {4, -234, 58, 2}, v2_double = {-58546803812859904, 2.0000004947651178},
      v16_int8 = {0, 0, -128, 64, 0, 0, 106, -61, 0, 0, 104, 66, 0, 0, 0, 64}, v8_int16 = {0,
        16512, 0, -15510, 0, 17000, 0, 16384}, v4_int32 = {4, -234, 58, 2}, v2_int64 = {
        -58546803812859904, 2.0000004947651178}, uint128 = <error reading variable>}
    

    Hier sieht man, dass die Werte stimmen (v4_float). Auch sieht man, dass mir printf die double-precision-Version der Werte geliefert hat. Ich bin noch weiter zu der Stelle gegangen, wo xmm1 in "Buffer" entladen wird und hab den Speicher an der Stelle untersucht:

    (gdb) x/4wf &Buffer
    0x80496f0 <Buffer>:     4       -234    58      2
    

    Wie man sieht, ist mit den Werten alles in Ordnung. Damit wäre meine Frage ja eigentlich an die C-Spezialisten gerichtet: wie sage ich printf(), dass es den Speicher als 32-bit-floats interpretieren soll? Ich hab mich ma hier umgesehen:
    http://www.cplusplus.com/reference/clibrary/cstdio/printf.html
    Es gibt zwar die Möglichkeit die Länge zu spezifizieren, was allerdings nur für Integer gilt. Ausprobiert hab ichs trotzdem. Funktioniert hat es nicht.

    Alternative Frage: Wie gebe ich etwas ohne vorgefertige Funktionen aus? Mit atwas meine ich Zeichenketten, Integer, Floats, usw...


  • Mod

    Azrael, il Meraz schrieb:

    Wie man sieht, ist mit den Werten alles in Ordnung. Damit wäre meine Frage ja eigentlich an die C-Spezialisten gerichtet: wie sage ich printf(), dass es den Speicher als 32-bit-floats interpretieren soll?

    Das ist nicht möglich. Allerdings kannst du float einfach in double-Werte konvertieren, indem du die niederwertigen 32bit auf 0 setzt (die Gleitkommadarstellung ist explizit so definiert, dass das möglich ist, wie auch einige andere Nettigkeiten, wie z.B., dass Gleitkommazahlen, als Integer interpretiert, in der gleichen Anordnung bzgl. < > usw. resultieren).
    Die float-Zahl 0 besteht nur aus 0-bits - das sollte dir genug Hinweis geben, um z.B. mit shuffle-Befehlen doch noch zum richtigen Ergebnis zu kommen. Bzw. mit sse2 hast du ja auch Konvert-Befehle SS->SD



  • hm, danke, dachte man muss nicht fummeln 😃
    hab jetzt folgendes für die Ausgabe gebaut:

    // printf only recognizes double-precision floating point values
    	// so we have to use a trick
    	movhlps %xmm1, %xmm2
    	cvtps2pd %xmm1, %xmm1
    	cvtps2pd %xmm2, %xmm2
    breakhere:
    
    	// output singles
    	movl $Buffer, %edi
    	movaps %xmm1, (%edi)
    	movaps %xmm2, 16(%edi)
    	pushl 28(%edi)
    	pushl 24(%edi)
    	pushl 20(%edi)
    	pushl 16(%edi)
    	pushl 12(%edi)
    	pushl 8(%edi)
    	pushl 4(%edi)
    	pushl (%edi)
    	pushl $output
    	call printf
    	addl $36, %esp
    

    Jetzt noch ne kleine frage: kann man auch sofort 8 byte auf den Stack pushen?


  • Mod

    Azrael, il Meraz schrieb:

    Jetzt noch ne kleine frage: kann man auch sofort 8 byte auf den Stack pushen?

    Nur im 64-bit Modus - allerdings ist push bei supersklaren Architekturen ohnehin nicht optimal (weil aufeinanderfolgende push wegen der Veränderung von esp von einander abhängen). Besser ist es (und ein optimierender Compiler tut das im allgemeinen), esp einmal hinreichend zu erniedrigen und dann die Argumente per mov zu füllen. Führst du mehrer calls hintereinander aus, brauchst du auch nur einmal genug Speicher reservieren (für den Aufruf der die meisten Argumente hat) und räumst erst zum Schluss auf. Das ist ggf. sogar effizienter als Konventionen wie stdcall, bei der die aufgerufene Funktion den Speicher entsprechend aufräumt. Man kann es sogar manchmal verwenden, um Funktionsargumente mehrfach zu verwenden. Dabei ist allerdings Vorsicht angebracht, denn die Argumente eines Funktionstyps sind in C oder C++ niemals const - es gibt also keine Garantie, dass die aufgerufene Funktion seine Argumente nicht verändert, ohne deren Definition zu kennen.
    Edit: in deinem Fall bietet sich evtl. vprintf an, um dir das Kopieren zu ersparen (va_list ist im Grunde nichts weiter als ein Zeiger, der auf das jeweilige Argument zeigt).



  • Also hab ich folgendes zur Verfügung (neben dem dword-weisen pushen):

    // output singles
    	movl $Buffer, %edi
    	movaps %xmm1, (%edi)
    	movaps %xmm2, 16(%edi)
    	pushl %edi
    	pushl $output
    	call vprintf
    	addl $8, %esp
    
    //oder
    
    	subl $36, %esp
    	movl $output, (%esp)
    	movups %xmm1, 4(%esp)
    	movups %xmm2, 20(%esp)
    	call printf
    	addl $36, %esp
    

    irgendwie gefällt mir vprintf auch besser. was ist denn effizienter? (Nicht, dass ich jetzt 500.000.000 mal etwas ausgeben wollte weils so schön schnell geht :D:D:D)


  • Mod

    edi brauchst du für vprintf nicht. Ein Nachteil der mov-Methode sei allerdings erwähnt: dies führt gegenüber push meist zu längerem Maschinencode.
    Nach cvtps2pd sollte es movupd/movapd heißen, andernfalls führt das ggf. zu Verzögerungen. movlpd/movhpd an Stelle von movupd kann ebenfalls effizienter sein, je nach Prozessortyp (Letzteres ist z.B. beim Athlon64 sinnvoll, da movupd dort VectorPath ist - keine Ahnung, wie es beim Phenom aussieht).



  • Also so:

    // output singles
    	movapd %xmm1, Buffer
    	movapd %xmm2, Buffer+16
    	pushl $Buffer
    	pushl $output
    	call vprintf
    	addl $8, %esp
    

    oder so:

    // output singles
    	movlpd %xmm1, Buffer
    	movhpd %xmm1, Buffer+8
    	movlpd %xmm2, Buffer+16
    	movhpd %xmm2, Buffer+24
    	pushl $Buffer
    	pushl $output
    	call vprintf
    	addl $8, %esp
    

    Was ist denn Vector Path? Google sagt mir, AMDs Decoder für Komplexere Maschinencodes würde so heißen... einfache Befehle würden über den direct path zu micro-operations und komplexe über den vector path übersetzt. Wie sieht das denn genau aus? und was ist mit Intel?


Anmelden zum Antworten