
Présentation
Emu est un langage de programmation de carte graphique de haut niveau qui peut être intégré dans du code normal dans le langage de programmation du système Rust .
Cet article se concentrera sur la syntaxe d'Emu, ses fonctionnalités et plusieurs exemples illustratifs de son utilisation dans du code réel seront présentés.
L'installation
- La bibliothèque que vous recherchez a besoin d'une dépendance OpenCL externe. Vous devez installer le pilote approprié à votre matériel.
Cargo.toml
texte ci-dessous. Cela téléchargera les dernières versions disponibles (si vous avez besoin d'un assemblage spécifique, alors au lieu de *
mettez la version dont vous avez besoin):
[dependencies] em = "*" // Emu ocl = "*" // OpenCL
Syntaxe
La syntaxe Emu est assez simple, car ce langage est uniquement destiné à l'écriture des fonctions du noyau qui sont compilées dans OpenCL .
Types de données
La langue Emu possède neuf types de données similaires à ceux de Rust. Voici un tableau de ces types de données:
Variables
Les variables sont déclarées à l'aide du mot clé let
, situé derrière l'identifiant, le signe deux-points, le type de données, le signe égal, la valeur affectée et le point-virgule.
let age: i32 = 54; let growth: f32 = 179.432; let married: bool = true;
Conversions
La conversion des types de données primitifs s'effectue à l'aide de l'opérateur binaire as
, à la suite du type cible. Je note que le type cible peut également être une unité de mesure (voir la section suivante):
let width: i16 = 324; let converted_width: i64 = width as i64;
Unités
La langue Emu vous permet de traiter les nombres comme des unités de mesure, ce qui est conçu pour simplifier les calculs scientifiques. Dans cet exemple, la length
variable length
initialement définie en mètres, mais ensuite d'autres unités de mesure y sont ajoutées:
let length: f32 = 3455.345;
Constantes prédéfinies
Emu possède un ensemble de constantes prédéfinies qui sont pratiques à utiliser dans la pratique. Voici le tableau correspondant.
Des constantes correspondant à des données scientifiques sont également définies. Vous pouvez trouver le tableau composé de ces constantes ici .
Déclarations conditionnelles
Les instructions conditionnelles Emu sont similaires aux instructions correspondantes dans Rust. Le code suivant utilise des constructions conditionnelles:
let number: i32 = 2634; let satisfied: bool = false; if (number > 0) && (number % 2 == 0) { satisfied = true; }
Pour les boucles
L'en-tête de la boucle For est défini comme for NUM in START..END
, oĂą NUM
est une variable qui prend des valeurs de la plage [START; END)
[START; END)
par l'unité.
let sum: u64 = 0; for i in 0..215 { sum += i; }
Boucles
Le titre de la boucle While est défini comme while (CONDITION)
, oĂą CONDITION
est la condition pour que la boucle passe à l'itération suivante. Ce code est similaire à l'exemple précédent:
let sum: u64 = 0; let idx: i32 = 0; while (idx < 215) { sum += idx; idx += 1; }
Boucles sans fin
Les boucles infinies n'ont pas de condition de sortie explicite et sont définies par le mot clé loop
. Cependant, elles peuvent ĂŞtre poursuivies ou interrompues par les instructions break
et continue
(comme les deux autres types de boucles).
let collapsed: u64 = 1; let idx: i32 = 0; loop { if idx % 2 == 0 { continue; } sum *= idx; if idx == 12 { break; } }
Retour de fonction
Comme dans tous les autres langages de programmation, l' return
est la sortie de la fonction courante. Il peut également retourner une certaine valeur si la signature de fonction (voir les sections suivantes) le permet.
let result: i32 = 23446; return result;
Autres opérateurs
- Opérateurs d'affectation disponibles:
=
, +=
, -=
, *=
, /=
, %=
, &=
, ^=
, <<=
, >>=
; - L'opérateur d'index est
[IDX]
; - Opérateur d'appel -
(ARGS)
; - Opérateurs unaires:
*
pour le déréférencement,! inverser les données booléennes, -
annuler les nombres; - Opérateurs binaires:
+
, -
, *
, /
, %
, &&
, ||
, &
, |
, ^
, >>
, <<
, >
, <
, >=
, <=
, ==
!=
.
Les fonctions
Il y a trois parties de fonctions sur Emu: l'identifiant, les paramètres et le corps de la fonction, consistant en une séquence d'instructions exécutables. Considérez la fonction d'ajouter deux nombres:
add(left f32, right f32) f32 { return left + right; }
Comme vous l'avez peut-être remarqué, cette fonction renvoie la somme de deux arguments qui lui sont passés à l'aide du type de données f32
.
Espaces d'adresses
Chaque paramètre de la fonction correspond à un espace d'adressage spécifique . Par défaut, tous les paramètres correspondent à l'espace __private__
.
L'ajout des préfixes global_
et local_
à l'identificateur de paramètre indique explicitement son espace d'adressage.
La documentation conseille d'utiliser le préfixe global_
pour tous les vecteurs et de ne pas préfixer autre chose.
Fonctions intégrées
Emu fournit un petit ensemble de fonctions intégrées (empruntées à OpenCL) qui vous permettent de gérer les données GPU:
get_work_dim()
- Retourne le nombre de dimensions;get_global_size()
- Retourne le nombre d'éléments globaux pour une dimension donnée;get_global_id()
- Retourne l'identifiant unique de l'élément pour la dimension spécifiée;get_global_size()
- Retourne le nombre d'éléments globaux pour une dimension donnée;get_local_id()
- Retourne un identifiant unique pour un élément local dans un groupe de travail spécifique pour une dimension donnée;get_num_groups()
- Retourne le nombre de groupes de travail pour une dimension donnée;get_group_id()
- Retourne un identifiant unique pour le groupe de travail.
Dans le code d'application, vous trouverez le plus souvent l'expression get_global_id(0)
, qui retourne l'index actuel de l'élément vectoriel associé à l'appel à votre fonction noyau.
Exécution de code
Considérez la syntaxe pour appeler les fonctions Emu à partir du code Rust standard. À titre d'exemple, nous utiliserons une fonction qui multiplie tous les éléments d'un vecteur par un nombre donné:
use em::emu; emu! { multiply(global_vector [f32], scalar f32) { global_vector[get_global_id(0)] *= scalar; } }
Pour traduire cette fonction en code OpenCL, vous devez mettre sa signature dans la macro de build!
comme suit:
use em::build;
D'autres actions se résument à appeler les fonctions Emu que vous avez écrites à partir du code Rust. Rien de plus simple:
fn main() { let vector = vec![0.4445, 433.245, 87.539503, 2.0]; let result = multiply(vector, 2.0).unwrap(); dbg!(result); }
Exemple d'application
Ce programme prend un scalaire comme premier argument, par lequel il est nécessaire de multiplier les arguments suivants. Le vecteur résultant sera imprimé sur la console:
use em::{build, emu};
Vous pouvez exécuter ce code avec la commande cargo run -- 3 2.1 3.6 6.2
. La conclusion qui en résulte répond aux attentes:
[src/main.rs:33] result = [ 6.2999997, 10.799999, 18.599998, ]
Lien vers OpenCL
Comme mentionné précédemment, Emu n'est qu'une abstraction sur OpenCL , et donc il a la capacité d'interagir avec la caisse ocl. Le code ci-dessous est tiré d'un exemple dans le référentiel officiel :
use em::emu;
Achèvement
J'espère que l'article vous a plu. Vous pouvez obtenir une réponse rapide à vos questions dans le chat en russe à Rust ( version pour débutants ).