Datenparallele Typen in C++26: Bedingte Ausführung von Operationen
Dank std::simd_mask ist es in C++26 möglich, Operationen auf data-parallel types bedingt auszuführen.
(Bild: SerbioVas/Shutterstock)
- Rainer Grimm
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.
Videos by heise
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 <experimental/simd>
#include <iostream>
#include <string_view>
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<class A>
stdx::simd<int, A> my_abs(stdx::simd<int, A> x)
{
where(x < 0, x) = -x; // Set elements where x is negative to their absolute value
return x;
}
int main()
{
const stdx::native_simd<int> a = 1;
println("a", a);
const stdx::native_simd<int> 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.
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 <experimental/simd>
#include <iostream>
#include <string_view>
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<int> a = 1;
println("a", a);
const stdx::native_simd<int> 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 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<size_t Bytes, class Abi>
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<Bytes, simd_abi::fixed_size<N>>
template< size_t Bytes >
using native_simd_mask = simd_mask<Bytes, simd_abi::native<Bytes>>
Wie geht‘s weiter?
In meinem vorerst letzten Artikel über data-parallel types möchte ich auf die besonderen Funktionen dafür eingehen.
(rme)