Всем привет!
В данном руководстве хочу рассказать как подружить CUDA C/С++ и Rust. И в качестве примера напишем небольшую программу на Rust для вычисления скалярного произведения векторов, вычисление скалярного произведения будет производиться на GPU с использованием CUDA C.
Кому интересно под кат!
CUDA C
Первым делом необходимо поставить компилятор CUDA — nvcc. Что такое CUDA и для чего это нужно описывать не буду, об этом можно почитать например тут. Скажу лишь что с её помощью можно писать код, который будет запускаться на NVIDIA видеокартах (в дальнейшем — GPU) и использовать всю их мощь для параллельных вычислений и обработки графики. Еще раз повторюсь, данный туториал не о том как писать код на CUDA, а о том как и из кода на Rust пользоваться её преимуществами и писать параллельные вычисления на GPU.
Итак устанавливаем nvcc и CUDA Toolkit. С этим сложностей не должно возникнуть подробная инструкция: на офф сайте.
RUST + CUDA C
В ходе данного туториала, как уже говорилось ранее, будем писать программу на Rust для нахождения скалярного произведения двух векторов, сам процесс вычисления будет происходить на GPU.
Начнем создание нашей программы. Далее я предполагаю, что nvcc успешно установлен, так же стоят rustc и cargo для компиляции rust кода.
Первым делом создадим папку проекта. В папке проекта создадим файл Cargo.toml, в котором находятся инструкции для сборщика cargo. Файл выглядит таким образом:
[package]
name = "rust_cuda" # название программы
version = "0.1.0" # версия программы
authors = ["MoonL1ght <ixav1@icloud.com>"] # информация об авторе
build = "build.rs" # скрипт для сборки rust
links = "cudart" # библиотека cuda, которая линкуется динамически
[dependencies]
libc = "0.2" # библиотека rust для работы С кодом
rand = "0.5.5" # библиотека rust для работы с случайными величинами
[build-dependencies]
cc = "1.0" # rust пакет для сборки С кода
Так же в корневой папке проекта создаем файл build.rs в котором будут находится инструкции для сборки программы на rust и компиляции CUDA C кода.
В корень проекта добавим папку src в которую поместим файлы с исходным кодом. В папке src создадим четыре файла: main.rs — код основной программы, dot.cpp — С++ binding (обертка для CUDA C), dot_gpu.h, dot_gpu.cu — файл в котором содержится код выполняемый на GPU.
Итого имеем такую структура проекта:
rust-cuda/
src/
main.rs
dot.cpp
dot_gpu.h
dot_gpu.cu
Cargo.toml
build.rs
В файле build.rs самое главное прописать это:
println!("cargo:rustc-link-search=native=/Developer/NVIDIA/CUDA-10.1/lib");
println!("cargo:rustc-link-search=/Developer/NVIDIA/CUDA-10.1/lib");
println!("cargo:rustc-env=LD_LIBRARY_PATH=/Developer/NVIDIA/CUDA-10.1/lib");
println!("cargo:rustc-link-lib=dylib=cudart");
где /Developer/NVIDIA/CUDA-10.1/lib — путь к исполняемым файлам CUDA, в unix подобной системе этот путь можно узнать например командой:
which nvcc
Помимо этого в файле build.rs нужно указать путь к файлам dot.cpp и dot_gpu.cpp:
.files(&["./src/dot.cpp", "./src/dot_gpu.cu"])
extern crate cc;
fn main() {
cc::Build::new()
.cuda(true)
.cpp(true)
.flag("-cudart=shared")
.files(&["./src/dot.cpp", "./src/dot_gpu.cu"])
.compile("dot.a");
println!("cargo:rustc-link-search=native=/Developer/NVIDIA/CUDA-10.1/lib");
println!("cargo:rustc-link-search=/Developer/NVIDIA/CUDA-10.1/lib");
println!("cargo:rustc-env=LD_LIBRARY_PATH=/Developer/NVIDIA/CUDA-10.1/lib");
println!("cargo:rustc-link-lib=dylib=cudart");
}
Теперь можно приступать к написанию основного кода программы. В файле main.rs нужно создать интерфейс С/С++ функции для вызова непосредственно из кода на Rust. Более подробно об это можно почитать в официальной документации в разделе FFI.
extern "C" {
// интерфейс C функции для расчета скалярного произведения двух векторов
fn dot(v1: *mut c_float, v2: *mut c_float, N: size_t) -> c_float;
}
Для её вызова надо использовать unsafe блок кода, в качестве аргументов передаем mutable pointer на тип Vec:
unsafe {
gpu_res = dot(v1.as_mut_ptr(), v2.as_mut_ptr(), VEC_SIZE);
}
extern crate libc;
extern crate rand;
use libc::{c_float, size_t};
use rand::Rng;
const VEC_SIZE: usize = 10;
const MAX: f32 = 10.;
const MIN: f32 = 0.;
extern "C" {
fn dot(v1: *mut c_float, v2: *mut c_float, N: size_t) -> c_float;
}
fn cpu_dot(v1: Vec<f32>, v2: Vec<f32>) -> f32 {
let mut res: f32 = 0.;
for i in 0..v1.len() {
res += v1[i] * v2[i];
}
return res;
}
fn main() {
let mut v1: Vec<f32> = Vec::new();
let mut v2: Vec<f32> = Vec::new();
let mut gpu_res: c_float;
let mut cpu_res: f32 = 0.;
let mut rng = rand::thread_rng();
for _ in 0..VEC_SIZE {
v1.push(rng.gen_range(MIN, MAX));
v2.push(rng.gen_range(MIN, MAX));
}
println!("{:?}", v1);
println!("{:?}", v2);
println!("GPU computing started");
unsafe {
gpu_res = dot(v1.as_mut_ptr(), v2.as_mut_ptr(), VEC_SIZE);
}
println!("GPU computing finished");
println!("GPU dot product result: {}", gpu_res);
cpu_res = cpu_dot(v1, v2);
println!("CPU dot product result: {}", cpu_res);
}
Теперь приступаем к написанию обвязки на C++, а так же кода для вычисления скалярного произведения векторов на CUDA C.
В файле dot.cpp напишем функцию обвязку, собственно эту функцию мы и вызываем из Rust кода:
extern "C" {
float dot(float *v1, float *v2, size_t N) {
float *gpu_res;
float res = 0.0;
gpu_res = gpu_dot(v1, v2, N); // вычисление на GPU
for (int i = 0; i < blocksPerGrid; i++) {
res += gpu_res[i];
}
free(gpu_res);
return res;
}
}
#include <iostream>
#include "dot_gpu.h"
using namespace std;
void display_vector(float *v, size_t N) {
cout << "[";
for (size_t i = 0; i < N; i++) {
cout << v[i];
if (i != N - 1) {
cout << ", ";
}
}
cout << "]" << endl;
}
extern "C" {
float dot(float *v1, float *v2, size_t N) {
cout << "Calling gpu dot product" << endl;
cout << "Got two vectors from rust:" << endl;
display_vector(v1, N);
display_vector(v2, N);
float *gpu_res;
float res = 0.0;
gpu_res = gpu_dot(v1, v2, N);
for (int i = 0; i < blocksPerGrid; i++) {
res += gpu_res[i];
}
free(gpu_res);
return res;
}
}
Далее представлен код из файла dot_gpu.cu в котором производится основное вычисление, объяснять сам код в данном туториале не буду, так как он не посвящен программированию на CUDA.
#include "dot_gpu.h"
__global__ void dot__(float *v1, float *v2, float *res, int N) {
__shared__ float cache [threadsPerBlock];
int tid = threadIdx.x + blockIdx.x * blockDim.x;
int cacheIndex = threadIdx.x;
float temp = 0.0;
while (tid < N) {
temp += v1[tid] * v2[tid];
tid += blockDim.x * gridDim.x;
}
cache[cacheIndex] = temp;
__syncthreads();
int i = blockDim.x / 2;
while (i != 0) {
if (cacheIndex < i) {
cache[cacheIndex] += cache[cacheIndex + i];
}
__syncthreads();
i /= 2;
}
if (cacheIndex == 0) {
res[blockIdx.x] = cache[0];
}
}
float * gpu_dot (float *v1, float *v2, size_t N) {
float *dev_v1, *dev_v2, *dev_res, *res;
res = new float[blocksPerGrid];
cudaMalloc((void**)&dev_v1, N * sizeof(float));
cudaMalloc((void**)&dev_v2, N * sizeof(float));
cudaMalloc((void**)&dev_res, blocksPerGrid * sizeof(float));
cudaMemcpy(dev_v1, v1, N * sizeof(float), cudaMemcpyHostToDevice);
cudaMemcpy(dev_v2, v2, N * sizeof(float), cudaMemcpyHostToDevice);
dot__<<<blocksPerGrid, threadsPerBlock>>>(dev_v1, dev_v2, dev_res, (int)N);
cudaMemcpy(res, dev_res, blocksPerGrid * sizeof(float), cudaMemcpyDeviceToHost);
cudaFree(dev_v1);
cudaFree(dev_v2);
cudaFree(dev_res);
return res;
}
Все наша маленькая программа написана и готова к сборке. Для того что бы её собрать в консоли вызовем команду:
cargo build
Для запуска:
cargo run
После сборки программы в основной директории проекта появится папка target. Исполняемый файл нашей программы будет находится в папке: ./target/debug/
При этом если мы просто запустим наш исполняемый файл то получим ошибку: dyld library not loaded. То есть он не может найти путь к динамической библиотеки cuda. Для решения данной проблемы можно перед запуском исполняемого файла в консоли прописать переменную окружения LD_LIBRARY_PATH=path_to_CUDA_lib_directory/ или же создать символьные линки в папке rust toolchain для CUDA:
ln -s /Developer/NVIDIA/CUDA-10.1/lib/* /Users/Alexander/.rustup/toolchains/nightly-x86_64-apple-darwin/lib
где /Users/Alexander/.rustup/toolchains/nightly-x86_64-apple-darwin/lib — мой путь к установленному rust toolchain-у, у вас он может немного отличаться.
При запуске программы через cargo run такой ошибки не возникало, потому что мы прописали переменную окружения LD_LIBRARY_PATH в файле build.rs.
В итоге
Мы имеем возможность запускать код CUDA C прямиком из кода на Rust. Для того, что бы проверить это, мы создали небольшую программу, она работает с векторами и все вычисления производит на GPU. Полный код так же можно посмотреть на github.
Комментарии (41)
tongohiti
15.04.2019 20:14Логичным продолжением был бы пост про, собственно, программирование с использованием CUDA…
rstm-sf
15.04.2019 22:24+1В корень проекта добавим папку src в которую поместим файлы с исходным кодом. В папке src создадим четыре файла: main.rs — код основной программы, dot.cpp — С++ binding (обертка для CUDA C), dot_gpu.h, dot_gpu.cu — файл в котором содержится код выполняемый на GPU.
По сути, CUDA используется, как и раньше использовалась. Статей, как использовать, на хабре достаточно.
M00nL1ght Автор
15.04.2019 23:33+3Одного поста не хватит, что бы описать программирование с использованием CUDA. В данной статье я лишь показал как использовать CUDA и Rust вместе. И как заметили выше сама по себе CUDA используется как обычно. А так, возможно, в дальнейшем еще опишу некоторые интересные моменты именно программирования CUDA в связке с Rust.
masai
15.04.2019 21:42+1А почему
.cpp
, если все равно всё вextern "C"
?rstm-sf
15.04.2019 22:16Потому-что функция написана на языке C.
What is the effect of extern “C” in C++?
Is extern “C” no longer needed anymore in cuda?masai
16.04.2019 02:07+1Мой вопрос всё же был не о том, что такое
extern "C"
. Просто C++ тут только ради<iostream>
иcout
. Можно было и на C всё написать вполне.M00nL1ght Автор
16.04.2019 08:48.cpp не ради iostream и cout, .cpp вообще здесь не ради чего то, в нем нет глубокого смысла и необходимости, можно вообще все запихнуть в один файл .c и написать в нем и функцию обертку которая будет вызываться из Rust и CUDA кернел. Я это сделал просто так, дабы показать возможность линковки нескольких исходных файлов (.cpp в частности) в добавок ко всему.
emerald_isle
16.04.2019 17:56Не совсем так даже. Скорее дело в том, что nvcc на самом деле компилятор (расширенный) C++, а не C, по каким-то причинам.
balsoft
15.04.2019 23:31+1Этот туториал на самом деле подойдёт для большинства других сишных библиотек.
M00nL1ght Автор
15.04.2019 23:36+1По сути да. И не только сишных, и с++ — шных тоже. В случае с CUDA самое главное это прописать пути к исполняемым файлам, что бы Rust корректно подгрузил их как динамическую либу.
veslemay
16.04.2019 15:27что бы Rust корректно подгрузил их как динамическую либу.
Раст ничего никуда не подгружает. Проблема в отсутствии прописанных путей/кривой линковке. С растом это никак не связано.
pronvis
16.04.2019 00:12+21. То есть непосредственно логику вычисления на GPU на расте написать не получится?
2. Есть ли под макось что-нибудь вроде стаба/мока для cuda? Я хочу разрабатывать и тестить на маке, но запускать на убунте где у меня 1080TIM00nL1ght Автор
16.04.2019 08:421. Нет, не получится. Код для вычисления на GPU все так же пишется на CUDA C. Да и честно говоря не вижу смысла переносить CUDA на Rust если достаточно в данном случае написать кернел для необходимых вычислений, вызвать этот кернел из Rust и результат вернуть обратно.
2. Не совсем понял при чем тут stub/mock и макось. Если это то о чем я подумал, то оно вроде вообще не имеет привязки к операционной системе. Поэтому все что вы хотите сделать, все возможно. На Rust/С/С++/CUDA можно писать и запускать на разных платформах, главное собрать правильно.antage
16.04.2019 08:59- Уверены? Rust умеет компилировать в nvptx.
M00nL1ght Автор
16.04.2019 09:10Вообщем да, вы правы, видел подобное, например тут и тут. Но честно говоря, я так не делал, мне проще и понятней написать кернел код на чистом CUDA C. Да и даже в статье, что я выше скинул говорится, что на данный момент поддержка nvptx и CUDA в частности делается с большими костылями и не совсем стабильная. Если есть еще какая то новая инфа по этому поводу буду рад ее услышать.
Aldrog
16.04.2019 14:58Интересно, насколько реально сделать безкостыльное GPGPU с помощью AMDGPU бэкенда LLVM?
M00nL1ght Автор
16.04.2019 16:07+1Не могу ничего сказать по поводу AMDGPU, посмотрите в сторону OpenCL если вам нужно более платформо-независимое решение.
veslemay
16.04.2019 10:38вызвать этот кернел из Rust и результат вернуть обратно.
А где у вас вызов кернела из rust?M00nL1ght Автор
16.04.2019 10:39Здесь:
unsafe { gpu_res = dot(v1.as_mut_ptr(), v2.as_mut_ptr(), VEC_SIZE); }
cosmrc
16.04.2019 11:50Раз уж на то пошло, почему было не сделать безопасную функцию, которая принимает 2 вектора, проверяет их длины и потом вызывает unsafe функцию?
В данной реализации это выглядит как пишем на С на Расте.M00nL1ght Автор
16.04.2019 12:11Так и надо сделать если писать полноценное приложение. Код из данного туториала конечно нельзя использовать в реальных задачах, да и зачем, ведь он просто считает произведение векторов, ничего полезного по сути он не делает. Это всего лишь небольшой пример, инструкция, как соединить Rust и CUDA. Далее на основе этого уже можно брать и писать «правильный» и «безопасный» unsafe код, оборачивать еще дополнительно во все то, что нужно.
veslemay
16.04.2019 15:18Это не вызов кернела. Это вызов си-функции из раста. К куде, кернелу и прочему не имеет никакого отношения.
А вызов кернела вот:
dot__<<<blocksPerGrid, threadsPerBlock>>>(dev_v1, dev_v2, dev_res, (int)N);
M00nL1ght Автор
16.04.2019 15:38+1Конечно, формально это вызов си-функции которая вызывает кернел, из раста нарямую нет смысла вызывать функцию кернела, просто потому, что помимо самого кернела, если вы посмотрите внимательно в файл dot_gpu.cu, есть предшествующий код в виде инициализации памяти на GPU, копирование переменных в GPU и тд, это все уже относится к СUDA.
Так что вызов си-функции обвязки из раста в которой находится CUDA-зависимый код в том числе вызов самого кернела и которая возвращает результат работы кернела, можно грубо назвать «вызовом кернела из раста».veslemay
16.04.2019 15:51формально это вызов си-функци
Это просто вызов си-функции. Какая она — это неважно.
что помимо самого кернела, если вы посмотрите внимательно в файл dot_gpu.cu, есть предшествующий код в виде инициализации памяти на GPU
Я знаю, строчка упомянутая мною именно оттуда.
это все уже относится к СUDA.
Это обычный сишный рантайм. К тому же это неважно, ведь даже если мы определяем по границе cuda, то границей является gpu_dot. И даже если принять вашу логику — кернелом будет gpu_dot, но вы её не вызываете — вы вызываете обёртку над gpu_dot.
Так что вызов си-функции обвязки из раста в которой находится CUDA-зависимый код в том числе вызов самого кернела и которая возвращает результат работы кернела, можно грубо назвать «вызовом кернела из раста».
Нельзя. Это вызов обёртки dot, которая вызывает обёртку, которая вызывает кернел. dot никоим образом не вызвает кернел.
Поэтому как это не называй — это обычный вызов сишной функции из раста. Этот вызов никак не связан с cuda, вообще никак.M00nL1ght Автор
16.04.2019 16:03Нельзя. Это вызов обёртки dot, которая вызывает обёртку, которая вызывает кернел. dot никоим образом не вызвает кернел.
Эту обертку поверх обертки можно убрать, не принципиально, тут уже на ваш вкус и зависит от архитектурного решения вашего приложения. В статье приведен лишь пример не более.
Поэтому как это не называй — это обычный вызов сишной функции из раста. Этот вызов никак не связан с cuda, вообще никак.
Да, это обычный вызов сишной функции в которой мы можем писать CUDA код (а можем и не писать, можем ее вообще не вызывать), спорить не буду, можете называть этот вызов функции как вам удобно)veslemay
16.04.2019 16:27Эту обертку поверх обертки можно убрать, не принципиально, тут уже на ваш вкус и зависит от архитектурного решения вашего приложения. В статье приведен лишь пример не более.
Это ничего не изменит.
Да, это обычный вызов сишной функции в которой мы можем писать CUDA код (а можем и не писать, можем ее вообще не вызывать), спорить не буду, можете называть этот вызов функции как вам удобно)
Ну это принципиальный момент. По-сути из статьи можно выкинуть cuda и ничего не изменится, т.к. статья чисто про вызов сишной функции из раста.M00nL1ght Автор
16.04.2019 16:57+1Это ничего не изменит.
Изменит)
И даже если принять вашу логику — кернелом будет gpu_dot, но вы её не вызываете — вы вызываете обёртку над gpu_dot.
Если принять мою логику то gpu_dot будет кернелом а уже эту функцию я могу вызвать из раст) Так что если принять мою логику то это изменит кое что.
Ну это принципиальный момент. По-сути из статьи можно выкинуть cuda и ничего не изменится, т.к. статья чисто про вызов сишной функции из раста.
Этот туториал в том числе можно использовать как инструкцию для вызова обычного си кода из раст, в комментариях это уже упоминалось, посмотрите выше. В статье рассказывается как писать и собирать Rust + С + CUDA C.veslemay
16.04.2019 17:58Изменит)
Нет, это не будет вызовом кернела. Это доказано мною выше.
Если принять мою логику то gpu_dot будет кернелом а уже эту функцию я могу вызвать из раст) Так что если принять мою логику то это изменит кое что.
Это логика несостоятельна. К тому же, зачем задним числом пытаться что-то изменять? Пока у вас gpu_dot не вызвается из раста — всё эти рассуждения не имеют смысла, т.к. вы показывали не это.
Этот туториал в том числе можно использовать как инструкцию для вызова обычного си кода из раст, в комментариях это уже упоминалось, посмотрите выше. В статье рассказывается как писать и собирать Rust + С + CUDA C.
Какое отношение к вызову си кода из раст имеет куда? Никакого. Как минимум вы должны написать «вызов си кода из раста НА ПРИМЕРЕ куды», но опять же — это будет полной глупостью, т.к. куда тут вообще не при делах.
Тоже самое и со сборкой. «сборка С++ кода через cargo на примере куда», но опять же — куда тут никаким образом не относится к теме.
В статье рассказывается как писать и собирать Rust + С + CUDA C.
Опять же, неверно. В статье не рассказывается как писать на С++ + cuda, а даже если бы и рассказывалось — причём тут раст? Так и пишите «как писать на С++ + cuda».
Я вам дам правильный заголовок. «пишем на С++ + cuda c» + «вызываем написанный таким образом код из раста». То, что написано у вас — неверно.M00nL1ght Автор
16.04.2019 18:11Когда будете писать свою статью назовете её как захотите. Я больше спорить с вами не буду, потому что у меня сложилось впечатление, что вы просто придираетесь к словам и к постановке предложений, ищите какой то тайный смысл в них.
veslemay
16.04.2019 18:15-1Ну т.е. статья — это чисто желтуха для «похайпить» на популярном базворде. Никакой куды нет, никакого раста нет, никакого вызова кернелов нет. Статье «вызываем си-фукцию из раста» никто бы 100 плюсов не наставил бы.
M00nL1ght Автор
16.04.2019 18:27Все с вами ясно, пустил в коменты на свою голову) вам заняться нечем? Так пишите свои статьи на чисто «похайпить», я вам что мешаю? Мне на это абсолютно все равно, не судите по себе других. Повторюсь, статья о том, как собрать Rust + С + CUDA и прописать зависимости, что бы все работало, а не о том как писать на CUDA, это все написано в самом начале. Это даже не статья, это небольшой туториал, все это так же отмечено в самом начале под заголовком, прочтите внимательно еще раз.
veslemay
16.04.2019 19:39Хорошо, про желтый заголовок я уже сказал — ответа не было. Пошло игнорирование. Теперь касательно текста:
И в качестве примера напишем небольшую программу на Rust для вычисления скалярного произведения векторов, вычисление скалярного произведения будет производиться на GPU с использованием CUDA C.
Где вы написали на rust программу для «вычисления скалярного произведения векторов»? Я отвечу — нигде. Вы написали программу вызова си-функции из rust.
Я слушаю про внимательность и тому подобное.M00nL1ght Автор
16.04.2019 20:47Где вы написали на rust программу для «вычисления скалярного произведения векторов»?
Открываем файл main.rs (.rs — означает, что он написан на языке Rust). Коментируем unsafe блок кода. Получаем такой файл:
main.rsextern crate libc; extern crate rand; use libc::{c_float, size_t}; use rand::Rng; const VEC_SIZE: usize = 10; const MAX: f32 = 10.; const MIN: f32 = 0.; extern "C" { fn dot(v1: *mut c_float, v2: *mut c_float, N: size_t) -> c_float; } fn cpu_dot(v1: Vec<f32>, v2: Vec<f32>) -> f32 { let mut res: f32 = 0.; for i in 0..v1.len() { res += v1[i] * v2[i]; } return res; } fn main() { let mut v1: Vec<f32> = Vec::new(); let mut v2: Vec<f32> = Vec::new(); let mut gpu_res: c_float; let mut cpu_res: f32 = 0.; let mut rng = rand::thread_rng(); for _ in 0..VEC_SIZE { v1.push(rng.gen_range(MIN, MAX)); v2.push(rng.gen_range(MIN, MAX)); } println!("{:?}", v1); println!("{:?}", v2); println!("GPU computing started"); // unsafe { //gpu_res = dot(v1.as_mut_ptr(), v2.as_mut_ptr(), VEC_SIZE); // } println!("GPU computing finished"); println!("GPU dot product result: {}", gpu_res); cpu_res = cpu_dot(v1, v2); println!("CPU dot product result: {}", cpu_res); }
veslemay
16.04.2019 20:54-1Какая наивность, какое враньё.
в нем ищем функцию cpu_dot, которая вычисляет скалярное произведение двух векторов на языке Rust
И получаем файл, т.к. спастил я цитату не полностью:
И в качестве примера напишем небольшую программу на Rust для вычисления скалярного произведения векторов, вычисление скалярного произведения будет производиться на GPU
вычисление скалярного произведения будет производиться на GPU
Здесь уточнение, как именно будет происходить вычисление «скалярного произведения» в «программу на Rust».
А что вы пытаетесь мне подсунуть?
cpu_dot
Который вы только что придумали, но самом деле вы то имели ввиду другое, и я это знаю, и я это доказал выше.
emerald_isle
16.04.2019 18:00+2Так просто — не получится. Это надо компилятор переписывать. На GPU вычисляется только часть кода, который ещё туда надо как-то загрузить. CUDA — только один из вариантов, есть ещё OpenCL, который чуть более переносимый, но тоже со своими проблемами.
Архитектура GPU немного другая, соответственно, код там нужен другой. Цикл примерно такой, host-приложение пишется на любом языке (чаще C), подготавливает данные, программу для GPU — отправляет туда, ждёт результата, потом вытягивает результат, и, например, печатает в терминал (или GUI/веб-сервис, или куда ещё вам надо). Цикл может повторяться несколько раз, и во время ожидания результата с GPU можно фактически ещё чем-то полезным заниматься в других потоках… тут не уверен — на CUDA не писал ничего сложнее простеньких приложений, скидывающих результат в консоль или csv-табличку, или рисующих график, например — поэтому необходимости такой не было.0xd34df00d
18.04.2019 18:32Это надо компилятор переписывать.
Бекенд, на самом деле.
Я году в 2016-м или 2017-м игрался с оффлоадингом вычислений из кода на плюсах на видеокарту, ну чтобы я писал вот обычный какой-нибудь цикл для перемножения двух векторов, а clang мне генерировал бинарь для CPU + код для GPU + код для связи между ними. Там даже что-то получалось и уходило на видеокарту, но производительность у этого решения была отвратная, я и забил. Может, сейчас получше стало.
Gymmasssorla
Полезная статья, однозначно плюсую.