Vektor Klasse mit SSE optimierung
-
Hallo,
ich habe mir eine Vektor Klasse mit SSE Optimierung geschrieben. Das ganze funktioniert soweit auch. Nur wenn ich diese in einem Objekt auf dem Heap benutzen will, welches mit new angelegt wird, gibt es einen Segfault.
Die Vektor Klasse
#include <xmmintrin.h> class Vector { public: Vector(); Vector(const __m128& m); Vector(const float w, const float x, const float y, const float z); union { __m128 m128; struct { float w, x, y, z; }; }; }; inline Vector operator + (const Vector &vector1, const Vector &vector2) { return Vector( _mm_add_ps(vector1.m128, vector2.m128) ); } inline Vector operator - (const Vector &vector1, const Vector &vector2) { return Vector( _mm_sub_ps(vector1.m128, vector2.m128) ); } Vector::Vector() { m128 = _mm_setzero_ps(); } Vector::Vector(const __m128& m) { m128 = m; } Vector::Vector(const float w, const float x, const float y, const float z) { m128 = _mm_set_ps(w, z, y, x); }Die Testklasse
#include "Vector.h" class Test { public: Vector v; Test(const float w, const float x, const float y, const float z); }; Test::Test(const float w, const float x, const float y, const float z) { v = Vector(w, x, y, z); }Und die Main:
#include "Test.h" #include <iostream> int main(int argc, char** argv) { Test temp1 = Test(1.0, 2.0, 3.0, 4.0); std::cout << temp1.v.w << temp1.v.x << temp1.v.y << temp1.v.z << std::endl; Test* temp2 = new Test(1.0, 2.0, 3.0, 4.0); std::cout << temp2->v.w << temp2->v.x << temp2->v.y << temp2->v.z << std::endl; return 0; }In der Main wird erstiges (Test temp1 = Test ...) noch ausgefürt doch wenn ich das Testobjekt auf den Heap anlege mit (Test* temp2 = new Test ...) gibt es einen Segfault.
Hat jemand eine Idee woran das liegen kann?
Gruß, Tim
-
sse operanden müssen fast immer 16-byte aligned sein, dass garantiert die operator new (oder auch malloc) aber normalerweise nicht. du hast im grunde 3 möglichkeiten:
- du schreibst deinen eigenen globalen operator new (ungünstig)
- du schreibst einen klassenspezifischen operator new
- du schreibst einen allokator dafür (und gegebenfalls einen klassenspezifischen operator new, der den allokator benutzt)die dritte option hat den vorteil, dass sie wiederverwendbar ist. dann selbst, wenn du operator new per klasse erwsetzt, dafsrt du nicht vergessen, dass dasselbe auch in allen klassen passieren muss, die einen Vektor als member haben.
dazu auch:
http://www.c-plusplus.net/forum/viewtopic-var-t-is-129867.html
-
Es gibt noch eine vierte Möglichkeit, auch wenn ich sie nicht empfehlen würde. Und zwar bietet SSE auch Instruktionen für nicht ausgerichteten Speicher, dementsprechend muss man aber mit Geschwindigkeitseinbuszen rechnen.
Und dass der Test mit automatischem Speicher funktioniert, ist auch nicht garantiert. Ich würde da mal in die Doku des Compilers gucken. IdR gibt es dafür Erweiterungen. Beim MSC wäre dies zB __declspec(align(16)).
Und schau dir nochmal an, was C++ bei der Konstruktion von Objekten zu bieten hat.
Test::Test(const float w, const float x, const float y, const float z) { v = Vector(w, x, y, z); } Test temp1 = Test(1.0, 2.0, 3.0, 4.0);Das zB ist keine glückliche Implementierung, und zwar in mehrerer Hinsicht.
-
groovemaster schrieb:
Und dass der Test mit automatischem Speicher funktioniert, ist auch nicht garantiert. Ich würde da mal in die Doku des Compilers gucken. IdR gibt es dafür Erweiterungen. Beim MSC wäre dies zB __declspec(align(16)).
das alignment ist bereits teil des __m128 typs, es ist also nicht notwendig, das noch selbst zu ergänzen. das problem mit automatischen speicher ist leider sehr real, insbesondere mit temporaries - meiner erfahrung nach ist g++ 3.x in dieser hinsicht völlig kaputt, mit g++ 4 habe ich keine erfahrung.
-
Das zB ist keine glückliche Implementierung, und zwar in mehrerer Hinsicht.
Kannst du das Bitte noch etwas spezifizieren? Es ging hier eigentlich auch nur darum, mein Problem darzustellen. In Wirklichkeit initialisiere und berechne ich in der Klasse mehrere Vektoren.
Mus ich dann für die Klasse Vektor nen new Operator überschreiben? Ich lege ja meine Vektoren nur auf dem Stack ab und nur das Objekt der Klasse Test liegt auf dem Heap. Oder wird für das Ablegen von Objekten auf dem Stack auch der new Operator verwendet?
Gruß, Tim
-
timbo schrieb:
Mus ich dann für die Klasse Vektor nen new Operator überschreiben? Ich lege ja meine Vektoren nur auf dem Stack ab und nur das Objekt der Klasse Test liegt auf dem Heap. Oder wird für das Ablegen von Objekten auf dem Stack auch der new Operator verwendet?
Das ist eine gute frage, theoretisch könnte eine implementation auch für stackbasierte variablen, operator new aufrufen (und ich meine nicht placement new). praktisch passiert das meines wissens aber nicht auf x86 plattformen, und nur dort ist jas SSE relevant.
letzlich musst du einen angepassten (gegenbenfalls vererbten) operator new für alle klassen haben, die ein entsprechendes alignment voraussetzen und direkt auf dem heap angelegt werden sollen, in diesem falle also nur test.
-
camper schrieb:
das alignment ist bereits teil des __m128 typs, es ist also nicht notwendig, das noch selbst zu ergänzen.
Hast Recht. Hab mich da zu sehr an meiner Implementierung orientiert, da ich dort nur x,y,z,w als Member habe und nicht m128.
camper schrieb:
das problem mit automatischen speicher ist leider sehr real, insbesondere mit temporaries
Yep, solche Probleme gibts trotz explizitem Alignment. IIRC hat zB folgender Operator bei mir nicht funktioniert (MSC):
template <> const vector<fp32, 4> operator *(vector<fp32, 4> lhs, const vector<fp32, 4>& rhs) { lhs *= rhs; return lhs; }Offenbar scheint der MSC Alignment bei Funktionsparametern nicht zu beachten. Folgendes funktioniert hingegen problemlos:
template <> const vector<fp32, 4> operator *(const vector<fp32, 4>& lhs, const vector<fp32, 4>& rhs) { vector<fp32, 4> tmp(lhs); tmp *= rhs; return tmp; }timbo schrieb:
Kannst du das Bitte noch etwas spezifizieren?
Besser wäre zB dies
Test::Test(float w, float x, float y, float z) : v(w, x, y, z) { } Test temp1(1.0, 2.0, 3.0, 4.0);
-
Hallo,
danke für eure Hinweise. Es funktioniert nun, wenn ich den new Operator der betroffenen Klassen, die Vector verwenden (hier "Test") überschreibe.
Die Operationen +, -, +=, -= funktionieren soweit. Ich sitze nun gerade am Skalarprodukt. Kann mir vielleicht jemand auf die Sprünge helfen.
__m128 result = _mm_mul_ps( vector1.m128, vector2.m128 ); return result.m128_f32[0] + result.m128_f32[1] + result.m128_f32[2] + result.m128_f32[3];Hier meckert der Compiler rum, dass er m128_f32 nicht kennt.
Gruß, Tim
-
AFAIK soll __m128 lediglich ein Typ sein, der 128 Bit breit ist. Dass er als eine Struktur daherkommt und über Member wie m128_f32 verfügt, darauf würde ich mich nicht verlassen. Beim GCC gibt es zB mit deinem Code Probleme. Kann dir im Moment auch nicht sagen, was die Intel Spezifikation dazu meint. Ich würde deshalb bei Bedarf einfach casten:
__m128 result = _mm_mul_ps( vector1.m128, vector2.m128 ); fp32 (&result32x4)[4] = reinterpret_cast<fp32(&)[4]>(result); return result32x4[0] + result32x4[1] + result32x4[2] + result32x4[3];fp32 ist ein 32 Bit Gleitkommatyp, den du selbst bereitstellen musst. Für gängige 32 Bit Rechner reicht dafür ein
typedef float fp32;Natürlich kannst du das auch etwas handlicher verpacken, sofern du sowas öfters brauchst.
-
Hi,
vielen Dank für deinen Hinweis. So funktioniert es nun.
Das Thema CPU Optimierung ist sicher auch ein großes Kapitel für sich. Mit der SSE Optimierung ist mein Programm jedenfalls genauso schnell bzw. minimal schneller als das ohne SSE Optimierung. Beim Profiling ist mir aufgefallen, dass die Rechenoperationen an sich schon schneller sind (fast doppelt so schnell) aber das laden der Daten in den __m128 Speicher braucht halt auch Zeit und somit gleicht sich das fast wieder aus.
Bei Gelegenheit werde ich mich auch mal intensiver mit dem Kapitel befassen.
Gruß, Tim