Pemrograman GPU Idiomatik di Rust: Emu Library


Pendahuluan


Emu adalah bahasa pemrograman kartu grafis tingkat tinggi yang dapat disematkan dalam kode reguler dalam bahasa pemrograman sistem Rust .


Artikel ini akan fokus pada sintaks Emu, fitur-fiturnya, dan beberapa contoh ilustrasi penggunaannya dalam kode nyata akan ditampilkan.


Instalasi


  1. Perpustakaan yang Anda cari membutuhkan ketergantungan OpenCL eksternal. Anda perlu menginstal driver yang sesuai untuk perangkat keras Anda.
  2. Cargo.toml teks di bawah ini. Ini akan mengunduh versi terbaru yang tersedia (jika Anda memerlukan perakitan khusus, maka daripada * meletakkan versi yang Anda butuhkan):

     [dependencies] em = "*" //   Emu ocl = "*" //   OpenCL 

Sintaks


Sintaks Emu cukup sederhana, karena bahasa ini dimaksudkan hanya untuk menulis fungsi kernel yang dikompilasi ke OpenCL .


Tipe data


Bahasa Emu memiliki sembilan tipe data yang mirip dengan yang ada di Rust. Berikut ini adalah tabel jenis data ini:


JudulDeskripsi
f32Tiga puluh dua angka floating point
i8Simbol atau angka delapan-bit
i16Menandatangani enam belas nomor bit
i32Menandatangani nomor bit tiga puluh dua
i64Menandatangani nomor bit enam puluh empat
u8Nomor delapan bit yang tidak ditandatangani
u16Nomor bit enam belas yang tidak ditandatangani
u32Nomor bit tiga puluh dua yang tidak ditandatangani
u64Nomor bit enam puluh empat yang tidak ditandatangani
boolNilai Boolean
[TYPE]Vektor yang terdiri dari variabel tipe TYPE

Variabel


Variabel dideklarasikan menggunakan kata kunci let , yang terletak di belakang pengidentifikasi, titik dua, tipe data, tanda sama, nilai yang diberikan, dan titik koma.


 let age: i32 = 54; let growth: f32 = 179.432; let married: bool = true; 

Konversi


Konversi tipe data primitif dilakukan dengan menggunakan operator biner, mengikuti tipe target. Saya perhatikan bahwa tipe target juga bisa menjadi unit pengukuran (lihat bagian selanjutnya):


 let width: i16 = 324; let converted_width: i64 = width as i64; 

Unit


Bahasa Emu memungkinkan Anda untuk memperlakukan angka sebagai satuan ukuran, yang dirancang untuk menyederhanakan perhitungan ilmiah. Dalam contoh ini, length variabel awalnya didefinisikan dalam meter, tetapi kemudian unit pengukuran lainnya ditambahkan ke dalamnya:


 let length: f32 = 3455.345; //  length += 7644.30405 as cm; //  length += 1687.3043 as mm; //  

Konstanta yang ditentukan sebelumnya


Emu memiliki seperangkat konstanta standar yang nyaman digunakan dalam praktik. Di bawah ini adalah tabel terkait.


JudulNilai
Y10 pangkat 24
Z10 pangkat 21
E10 pangkat 18
P10 pangkat 15
T10 pangkat 12
G10 pangkat 9
M10 pangkat 6
k10 pangkat 3
h10 pangkat 2
D10 pangkat 1
d10 pangkat -1
c10 pangkat -2
m10 pangkat -3
u10 pangkat -6
n10 pangkat -9
p10 pangkat -12
f10 pangkat -15
a10 ke tingkat -18
z10 pangkat -21
y10 pangkat -24

Konstanta yang berhubungan dengan data ilmiah juga didefinisikan. Anda dapat menemukan tabel yang terdiri dari konstanta ini di sini .


Pernyataan bersyarat


Pernyataan bersyarat Emu mirip dengan pernyataan terkait di Rust. Kode berikut menggunakan konstruksi kondisional:


 let number: i32 = 2634; let satisfied: bool = false; if (number > 0) && (number % 2 == 0) { satisfied = true; } 

Untuk loop


Header untuk loop didefinisikan sebagai for NUM in START..END , di mana NUM adalah variabel yang mengambil nilai dari rentang [START; END) [START; END) melalui unit.


 let sum: u64 = 0; for i in 0..215 { sum += i; } 

Sementara loop


Judul loop Sementara didefinisikan sebagai while (CONDITION) , di mana CONDITION adalah kondisi bagi loop untuk melanjutkan ke iterasi berikutnya. Kode ini mirip dengan contoh sebelumnya:


 let sum: u64 = 0; let idx: i32 = 0; while (idx < 215) { sum += idx; idx += 1; } 

Loop tak berujung


Loop tak terbatas tidak memiliki kondisi keluar yang eksplisit dan ditentukan oleh kata kunci loop . Namun, mereka dapat dilanjutkan atau diinterupsi oleh pernyataan break dan continue (seperti dua jenis loop lainnya).


 let collapsed: u64 = 1; let idx: i32 = 0; loop { if idx % 2 == 0 { continue; } sum *= idx; if idx == 12 { break; } } 

Kembali dari fungsi


Seperti dalam semua bahasa pemrograman lain, return adalah output dari fungsi saat ini. Itu juga dapat mengembalikan nilai tertentu jika tanda tangan fungsi (lihat bagian berikut) memungkinkan ini.


 let result: i32 = 23446; return result; 

Operator lain


  • Operator penugasan yang tersedia: = , += , -= , *= , /= , %= , &= , ^= , <<= , >>= ;
  • Operator indeks adalah [IDX] ;
  • Hubungi Operator - (ARGS) ;
  • Operator unary: * untuk dereferencing ,! untuk membalikkan data Boolean, - untuk meniadakan angka;
  • Operator biner: + , - , * , / , % , && , || , & , | , ^ , >> , << , > , < , >= , <= , == , == != .

Fungsi


Ada tiga bagian fungsi pada Emu: pengidentifikasi, parameter, dan isi fungsi, yang terdiri dari urutan instruksi yang dapat dieksekusi. Pertimbangkan fungsi menambahkan dua angka:


 add(left f32, right f32) f32 { return left + right; } 

Seperti yang mungkin telah Anda perhatikan, fungsi ini mengembalikan jumlah dua argumen yang diteruskan menggunakan tipe data f32 .


Address Spaces


Setiap parameter fungsi sesuai dengan ruang alamat tertentu . Secara default, semua parameter sesuai dengan ruang __private__ .


Menambahkan awalan global_ dan local_ ke pengidentifikasi parameter secara eksplisit menunjukkan ruang alamatnya.


Dokumentasi menyarankan penggunaan awalan global_ untuk semua vektor dan tidak awalan apa pun.


Fungsi bawaan


Emu menyediakan sejumlah kecil fungsi bawaan (diambil dari OpenCL) yang memungkinkan Anda mengelola data GPU:


  • get_work_dim() - Mengembalikan jumlah dimensi;
  • get_global_size() - Mengembalikan jumlah elemen global untuk dimensi yang diberikan;
  • get_global_id() - Mengembalikan pengidentifikasi unik elemen untuk dimensi yang ditentukan;
  • get_global_size() - Mengembalikan jumlah elemen global untuk dimensi yang diberikan;
  • get_local_id() - Mengembalikan pengidentifikasi unik untuk elemen lokal dalam kelompok kerja tertentu untuk dimensi tertentu;
  • get_num_groups() - Mengembalikan jumlah workgroups untuk dimensi yang diberikan;
  • get_group_id() - Mengembalikan pengidentifikasi unik untuk workgroup.

Dalam kode aplikasi, paling sering Anda akan menemukan ekspresi get_global_id(0) , yang mengembalikan indeks saat ini dari elemen vektor yang terkait dengan panggilan ke fungsi kernel Anda.


Eksekusi kode


Pertimbangkan sintaks untuk memanggil fungsi Emu dari kode Rust biasa. Sebagai contoh, kita akan menggunakan fungsi yang mengalikan semua elemen vektor dengan angka yang diberikan:


 use em::emu; emu! { multiply(global_vector [f32], scalar f32) { global_vector[get_global_id(0)] *= scalar; } } 

Untuk menerjemahkan fungsi ini ke dalam kode OpenCL, Anda harus meletakkan tanda tangannya di makro build! sebagai berikut:


 use em::build; //    build! {...} extern crate ocl; use ocl::{flags, Platform, Device, Context, Queue, Program, Buffer, Kernel}; build! { multiply [f32] f32 } 

Tindakan selanjutnya datang ke memanggil fungsi Emu yang Anda tulis dari kode Rust. Itu tidak bisa lebih mudah:


 fn main() { let vector = vec![0.4445, 433.245, 87.539503, 2.0]; let result = multiply(vector, 2.0).unwrap(); dbg!(result); } 

Contoh aplikasi


Program ini menggunakan skalar sebagai argumen pertama, dimana perlu untuk melipatgandakan argumen berikut. Vektor yang dihasilkan akan dicetak ke konsol:


 use em::{build, emu}; //    build! {...} extern crate ocl; use ocl::{flags, Buffer, Context, Device, Kernel, Platform, Program, Queue}; emu! { multiply(global_vector [f32], scalar f32) { global_vector[get_global_id(0)] *= scalar; } } build! { multiply [f32] f32 } fn main() { //     : let args = std::env::args().collect::<Vec<String>>(); if args.len() < 3 { panic!(": cargo run -- <SCALAR> <NUMBERS>..."); } //      : let scalar = args[1].parse::<f32>().unwrap(); //      : let vector = args[2..] .into_iter() .map(|string| string.parse::<f32>().unwrap()) .collect(); //    : let result = multiply(vector, scalar).unwrap(); dbg!(result); } 

Anda dapat menjalankan kode ini dengan cargo run -- 3 2.1 3.6 6.2 perintah. Kesimpulan yang dihasilkan memenuhi harapan:


 [src/main.rs:33] result = [ 6.2999997, 10.799999, 18.599998, ] 

Tautan ke OpenCL


Seperti yang disebutkan sebelumnya, Emu hanyalah abstraksi dari OpenCL , dan karena itu ia memiliki kemampuan untuk berinteraksi dengan ocl crate . Kode di bawah ini diambil dari contoh di repositori resmi :


 use em::emu; //  "ocl"        Rust: extern crate ocl; use ocl::{flags, Platform, Device, Context, Queue, Program, Buffer, Kernel}; //  Emu    (OpenCL)   //     "EMU: &'static str": emu! { //     : multiply(global_buffer [f32], coeff f32) { global_buffer[get_global_id(0)] *= coeff; } } fn multiply(global_buffer: Vec<f32>, coeff: f32) -> ocl::Result<Vec<f32>> { //        , //  , ,   : let platform = Platform::default(); let device = Device::first(platform)?; let context = Context::builder() .platform(platform) .devices(device.clone()) .build()?; let program = Program::builder() .devices(device) .src(EMU) .build(&context)?; let queue = Queue::new(&context, device, None)?; let dims = global_buffer.len(); //    : let buffer = Buffer::<f32>::builder() .queue(queue.clone()) .flags(flags::MEM_READ_WRITE) .len(dims) .copy_host_slice(&global_buffer) .build()?; //       , //    : let kernel = Kernel::builder() .program(&program) .name("multiply") .queue(queue.clone()) .global_work_size(dims) .arg(&buffer) .arg(&coeff) .build()?; //    (    //   : unsafe { kernel.cmd() .queue(&queue) .global_work_offset(kernel.default_global_work_offset()) .global_work_size([dims, 0, 0]) .local_work_size(kernel.default_local_work_size()) .enq()?; } //  ,         // "dims": let mut vector = vec![0.0f32; dims]; buffer.cmd() .queue(&queue) .offset(0) .read(&mut vector) .enq()?; Ok(vector) } fn main() { let initial_data = vec![3.7, 4.5, 9.0, 1.2, 8.9]; //   ,   Emu,  //  "initial_data": let final_data = multiply(initial_data, 3.0).unwrap(); println!("{:?}", final_data); } 

Penyelesaian


Saya harap Anda menikmati artikel ini. Anda bisa mendapatkan jawaban cepat untuk pertanyaan Anda di obrolan bahasa Rusia di Rust ( versi untuk pemula ).


Source: https://habr.com/ru/post/id454678/


All Articles