Geschwindigkeit einer 3x3 Matrix



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


  • Mod

    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


  • Mod

    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)


  • Mod

    TGGC schrieb:

    'Nen Haufen Arbeit, um 66 fps statt 60 fps zu haben. 😉
    Bye, TGGC (Der Held ist zurück)

    im vergleich dazu was man an zeit investiert hat um überhaupt was zu sehen, ist das wochenende um es auf SIMD zu optimieren eigentlich nichts.

    rapso->greets();



  • Funktionen die in Bibliotheken liegen können doch IMHO keinen Speicher für das Programm das die Lib benutzt allokieren, bzw das Programm kann diesen Speicher nicht deallokieren, glaube ich...

    D.h doch das ich nur Funktionen in die Bibliothek schreiben könnte, aber die Klassen, wie z.B. Vector3 oder Matrix33 im Hauptprogramm lassen muss, da diese Klassen sich bei mir "tief"( Klon auf dem Heap ) kopieren können.

    Und Zeiger auf Funktionen werden doch IMHO bei ausführung nicht geinlined.
    Werden die Funktionen, die man aus der Lib aufruft geinlined, wenn man sie
    als inline deklariert?

    Wiedersprecht mir wenn ich mich irre.

    Danke!



  • rapso schrieb:

    im vergleich dazu was man an zeit investiert hat um überhaupt was zu sehen, ist das wochenende um es auf SIMD zu optimieren eigentlich nichts.

    rapso->greets();

    Tja, vorausgesetzt, das man dies hat. 😎

    Bye, TGGC (Der Hed ist zurück)


Anmelden zum Antworten