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 exitNun 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.176593Mit 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 2Wie 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...
-
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, %espJetzt noch ne kleine frage: kann man auch sofort 8 byte auf den Stack pushen?
-
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, %espirgendwie 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)
-
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, %espoder 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, %espWas 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?