Datenparallele Typen in C++26: SIMD-Algorithmen
Die Bibliothek fĂĽr data-parallel types bringt vier spezielle Algorithmen fĂĽr SIMD Vektoren: min, max, minmax und clamp.
(Bild: SerbioVas/Shutterstock)
- Rainer Grimm
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.
Die Kurzserie zu datenparallelen Typen hat bisher folgende Themen behandelt:
Videos by heise
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.
Minimum und Maximum
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 <experimental/simd>
#include <iomanip>
#include <iostream>
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<int, 8> 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<int, 8> 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.
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.
Einpassen in Grenzwerte mit clamp
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 <cstddef>
#include <cstdint>
#include <experimental/simd>
#include <iomanip>
#include <iostream>
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<int, 8> 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<int, 8> lo1{INT8_MIN};
stdx::fixed_size_simd<int, 8> hi1{INT8_MAX};
const auto b = stdx::clamp(a, lo1, hi1);
println("b", b);
stdx::fixed_size_simd<int, 8> lo2{0};
stdx::fixed_size_simd<int, 8> hi2{UINT8_MAX};
const auto c = stdx::clamp(a, lo2, hi2);
println("c", c);
}
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.
Wie geht’s weiter?
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)