Geschwindigkeit einer 3x3 Matrix
-
Ich hab mir eine 3x3 Matrix geschrieben um damit Vektoren im 3-D Raum zu transformieren.
Ich wollte nun mal wissen, wer auch so etwas geschrieben hat und
wie schnell dieser Code ist.
Und, falls es zu große Geschwindigkeitsunterschiede gibt, was ich falsch gemacht habe.Meine Matrix braucht um 300.000 Vektoren ( = 100.000 Triangles ) zu transformieren auf meinem 2.0Ghz Athlon mit WinXP im Durchschnitt 15.5 Ms.
Postet auch bei anderen Prozessoren und Taktraten, ich werde mir das dann umrechnen.( oder es zumindest versuchen....
)
Danke für alle Antworten.
-
Checke es doch mal gegen die DX9X-Funktionen, die sind ja auch für MMX, SSE, SSE2, 3D Now! (Enhanced / Pro) optimiert...
angeblich...
-
Sgt. Nukem schrieb:
die sind ja auch für MMX, SSE, SSE2, 3D Now! (Enhanced / Pro) optimiert...
Die einfachen aber nicht, zB Vektoraddition oder Scalarprodukt. Weil um zB SSE in Gang zu bekommen muss die CPU erstmal 80 Taktzyklen lang sich selbst darauf vorbereiten (hab ich irgendwo gelesen). Das löhnte sich also nicht (der Kunjunktivmaster ist zurück
)
-
Für 300.000 3D-Vektoren braucht die von mir geschriebene 3DNow!-optimierte Mathe-Bibliothek ca. 6 ms (Athlon 2.1 GHz, WinXP).
-
15.5 Ms.
15.5 Megasekunden? das wäre aber verdammt lang. Das wären ja fast 180 Tage
-
0x00000001 schrieb:
Weil um zB SSE in Gang zu bekommen muss die CPU erstmal 80 Taktzyklen lang sich selbst darauf vorbereiten (hab ich irgendwo gelesen). Das löhnte sich also nicht (der Kunjunktivmaster ist zurück
)
Stimmt, Vektoradditionen und Skalarprodukte werden ja meistens nur einmal am Anfang bei Initialisierung benutzt...
-
Sgt. Nukem schrieb:
0x00000001 schrieb:
Weil um zB SSE in Gang zu bekommen muss die CPU erstmal 80 Taktzyklen lang sich selbst darauf vorbereiten (hab ich irgendwo gelesen). Das löhnte sich also nicht (der Kunjunktivmaster ist zurück
)
Stimmt, Vektoradditionen und Skalarprodukte werden ja meistens nur einmal am Anfang bei Initialisierung benutzt...
Du hast [ironie] [/ironie] vergessen
Das mit den Taktzyklen, kommt vor, wenn man SIMD benutzt hat und dann wieder die normale FPU braucht. Und da nur bei MMX und 3DNow!, weil diese intern die FPU Register benutzen. Daher müssen diese vor FPU-Benutzung mit dem Befehl EMMS gesäubert werden und der dauert etwas länger. Bei SSE, und Nachfolgern ist das nicht nötig, weil diese andere Register benutzen. Also kann man SIMD bedenkenlos auch in inneren Schleifen benutzen
-
Ich hab die Zeit auf 13Ms gedrückt.
Allerdings verwende ich keinerlei Optimierungen.@Tomas Riker:
Benutzt du C++ benutzt oder die Routinen in Assembler geschrieben?
Könntest du mir eine gute Seite über 3D-Now! bzw. SSE empfehlen?P.S. ich verwende OpenGL und benutzte deswegen die Direct3D Methoden nicht.
-
[werbungmachfürdavid]
schau doch mal auf seine seite
http://www.scherfgen-software.net/index.php?action=tutorials&topic=3dnow
[/werbungmachfürdavid]
-
Beowulf schrieb:
P.S. ich verwende OpenGL und benutzte deswegen die Direct3D Methoden nicht.
Du solltest sie später ja auch nicht benutzen, sondern lediglich in Deinem Test-Framework, um die Geschwindigkeitsdifferenzen festzustellen...
-
Kann man also unterm Strich sagen, dass es sich nicht lohnt wegen einfachen Berechnungen spezielle Hardware anzuschmeißen?
Ich meine jetzt zB wenn ich nur ein einziges Scalarprodukt ausrechnen will, und nicht gleich 300k Stück auf einmal.Die D3dX machen sowas auch nicht mit (besonderer) Hardware, ich habs mal rausgesucht:
D3DXINLINE FLOAT D3DXVec3Dot ( CONST D3DXVECTOR3 *pV1, CONST D3DXVECTOR3 *pV2 ) { #ifdef D3DX_DEBUG if(!pV1 || !pV2) return 0.0f; #endif return pV1->x * pV2->x + pV1->y * pV2->y + pV1->z * pV2->z; }
Aber was ist denn nun besser? Warum nutzt MS kein SSE, wenn es, wie ChockoCookie sagt, direkt verfügbar ist? Nagut, vielleicht wegen Abwärtskompatibilität oder sowas...
-
0x00000001 schrieb:
Kann man also unterm Strich sagen, dass es sich nicht lohnt wegen einfachen Berechnungen spezielle Hardware anzuschmeißen?
Ich meine jetzt zB wenn ich nur ein einziges Scalarprodukt ausrechnen will, und nicht gleich 300k Stück auf einmal.es lohnt sich, mit der fpu brauchst du ca 70% mehr zeit als mit SSE. und wenn mit SSE3 endlich vertical-instructions drine sind, wird es noch ein wenig fixer.
0x00000001 schrieb:
Die D3dX machen sowas auch nicht mit (besonderer) Hardware, ich habs mal rausgesucht:
Aber was ist denn nun besser? Warum nutzt MS kein SSE, wenn es, wie ChockoCookie sagt, direkt verfügbar ist? Nagut, vielleicht wegen Abwärtskompatibilität oder sowas...
ich frage mich allerding was das soll:
D3DXINLINE FLOAT D3DXVec3Dot ( CONST D3DXVECTOR3 *pV1, CONST D3DXVECTOR3 *pV2 ) { #ifdef D3DX_DEBUG if(!pV1 || !pV2) return 0.0f; #endif return pV1->x * pV2->x + pV1->y * pV2->y + pV1->z * pV2->z; }
wenn man nicht möchte dass es einen nullzeiger gibt, dann macht man eben referenzen rein die keine 'nullzeiger' sein könnnen (per definition).
rapso->greets();
-
lass mich raten...directX ist ein(e) C API?
bye
tt
-
So, und jetzt benutzt eure Optimierungen mal praktisch und sagt was da raus gekommen ist.
Bye, TGGC (Der Held ist zurück)
-
TheTester schrieb:
directX ist ein(e) C API?
Nein, DirectX basiert auf dem COM Modell, also so ziemlich das Objektorientierteste was M$ jemals zustande gebracht hat
0x00000001 schrieb:
Kann man also unterm Strich sagen, dass es sich nicht lohnt wegen einfachen Berechnungen spezielle Hardware anzuschmeißen?
Ich meine jetzt zB wenn ich nur ein einziges Scalarprodukt ausrechnen will, und nicht gleich 300k Stück auf einmal.Imho kann man das so sagen. Denn immerhin müssen die Daten einige Voraussetzungen erfüllen damit man sie effizient mit SIMD verarbeiten kann. zB sollten sie in 4er oder 8er Blöcken im Speicher liegen. Also müsste man bei diesem 3D Vektor eine vierte Dummy Koordinate hinzufügen, damit man sich den Geschwindigkeitsvorteil nicht zu Nichte macht. Aufwand > Nutzen
-
meine vectoren sind immer 4 lang, es gibt zwar als class Vector3 (richtungsvector) und Vector4 (ortsvector), das aber nur um zur compilierzeit die richtigen funktionen aufzurufen, es ist sowieso unsinnig die simd einheiten mit nicht allignten variablen zu nutzen, weil dadurch die performancen so gut wie garnicht besser wird. wirklich nur 3float zu verwenden würde nur etwas bringen wenn man ein _langes_ array hat in dem man _float_für_float_ abarbeitet, das ist aber ziemlich selten der fall in der spielegrafik.
um tggcs frage zu beantworten.
mein software occlusion culler ist dank mmx ca 60% schneller geworden, mein radiosityraytracer dank 3dnow ca 40% schneller und meine aktuelle engine läuft mit sse ca 10% schneller was ziemlich viel ist, da die last zu 90% bei der grafikkarte liegt.
rapso->greets();
-
Ähhh TomasRiker...
mir ist da eine Idee gekommen. Wie testest du?Ich lege ein Array von 300.000 Vektoren auf dem Heap an und transformiere sie dort. In etwa:
#define NUM_VEC 300000 Vector3* pVectors = new Vector3[NUM_VEC] Matrix33 Mat; for( int i = 0; i < NUM_VEC; i++ ) { Mat.Multiply( &pVectors[i] ); } delete [] pVector;
Mit einer anderen Methode kam ich auf 5 ms.
Sie war in etwa so:
Vector3 Vec; Matrix33 Mat; for( int i = 0; i < 300000; i++ ) { Mat.Multiply( &Vec ); }
Allerdings hab ich nun ein neues Problem, bzw. etwas was mir ziemlich unverständlich ist.
Ich hab eine Matrix33::Multiply geschrieben was einen Vector3* übernimmt und das Ergebnis im selben zurückliefert.
Dazu muss ich den Vector temporär kopieren.
Seltsamerweise ist diese Funktion schneller als eine andere Funktion die
2 Vector3* übernimmt und wobei der 1. Zeiger auf einen zu transformierenden
Vector zeigt und in den anderen Zeiger der transformierte Vector abgelegt wird.Diese Funktion sollte doch IMHO schneller sein als die erste, in der immer eine temporäre Kopie erstellt werden muss.
Irgendeine Idee warum dass so sein könnte?
P.S die Funktionen sind alle geinlined.( __forceinline )
Vielleicht liegt es daran.
-
Beowulf schrieb:
Ähhh TomasRiker...
mir ist da eine Idee gekommen. Wie testest du?So:
p_in = new float[100000 * 3]; p_out = new float[100000 * 3]; fillWithNoise(p_in, 100000 * 3); startTime = timeGetTime(); count = 0; while(timeGetTime() < startTime + 1000) { transform3DVectorsBy33Matrix(p_in, p_out, 100000, 3 * sizeof(float), matrix); count += 100; } printf("%.2f k/ms\n", static_cast<float>(count) / static_cast<float>(timeGetTime() - startTime)); delete[] p_in; delete[] p_out;
Und das ist die Funktion für 3DNow! (die Funktion "transform3DVectorsBy33Matrix" ist ein Funktionszeiger, der bei der Initialisierung der Bibliothek automatisch gesetzt wird - entweder auf eine Standard-C++-Implementierung oder auf die 3DNow!-Version):
bool T_3DNow_transform3DVectorsBy33Matrix(const float* p_vectorsIn, float* p_vectorsOut, int numVectors, int stride, const float* p_matrix) { float m31m32[2] = {p_matrix[2], p_matrix[5]}; float m33m33[2] = {p_matrix[8], p_matrix[8]}; __asm { femms; // Register setzen mov eax, p_vectorsIn; mov ebx, p_vectorsOut; mov ecx, p_matrix; mov edi, numVectors; mov esi, stride; // die Matrix in die Register laden movq mm3, [ecx]; // mm3 = (m11, m21) movq mm4, [ecx+12]; // mm4 = (m12, m22) movq mm5, [ecx+24]; // mm5 = (m13, m23) movq mm6, m31m32; // mm6 = (m31, m32) movq mm7, m33m33; // mm7 = (m33, m33) $transform: // Vektor in den Cache laden prefetch [eax]; // Sind wir fertig? cmp edi, 0; jle $end; movq mm0, [eax]; // mm0 = (in.x, in.y) pfmul mm0, mm3; // mm0 = (in.x * m11, in.y * m21) movq mm1, [eax]; // mm1 = (in.x, in.y) pfmul mm1, mm4; // mm1 = (in.x * m12, in.y * m22) pfacc mm0, mm1; // mm0 = (in.x * m11 + in.y * m21, in.x * m12 + in.y * m22) movq mm1, [eax+4]; // mm1 = (in.y, in.z) punpckhdq mm1, mm1; // mm1 = (in.z, in.z) pfmul mm1, mm6; // mm1 = (in.z * m31, in.z * m32) pfadd mm0, mm1; // mm0 = (in.x * m11 + in.y * m21 + in.z * m31, in.x * m12 + in.y * m22 + in.z * m32) movq mm1, [eax]; // mm1 = (in.x, in.y) pfmul mm1, mm5; // mm1 = (in.x * m13, in.y * m23) movq mm2, [eax+4]; // mm2 = (in.y, in.z) punpckhdq mm2, mm2; // mm2 = (in.z, in.z) pfmul mm2, mm7; // mm2 = (in.z * m33, in.z * m33) pfacc mm1, mm1; // mm1 = (in.x * m13 + in.y * m23, in.x * m13 + in.y * m23) pfadd mm1, mm2; // mm1 = (in.x * m13 + in.y * m23 + in.z * m33, in.x * m13 + in.y * m23 + in.z * m33) // Vektor ausgeben movq [ebx+4], mm1; movq [ebx], mm0; // Zähler verringern und nächsten Vektor transformieren dec edi; add eax, esi; add ebx, esi; jmp $transform; $end: femms; } return true; }
Die Funktion transformiert die Vektoren also nicht an Ort und Stelle, sondern schreibt das Ergebnis in einen anderen Speicherbereich. Eine Version für das Transformieren an Ort und Stelle könnte vielleicht noch etwas schneller sein.
-
Danke für alle Antworten.
Um sie zu verstehen werd ich mich jetzt erst mal intensiv mit
3D-Now und Assembler beschäftigen und mir ebenfalls eine eigene Mathebibliothek schreiben. Momentan ist noch alles im selben Projekt...
Der Link von miller_m dazu sehr gut geeingnet.Bis die Tage...
Beowulf
-
'Nen Haufen Arbeit, um 66 fps statt 60 fps zu haben.
Bye, TGGC (Der Held ist zurück)