Künstliche Intelligenz

Datenparallele Typen in C++26: SIMD-Algorithmen


Die SIMD-Bibliothek bietet in C++26 portable Typen zur expliziten Angabe von Datenparallelität und zur Strukturierung von Daten für einen effizienteren SIMD-Zugriff.




Rainer Grimm ist seit vielen Jahren als Softwarearchitekt, Team- und Schulungsleiter tätig. Er schreibt gerne Artikel zu den Programmiersprachen C++, Python und Haskell, spricht aber auch gerne und häufig auf Fachkonferenzen. Auf seinem Blog Modernes C++ beschäftigt er sich intensiv mit seiner Leidenschaft C++.

Die Kurzserie zu datenparallelen Typen hat bisher folgende Themen behandelt:

Mit dem aktuellen Beitrag schließe ich die Serie ab. Diesmal geht es um vier spezielle Algorithmen für SIMD-Vektoren: min, max, minmax und clamp.

Den zwei Algorithmen min und max ist gemein, dass sie jeweils zwei SIMD-Vektoren annehmen und einen SIMD-Vektor zurückgeben. Dieser enthält die elementweisen Minimum oder Maximum der Eingabevektoren. Der minmax-Algorithmus nimmt ebenfalls zwei SIMD-Vektoren an und gibt ein Paar von SIMD-Vektoren zurück. Der erste Vektor des Paares enthält die elementweisen Minimum, der zweite die elementweisen Maximum der Eingabevektoren.

Das folgende Beispiel zeigt die drei Algorithmen in Aktion:


// minmax.cpp

#include 
#include 
#include 

namespace stdx = std::experimental;
 
void println(auto rem, auto const v) {
    std::cout << rem << ": ";
    for (std::size_t i = 0; i != v.size(); ++i)
        std::cout << std::setw(2) << v[i] << ' ';
    std::cout << '\n';
}

void printPairs(auto rem, auto const v1) {
    std::cout << rem << ": ";
    for (std::size_t i = 0; i != v1.first.size(); ++i)
        std::cout << '(' << v1.first[i] << ", " << v1.second[i] << ')' << ' ';
    std::cout << '\n';
}

int main() {

    stdx::fixed_size_simd a{[](int i) {
        static constexpr auto c = {10, 9, 8, 7, 6, 5, 4, 3};
        return c.begin()[i];
    }};
    println("a", a);
    
    stdx::fixed_size_simd b{[](int i) {
        static constexpr auto c = {3, 4, 5, 6, 7, 8, 9, 10,};
        return c.begin()[i];
    }};
    println("b", b);

    std::cout << '\n';

   auto minimum = stdx::min(a, b);
   println("minimum", minimum);

   auto maximum = stdx::max(a, b);
   println("maximum", maximum);

   /*
   auto minmax = stdx::minmax(a, b);
   printPairs("minmax", minmax);
   */

}


Als Eingabevektoren verwende ich die SIMD-Vektoren a und b. Diese werden auf eine besondere Art initialisiert. Dazu lege ich eine Initialisierungsliste c in der Lambda-Funktion an, die einen Iterator auf sie zurückgibt.



Der Screenshot zeigt die Ausgabe des Programms mit min und max.

Die Anwendung des Algorithmus minmax habe ich auskommentiert, weil ich die Zeile stdx::minmax(a, b) weder mit dem GCC noch mit dem clang -Compiler übersetzen konnte.

std::datapar::clamp wendet elementweise die Funktion std::clamp auf den SIMD-Vektor an. Dabei wird jedes Element in einen minimalen und maximalen Grenzwert eingesperrt.

Das folgende Programm basiert auf einem Beispiel aus cppreference:


// clamp.cpp

#include 
#include 
#include 
#include 
#include 

namespace stdx = std::experimental;
 
void println(auto rem, auto const v) {
    std::cout << rem << ": ";
    for (std::size_t i = 0; i != v.size(); ++i)
        std::cout << std::setw(4) << v[i] << ' ';
    std::cout << '\n';
}
 
int main() {

    std::cout << "INT8_MIN: " << INT8_MIN << '\n';
    std::cout << "INT8_MAX: " << INT8_MAX << '\n';
    std::cout << "UINT8_MAX: " << UINT8_MAX << '\n';

    std::cout << '\n';

    stdx::fixed_size_simd a{[](int i) {
        static constexpr auto c = {-129, -128, -1, 0, 42, 127, 128, 255};
        return c.begin()[i];
    }};
    println("a", a);
 
    stdx::fixed_size_simd lo1{INT8_MIN};
    stdx::fixed_size_simd hi1{INT8_MAX};
    const auto b = stdx::clamp(a, lo1, hi1);
    println("b", b);
 
    stdx::fixed_size_simd lo2{0};
    stdx::fixed_size_simd hi2{UINT8_MAX};
    const auto c = stdx::clamp(a, lo2, hi2);
    println("c", c);

}




Der Screenshot zeigt die Ausgabe des Programms mit clamp

Schön ist in der Ausgabe des SIMD-Vektors b zu sehen, wie die Werte des SIMD-Vektors a in die Grenzwerte INT8_MIN und INT8_MAX eingepasst werden. Bei dem SIMD-Vektor c kommen hingegen die Grenzwerte 0 und UINT8_MAX zum Einsatz.

Nun ist es Zeit für meinen zweiten Durchlauf durch den neuen C++26-Standard. Dabei werde ich mich in erster Linie auf die Funktionen konzentrieren, die ich im ersten Durchgang nicht im Detail behandelt habe.

Beginnen werde ich mit Contracts.


(rme)



Source link

Beliebt

Die mobile Version verlassen