
1. Introdução
O UEM é uma linguagem de programação de placa gráfica de alto nível que pode ser incorporada ao código regular na linguagem de programação do sistema Rust .
Este artigo abordará a sintaxe do Emu, seus recursos e também mostrará alguns exemplos ilustrativos de seu uso em código real.
Instalação
- A biblioteca revisada precisa de uma dependência externa do OpenCL. Você precisa instalar o driver apropriado para o seu hardware.
Cargo.toml
texto abaixo. Isso fará o download das versões disponíveis mais recentes (se você precisar de um assembly específico, em vez de *
coloque a versão necessária):
[dependencies] em = "*" // Emu ocl = "*" // OpenCL
Sintaxe
A sintaxe da Emu é bastante simples, porque esse idioma é destinado apenas para escrever funções do kernel que são transmitidas no OpenCL durante a compilação.
Tipos de dados
A linguagem Emu possui nove tipos de dados semelhantes aos do Rust. A seguir, é apresentada uma tabela desses tipos de dados:
Variáveis
As variáveis são declaradas usando a palavra-chave let
, localizada atrás do identificador, dois pontos, tipo de dados, sinal de igual, valor atribuído e ponto e vírgula.
let age: i32 = 54; let growth: f32 = 179.432; let married: bool = true;
Conversões
A conversão de tipos de dados primitivos é realizada usando o operador binário as
, seguindo o tipo de destino. Observo que o tipo de destino também pode ser uma unidade de medida (consulte a próxima seção):
let width: i16 = 324; let converted_width: i64 = width as i64;
Unidades
A linguagem Emu permite tratar números como unidades de medida, projetadas para simplificar cálculos científicos. Neste exemplo, o length
variável length
definido inicialmente em metros, mas outras unidades de medida são adicionadas a ele:
let length: f32 = 3455.345;
Constantes predefinidas
A UEM possui um conjunto de constantes predefinidas que são convenientes para uso na prática. Abaixo está a tabela correspondente.
Também são definidas constantes correspondentes a dados científicos. Você pode encontrar a tabela que consiste nessas constantes aqui .
Instruções condicionais
As declarações condicionais da UEM são semelhantes às declarações correspondentes no Rust. O código a seguir usa construções condicionais:
let number: i32 = 2634; let satisfied: bool = false; if (number > 0) && (number % 2 == 0) { satisfied = true; }
Para loops
O cabeçalho do loop For é definido como for NUM in START..END
, onde NUM
é uma variável que recebe valores do intervalo [START; END)
[START; END)
através da unidade.
let sum: u64 = 0; for i in 0..215 { sum += i; }
Enquanto loops
O título do loop While é definido como while (CONDITION)
, em que CONDITION
é a condição para o loop prosseguir para a próxima iteração. Este código é semelhante ao exemplo anterior:
let sum: u64 = 0; let idx: i32 = 0; while (idx < 215) { sum += idx; idx += 1; }
Loops sem fim
Loops infinitos não têm uma condição de saída explícita e são definidos pela palavra-chave loop
. No entanto, eles podem ser continuados ou interrompidos pelas instruções break
e continue
(como os outros dois tipos de loops).
let collapsed: u64 = 1; let idx: i32 = 0; loop { if idx % 2 == 0 { continue; } sum *= idx; if idx == 12 { break; } }
Retorno da função
Como em todas as outras linguagens de programação, a return
é a saída da função atual. Também pode retornar um determinado valor se a assinatura da função (consulte as seções a seguir) permitir isso.
let result: i32 = 23446; return result;
Outros operadores
- Operadores de atribuição disponíveis:
=
, +=
, -=
, *=
, /=
, %=
, &=
, ^=
, <<=
, >>=
; - O operador de índice é
[IDX]
; - Operador de chamada -
(ARGS)
; - Operadores unários:
*
para cancelamento de referência ,! inverter dados booleanos, -
negar números; - Operadores binários:
+
, -
, *
, /
, %
, &&
, ||
, &
, |
, ^
, >>
, <<
, >
, <
, >=
, <=
, ==
, ==
!=
.
Funções
Existem três partes de funções no Emu: o identificador, parâmetros e o corpo da função, consistindo em uma sequência de instruções executáveis. Considere a função de adicionar dois números:
add(left f32, right f32) f32 { return left + right; }
Como você deve ter notado, essa função retorna a soma dos dois argumentos passados para ela usando o tipo de dados f32
.
Espaços de endereço
Cada parâmetro da função corresponde a um espaço de endereço específico . Por padrão, todos os parâmetros correspondem ao espaço __private__
.
Adicionar os prefixos global_
e local_
ao identificador de parâmetro indica explicitamente seu espaço de endereço.
A documentação aconselha o uso do prefixo global_
para todos os vetores e não o prefixo de mais nada.
Funções incorporadas
A UEM fornece um pequeno conjunto de funções internas (extraídas do OpenCL) que permitem gerenciar dados da GPU:
get_work_dim()
- Retorna o número de dimensões;get_global_size()
- Retorna o número de elementos globais para uma determinada dimensão;get_global_id()
- Retorna o identificador exclusivo do elemento para a dimensão especificada;get_global_size()
- Retorna o número de elementos globais para uma determinada dimensão;get_local_id()
- Retorna um identificador exclusivo para um elemento local dentro de um grupo de trabalho específico para uma determinada dimensão;get_num_groups()
- Retorna o número de grupos de trabalho para uma determinada dimensão;get_group_id()
- Retorna um identificador exclusivo para o grupo de trabalho.
No código do aplicativo, na maioria das vezes você encontrará a expressão get_global_id(0)
, que retorna o índice atual do elemento vetorial associado à chamada para sua função do kernel.
Execução de código
Considere a sintaxe para chamar funções de Emu a partir do código Rust regular. Como exemplo, usaremos uma função que multiplica todos os elementos de um vetor por um determinado número:
use em::emu; emu! { multiply(global_vector [f32], scalar f32) { global_vector[get_global_id(0)] *= scalar; } }
Para converter essa função em código OpenCL, você precisa colocar sua assinatura na macro de build!
da seguinte maneira:
use em::build;
Outras ações se resumem a chamar funções de Emu que você escreveu a partir do código Rust. Não poderia ser mais fácil:
fn main() { let vector = vec![0.4445, 433.245, 87.539503, 2.0]; let result = multiply(vector, 2.0).unwrap(); dbg!(result); }
Exemplo de aplicação
Este programa assume um escalar como o primeiro argumento, pelo qual é necessário multiplicar os seguintes argumentos. O vetor resultante será impresso no console:
use em::{build, emu};
Você pode executar este código com o comando cargo run -- 3 2.1 3.6 6.2
. A conclusão resultante atende às expectativas:
[src/main.rs:33] result = [ 6.2999997, 10.799999, 18.599998, ]
Link para OpenCL
Como mencionado anteriormente, o Emu é apenas uma abstração do OpenCL e, portanto, tem a capacidade de interagir com o oclate. O código abaixo é retirado de um exemplo no repositório oficial :
use em::emu;
Conclusão
Espero que tenham gostado do artigo. Você pode obter uma resposta rápida para suas perguntas no bate-papo em russo no Rust ( versão para iniciantes ).