boost::array - Semantik


  • Mod

    (D)Evil schrieb:

    Nicht zurück geben, aber sowas ist eleganter:

    void foo(arr& dest)
    { /* fill dest */ }
    

    ... sparst du dir das kopieren ...

    Dafür opferst du damit die einfache Initialisierung und benötigst zusätzlich ein bereits konstruiertes Objekt. Effizienter ist das jedenfalls nicht, und Eleganz vermisse ich auch.



  • ? Ob ich jetzt folgendes mache:

    std::string result(const char* sample)
    { return std::string(sample); }
    
    int main()
    {
        const std::string my_text(result("123456789"));
    }
    

    oder

    void result(const char* sample, std::string & dest)
    { dest = sample; }
    
    int main()
    {
        std::string my_text;
        result("123456789", dest);
    }
    

    ... da sparst du dir 1x kopieren ... ob du es willst, oder nicht.
    okay ich kann dafür my_text nicht const setzen ... 😉


  • Mod

    (D)Evil schrieb:

    ... da sparst du dir 1x kopieren ...

    wo? Im ersten Fall ist keine Kopie involviert, die nicht nach 12.8/15 eliminiert werden darf (und konsequenterweise von jedem anständigen Compiler eliminiert werden wird).



  • Na ja, aber allein um die Objekte in das boost::array hinein zu kopieren wird der Copy-Ctor aufgerufen.

    Mein Problem ist, dass die Funktion teil des performance-kritischen Teils meines Programmes ist. In Wirklichkeit schaut das so aus:

    class VectorArraySSE
    {
    	public:
    		// ... ein paar Operatoren
    
    	private:
    		boost::array<VectorSSE, 3> v;
    };
    
    typedef boost::array<Vector3D, 4> VectorArray3D;
    inline VectorArray3D convertToVector3D(const VectorArraySSE& va)
    {
    	const unsigned int shuffle1 = _MM_SHUFFLE(0, 1, 2, 3);
    	const unsigned int shuffle2 = _MM_SHUFFLE(4, 3, 0, 1);
    	const VectorSSE x0y0x1y1(_mm_unpacklo_ps(va[0].vec, va[1].vec));
    	const VectorSSE x0x1z0z1(_mm_movelh_ps(va[0].vec, va[2].vec));
    	const VectorSSE a(_mm_shuffle_ps(x0y0x1y1.vec, x0x1z0z1.vec, shuffle1));
    	const VectorSSE b(_mm_shuffle_ps(va[2].vec, x0y0x1y1.vec, shuffle2));
    
    	const unsigned int shuffle3 = _MM_SHUFFLE(3,4, 0, 1);
    	const VectorSSE x3y3x4y4(_mm_unpackhi_ps(va[0].vec, va[1].vec));
    	const VectorSSE c(_mm_shuffle_ps(x3y3x4y4.vec, va[2].vec, shuffle1));
    	const VectorSSE d(_mm_shuffle_ps(va[2].vec, x3y3x4y4.vec, shuffle3));
    
    	VectorArray3D retval = {{Vector3D(a.v[0], a.v[1], a.v[2]),
    		Vector3D(b.v[0], b.v[1], b.v[2]),
    		Vector3D(c.v[0], c.v[1], c.v[2]),
    		Vector3D(d.v[0], d.v[1], d.v[2])}};
    
    	return retval;
    }
    

    Das Ganze ist SSE-Zeugs, und wandelt 3 SSE-Vektoren (Structure of Arrays) in 4 normale 3D-Vektoren (Array of Structures) um.

    So wie's momentan ist, hab ich bei jedem Aufruf der Funktion zuerst 4 normale Vector3D-Ctor Aufrufe (zum erstellen der Objekte) und nachher 4 Copy-Ctor Aufrufe, wobei erstere evtl. vom Compiler ja wegoptimiert werden koennten, oder seh ich das falsch?
    Wuerde ich das ganze so Umschreiben, wie (D)Evil vorschlaegt (4 Vector3D* als Eingangsparameter), muesste ich trotzdem immer noch 4 Vector3D-Ctors aufrufen, gewinne also gar nix (sondern verliere eher noch was, weil ich die 4 Pointer vorher ja als normale Vector3D initialisieren muss).


  • Mod

    Blue-Tiger schrieb:

    So wie's momentan ist, hab ich bei jedem Aufruf der Funktion zuerst 4 normale Vector3D-Ctor Aufrufe (zum erstellen der Objekte) und nachher 4 Copy-Ctor Aufrufe, wobei erstere evtl. vom Compiler ja wegoptimiert werden koennten, oder seh ich das falsch?

    Die Copy-ctor-Aufrufe können elimniert werden, nicht die Anderen. Im Übrigen ist dieser Art von Code ohnehin eher ein Spezialfall - allemal sollte deine Vector3D-Klasse über einen Konstruktor verfügen, der direkt mit SSE-Vektoren arbeiten kann. Dann lassen sich einige Variablen einsparen, was durchaus in effizienterem Code resultieren kann. So wie es jetzt aussieht, könntest du auf den SSE-Code auch ganz verzichten, und die Vector3Ds direkt aus den Komponenten von VectorArraySSE konstruieren.



  • camper schrieb:

    Blue-Tiger schrieb:

    So wie's momentan ist, hab ich bei jedem Aufruf der Funktion zuerst 4 normale Vector3D-Ctor Aufrufe (zum erstellen der Objekte) und nachher 4 Copy-Ctor Aufrufe, wobei erstere evtl. vom Compiler ja wegoptimiert werden koennten, oder seh ich das falsch?

    Die Copy-ctor-Aufrufe können elimniert werden, nicht die Anderen. Im Übrigen ist dieser Art von Code ohnehin eher ein Spezialfall - allemal sollte deine Vector3D-Klasse über einen Konstruktor verfügen, der direkt mit SSE-Vektoren arbeiten kann. Dann lassen sich einige Variablen einsparen, was durchaus in effizienterem Code resultieren kann. So wie es jetzt aussieht, könntest du auf den SSE-Code auch ganz verzichten, und die Vector3Ds direkt aus den Komponenten von VectorArraySSE konstruieren.

    Na ja, jeder der 4 Vector3D enthaelt Komponenten aus jedem der VectorSSEs, die im VectorArraySSE gespeichert sind. Wenn ich diese "Zerlegung" in den Vector3D-Ctor verschiebe, muss ich sie 4 mal vornehmen (fuer jeden Vector 1x), das waer eine Vervierfachung der Laufzeit, und der Code ist wie gesagt performancekritisch 😉


  • Mod

    Blue-Tiger schrieb:

    camper schrieb:

    Blue-Tiger schrieb:

    So wie's momentan ist, hab ich bei jedem Aufruf der Funktion zuerst 4 normale Vector3D-Ctor Aufrufe (zum erstellen der Objekte) und nachher 4 Copy-Ctor Aufrufe, wobei erstere evtl. vom Compiler ja wegoptimiert werden koennten, oder seh ich das falsch?

    Die Copy-ctor-Aufrufe können elimniert werden, nicht die Anderen. Im Übrigen ist dieser Art von Code ohnehin eher ein Spezialfall - allemal sollte deine Vector3D-Klasse über einen Konstruktor verfügen, der direkt mit SSE-Vektoren arbeiten kann. Dann lassen sich einige Variablen einsparen, was durchaus in effizienterem Code resultieren kann. So wie es jetzt aussieht, könntest du auf den SSE-Code auch ganz verzichten, und die Vector3Ds direkt aus den Komponenten von VectorArraySSE konstruieren.

    Na ja, jeder der 4 Vector3D enthaelt Komponenten aus jedem der VectorSSEs, die im VectorArraySSE gespeichert sind. Wenn ich diese "Zerlegung" in den Vector3D-Ctor verschiebe, muss ich sie 4 mal vornehmen (fuer jeden Vector 1x), das waer eine Vervierfachung der Laufzeit, und der Code ist wie gesagt performancekritisch 😉

    Du unternimmst sie doch sowieso 4 mal:

    VectorArray3D retval = {{Vector3D(a.v[0], a.v[1], a.v[2]),
            Vector3D(b.v[0], b.v[1], b.v[2]),
            Vector3D(c.v[0], c.v[1], c.v[2]),
            Vector3D(d.v[0], d.v[1], d.v[2])}};
    

    wieso ist das nicht

    VectorArray3D retval = {Vector3D(a), Vector3D(b), Vector3D(c), Vector3D(d)}};
    

    Wenn du wirklich Wert auf Performance legst, sollte Vector3D wahrscheinlich sowieso nur ein Wrapper um VectorSSE sein.



  • camper schrieb:

    Blue-Tiger schrieb:

    Na ja, jeder der 4 Vector3D enthaelt Komponenten aus jedem der VectorSSEs, die im VectorArraySSE gespeichert sind. Wenn ich diese "Zerlegung" in den Vector3D-Ctor verschiebe, muss ich sie 4 mal vornehmen (fuer jeden Vector 1x), das waer eine Vervierfachung der Laufzeit, und der Code ist wie gesagt performancekritisch 😉

    Du unternimmst sie doch sowieso 4 mal:

    VectorArray3D retval = {{Vector3D(a.v[0], a.v[1], a.v[2]),
            Vector3D(b.v[0], b.v[1], b.v[2]),
            Vector3D(c.v[0], c.v[1], c.v[2]),
            Vector3D(d.v[0], d.v[1], d.v[2])}};
    

    wieso ist das nicht

    VectorArray3D retval = {Vector3D(a), Vector3D(b), Vector3D(c), Vector3D(d)}};
    

    Ok, das erspart Tipperei, aber warum koennte ich mir damit Variablen sparen?

    Wenn du wirklich Wert auf Performance legst, sollte Vector3D wahrscheinlich sowieso nur ein Wrapper um VectorSSE sein.

    warum?


  • Mod

    inline VectorArray3D convertToVector3D(const VectorArraySSE& va)
    {
        __m128 x0x1x2x3 = va[0].vec; // kann man ggf. einsparen, hier erst einmal der Übersichtlichkeit wegen
        __m128 y0y1y2y3 = va[1].vec;
        __m128 z0z1z2z3 = va[2].vec;
        __m128 x0y0x1y1 = _mm_unpacklo_ps( x0x1x2x3, y0y1y2y3 ); // 2
        __m128 x2y2x3y3 = _mm_unpackhi_ps( x0x1x2x3, y0y1y2y3 ); // 2
    
        VectorArray3D retval = {
            Vector3D( _mm_shuffle_ps( x0y0x1y1, z0z1z2z3, _MM_SHUFFLE( 0, 1, 0, 0 ) ) ), // 2
            Vector3D( _mm_shuffle_ps( x0y0x1y1, z0z1z2z3, _MM_SHUFFLE( 2, 3, 1, 1 ) ) ), // 2
            Vector3D( _mm_shuffle_ps( x2y2x3y3, z0z1z2z3, _MM_SHUFFLE( 0, 1, 2, 2 ) ) ), // 2
            Vector3D( _mm_shuffle_ps( x2y2x3y3, z0z1z2z3, _MM_SHUFFLE( 2, 3, 3, 3 ) ) )  // 2
        };
    
        return retval;
    }
    

    in Kommentaren jeweils die Mindestanzahl an SSE-Instruktionen, die generiert werden müssten. Das ist regelmäßiger (man erkennt sehr schnell, dass sich etwa die shuffle-Indizes nach einem Muster ändern, und 4 hat dort sowieso nichts zu suchen) und einfacher auf Fehler zu überprüfen (man kann sogar noch etwas einsparen: x0x1y0y1/x2x3y2y3 kann mit weniger Instruktionen generiert werden) . Ist Vector3D kein Wrapper um SSE-Register, dann kann bei der Initialisierung (egal ob nun direkt oder über Komponenten) nur Komponentenweise kopiert werden, dann war die ganze Arbeit per SSE aber völlig überflüssig. Im Übrigen ist das wahrscheinlich ohnehin mit zu feiner Granularität optimiert - die hohe Zahl an hin- und hergeshuffle indiziert, dass man bei einer einzelnen Transformation nicht viel gewinnen wird. So oder so hat das alles eigentlich nichts mit dem urspünglichen Thema zu tun.



  • Thx fuer die Erklaerungen 🙂

    EDIT: aber warum benoetigt ein _mm_unpackhi_ps 2 Instruktionen?

    EDIT2: koenntest du mir nochmal genauer erklaeren, WIE man mit _MM_SHUFFLE angibt, wie geshufflt werden soll? ich glaub ich hab da was falsch verstanden 😕
    (ich erhalte z. B. mit deinem obigen Code komplett falsche Shuffles, ich muss die Reihenfolge der Zahlen im _MM_SHUFFLE Ausdruck umdrehen, um die richtigen zu erhalten)


Anmelden zum Antworten