C++Guns – RoboBlog blogging the bot

05.06.2018

C++ Guns: fold over std::tuple und erzeugten Assembler Code

Filed under: Allgemein — Tags: — Thomas @ 00:06

Part 1: print std::array with std::integer_sequence
Part 2: convert tuple to parameter pack
Part 3: print std::array with std::apply and fold
Part 4: fold over std::tuple und erzeugten Assembler Code
Part 5: fold over std::tuple of std::vector of Types ...
Part 6: apply generic lambda to tuple
Part 7: Play with std::tuple and std::apply

Hier ein Ausschnitt aus dem Kniffel Spiel Code. Habe jede gültige Kombination als einen Typ dargestellt mit einem gemeinsamen Typ Score, welcher die erzielten Punkte enthält. Um die gesamte Punktzahl zu erhalten, muss über jeden Typ im tuple iteriert werden und die einzelnen Punkte aufaddiert. Dies wird mit std::apply und fold erledigt.
Alternativ dazu wird in func2() nur über sieben Score Objekte iteriert und auf addiert. Mit Compiler Optimierung O1 ist der generierte Assembler Code mit dem std::apply und fold definitiv besser. Es werden sechs Addition Instruktionen erzeugt. Genau soviel wie benötigt werden um sieben Zahlen zu generieren ;)

Der Assembler Code von func2() hingegen zeigt eine Schleife. Dies finde ich bei so kleinen Datenmengen unnötig. Natürlich kann man mit Optimierung O3 loop unrolling aktivieren, aber der erzeugte Code wird damit noch schlechter. Die manuelle Aktivierung mit O1 und -funroll-loops oder -fpeel-loops erzeugt am Ende auch nur sechs Addition Instruktionen, aber dann ist loop unrolling für alle fix size Schleifen aktiviert. Auch für die, wo man es lieber nicht haben möchte.

Und natürlich geht mit der func2() Variante die Typ Information verloren. Also, std::apply und fold um über Typen zu iterieren. Find ich gut.

struct Score 
{
    int points = 0;
    
};

struct Dreierpasch : Score {
};

struct Viererpasch : Score {
};

struct FullHouse : Score {
};

struct KleineStrasse : Score {
};

struct GrosseStrasse : Score {
};

struct Kniffel : Score {
};

struct Chance : Score {
};

struct LowerScore;
namespace std {
    template<>
    struct tuple_size<LowerScore> : std::integral_constant<size_t,7> {};
}

struct LowerScore : std::tuple<Dreierpasch, Viererpasch, FullHouse, KleineStrasse, GrosseStrasse, Kniffel, Chance>
{
    int totalPoints() const {
        auto f = [](const auto...x){ return (x.points + ...); };
        return std::apply(f, *this );
    }
};

auto func(const LowerScore& score) {
    return score.totalPoints();
}

auto func2(const std::array<Score,7>& score) {
    int points = 0;
    for(const Score& s : score) {
        points += s.points;
    }
    return points;
}
func(LowerScore const&):
  movl (%rdi), %eax
  addl 4(%rdi), %eax
  addl 8(%rdi), %eax
  addl 12(%rdi), %eax
  addl 16(%rdi), %eax
  addl 20(%rdi), %eax
  addl 24(%rdi), %eax
  ret
func2(std::array<Score, 7ul> const&):
  leaq 28(%rdi), %rdx
  movl $0, %eax
.L3:
  addl (%rdi), %eax
  addq $4, %rdi
  cmpq %rdx, %rdi
  jne .L3
  ret

02.06.2018

C++ Guns: convert tuple to parameter pack

Filed under: Allgemein — Tags: — Thomas @ 10:06

Part1: print std::array with std:integer_sequence
Part2: convert tuple to parameter pack
Part 3: print std::array with std::apply and fold
Part 4: fold over std::tuple und erzeugten Assembler Code
Part 5: fold over std::tuple of std::vector of Types ...
Part 6: apply generic lambda to tuple
Part 7: Play with std::tuple and std::apply

Guckst du hier https://en.cppreference.com/w/cpp/language/parameter_pack
und hier https://en.cppreference.com/w/cpp/language/fold
und da https://stackoverflow.com/questions/47216057/how-to-write-a-fold-sum-function-for-c-tuple

parameter pack zu std::tuple ist einfach

template<typename...Ts>
auto func(const Ts&...args) {
    return std::tuple(args...);
}

auto func2() {
    return func(1, 2.0, "test");
}

https://stackoverflow.com/questions/47216057/how-to-write-a-fold-sum-function-for-c-tuple

Der template Type Ts besteht aus den drei einzelnen Typen int, double und const char*. Die Variable args... hält entsprechend die drei Werte. Das sieht man an den drei Punkten "..." bei der Type und Funktions Argument Definition. Die sagen aus, dass hier noch mehr kommt. Die drei Punkte beim erstellen vom tuple packen den parameter pack aus. Sinngemäß wird der Ausdruck umgewandelt zu

return std::tuple<int,double,const char>{arg1, arg2, arg3};

Andersherum, also vom tuple zum parameter pack ist nicht so einfach. Ein Weg führt über std::integer_sequence wie ich es im anderen Post gezeigt habe. Aber das ist wieder so viel Template gedöns und man braucht zwei Funktionen. Ein anderer Weg führt über C++17 std::apply und fold, wie in dem stackoverflow Post gezeigt. Man benötigt zeitgleich aber eine Funktion, welche beim fold auf die Argumente angewandt werden kann. Das ganze bewegt sich weg von dem WIE etwas passiert, hin zu dem WAS passieren soll. Dem bin ich positiv gegenübergestellt.

auto t = std::make_tuple( 1, 2.0 );
auto func = []( auto... v ){ return ( v + ... ); };
auto sum = std::apply(func, t);

In std::apply steckt eine std::integer_sequence drin, welche aus dem tuple erstellt und der Funktion übergeben wird. damit haben wir wieder ein parameter pack. Mittels lambda Funktion spart man sich auch wieder die Templates. Letztendlich müssen wir mit dem parameter pack irgendetwas tun, also wird der plus operator mittels fold aufgerufen.

Das ist schon eine ganz andere Art zu Programmieren, so ganz ohne explizite Schleifen. Aber ich finde es gut. Verhindert Fehler und der Compiler kann mehr tun.
Zum Abschluss noch ein kurzer Spaß. Was passiert, wenn man ein tuple in ein parameter pack umwandelt und das zurück in ein tuple? Und wie sieht der Assembercode dazu aus? Probieren wir es aus!

auto func(const std::tuple<int,double,const char*>& t) {
    auto f = [](const auto&...v){ return std::tuple{v...}; };
    return std::apply(f, t);   
}
func(std::tuple<int, double, char const*> const&):
  movq %rdi, %rax
  movq (%rsi), %rdx
  movq %rdx, (%rdi)
  movsd 8(%rsi), %xmm0
  movsd %xmm0, 8(%rdi)
  movl 16(%rsi), %edx
  movl %edx, 16(%rdi)
  ret

Nun es passiert nichts weiter ;) Es gibt Sieben Assembler Instruktionen. Im Prinzip werden die Argumente nur kopiert. Jede Kopieraktion braucht zwei Instruktionen, da nicht von Speicher zu Speicher kopiert werden kann. Nur von Speicher zu Register und von Register zu Speicher. Die Argumente stehen im rsi Register. Wenn mich nicht alles täuscht, wird erst der const char Pointer kopiert. Also ein quadword mit 64 Byte. Danach kommt das double, erkennt man am xmm0 Register. Und zuletzt das int mit einer movl Instruktion.

C++ Guns: print std::array or std::tuple with std::apply and fold

Filed under: Allgemein — Tags: — Thomas @ 10:06

Part 1: print std::array with std:integer_sequence
Part 2: convert tuple to parameter pack
Part 3: print std::array or std::tuple with std::apply and fold
Part 4: fold over std::tuple und erzeugten Assembler Code
Part 5: fold over std::tuple of std::vector of Types ...
Part 6: apply generic lambda to tuple
Part 7: Play with std::tuple and std::apply

Da ein std::array eigentlich auch nur so etwas wie ein tuple ist, nur nicht mit unterschiedlichen Typen, kann die ganze parameter pack und fold Sache auch auf std::array angewandt werden.

template<typename T, size_t N>
std::ostream& operator<<(std::ostream& s, const std::array<T,N>& arr) {
    std::apply([&](const auto&...v){ (s << ... << v);  }, arr); 
    return s;
}
 
void func(const std::array<double,2>& arr) {
    std::cout << arr;
}

Ja, eigentlich ganz hübsch.

01.06.2018

C++ Guns: Ein Paar Würfel

Filed under: Allgemein — Tags: — Thomas @ 14:06

Ich möchte zum Spaß ein Wüfelspiel programmieren. Dazu müssen einzelne Würfel implementiert werden. Doch was ist ein Würfel eigentlich? Offensichtlich ein Zufallszahlengenerator z.B. std::minstd_rand. Aber ein Würfel repräsentiert auch eine Gleichverteilung Ganzer Zahlen im Bereich von z.B. 1 bis 6. Und ein Würfel hat ein Status, seine Augenzahl. Die ändert sich ja nicht, egal wie oft man einen Würfel anschaut. Außer der Würfel wird neu geworfen, also gibt es eine roll() Funktion.
Und, ganz wichtig: Ein Würfel kann default initialisiert werden. Denn sobald ein Würfel existiert, zeigt er seine Augenanzahl an. Diese ist natürlich zufällig, aber existiert immer.

Zusammen mit meinem Generic Data Type Design Pattern sieht der Code etwas komisch aus, macht aber genau was er soll. Im Unterschied zu vorherigen Implementationen dieses Pattern ändern ein Würfel ständig seinen Zustand. Daher gibt es neben den einheitlichen const getter Funktionen z.B. number(), diesmal auch non-const setter, welcher aber als privat! deklariert sind. Denn nur der Würfel selbst darf seinen Zustand ändern.

Für mich wichtig ist, dass das Erstellen und Benutzen von Würfel-Objekten einfach und intuitiv ist. Sobald ein Würfel erstellt worden ist, ist er da und hat eine Zahl. Wenn der Würfel neu geworfen wird, ändert sich die Zahl. Ansonsten kann der Würfel nichts. Und so ist es doch gut.

Was mich noch richtig stört ist das seedn der Zufallgeneratoren. Jeder Würfel IST EIN Zufallsgenerator. Also muss der Seed immer unterschiedlich sein. Die Zeit in Sekunden kann hierfür nicht genommen werden, diese Ändert sich ja nur jede Sekunde. Daher wird noch auf einen realen Hardware Zufallsgenerator zugegriffen und der endgültige Seed mit std::seed_seq erstellt. std::seed_seq selbst darf aber kein temporäres Objekt sein, da der Zufallszahlengenerator beim initialisieren das std::seed_seq Objekt verändern darf.

template<int N=6>
struct Dice : std::tuple<std::minstd_rand, std::uniform_int_distribution<int>, int> {
    Dice() {
        // Dreck.
        std::seed_seq seed{unsigned(42), std::random_device()(), unsigned(std::time(nullptr))};
        gen() = std::minstd_rand(seed);
        dis() = std::uniform_int_distribution<int>(1,N);
        number() = dis()(gen());
    }
    
    auto roll() {
        number() = dis()(gen());
        return number();
    }
    
    auto number() const {
        return std::get<2>(*this);
    }
    
private:
    auto& gen() {
        return std::get<0>(*this);
    }
    
    auto& dis() {
        return std::get<1>(*this);
    }
    
    auto& number() {
        return std::get<2>(*this);
    }
};

template<int M>
struct Dices : std::array<Dice<>,M> {
    void rollAll() {
        for(auto& x : *this) {
            x.roll();
        }
    }
};


int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);
    
    Dices<5> dices;
    
    for(int i=0; i < 3; ++i) {
        for(const auto& x : dices) {
            std::cout << x.number() << " ";
        }
        std::cout << "\n";
        dices.rollAll();
    }
    return 0;
}

5 2 3 5 1
5 6 3 1 3
2 6 5 5 2

18.05.2018

C++ Guns: Choose the right datatype

Filed under: Allgemein — Tags: — Thomas @ 19:05

Hier ein sehr kleiner Ausschnitt aus der Praxis und der Assembercode. Verwendet wird ein Vector basierend auf SSEvalarray.

auto func(const SSEVector<float,3>& UBAR) {
    const auto U = UBAR[1]/UBAR[0];
    const auto V = UBAR[2]/UBAR[0];
    // more code which use U and V
    return U*V;
}
func(Vector<float, 3> const&):
  movss (%rdi), %xmm2
  movss 4(%rdi), %xmm0
  divss %xmm2, %xmm0
  movss 8(%rdi), %xmm1
  divss %xmm2, %xmm1
  mulss %xmm1, %xmm0

Wir sehen ein paar MOVs und insbesondere zwei DIVs. Die Eingabevariable ist aber ein SSE Type. Warum sind dann auch zwei DIVs im Assember Code zu sehen? Na weil zwei Divisionen im C++ Code stehen. Um das zu verbessern muss man davon absehen zusammengehörige Dinge als einzelne Skalare zu implementieren.

auto func(const Vector<float,3>& UBAR) {
  Vector<float,3> UV = UBAR/UBAR[0]; 
  return UV;
func(Vector<float, 3> const&):
  movss (%rdi), %xmm1
  shufps $0, %xmm1, %xmm1
  movaps (%rdi), %xmm0
  divps %xmm1, %xmm0

Diesmal existiert nur eine DIV Instruktion für gepackte Werte. Die Shuffle Instruktion verteil den Skalar in UBAR[0] auf alle 32Bit Einheiten im SSE Register. Allerdings ist der Code etwas verwirrend. Die Variable UV ist ein Vector mit drei Elementen. Aber es werden nur zwei Benutzt. Die Semantik ist etwas kaputt.
Der Vektor UBAR stellt soll eigentlich zwei Variablen darstellen. Geschwindigkeit und Durchfluss. Wenn das Programm entsprechend modelliert wird...

struct U_t {    
  Vector<double,2> q;
  double H;
};

auto func(const U_t& UBAR) {
  Vector<double,2> v = UBAR.q/UBAR.H; 
  return v;
}
func(U_t const&):
  movsd 16(%rdi), %xmm1
  unpcklpd %xmm1, %xmm1
  movapd (%rdi), %xmm0
  divpd %xmm1, %xmm0

Der Assemblercode bleibt im Prinzip gleich, nur dass jetzt ohne Verlust double statt float genutzt werden kann. Und die Semantik ist wieder hergestellt :)

17.05.2018

C++ Guns: Point Concept

Filed under: Allgemein — Tags: — Thomas @ 11:05

Ich hab mich mal schnell in Concepts eingearbeitet und provisorisch die std library concepts implementiert. Darauf hin habe ich mir ein Concept eines PointND überlegt. Nun das müsste einfach nur ein Container sein, mit den Funktionen x(), y() und Operatoren für +, -, *, /.

So wurde aus dem Stück Code mit viel Templates:

template<typename Array>
inline auto operator+(const PointND<Array>& lhs, const PointND<Array>& rhs) {
    return PointND<Array>{static_cast<Array>(lhs) + static_cast<Array>(rhs)};
}

dieses Stück:

template<typename PointND>
concept bool Point =
        requires(PointND p1, PointND p2) {
        typename PointND::Base;
        requires std::Container<PointND>;
{p1.x()} -> typename PointND::const_reference;
{p1.y()} -> typename PointND::const_reference;
{p1+p2} -> PointND;
{p1-p2} -> PointND;
{p1*p2} -> PointND;
{p1/p2} -> PointND;
};

template<Point PointND>
inline auto operator+(const PointND& lhs, const PointND& rhs) {
    return PointND{static_cast<typename PointND::Base>(lhs) + static_cast<typename PointND::Base>(rhs)};
}

Na das ist doch schon besser.

https://sourceforge.net/p/acpl/code/ci/master/tree/acpl/acpllib/include/core/stdconcepts.hpp
https://sourceforge.net/p/acpl/code/ci/master/tree/acpl/acpllib/include/core/geometry/point.hpp

16.05.2018

C++ Guns: SIMD PointND

Filed under: Allgemein — Tags: — Thomas @ 10:05

Kommen wir wieder zurück zu meinem Lieblingsdatentype: PointND. Diesmal mit SSE. In den letzten Posts hab ich den Type SIMDarray vorgestellt zum gleichzeitigen prozessieren von mehreren Werten. Danach folge SIMDvalarray Type welcher arithmetische Operation +, -, *, /, definierte. Was braucht es noch mehr zum SIMD PointND? Eigentlich nichts. Ein PointND ist ein fixed size array der Länge N. Man kann noch ein paar Funktionen bereit stellen um explizit auf X und Y zuzugreifen. Aber das ist eigentlich nur Syntax Zucker und bringt keine neue Funktionalität. Also versuchen wir es:

template<typename Array>
struct PointND : Array {
  static_assert(Array().size() >= 2);
  using value_type = typename Array::value_type;

  const value_type& x() const {
    return (*this)[0];
  }

  const value_type& y() const {
    return (*this)[1];
  }
};

template<typename Array>
inline auto operator+(const PointND<Array>& lhs, const PointND<Array>& rhs) {
    return PointND<Array>{static_cast<Array>(lhs) + static_cast<Array>(rhs)};
}

template<typename Array>
auto func(const PointND<Array>& p1, const PointND<Array>& p2) {
    return (p1+p2).y();
}

auto func2(const PointND<SIMDvalarray<double, 2>>& p1, const PointND<SIMDvalarray<double, 2>>& p2) {
    return func(p1, p2);
}
acpl::func2(acpl::PointND<acpl::SIMDvalarray<double, 2ul> > const&, acpl::PointND<acpl::SIMDvalarray<double, 2ul> > const&):
  movapd (%rsi), %xmm0
  addpd (%rdi), %xmm0
  unpckhpd %xmm0, %xmm0

Also ein paar Sachen sind ja schon nervig. Zum einem die Templates. Für Funktionsargumente ist das zu verbose. Es zählt im Grunde nur, dass der Type ein PointND ist. Die Anzahl der Dimensionen, das zugrunde liegende Array oder value_type (int, double), spielt eigentlich keine Rolle. Der Compiler kennt den genauen Aufbau des Types und kann alles Nötige daraus Ableiten. Das selbe gilt auch für Point2D für nur 2D Funktionen. Hier müssten eigentlich Concepts weiter helfen....

Die zweite störende Sache sind die sich ständig wiederholenden operation+-*/ Funktionen. Weglassen kann man sie für PointND nicht. Es wird dann z.B. operator+ für SIMDvalarray aufgerufen, und das Ergebnis ist wieder ein SIMDvalarray, kein PointND. So ist dann der Zugriff auf Y wie im Code gezeigt nicht mehr möglich. Der Compiler ist so schlau zu erkennen, dass hier nur der Type geändert wird. Dies hat keinen Einfluss auf die Laufzeit, wie im Assembler Code zu sehen ist.

15.05.2018

C++ Guns: use templates and inline; don't use libraries

Filed under: Allgemein — Tags: — Thomas @ 19:05

Der Compiler kann wirklich ganz viel Optimieren, solange er das komplette Programm als Code sehen kann. Wird irgendwo eine Funktion aus einer Laufzeit-Bibliothek aufgerufen, ist es vorbei mit der Optimierung. Ein kleines Beispiel soll das verdeutlichen:

void foo(const int &x); 

int bar() {
    int x = 0;
    int y = 0;
    for (int i = 0; i < 10; i++) {
        foo(x);
        y += x;
    }
    return y;
}

Die Funktion foo ist nicht definiert, das gäbe einen Linker Error. Wichtig ist erst einmal, dass zur Compilezeit nicht bekannt ist, was die funktion foo tut. Dementsprechend können keine Annahmen getroffen werden um weitere Optimierungen vorzunehmen. Die Assembercode sieht aus wie erwartet:

bar():
  pushq %rbp           // safe register rbp rbx
  pushq %rbx
  subq $24, %rsp       // allocate memory on stack
  movl $0, 12(%rsp)    // x=0
  movl $10, %ebx       // i=10 
  movl $0, %ebp        // y=0
.L2:                   // for...
  leaq 12(%rsp), %rdi
  call foo(int const&)
  addl 12(%rsp), %ebp  // y += x
  subl $1, %ebx        // --i
  jne .L2
  movl %ebp, %eax     // return y
  addq $24, %rsp      // release memory on stack
  popq %rbx           // restore register
  popq %rbp
  ret

Der C++ Code wurde quasi 1:1 in Assember umgesetzt. Nichts besonderes.
Wenn jetzt die Funktion bar() bekannt ist und zum Beispiel einfach eine leere Funktion ist, ändern die etwas am erzeugten Assembler Code? Allerdings!

foo(int const&):
  ret
bar():
  movl $0, %eax
  ret

Der Compiler hat quasi alles weg-optimiert. Der Funktionsaufruf von foo(), die Addition von y, die Schleife. Das sichern der Register ist nicht mehr notwenig. Stack Speicher wird nicht genutzt. Der Compiler hat erkannt, dass die Funktion immer 0 zurück gibt.

Also: benutzt templates und inline, statt klassische Bibliotheks-Funktions-Aufrufe.

C++ Guns: SIMDvalarray; std::valarray for SSE

Filed under: Allgemein — Tags: — Thomas @ 11:05

SIMDvalarray is the class for representing and manipulating arrays of values like std::valarray but it uses SIMD techniques like SSE, AVX ... and has no subscript operators like slicing.

Einfach SIMDarray mit Operatoren +, -, *, /, +=, -=, *=, /=

template<typename T, std::size_t N>
struct SIMDvalarray : acpl::SIMDarray<T,N> 
{ };

...

SIMDvalarray arr1 {1.0, 2.0};
SIMDvalarray arr2 {2.0, 4.0};
SIMDvalarray erg1 = arr1+arr2;

Erzeugt packed values SIMD Assember Code, wie gewünscht. Und mit C++17 Decudtion Guidelines muss man die nervigen, redundanten Template Parameter auch nicht mehr mit angeben.

https://sourceforge.net/p/acpl/code/ci/master/tree/acpl/acpllib/include/core/util/SIMDvalarray.hpp

C++ Guns: SIMDarray; std::array for SSE

Filed under: Allgemein — Tags: — Thomas @ 09:05

SIMD (Single Instruction, Multiple Data) - besser bekannt als MMX, SSE, AVX u.s.w ist super für die Datenverarbeitung geeignet. So können, je nach Ausstattung der CPU, viele Gleitkommazahlen gleichzeitig verarbeitet werden. So bietet SSE 128 bit, also 16 Byte, große Register. Hier können zwei double Variable gleichzeitig verarbeitet werden. Zum Beispiel für einfach Point2D Operationen wie Addition, Division. Hingegen ist es mit AVX und 256 bit Register Möglich gleich vier double Variablen gleichzeitig zu verarbeiten (Point3D). Seit 2013 gibt es AVX512 mit 512 bit bzw. 64 Byte Register. Willkommen bei PointND.

Die Compiler unterstützen dies auch gut, allerdings wird nicht so ohne weiteres der Assember Code erzeugt, den ich mir vorstelle. Ich möchte nicht in Schleifen tollen SIMD Code erstellt bekommen, sondern bei normalen Operationen von PointND wie + - * /. Einfach, weil die Schleifen in meinem Algorithmen nie trivial sind und ich sehe mehr Optimierungspotential für einfache PointND Operationen.

Einfaches Beispiel

#include <array>

struct Point2D : public std::array<double,2> {
};

inline Point2D operator+(const Point2D& lhs, const Point2D& rhs) {
    return Point2D{lhs[0]+rhs[0], lhs[1]+rhs[1]};
}

auto func(const Point2D& p1, const Point2D& p2) {
    return p1+p2;
}

Der generierte Assembercode enthält zwei Additions-Instruktionen addsd. Die beiden letzten Buchstaben geben den Datentype an und ob es eine gepackte Operation ist, also mehrere Zahlen gleichzeitig. sd steht für single value double precision. Also eine Zahl. Erwartet hätte ich aber addpd - packed value double precision.

operator+(Point2D const&, Point2D const&):
  movsd xmm0, QWORD PTR [rdi]
  addsd xmm0, QWORD PTR [rsi]
  movsd xmm1, QWORD PTR [rdi+8]
  addsd xmm1, QWORD PTR [rsi+8]

Man kann dem Compiler mit der Erweiterung "Vector Instructions" aber etwas auf die Sprünge helfen. Durch die Definition des Attributs vector_size wird SSE Code produziert, so wie ich es mir vorstelle. Hier das Beispiel:

#include <array>

struct Point2D {
    typedef double v2d __attribute__ ((vector_size (16)));
    v2d _data;

    const double& operator[](size_t pos) const {
        return _data[pos];
    }
};

inline Point2D operator+(const Point2D& lhs, const Point2D& rhs) {
    return Point2D{lhs._data + rhs._data};
}

auto func(const Point2D& p1, const Point2D& p2) {
    return p1+p2;
}

Es wird nur noch eine Addition Instruktion erzeugt. Wir haben die Geschwindigkeit der Operation verdoppelt!

func(Point2D const&, Point2D const&):
  movapd xmm0, XMMWORD PTR [rdi]
  addpd xmm0, XMMWORD PTR [rsi]

Darauf lässt sich doch aufbauen und analog zu std::array ein SIMDarray erzeugen. Damit ist es Möglich für beliebige Arithmetische Typen beliebiger Anzahl effizienten SIMD Code zu erzeugen. Gesagt, getan. Die Klasse findet ihr in ACPL core/util/SIMDarray.hpp

https://sourceforge.net/p/acpl/code/ci/master/tree/acpl/acpllib/include/core/util/SIMDarray.hpp

https://gcc.gnu.org/onlinedocs/gcc-8.1.0/gcc/Vector-Extensions.html#Vector-Extensions

« Newer PostsOlder Posts »

Powered by WordPress