
Introduccion
Emu es un lenguaje de programación de tarjetas gráficas de alto nivel que puede integrarse en un código normal en el lenguaje de programación del sistema Rust .
Este artículo se centrará en la sintaxis de Emu, sus características, y también mostrará algunos ejemplos ilustrativos de su uso en código real.
Instalación
- La biblioteca que está buscando necesita una dependencia externa de OpenCL. Necesita instalar el controlador apropiado para su hardware.
Cargo.toml
texto a continuación. Esto descargará las últimas versiones disponibles (si necesita un ensamblaje específico, en lugar de *
ponga la versión que necesita):
[dependencies] em = "*" // Emu ocl = "*" // OpenCL
Sintaxis
La sintaxis de Emu es bastante simple, porque este lenguaje está destinado solo a escribir funciones del núcleo compiladas en OpenCL .
Tipos de datos
El lenguaje Emu tiene nueve tipos de datos que son similares a los de Rust. La siguiente es una tabla de estos tipos de datos:
Variables
Las variables se declaran usando la palabra clave let
, que se encuentra detrás del identificador, dos puntos, tipo de datos, signo igual, valor asignado y punto y coma.
let age: i32 = 54; let growth: f32 = 179.432; let married: bool = true;
Conversiones
La conversión de tipos de datos primitivos se realiza utilizando el operador binario as
, siguiendo el tipo de destino. Observo que el tipo de objetivo también puede ser una unidad de medida (consulte la siguiente sección):
let width: i16 = 324; let converted_width: i64 = width as i64;
Unidades
El lenguaje Emu le permite tratar los números como unidades de medida, que está diseñado para simplificar los cálculos científicos. En este ejemplo, la length
variable length
define inicialmente en metros, pero luego se le agregan otras unidades de medida:
let length: f32 = 3455.345;
Constantes predefinidas
Emu tiene un conjunto de constantes predefinidas que son convenientes de usar en la práctica. A continuación se muestra la tabla correspondiente.
También se definen las constantes correspondientes a los datos científicos. Puede encontrar la tabla que consta de estas constantes aquí .
Declaraciones condicionales
Las declaraciones condicionales de Emu son similares a las declaraciones correspondientes en Rust. El siguiente código usa construcciones condicionales:
let number: i32 = 2634; let satisfied: bool = false; if (number > 0) && (number % 2 == 0) { satisfied = true; }
Para bucles
El encabezado del bucle For se define como for NUM in START..END
, donde NUM
es una variable que toma valores del rango [START; END)
[START; END)
través de la unidad.
let sum: u64 = 0; for i in 0..215 { sum += i; }
Mientras bucles
El título del ciclo While se define como while (CONDITION)
, donde CONDITION
es la condición para que el ciclo proceda a la siguiente iteración. Este código es similar al ejemplo anterior:
let sum: u64 = 0; let idx: i32 = 0; while (idx < 215) { sum += idx; idx += 1; }
Bucles sin fin
Los bucles infinitos no tienen una condición de salida explícita y están definidos por la palabra clave del loop
. Sin embargo, pueden continuarse o interrumpirse por las declaraciones break
y continue
(como los otros dos tipos de bucles).
let collapsed: u64 = 1; let idx: i32 = 0; loop { if idx % 2 == 0 { continue; } sum *= idx; if idx == 12 { break; } }
Regresar de la función
Como en todos los demás lenguajes de programación, la return
es la salida de la función actual. También puede devolver un cierto valor si la firma de la función (consulte las siguientes secciones) lo permite.
let result: i32 = 23446; return result;
Otros operadores
- Operadores de asignación disponibles:
=
, +=
, -=
, *=
, /=
, %=
, &=
, ^=
, <<=
, >>=
; - El operador de índice es
[IDX]
; - Operador de llamadas -
(ARGS)
; - Operadores unarios:
*
para desreferenciar ,! para invertir datos booleanos, -
para negar números; - Operadores binarios:
+
, -
, *
, /
, %
, &&
, ||
, &
, |
, ^
, >>
, <<
, >
, <
, >=
, <=
, ==
, ==
!=
.
Las funciones
Hay tres partes de funciones en Emu: el identificador, los parámetros y el cuerpo de la función, que consiste en una secuencia de instrucciones ejecutables. Considere la función de sumar dos números:
add(left f32, right f32) f32 { return left + right; }
Como habrás notado, esta función devuelve la suma de dos argumentos que se le pasan usando el tipo de datos f32
.
Espacios de direcciones
Cada parámetro de la función corresponde a un espacio de direcciones específico . Por defecto, todos los parámetros corresponden al espacio __private__
.
Agregar los prefijos global_
y global_
al identificador del parámetro indica explícitamente su espacio de direcciones.
La documentación aconseja usar el prefijo global_
para todos los vectores y no agregar nada más.
Funciones incorporadas
Emu proporciona un pequeño conjunto de funciones integradas (tomadas de OpenCL) que le permiten administrar los datos de la GPU:
get_work_dim()
- Devuelve el número de dimensiones;get_global_size()
- Devuelve el número de elementos globales para una dimensión dada;get_global_id()
- Devuelve el identificador único del elemento para la dimensión especificada;get_global_size()
- Devuelve el número de elementos globales para una dimensión dada;get_local_id()
- Devuelve un identificador único para un elemento local dentro de un grupo de trabajo específico para una dimensión dada;get_num_groups()
- Devuelve el número de grupos de trabajo para una dimensión dada;get_group_id()
- Devuelve un identificador único para el grupo de trabajo.
En el código de la aplicación, con mayor frecuencia encontrará la expresión get_global_id(0)
, que devuelve el índice actual del elemento vector asociado con la llamada a la función de su núcleo.
Ejecución de código
Considere la sintaxis para llamar a las funciones Emu desde el código Rust normal. Como ejemplo, utilizaremos una función que multiplique todos los elementos de un vector por un número dado:
use em::emu; emu! { multiply(global_vector [f32], scalar f32) { global_vector[get_global_id(0)] *= scalar; } }
Para traducir esta función en código OpenCL, ¡debe poner su firma en la macro de build!
como sigue:
use em::build;
Otras acciones se reducen a llamar a las funciones de Emu que escribió desde el código Rust. No podría ser más fácil:
fn main() { let vector = vec![0.4445, 433.245, 87.539503, 2.0]; let result = multiply(vector, 2.0).unwrap(); dbg!(result); }
Ejemplo de aplicación
Este programa toma un escalar como primer argumento, por el cual es necesario multiplicar los siguientes argumentos. El vector resultante se imprimirá en la consola:
use em::{build, emu};
Puede ejecutar este código con el comando cargo run -- 3 2.1 3.6 6.2
. La conclusión resultante cumple con las expectativas:
[src/main.rs:33] result = [ 6.2999997, 10.799999, 18.599998, ]
Enlace a OpenCL
Como se mencionó anteriormente, Emu es solo una abstracción sobre OpenCL y, por lo tanto, tiene la capacidad de interactuar con la caja . El siguiente código está tomado de un ejemplo en el repositorio oficial :
use em::emu;
Finalización
Espero que hayas disfrutado el artículo. Puede obtener una respuesta rápida a sus preguntas en el chat de idioma ruso en Rust ( versión para principiantes ).