C++Guns – RoboBlog

10.05.2018

C++ Guns: perfekt generiertes Assember von meiner Geometry Library

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

Schaut euch mal folgenden Anfang einer Line2D intersect Funktion an. Erstaunlich wie sauber und perfekt der Assember Code aus meinem C++ Code generiert wird. Da ist absolut kein Overhead zu erkennen. Keine Funktionsaufrufe, kein unnötiges Laden und zwischenspeichern von Daten. Keine temporäre Objekte auf dem Stack. Überhaupt keine Stack Nutzung. Es gibt im Code fünf Subtraktionen und zwei Multiplikationen. Das resultiert im Assember Code mit fünf Subtraktionen, zwei Multiplikationen und vier explizite Kopier-Befehle für doubles. Besser geht es doch gar nicht mehr. Und das alles ist nur mit Optimerungslevel O1 compiliert. Nicht O2, nicht fanzy O3, nein, nur O1. GNU GCC alle gängigen Versionen. Clang kann es natürlich nicht mit O1, nur mit O2, aber dann sieht der ASM Code nicht mehr so schön symmetrisch aus ;) Bei Intel funktioniert es auch mit O1, die ASM Code Anordnung ist etwas anders. Microsoft... nein.
Und die wichtigsten Zeilen vom C++ Code sind doch auch ausdrucksstark.

#include <array>

struct Point2D : public std::array<double,2> {
    inline const auto& x() const { return at(0); }
    inline const auto& y() const { return at(1); }
};

inline const Point2D operator-(const Point2D& p1, const Point2D& p2) {
    return Point2D{p1.x()-p2.x(), p1.y()-p2.y()};
}

struct Line2D : public std::array<Point2D, 2> {
    inline const Point2D& p1() const { return at(0); }
    inline const Point2D& p2() const { return at(1); }
};

auto func(const Line2D& line1, const Line2D& line2) {
    Point2D a = line1.p2() - line1.p1();
    Point2D b = line2.p1() - line2.p2();
    const auto denominator = a.y() * b.x() - a.x() * b.y();
    return denominator;
}
func(Line2D const& line1, Line2D const& line2):
  movsd (%rsi),   %xmm0 # line2 x1
  subsd 16(%rsi), %xmm0 # line2 x2
  movsd 24(%rdi), %xmm1 # line1 y2
  subsd 8(%rdi),  %xmm1 # line1 y1
  mulsd %xmm1,    %xmm0
  movsd 8(%rsi),  %xmm1 # line2 y1
  subsd 24(%rsi), %xmm1 # line2 y2
  movsd 16(%rdi), %xmm2 # line1 x2
  subsd (%rdi),   %xmm2 # line1 x1
  mulsd %xmm2,    %xmm1
  subsd %xmm1,    %xmm0
  ret 

Übrigends bekommt man den selben Assember Code auch mit float statt double. Und für int bleibt die Struktur auch die selbe, es werden nur die normalen Register benutzt. Nur bei long double kommen ein paar Kopierbefehle dazu, da die normalen floating point Register der CPU genutzt werden, statt SSE. Da SSE aber 128bit floating point Zahlen verarbeiten kann, muss ich wohl die CPU beim Compiler angeben.

// Nachtrag
Einen setz ich noch drauf: SSE mit GNU gcc Vector Instructions Extension. Point2D mit double als Type lässt sich wunderbar per SSE verarbeiten. So können zwei Subtraktionen/Multiplikationen parallel ausgeführt werden. Damit lässt sich extrem kompakte (und super effizienter) ASM Code generieren.

#include <smmintrin.h>

using v2sd = double __attribute__ ((vector_size (16)));
struct  Point2D : public std::tuple<v2sd> {
....
}

auto func(const Line2D& line1, const Line2D& line2) {
    const Point2D a = line1.p2() - line1.p1();
    const Point2D b = (line2.p1() - line2.p2());
    const auto denominator = a.y()*b.x() - a.x()*b.y();
    return denominator;
}

Nur noch drei Substraktionen. Leider kommen so unpack Anweisungen dazu.

func(Line2D const&, Line2D const&):
  movapd 16(%rdi), %xmm1
  subpd (%rdi), %xmm1
  movapd (%rsi), %xmm2
  subpd 16(%rsi), %xmm2
  movsd %xmm2, %xmm0
  movapd %xmm1, %xmm4
  unpckhpd %xmm4, %xmm4
  mulsd %xmm4, %xmm0
  unpckhpd %xmm2, %xmm2
  mulsd %xmm2, %xmm1
  subsd %xmm1, %xmm0

No Comments

No comments yet.

RSS feed for comments on this post.

Sorry, the comment form is closed at this time.

Powered by WordPress