Entwicklung & Code
Datenparallele Typen in C++26: Bedingte Ausführung von Operationen
Leider habe ich in meinem letzten Beitrag „Datenparallele Typen in C++26: ein Beispiel aus der Praxis“ vergessen, eine Funktion der neuen Bibliothek vorzustellen. Das hole ich in diesem Artikel nach.
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++.
Where-Ausdruck
Das neue Schlüsselwort where
erzeugt einen sogenannten Where-Ausdruck. Damit lassen sich die Elemente eines SIMD-Vektors bedingt ansprechen.
Folgendes Beispiel bringt dieses Verhalten auf den Punkt:
// where.cpp
#include
#include
#include
namespace stdx = std::experimental;
void println(std::string_view name, auto const& a)
{
std::cout << name << ": ";
for (std::size_t i{}; i != std::size(a); ++i)
std::cout << a[i] << ' ';
std::cout << '\n';
}
template
stdx::simd my_abs(stdx::simd x)
{
where(x < 0, x) = -x; // Set elements where x is negative to their absolute value
return x;
}
int main()
{
const stdx::native_simd a = 1;
println("a", a);
const stdx::native_simd b([](int i) { return i - 2; });
println("b", b);
const auto c = a + b;
println("c", c);
const auto d = my_abs(c);
println("d", d);
}
In der Funktion my_abs
kommt die where
-Funktion zum Einsatz: where(x < 0, x) = -x;
bewirkt, dass alle Elemente des SIMD-Vektors, die kleiner als Null sind, auf ihren absoluten Wert gesetzt werden.
Der Screenshot zeigt die Ausgabe des Beispielcodes.
In diesem Fall kommen SSE2-Befehle zum Einsatz. Der SIMD-Vektor ist 128 Bit groß.
Die where
-Expression kann mit einem bool
-Ausdruck oder einer simd_mask
parametrisiert werden.
Obiges Codebeispiel lässt sich auch mit einer simd_mask
implementieren. Folgender Code zeigt die Umsetzung:
// whereMask.cpp
#include
#include
#include
namespace stdx = std::experimental;
void println(std::string_view name, auto const& a)
{
std::cout << std::boolalpha << name << ": ";
for (std::size_t i{}; i != std::size(a); ++i)
std::cout << a[i] << ' ';
std::cout << '\n';
}
int main()
{
const stdx::native_simd a = 1;
println("a", a);
const stdx::native_simd b([](int i) { return i - 2; });
println("b", b);
const auto c = a + b;
println("c", c);
const stdx::native_simd_mask x = c < 0;
println("x", x);
auto d = c;
where(x, d) *= -1;
println("d", d);
}
Beginnen möchte ich meine Erläuterung mit den letzten fünf Zeilen der Main-Funktion. Zuerst erzeuge ich die simd_mask
x, indem das Prädikat c < 0
auf jedes Element des SIMD-Vektors c angewendet wird.
Die Maske x hat die gleiche Länge wie der SIMD- Vektor, besitzt aber nur Wahrheitswerte. Damit diese Wahrheitswerte als true
oder false
und nicht als 1 oder 0 dargestellt werden, habe ich der Funktion println
den Streammanipulator std::boolalpha
hinzugefügt.
Zusätzlich muss ich den SIMD-Vektor d mit c initialisieren, da c konstant ist. Nun lässt sich die Expression where(x, d) *= -1;
auf d anwenden. Dabei wird jedes Element des SIMD-Vektors negiert, wenn die Maske den Wert true
besitzt.
Der Screenshot zeigt die Ausgabe des Codes mit simd_mask.
Der Datentyp simd_mask
ist dem Datentyp simd
sehr ähnlich. Der wesentliche Unterschied besteht darin, dass simd
alle Standard-Ganzzahltypen, Zeichentypen und die Typen float und double annehmen kann. Im Gegensatz dazu unterstützt simd_mask
nur Wahrheitswerte.
Die Definition von simd_mask
sieht folgendermaßen aus:
template
class basic_simd_mask
Der Abi-Tag bestimmt die Anzahl der Elemente und deren Speicherplatz. Zur Vollständigkeit sind hier noch einmal die ABI-Tags:
scalar
: Speichern eines einzelnen Elementsfixed_size
: Speichern einer bestimmten Anzahl von Elementencompatible
: gewährleistet ABI-Kompatibilitätnative
: am effizientestenmax_fixed_size
: maximale Anzahl von Elementen, die von fixed_size garantiert unterstützt werden
Entsprechend zu simd
besitzt simd_mask
auch zwei Aliase:
template< size_t Bytes, int N >
using fixed_size_simd_mask = simd_mask>
template< size_t Bytes >
using native_simd_mask = simd_mask>
Wie geht‘s weiter?
In meinem vorerst letzten Artikel über data-parallel types möchte ich auf die besonderen Funktionen dafür eingehen.
(rme)