C++Guns – RoboBlog blogging the bot

07.09.2022

C++ Guns: Streams display the format flags

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

Display which format flags are currently set e.g. fixed, scientific, dec, hex

std::ostream& operator<<(std::ostream& s, const std::ios::fmtflags f) {
   if(f & std::ios::boolalpha) s << "boolalpha ";
   if(f & std::ios::dec) s << "dec ";
   if(f & std::ios::hex) s << "hex ";
   if(f & std::ios::oct) s << "oct ";
   if(f & std::ios::fixed) s << "fixed ";
   if(f & std::ios::scientific) s << "scientific ";
   if(f & std::ios::right) s << "right ";
   if(f & std::ios::left) s << "left ";
   if(f & std::ios::internal) s << "internal ";
   if(f & std::ios::showbase) s << "showbase ";
   if(f & std::ios::showpoint) s << "showpoint ";
   if(f & std::ios::showpos) s << "showpos ";
   if(f & std::ios::uppercase) s << "uppercase ";
   if(f & std::ios::unitbuf) s << "unitbuf ";
   if(f & std::ios::skipws) s << "skipws ";
   return s;
}

std::cout << std::cout.flags() << "\n";

Example output

dec fixed skipws 

https://en.cppreference.com/w/cpp/io/ios_base/flags

03.08.2022

Fenster Wärmedämmen (Sommer & Winter)

Filed under: Allgemein — Thomas @ 09:08

02.08.2022

Im zweiten Versuch belege ich ein Fenster im Flur komplett mit 4cm Styropor und wiederhole die Temperatur Messung zwischen gedämmten und ungedämmten Fenster. Das Styropor selbst habe ich auf einer Seite mit Farbe und Leim etwas widerstandsfähiger gemacht. Die Seite welche an das Fensterglas kommt bleibt unbehandelt.

fenster1

Die Messreihe zeigt wie erwartet eine deutlich geringere Temperatur am Fenster mit Styropor. Es stellte sich allerdings nur ein maximaler Temperaturunterschied von 4.3 grad ein. Allerdings war die Tageshöchsttemperatur auch ein paar Gerade geringer als bei der letzten Messung. Interessant ist noch der Peak um etwa 18:45 Uhr. Hier scheint die Sonne sich noch einmal kurz einen Weg zwischen den Gebäuden gesucht zu haben.

fenster_02.08.2022


19.07.2022

Im Sommer scheint die Sonne mit runter gelassenen Rolladen stark durch die (einfach verglaste) Fenster, man spürt richtig die Wärme mit der Hand. In der Garage liegt noch etwa 2cm dickes Styropor. Das werde ich probeweise außen am Fenster zwischen Glas und Rolladen klemmen, so dass eine Dämmung entsteht. Das Stück ist nicht groß genug für das komplette Fenster, aber so 1/4 sollte es abdecken. Wichtig ist hierbei, dass das Styropor dicht am Glas anliegt und keine Luft dazwischen zirkulieren kann, sonst ist der Dämmeffekt damit.

Um ein paar Zahlen zu bekommen werde ich innen am Glas die Temperatur messen, sowie von einem ungedämmten Fenster. Und da noch zwei weitere Temperaturfühler zur Verfügung stehen auch noch Wand und Raumtemperatur.

Es zeigt sich ein Unterschied von 5 grad zwischen gedämmten und ungedämmten Fenster. Das ist vielversprechend! Mit besser zugeschnittenen und mehr verdeckte Fläche sollte der Effekt größer sein.

fenster_19.07.2022

30.05.2022

C++ Guns: MPI Dataype; send struct

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

Das Beispiel habe ich von https://www.mpi-forum.org/docs/mpi-3.1/mpi31-report/node425.htm
Jeder Thread erstellt einen MPI Datentyp welcher die Offsett Addressen der struct Member Variablen hat.
Thread 1 sendet Daten zu Thread 0
Thread 0 empfaenge Daten von Thread1 und seine eigenen Daten, so dass alle in einem Array dann liegen.

// This example is based on https://www.mpi-forum.org/docs/mpi-3.1/mpi31-report/node425.htm
// Jeder Thread erstellt einen MPI Datentyp welcher die Offsett Addressen der struct Member Variablen hat.
// Thread 1 sendet Daten zu Thread 0
// Thread 0 empfaenge Daten von Thread1 und seine eigenen Daten, so dass alle in einem Array dann liegen.

#include <iostream>
#include <array>

#include <mpi.h>

int my_rank;
int nThreads;

struct basetype_t {
  double volstep = 0;
  double volTot = 0;
};

// Vererbung ist nicht erlaubt
struct type_t  {
  char ID[50];
  int i = 0;
  float x = 0;
  // Ein Container wie std::vector zwischen den Datentypen scheint wohl zu funktionieren, ist in Fortran aber explizit nicht erlaubt.
//   std::vector<int> unused;
  bool l = false;
  double d = 0;
  basetype_t base;
};

// Check MPI Error code
void check(int ierr) {
  if (ierr != MPI_SUCCESS) {
    char err_recvbuffer[MPI_MAX_ERROR_STRING];
    int resultlen;
    MPI_Error_string(ierr, err_recvbuffer, &resultlen);
    std::cerr << err_recvbuffer << "\n";
    MPI_Finalize();
  }
}

// create new MPI datatype based on the addresses of the member variables of the type we want to send
MPI_Datatype createMPItyp() {
  type_t foo;
  MPI_Aint base;
  check(MPI_Get_address(&foo, &base));

  // Fuer jede member Variable die gesendet werden soll, Typ und Addresse bestimmen
  const int nMembervarsToSend = 7;
  std::array<MPI_Datatype, nMembervarsToSend> types;
  std::array<int,nMembervarsToSend> blocklen;
  std::array<MPI_Aint, nMembervarsToSend> disp;

  types[0] = MPI_INT;
  blocklen[0] = 1;
  check(MPI_Get_address(&foo.i, &disp[0]));

  types[1] = MPI_FLOAT;
  blocklen[1] = 1;
  check(MPI_Get_address(&foo.x, &disp[1]));

  types[2] = MPI_LOGICAL;
  blocklen[2] = 1;
  check(MPI_Get_address(&foo.l, &disp[2]));

  types[3] = MPI_DOUBLE;
  blocklen[3] = 1;
  check(MPI_Get_address(&foo.d, &disp[3]));

  types[4] = MPI_CHAR;
  blocklen[4] = sizeof(foo.ID);
  check(MPI_Get_address(&foo.ID, &disp[4]));

  types[5] = MPI_DOUBLE;
  blocklen[5] = 1;
  check(MPI_Get_address(&foo.base.volstep, &disp[5]));

  types[6] = MPI_DOUBLE;
  blocklen[6] = 1;
  check(MPI_Get_address(&foo.base.volTot, &disp[6]));

  if(my_rank == 0) {
      std::cout << "Base Address " << std::hex << base << "\n";
      std::cout << "Addresses   ";
      for(auto& x : disp) {
        std::cout << " " << std::hex << x;
      }
      std::cout << std::dec << "\n";
  }

  // Addresse zu Offset umrechnen
  for(auto& x : disp) {
    x -= base;
  }

  if(my_rank == 0) {
    std::cout << "Displacement";
    for(auto& x : disp) {
      std::cout << " " << x;
    }
    std::cout << "\n";
  }

  MPI_Datatype newMPItype;
  check(MPI_Type_create_struct(nMembervarsToSend, blocklen.data(), disp.data(), types.data(), &newMPItype));
  check(MPI_Type_commit(&newMPItype));

  return newMPItype;
}

void doRank0(MPI_Datatype newMPItype) {
    type_t sendbuffer;
    strcpy(sendbuffer.ID, "Kreis100");
    sendbuffer.i = 10;
    sendbuffer.x = 1.2f;
    sendbuffer.d = 1.23;
    sendbuffer.l = true;
    sendbuffer.base.volstep = 1.34;
    sendbuffer.base.volTot = 1.56;

    int  displacements[nThreads], counts[nThreads];

    std::vector<type_t> recvbuffer(2);
    std::cout << my_rank << " Receiving...\n";

    int root_rank = 0;
    displacements[0] = 0;
    displacements[1] = 1;
    counts[0] = 1;
    counts[1] = 1;
    // MPI_Gatherv(recvbuffer_send,count_send, datatype_send, recvbuffer_recv, counts_recv, displacements, datatype_recv, root,comm)
    check(MPI_Gatherv(&sendbuffer, 1, newMPItype, recvbuffer.data(), counts, displacements, newMPItype, root_rank, MPI_COMM_WORLD));
    std::cout << my_rank << " Done receiving\n";

    std::cout << my_rank << " content of struct:\n";
    for(const type_t& buf : recvbuffer) {
      std::cout << "ID "  << buf.ID << "\n";
      std::cout << "i "  << buf.i << "\n";
      std::cout << "x "  << buf.x << "\n";
      std::cout << "d "  << buf.d << "\n";
      std::cout << "l "  << buf.l << "\n";
      std::cout << "volstep "  << buf.base.volstep << "\n";
      std::cout << "volTot "  << buf.base.volTot << "\n\n";
    }
}

void doRank1(MPI_Datatype newMPItype) {
    type_t sendbuffer;
    int  displacements[nThreads], counts[nThreads];

    strcpy(sendbuffer.ID, "Kreis200");
    sendbuffer.i = 20;
    sendbuffer.x = 2.2;
    sendbuffer.d = 2.23;
    sendbuffer.l = true;
    sendbuffer.base.volstep = 2.34;
    sendbuffer.base.volTot = 2.56;

    std::cout << my_rank << " Sending...\n";
    // MPI_Gatherv(recvbuffer_send,count_send, datatype_send, recvbuffer_recv, counts_recv, displacements, datatype_recv, root,comm)
    int root_rank = 0;
    check(MPI_Gatherv(&sendbuffer, 1, newMPItype, NULL, counts, displacements, newMPItype, root_rank, MPI_COMM_WORLD));
    std::cout << my_rank << " Done sending\n";
}

int main(int argc, char* argv[]) {
    MPI_Init(&argc, &argv);

    // Get number of processes and check only 2 processes are used
    MPI_Comm_size(MPI_COMM_WORLD, &nThreads);
    if(nThreads != 2) {
        std::cout << "Start with 2 threads.\n";
        MPI_Abort(MPI_COMM_WORLD, EXIT_FAILURE);
    }

    MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
    MPI_Datatype newMPItype = createMPItyp();

    switch(my_rank) {
      case 0: doRank0(newMPItype); break;
      case 1: doRank1(newMPItype); break;
    };

    MPI_Finalize();

    return EXIT_SUCCESS;
}

$ mpic++ -g -ggdb -Wall test_MPI_struct.cpp

$ mpiexec -n 2 ./a.out
1 Sending...
1 Done sending
Base Address 7ffd83f7e180
Addresses 7ffd83f7e1b4 7ffd83f7e1b8 7ffd83f7e1bc 7ffd83f7e1c0 7ffd83f7e180 7ffd83f7e1c8 7ffd83f7e1d0
Displacement 52 56 60 64 0 72 80
0 Receiving...
0 Done receiving
0 content of struct:
ID Kreis100
i 10
x 1.2
d 1.23
l 1
volstep 1.34
volTot 1.56

ID Kreis200
i 20
x 2.2
d 2.23
l 1
volstep 2.34
volTot 2.56

14.05.2022

Kaffeetassenwärmer Optimierungsaufgabe

Filed under: Allgemein — Tags: , — Thomas @ 07:05

Niemand mag kalten Kaffee! Darum muss ein Kaffeetassenwärmer her! Aber das Zeug was man kaufen kann taugt alles nichts! USB2/1 liefert nicht genügend Leistung und USB3 ist eine Vergewaltigung der kleine Kabelchen. Noch dazu habe ich keinen USB3 Port am Laptop. Eine simple Heizplatte mit einem EIN/AUS Schalter langt doch vollkommen. So wie die in meiner Kaffeemaschine....

In der Bastelkiste finden sich noch ein paar Hochlast Widerstände. Und ein Spielzeug Transformator, welcher ca 16W Leistung liefert, ist auch noch vorhanden. Da müsste sich doch eine passende Kombination von Widerständen finden lassen?! Meine Versuche von Hand zeigen ein mögliches, aber nicht optimales Ergebnis: kein Widerstand wird überlastet, die Leistung in der Summe ist auch okay, aber die Widerstände werden sehr ungleich belastet. Einige bleiben fast kalt, andere werden sehr heiß.

Ein Computerprogramm soll es lösen!

Jede Kombination aus Widerstand, parallel und seriell Schaltung sollte von der Laufzeit her kein Problem sein. Aber da ich momentan keine Idee habe, wie man da alle Möglichkeiten durchgeht, will ich es erst mal als Optimierungsproblem beschreiben.

Die constraints sind, dass kein Widerstand überlastet wird. Also, dass die elektrische Leistung gebildet aus Spannung und Strom nicht über die maximale Leistung des Widerstandes liegt.
Die fitness Funktion berechnet die elektrische Leistung aller Widerstände, diese soll maximal sein.

Da die zufällig erstellte Schaltung durchaus komplex werden kann, langen die einfachen Rechenregeln für parallel und seriell Schaltung nicht. Da wird sich ein elektrisches Netzwerk bilden mit Knoten und Schleifen Regeln. Kirchhoffsche Regeln ... die werden in eine Matrix übersetzt die man dann löschen kann. Ich glaube so war das.

Mein erster Versuch sieht so aus:

--+--| 47 |--+----+--| 68 |------------+---
  +--| 47 |--+    |                    |
  +--| 47 |--+    +--| 33 |--+--| 1 |--+
  +--| 47 |--+    +--| 12 |--+

Eine parallel Schaltung aus vier 47 Ohm Widerständen. Das ergibt 47/4=11.75 Ohm. Plus eine parallel Schaltung von 68 Ohm mit einer Kombination einer parallel Schaltung von 33 mit 12 Ohm in Reihe zu 1 Ohm. Das ergibt in der Summe (gemessen) 20.9 Ohm. Bei einer Spannung von 16V ergibt das ein Strom von 16/20.9=0.77A und einer Leistung von 16*0.77=12.3W Ob das langt, den Kaffee warm zu halten, muss noch ermittelt werden ;)

Hier ein Bild des fliegenden Aufbaus:
kaffeetasse1

16.02.2022

C++ Guns: Play with std::tuple and std::apply

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

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

#include <tuple>
#include <string_view>
#include <iostream>

void print(int i, std::string_view str, double x) {
    std::cout << i << " " << str << " " << x << "\n";
}



int main() {
    std::tuple tp {1, "abc", 3.1};

    // Ein Tuple in seine Einzelteile zerlegten durch eine Funktion die genau
    // die Tuple Elemente als Parameter hat
    std::apply(print, tp);


    // Ein Tuple in sein erstes Element und den Rest zerlergen.
    // Der Rest ist ne variadic argument list
    // Die Funktion print2 ist absichtlich ein Lambda, 
    // da das sonst mit dem auto Argument Typ nicht funktioniert. 
    // Will man print2 als freie Funktion haben, muss man sie als template schreiben.
    auto print2 = [](const int i, const auto&... restArgs) {
        std::cout << i << " Anzahl Restargumente: " << sizeof...(restArgs) << "\n";
    };
    std::apply(print2, tp);
}

1 abc 3.1
1 Anzahl Restargumente: 2

15.02.2022

LED Übersicht Lichtstärke mcd Abstrahlwinkel grad

Filed under: Allgemein — Thomas @ 23:02

LEDs

02.10.2021

CppCon 2021

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

Differentiable Programming in C++

GraphBLAS: Building a C++ Matrix API for Graph Algorithms

Misra Parallelism Safety-critical Guidelines for C++11, 17, Then C++20, 23

Faster, Easier, Simpler Vectors

Making Out the Most of Your Compiler

SIMD in C++20: EVE of a new Era
POINTER ALARM

Testing Compile-time Constructs Within a Runtime Unit Testing Framework

Back To Basics: Undefined Behavior

A Crash Course in Calendars, Dates, Time, and Time Zones

Design Idioms from an Alternate Universe

The Basics of Profiling

Generic Graph Libraries in C++20

3D Graphics for Dummies

Cool New Stuff in Gdb 9 and Gdb 10

Branchless Computing: Why Conditions Are Bad for Your Code, and What Can You Do About It

Asserting Your Way to Faster Programs

Correctly Calculating min, max, and More: What Can Go Wrong?

C++23 Standard Library Preview

18.09.2021

Vektorisieren leicht gemacht

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

Als Beispiel sollen ein paar reduzierte Zeilen Code aus der Datei computeFluxes.h [1] aus dem Vplna-OP2 [2] Projekt dienen, welche auf unterschiedliche Arten vektorisiert werden sollen. Um so nah wie möglich am original Code zu bleiben, werden die Pointer Argumente der Funktionen, welche Arrays darstellen, wo es möglich ist nicht durch einen passerenden Typen ersetzt. Zu erst werden die GCC Vector Extensions benutzt, danach der Vektorisierer von GCC und zum Schluss OpenMP mit der SIMD Direktiven. Mit OpenMP ist es nicht nur möglich mehrere Tasks auf viele CPU Kerne laufen zu lassen, es lassen sich auch einzelne Schleifen vektorisieren. Untersucht wird der erzeugte ASM Code mit godbolt [3]

Compiliert wird mit GCC 11.2 -O2. Die stärke Optimierung -O3 schaltet zwar den Vektorisierer ein, aber erzeugt auch ASM Code der nicht mehr so leicht nachzuvollziehen ist. Der Vektorisierer kann manuell mit -ftree-vectorize [4] aktiviert werden. Daher wird der Quelltest mit dem Optionen -O2 -ftree-vectorize untersucht.

Zusammenfassung

Die GCC Vector Extensions bieten für dieses Beispiel die beste Art zu Vektorisieren, da der C++ Code am besten verständlich ist und ein hohen Grad an Vektorisierung ermöglicht. Es müssen keine Schleifen von Hand eingebaut oder mit OpenMP spezialisiert werden. Es ist Möglich den Code "einfach so hin zu schreiben". Es ist noch zu erwähnen, dass die "GCC Vector Extensions" Variante nur vier Zeilen C++ Code benötigt!

Der direkte Vergleich der Beispiele zeigt es deutlich. Verglichen wird die Anzahl relevanter C++ Code Zeilen (LOC), die totale Anzahl ASM Instruktionen, Anzahl SIMD Instruktionen die Vektorisiert werden könnten, Anzahl SIMD Instruktionen die vectorisiert wurden in Prozent. Sowie die Anzahl Sprung Anweisungen (Schleifen). Da bei jedem Sprung die Branch Prediction falsch liegen kann sind Sprünge als Performance Kritisch anzusehen.

Variante LOC Anzahl Instruktionen Anzahl SIMD Instruktionen Vektorisiert % Sprung Anweisung
GCC Vector Extensions 4 28 25 84.0 Nein
-O2 -ftree-vectorize Original Code 12 35 34 0.0 Nein
-O2 -ftree-vectorize Händisch Schleifen eingefügt 13 30 27 85.2 Nein
OpenMP 15 37 26 11.5 Ja

Es kann niemals eine 100%ige Vektorisierung stattfinden, da es wie indiesem Beispiel immer Stellen gibt, bei denen einzelne Skalare in unterschiedlichen Register stehen zusammen gezählt werden müssen. Der Unterschied zwischen 84% und 85.2% ist nicht relevant, da dieses Beispiel nur sehr wenige Instruktionen hat. Ein Prozent würde weniger als eine Instruktion entsprechen.

GCC Vector Extensions

GCC bietet mit Vector Extension die Möglichkeit SIMD Register direkt zu nutzen. Einfache Operationen wie Addition, Multiplikation sind darauf definiert. Damit lässt sich eine einfache Vector Klasse erstellen die es ermöglicht, lesbaren Code zu schreiben. Zusammen mit -ftree-vectorize ergibt sich die bestmögliche Abdeckung an Vektorizationen.

Das Scalarprodukt ist in Zeilen 5-16 makiert.

GCC -O2 -ftree-vectorize

float computeFluxes(const Vec4D& cellLeft, const float  *alphaleft,
                    const Vec2D& leftcellCenters, const Vec2D& edgeCenters,
                    const std::array<Vec2D,4>& leftGradient
) {
    Vec4D leftCellValues = cellLeft;
    
    Vec2D dxyl = edgeCenters - leftcellCenters;

    leftCellValues += alphaleft[0] * dot_product(dxyl, leftGradient);
 
    return leftCellValues[0] + leftCellValues[1] + leftCellValues[2] + leftCellValues[3]; 
}


computeFluxes(Vec4D const&, float const*, Vec2D const&, Vec2D const&, std::array<Vec2D, 4ul> const&):
        movq    (%rdx), %xmm1
        movq    (%rcx), %xmm0
        subps   %xmm1, %xmm0
        movups  (%r8), %xmm2
        movups  16(%r8), %xmm4
        movaps  %xmm2, %xmm1
        shufps  $221, %xmm4, %xmm2
        shufps  $136, %xmm4, %xmm1
        movaps  %xmm0, %xmm3
        shufps  $0xe5, %xmm0, %xmm0
        shufps  $0, %xmm0, %xmm0
        shufps  $0, %xmm3, %xmm3
        mulps   %xmm0, %xmm2
        mulps   %xmm3, %xmm1
        addps   %xmm2, %xmm1
        movss   (%rsi), %xmm0
        shufps  $0, %xmm0, %xmm0
        mulps   %xmm0, %xmm1
        addps   (%rdi), %xmm1
        movaps  %xmm1, %xmm0
        movaps  %xmm1, %xmm2
        shufps  $85, %xmm1, %xmm0
        addss   %xmm1, %xmm0
        unpckhps        %xmm1, %xmm2
        shufps  $255, %xmm1, %xmm1
        addss   %xmm2, %xmm0
        addss   %xmm1, %xmm0
        ret

Vektorisieren mit -O2 -ftree-vectorize Original Code

Das erste Listening zeigt auf der linken Seite die zu untersuchenden Codezeilen. Auf der rechten Seite die erzeugte ASM Ausabe. Die verwendeten ASM Befehle movss, addss, subss, mulss bedeuten: Kopieren, Addieren, Subtrahieren, Multiplizieren. Jeweils mit dem Kürzel "ss" welches man sich als "scalar single (precision)" merken kann. Die vektorisierte Variante wäre dementsprechend "ps" für "packed single (precision)".

Es fällt auf, dass keine "ps" Befehle vorkommen. Auch mit eingeschalteten Vektorisierer mittels -O3 oder -ftree-vectorize werden keine "ps" ASM Befehle erzeugt, da schlicht keine Schleifen im Code vorhanden sind, die vektorisiert werden können.

Die beispielhaft markierten Zeilen 11 und 12 im C++ Code entsprechend den ASM Befehlen 2-5.

GCC -O2

float computeFluxes(const float *cellLeft, const float *alphaleft,
                    const float *leftcellCenters, const float *edgeCenters,
                    const float *leftGradient
) {
    float leftCellValues[4];
    leftCellValues[0] = cellLeft[0];
    leftCellValues[1] = cellLeft[1];
    leftCellValues[2] = cellLeft[2];
    leftCellValues[3] = cellLeft[3];

    float dxl = (edgeCenters[0] - leftcellCenters[0]);
    float dyl = (edgeCenters[1] - leftcellCenters[1]);

    leftCellValues[0] += alphaleft[0] * ((dxl * leftGradient[0])+(dyl * leftGradient[1]));
    leftCellValues[1] += alphaleft[0] * ((dxl * leftGradient[2])+(dyl * leftGradient[3]));
    leftCellValues[2] += alphaleft[0] * ((dxl * leftGradient[4])+(dyl * leftGradient[5]));
    leftCellValues[3] += alphaleft[0] * ((dxl * leftGradient[6])+(dyl * leftGradient[7]));

    return leftCellValues[0] + leftCellValues[1] + leftCellValues[2] + leftCellValues[3]; 
}
computeFluxes(float const*, float const*, float const*, float const*, float const*):
        movss   (%rcx), %xmm1
        movss   4(%rcx), %xmm2
        subss   (%rdx), %xmm1
        subss   4(%rdx), %xmm2
        movss   (%r8), %xmm0
        movss   4(%r8), %xmm3
        movss   12(%r8), %xmm5
        movss   (%rsi), %xmm4
        mulss   %xmm2, %xmm3
        mulss   %xmm1, %xmm0
        mulss   %xmm2, %xmm5
        addss   %xmm3, %xmm0
        movss   8(%r8), %xmm3
        mulss   %xmm1, %xmm3
        mulss   %xmm4, %xmm0
        addss   (%rdi), %xmm0
        addss   %xmm5, %xmm3
        movss   20(%r8), %xmm5
        mulss   %xmm2, %xmm5
        mulss   %xmm4, %xmm3
        addss   4(%rdi), %xmm3
        mulss   28(%r8), %xmm2
        addss   %xmm3, %xmm0
        movss   16(%r8), %xmm3
        mulss   %xmm1, %xmm3
        mulss   24(%r8), %xmm1
        addss   %xmm5, %xmm3
        addss   %xmm2, %xmm1
        mulss   %xmm4, %xmm3
        addss   8(%rdi), %xmm3
        mulss   %xmm4, %xmm1
        addss   12(%rdi), %xmm1
        addss   %xmm3, %xmm0
        addss   %xmm1, %xmm0
        ret

-O2 -ftree-vectorize Händisch Schleifen eingefügt

Die markierten Zeilen im ersten Listening 1 werden durch eine Schleife ersetzt und mittels -ftree-vectorize vektorisiert. Dabei fiel auf, dass der Vektorisierer erst ab einer Schleifenlänge von 3 aktiv wird. Ob dies eine Compiler Einstellung oder eine allgemeine Beschränkung von SIMD ist, ist nicht bekannt.
Die nun markierten Zeilen 2-4, insbesondere Zeile 4, zeigt nun die "ps" Version der Subtraktion.

Die Zeilen 16-19 zeigen eine weitere Möglichkeit zum Vektorisieren.

GCC -O2 -ftree-vectorize

float computeFluxes(const float *cellLeft, const float *alphaleft,
                    const float *leftcellCenters, const float *edgeCenters,
                    const float *leftGradient
) {
    float leftCellValues[4];
    leftCellValues[0] = cellLeft[0];
    leftCellValues[1] = cellLeft[1];
    leftCellValues[2] = cellLeft[2];
    leftCellValues[3] = cellLeft[3];

    float dxyl[3];
    for(int i=0; i < 3; ++i) {
        dxyl[i] = (edgeCenters[i] - leftcellCenters[i]);
    }
    
    leftCellValues[0] += alphaleft[0] * ((dxyl[0] * leftGradient[0])+(dxyl[1] * leftGradient[1]));
    leftCellValues[1] += alphaleft[0] * ((dxyl[0] * leftGradient[2])+(dxyl[1] * leftGradient[3]));
    leftCellValues[2] += alphaleft[0] * ((dxyl[0] * leftGradient[4])+(dxyl[1] * leftGradient[5]));
    leftCellValues[3] += alphaleft[0] * ((dxyl[0] * leftGradient[6])+(dxyl[1] * leftGradient[7]));
 
    return leftCellValues[0] + leftCellValues[1] + leftCellValues[2] + leftCellValues[3]; 
}
computeFluxes(float const*, float const*, float const*, float const*, float const*):
        movq    (%rdx), %xmm0
        movq    (%rcx), %xmm2
        subps   %xmm0, %xmm2
        movss   4(%r8), %xmm3
        movss   12(%r8), %xmm5
        movss   (%r8), %xmm0
        movss   (%rsi), %xmm4
        movaps  %xmm2, %xmm1
        shufps  $0xe5, %xmm2, %xmm2
        mulss   %xmm1, %xmm0
        mulss   %xmm2, %xmm3
        mulss   %xmm2, %xmm5
        addss   %xmm3, %xmm0
        movss   8(%r8), %xmm3
        mulss   %xmm1, %xmm3
        mulss   %xmm4, %xmm0
        addss   (%rdi), %xmm0
        addss   %xmm5, %xmm3
        movss   20(%r8), %xmm5
        mulss   %xmm2, %xmm5
        mulss   %xmm4, %xmm3
        addss   4(%rdi), %xmm3
        mulss   28(%r8), %xmm2
        addss   %xmm3, %xmm0
        movss   16(%r8), %xmm3
        mulss   %xmm1, %xmm3
        mulss   24(%r8), %xmm1
        addss   %xmm5, %xmm3
        addss   %xmm2, %xmm1
        mulss   %xmm4, %xmm3
        addss   8(%rdi), %xmm3
        mulss   %xmm4, %xmm1
        addss   12(%rdi), %xmm1
        addss   %xmm3, %xmm0
        addss   %xmm1, %xmm0
        ret

Die Zeilen 16-19 im 2. Listening wurde durch eine Schleife ersetzt. Diese entsprechen jetzt Zeile 16-18 im 3. Listening. Die Schleife wurde vektorisiert, wie im ASM Output Zeile 5-22 zu sehen ist.
Es wurden einige shufps (shuffel packed single precision) Befehle erzeugt, um die Werte innerhalb eines SIMD Registers zu tauschen. Die erzeugte ASM Ausabe besteht nun hauptsächlich aus "ps" Befehlen und ist hiermit vektorisiert.

GCC -O2 -ftree-vectorize

float computeFluxes(const float *cellLeft, const float *alphaleft,
                    const float *leftcellCenters, const float *edgeCenters,
                    const float *leftGradient
) {
    float leftCellValues[4];
    leftCellValues[0] = cellLeft[0];
    leftCellValues[1] = cellLeft[1];
    leftCellValues[2] = cellLeft[2];
    leftCellValues[3] = cellLeft[3];

    float dxyl[3];
    for(int i=0; i < 3; ++i) {
        dxyl[i] = (edgeCenters[i] - leftcellCenters[i]);
    }

    for(int i=0; i < 4; ++i) {
        leftCellValues[i] += alphaleft[0] * ((dxyl[0] * leftGradient[i*2])+(dxyl[1] * leftGradient[i*2+1]));
    }
 
    return leftCellValues[0] + leftCellValues[1] + leftCellValues[2] + leftCellValues[3]; 
}
computeFluxes(float const*, float const*, float const*, float const*, float const*):
        movq    (%rdx), %xmm1
        movq    (%rcx), %xmm0
        subps   %xmm1, %xmm0
        movups  (%r8), %xmm2
        movups  16(%r8), %xmm4
        movaps  %xmm2, %xmm1
        shufps  $136, %xmm4, %xmm2
        shufps  $221, %xmm4, %xmm1
        movaps  %xmm0, %xmm3
        shufps  $0xe5, %xmm0, %xmm0
        shufps  $0, %xmm0, %xmm0
        mulps   %xmm0, %xmm1
        movaps  %xmm3, %xmm0
        shufps  $0, %xmm0, %xmm0
        mulps   %xmm0, %xmm2
        movss   (%rsi), %xmm0
        shufps  $0, %xmm0, %xmm0
        addps   %xmm2, %xmm1
        mulps   %xmm0, %xmm1
        movups  (%rdi), %xmm0
        addps   %xmm0, %xmm1
        movaps  %xmm1, %xmm0
        movaps  %xmm1, %xmm2
        shufps  $85, %xmm1, %xmm0
        addss   %xmm1, %xmm0
        unpckhps        %xmm1, %xmm2
        shufps  $255, %xmm1, %xmm1
        addss   %xmm2, %xmm0
        addss   %xmm1, %xmm0
        ret

Vektorisieren mit OpenMP

OpenMP besitzt seit Version 4.0 die SIMD Direktive welche ebenfalls "ps" ASM Befehle erzeugen kann. Listenin 4 zeigt die OpenMP Version mit entsprechenden ASM Output.

Die Vektorisierung der ersten Schleife funktioniert nun auch mit einer Länge von 2 statt 3, wie in Zeile 10 des ASM Output zu sehen ist.
Die Vektorisierung der zweiten Schleife schlägt allerdings fehl. Zu erkennen an den "ss" Instruktionen und dem Label .L2 welcher der Rücksprungort der for() Schleife ist.

GCC -O2 -fopenmp

float computeFluxes(const float *cellLeft, const float *alphaleft,
                    const float *leftcellCenters, const float *edgeCenters,
                    const float *leftGradient
) {
    float leftCellValues[4];
    leftCellValues[0] = cellLeft[0];
    leftCellValues[1] = cellLeft[1];
    leftCellValues[2] = cellLeft[2];
    leftCellValues[3] = cellLeft[3];

    float dxyl[2];
#pragma omp simd    
    for(int i=0; i < 2; ++i) {
        dxyl[i] = (edgeCenters[i] - leftcellCenters[i]);
    }

#pragma omp simd        
    for(int i=0; i < 4; ++i) {
        leftCellValues[i] += alphaleft[0] * ((dxyl[0] * leftGradient[i*2])+(dxyl[1] * leftGradient[i*2+1]));
    }
    
    return leftCellValues[0] + leftCellValues[1] + leftCellValues[2] + leftCellValues[3]; 
}
computeFluxes(float const*, float const*, float const*, float const*, float const*):
        movss   4(%rdi), %xmm0
        movq    (%rcx), %xmm1
        movq    %rdi, %rax
        movss   (%r8), %xmm2
        movss   (%rsi), %xmm3
        movss   %xmm0, -20(%rsp)
        movq    (%rdx), %xmm0
        movq    8(%rdi), %rdi
        subps   %xmm0, %xmm1
        movss   4(%r8), %xmm0
        movq    %rdi, -16(%rsp)
        movaps  %xmm1, %xmm4
        shufps  $0xe5, %xmm1, %xmm1
        mulss   %xmm4, %xmm2
        mulss   %xmm1, %xmm0
        addss   %xmm2, %xmm0
        mulss   %xmm3, %xmm0
        addss   (%rax), %xmm0
        movl    $1, %eax
        movss   %xmm0, -24(%rsp)
.L2:
        movss   (%r8,%rax,8), %xmm0
        movss   4(%r8,%rax,8), %xmm2
        mulss   %xmm4, %xmm0
        mulss   %xmm1, %xmm2
        addss   %xmm2, %xmm0
        mulss   %xmm3, %xmm0
        addss   -24(%rsp,%rax,4), %xmm0
        movss   %xmm0, -24(%rsp,%rax,4)
        addq    $1, %rax
        cmpq    $4, %rax
        jne     .L2
        movss   -24(%rsp), %xmm0
        addss   -20(%rsp), %xmm0
        addss   -16(%rsp), %xmm0
        addss   -12(%rsp), %xmm0
        ret

Anhang

Die für Vector Klasse für die GCC Vector Extensions Variante.

#include <array>

struct Vec2D {
    typedef float vec_type __attribute__ ((vector_size (2*sizeof(float))));
    vec_type _data;

    const float& operator[](int i) const {
        return _data[i];
    }   
};

inline Vec2D operator-(const Vec2D& lhs, const Vec2D& rhs) {
    return Vec2D{lhs._data - rhs._data};
}

struct Vec4D {
    typedef float vec_type __attribute__ ((vector_size (4*sizeof(float))));
    vec_type _data;

    float& operator[](int i) {
        return _data[i];
    }
};

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

inline Vec4D operator*(const float lhs, const Vec4D& rhs) {
    return Vec4D{lhs * rhs._data};
}

inline Vec4D dot_product(const Vec2D& v1, const std::array<Vec2D,4>& v2) {
    Vec4D result;
    for(int i=0; i < 4; ++i) {
        result[i] = ((v1[0] * v2[i][0]) + (v1[1] * v2[i][1]));
    }
    return result;
}

[1] https://github.com/reguly/volna/blob/master/sp/computeFluxes.h
[2] https://gmd.copernicus.org/articles/11/4621/2018/
[3] https://godbolt.org/
[4] https://gcc.gnu.org/onlinedocs/gcc-11.2.0/gcc/Optimize-Options.html#index-ftree-vectorize
[5] https://gcc.gnu.org/onlinedocs/gcc/Vector-Extensions.html

18.06.2021

C++ Guns: template Spezialisierung mit concepts

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

Die requires Klausel ist echt toll!

#include <array>
#include <iostream>
 
template<typename ...Ts> 
requires (sizeof...(Ts) > 0)
void f(Ts...) {
    std::cout << "not empty\n";
}

template<typename ...Ts> 
requires (sizeof...(Ts) == 0)
void f(Ts...) {
    std::cout << "empty\n";
}

 
int main() {
    f('a', 1);  // Ts... expands to void f(char, int)
    f(0.1);     // Ts... expands to void f(double)
    f();     // Ts... expands to void f()
}

28.04.2021

C++ Guns: mit concepts zur Compilezeit testen, ob ein enum ein bestimmten enumerator hat

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

Ja concepts sind schon toll ;)

Grüße an euIRCnet #c++

enum class A {aa, bb};
enum class B {bb};
enum class C {aa, bb};


template<typename T>
struct Isso {
    const static bool value = false;
};


template<typename T>
requires T::aa == T::aa
struct Isso<T> {
    const static bool value = true;
};


static_assert(Isso<A>::value);
static_assert(Isso<B>::value == false);
static_assert(Isso<C>::value);
« Newer PostsOlder Posts »

Powered by WordPress