
Einführung
Emu ist eine Programmiersprache für Grafikkarten auf hoher Ebene, die in regulären Code in der Programmiersprache des Rust- Systems eingebettet werden kann.
Dieser Artikel konzentriert sich auf die Syntax von Emu und seine Funktionen und zeigt einige anschauliche Beispiele für die Verwendung in echtem Code.
Installation
- Die gesuchte Bibliothek benötigt eine externe OpenCL-Abhängigkeit. Sie müssen den für Ihre Hardware geeigneten Treiber installieren.
Cargo.toml
Text. Dadurch werden die neuesten verfügbaren Versionen heruntergeladen (wenn Sie eine bestimmte Baugruppe benötigen, geben Sie anstelle von *
gewünschte Version ein):
[dependencies] em = "*" // Emu ocl = "*" // OpenCL
Syntax
Die Emu-Syntax ist recht einfach, da diese Sprache nur zum Schreiben von Kernelfunktionen vorgesehen ist, die in OpenCL kompiliert wurden.
Datentypen
Die Emu-Sprache verfügt über neun Datentypen, die denen in Rust ähnlich sind. Das Folgende ist eine Tabelle dieser Datentypen:
Variablen
Variablen werden mit dem Schlüsselwort let
deklariert, das sich hinter dem Bezeichner, dem Doppelpunkt, dem Datentyp, dem Gleichheitszeichen, dem zugewiesenen Wert und dem Semikolon befindet.
let age: i32 = 54; let growth: f32 = 179.432; let married: bool = true;
Konvertierungen
Die Konvertierung primitiver Datentypen erfolgt mit dem Binäroperator as
nach dem Zieltyp. Ich stelle fest, dass der Zieltyp auch eine Maßeinheit sein kann (siehe nächster Abschnitt):
let width: i16 = 324; let converted_width: i64 = width as i64;
Einheiten
Mit der Emu-Sprache können Sie Zahlen als Maßeinheiten behandeln, um wissenschaftliche Berechnungen zu vereinfachen. In diesem Beispiel wird die variable length
zunächst in Metern definiert, dann werden jedoch andere Maßeinheiten hinzugefügt:
let length: f32 = 3455.345;
Vordefinierte Konstanten
Emu verfügt über eine Reihe vordefinierter Konstanten, die in der Praxis bequem zu verwenden sind. Unten ist die entsprechende Tabelle.
Auch Konstanten, die wissenschaftlichen Daten entsprechen, werden definiert. Die aus diesen Konstanten bestehende Tabelle finden Sie hier .
Bedingte Anweisungen
Emu-bedingte Anweisungen ähneln den entsprechenden Anweisungen in Rust. Der folgende Code verwendet bedingte Konstrukte:
let number: i32 = 2634; let satisfied: bool = false; if (number > 0) && (number % 2 == 0) { satisfied = true; }
Für Schleifen
Der Header der For-Schleife ist wie for NUM in START..END
, wobei NUM
eine Variable ist, die Werte aus dem Bereich [START; END)
[START; END)
durch Einheit.
let sum: u64 = 0; for i in 0..215 { sum += i; }
While-Schleifen
Der Titel der While-Schleife ist definiert als while (CONDITION)
, wobei CONDITION
die Bedingung ist, damit die Schleife zur nächsten Iteration CONDITION
. Dieser Code ähnelt dem vorherigen Beispiel:
let sum: u64 = 0; let idx: i32 = 0; while (idx < 215) { sum += idx; idx += 1; }
Endlose Schleifen
Endlosschleifen haben keine explizite Beendigungsbedingung und werden durch das Schlüsselwort loop
definiert. Sie können jedoch durch die Anweisungen break
und continue
fortgesetzt oder unterbrochen werden (wie die beiden anderen Schleifentypen).
let collapsed: u64 = 1; let idx: i32 = 0; loop { if idx % 2 == 0 { continue; } sum *= idx; if idx == 12 { break; } }
Rückkehr von der Funktion
Wie in allen anderen Programmiersprachen ist die return
die Ausgabe der aktuellen Funktion. Es kann auch einen bestimmten Wert zurückgeben, wenn die Funktionssignatur (siehe folgende Abschnitte) dies zulässt.
let result: i32 = 23446; return result;
Andere Betreiber
- Verfügbare Zuweisungsoperatoren:
=
, +=
, -=
, *=
, /=
, %=
, &=
, ^=
, <<=
, >>=
; - Der Indexoperator ist
[IDX]
; (ARGS)
- (ARGS)
;- Unäre Operatoren:
*
zum Dereferenzieren! Boolesche Daten invertieren, -
Zahlen negieren; - Binäroperatoren:
+
, -
, *
, /
, %
, &&
, ||
, &
, |
, ^
, >>
, <<
, >
, <
, >=
, <=
, ==
!=
.
Funktionen
Emu besteht aus drei Teilen von Funktionen: dem Bezeichner, den Parametern und dem Hauptteil der Funktion, die aus einer Folge ausführbarer Anweisungen bestehen. Betrachten Sie die Funktion des Hinzufügens von zwei Zahlen:
add(left f32, right f32) f32 { return left + right; }
Wie Sie vielleicht bemerkt haben, gibt diese Funktion die Summe von zwei Argumenten zurück, die mit dem Datentyp f32
an sie f32
.
Adressräume
Jeder Parameter der Funktion entspricht einem bestimmten Adressraum . Standardmäßig entsprechen alle Parameter dem Bereich __private__
.
Das Hinzufügen der Präfixe global_
und local_
zur Parameterkennung gibt explizit den Adressraum an.
In der Dokumentation wird global_
für alle Vektoren das Präfix global_
zu verwenden und nichts anderes vorangestellt zu haben.
Eingebaute Funktionen
Emu bietet eine kleine Reihe integrierter Funktionen (aus OpenCL), mit denen Sie GPU-Daten verwalten können:
get_work_dim()
- Gibt die Anzahl der Dimensionen zurück.get_global_size()
- Gibt die Anzahl der globalen Elemente für eine bestimmte Dimension zurück.get_global_id()
- Gibt den eindeutigen Bezeichner des Elements für die angegebene Dimension zurück.get_global_size()
- Gibt die Anzahl der globalen Elemente für eine bestimmte Dimension zurück.get_local_id()
- Gibt einen eindeutigen Bezeichner für ein lokales Element innerhalb einer bestimmten Arbeitsgruppe für eine bestimmte Dimension zurück.get_num_groups()
- Gibt die Anzahl der Arbeitsgruppen für eine bestimmte Dimension zurück.get_group_id()
- Gibt eine eindeutige Kennung für die Arbeitsgruppe zurück.
Im Anwendungscode finden Sie meistens den Ausdruck get_global_id(0)
, der den aktuellen Index des get_global_id(0)
zurückgibt, das dem Aufruf Ihrer Kernelfunktion zugeordnet ist.
Codeausführung
Berücksichtigen Sie die Syntax zum Aufrufen von Emu-Funktionen aus regulärem Rust-Code. Als Beispiel verwenden wir eine Funktion, die alle Elemente eines Vektors mit einer bestimmten Zahl multipliziert:
use em::emu; emu! { multiply(global_vector [f32], scalar f32) { global_vector[get_global_id(0)] *= scalar; } }
Um diese Funktion in OpenCL-Code zu übersetzen, müssen Sie ihre Signatur in das build!
Makro einfügen build!
wie folgt:
use em::build;
Weitere Aktionen sind das Aufrufen von Emu-Funktionen, die Sie aus Rust-Code geschrieben haben. Einfacher geht es nicht:
fn main() { let vector = vec![0.4445, 433.245, 87.539503, 2.0]; let result = multiply(vector, 2.0).unwrap(); dbg!(result); }
Anwendungsbeispiel
Dieses Programm verwendet als erstes Argument einen Skalar, mit dem die folgenden Argumente multipliziert werden müssen. Der resultierende Vektor wird auf der Konsole gedruckt:
use em::{build, emu};
Sie können diesen Code mit dem Befehl cargo run -- 3 2.1 3.6 6.2
ausführen. Die daraus resultierende Schlussfolgerung entspricht den Erwartungen:
[src/main.rs:33] result = [ 6.2999997, 10.799999, 18.599998, ]
Link zu OpenCL
Wie bereits erwähnt, ist Emu nur eine Abstraktion über OpenCL und kann daher mit der Ocl- Kiste interagieren. Der folgende Code stammt aus einem Beispiel im offiziellen Repository :
use em::emu;
Fertigstellung
Ich hoffe dir hat der Artikel gefallen. Eine schnelle Antwort auf Ihre Fragen erhalten Sie im russischsprachigen Chat in Rust ( Version für Anfänger ).