Всем привет!

В данном руководстве хочу рассказать как подружить 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.

Скалярное произведение двух векторов.
Пусть у нас есть два вектора: $a=[a_1,a_2,...a_n]$ и $b=[b_1,b_2,...,b_n]$, скалярное произведение этих векторов:

$a \cdot b = \sum_{i=1}^{n}{a_ib_i}$



Начнем создание нашей программы. Далее я предполагаю, что 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"])

Весь код файла build.rs
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);
}

Полный код файла main.rs
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;
  }
}

Полный код файла dot.cpp
#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.

dot_gpu.cu
#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)


  1. Gymmasssorla
    15.04.2019 16:58
    +2

    Полезная статья, однозначно плюсую.


  1. tongohiti
    15.04.2019 20:14

    Логичным продолжением был бы пост про, собственно, программирование с использованием CUDA…


    1. 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 используется, как и раньше использовалась. Статей, как использовать, на хабре достаточно.


    1. M00nL1ght Автор
      15.04.2019 23:33
      +3

      Одного поста не хватит, что бы описать программирование с использованием CUDA. В данной статье я лишь показал как использовать CUDA и Rust вместе. И как заметили выше сама по себе CUDA используется как обычно. А так, возможно, в дальнейшем еще опишу некоторые интересные моменты именно программирования CUDA в связке с Rust.


  1. masai
    15.04.2019 21:42
    +1

    А почему .cpp, если все равно всё в extern "C"?


    1. 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?


      1. masai
        16.04.2019 02:07
        +1

        Мой вопрос всё же был не о том, что такое extern "C". Просто C++ тут только ради <iostream> и cout. Можно было и на C всё написать вполне.


        1. M00nL1ght Автор
          16.04.2019 08:48

          .cpp не ради iostream и cout, .cpp вообще здесь не ради чего то, в нем нет глубокого смысла и необходимости, можно вообще все запихнуть в один файл .c и написать в нем и функцию обертку которая будет вызываться из Rust и CUDA кернел. Я это сделал просто так, дабы показать возможность линковки нескольких исходных файлов (.cpp в частности) в добавок ко всему.


        1. emerald_isle
          16.04.2019 17:56

          Не совсем так даже. Скорее дело в том, что nvcc на самом деле компилятор (расширенный) C++, а не C, по каким-то причинам.


  1. balsoft
    15.04.2019 23:31
    +1

    Этот туториал на самом деле подойдёт для большинства других сишных библиотек.


    1. M00nL1ght Автор
      15.04.2019 23:36
      +1

      По сути да. И не только сишных, и с++ — шных тоже. В случае с CUDA самое главное это прописать пути к исполняемым файлам, что бы Rust корректно подгрузил их как динамическую либу.


      1. veslemay
        16.04.2019 15:27

        что бы Rust корректно подгрузил их как динамическую либу.

        Раст ничего никуда не подгружает. Проблема в отсутствии прописанных путей/кривой линковке. С растом это никак не связано.


  1. pronvis
    16.04.2019 00:12
    +2

    1. То есть непосредственно логику вычисления на GPU на расте написать не получится?
    2. Есть ли под макось что-нибудь вроде стаба/мока для cuda? Я хочу разрабатывать и тестить на маке, но запускать на убунте где у меня 1080TI


    1. M00nL1ght Автор
      16.04.2019 08:42

      1. Нет, не получится. Код для вычисления на GPU все так же пишется на CUDA C. Да и честно говоря не вижу смысла переносить CUDA на Rust если достаточно в данном случае написать кернел для необходимых вычислений, вызвать этот кернел из Rust и результат вернуть обратно.

      2. Не совсем понял при чем тут stub/mock и макось. Если это то о чем я подумал, то оно вроде вообще не имеет привязки к операционной системе. Поэтому все что вы хотите сделать, все возможно. На Rust/С/С++/CUDA можно писать и запускать на разных платформах, главное собрать правильно.


      1. antage
        16.04.2019 08:59

        1. Уверены? Rust умеет компилировать в nvptx.


        1. M00nL1ght Автор
          16.04.2019 09:10

          Вообщем да, вы правы, видел подобное, например тут и тут. Но честно говоря, я так не делал, мне проще и понятней написать кернел код на чистом CUDA C. Да и даже в статье, что я выше скинул говорится, что на данный момент поддержка nvptx и CUDA в частности делается с большими костылями и не совсем стабильная. Если есть еще какая то новая инфа по этому поводу буду рад ее услышать.


          1. Aldrog
            16.04.2019 14:58

            Интересно, насколько реально сделать безкостыльное GPGPU с помощью AMDGPU бэкенда LLVM?


            1. M00nL1ght Автор
              16.04.2019 16:07
              +1

              Не могу ничего сказать по поводу AMDGPU, посмотрите в сторону OpenCL если вам нужно более платформо-независимое решение.


            1. Gorthauer87
              16.04.2019 18:21

              Вообще есть попытки прикрутить SPIRV
              https://github.com/MaikKlein/rlsl


              1. Aldrog
                17.04.2019 12:29

                Я имел в виду аналог амдшных hcc и HIP для C++, только для Rust.
                SPIR-V интереснее, т.к. более переносимо, но должно быть сложнее в реализации (hcc, как и rustc использует LLVM).


      1. veslemay
        16.04.2019 10:38

        вызвать этот кернел из Rust и результат вернуть обратно.

        А где у вас вызов кернела из rust?


        1. M00nL1ght Автор
          16.04.2019 10:39

          Здесь:

          unsafe {
            gpu_res = dot(v1.as_mut_ptr(), v2.as_mut_ptr(), VEC_SIZE);
          }
          


          1. cosmrc
            16.04.2019 11:50

            Раз уж на то пошло, почему было не сделать безопасную функцию, которая принимает 2 вектора, проверяет их длины и потом вызывает unsafe функцию?
            В данной реализации это выглядит как пишем на С на Расте.


            1. M00nL1ght Автор
              16.04.2019 12:11

              Так и надо сделать если писать полноценное приложение. Код из данного туториала конечно нельзя использовать в реальных задачах, да и зачем, ведь он просто считает произведение векторов, ничего полезного по сути он не делает. Это всего лишь небольшой пример, инструкция, как соединить Rust и CUDA. Далее на основе этого уже можно брать и писать «правильный» и «безопасный» unsafe код, оборачивать еще дополнительно во все то, что нужно.


          1. veslemay
            16.04.2019 15:18

            Это не вызов кернела. Это вызов си-функции из раста. К куде, кернелу и прочему не имеет никакого отношения.

            А вызов кернела вот:

            	dot__<<<blocksPerGrid, threadsPerBlock>>>(dev_v1, dev_v2, dev_res, (int)N);
            


            1. M00nL1ght Автор
              16.04.2019 15:38
              +1

              Конечно, формально это вызов си-функции которая вызывает кернел, из раста нарямую нет смысла вызывать функцию кернела, просто потому, что помимо самого кернела, если вы посмотрите внимательно в файл dot_gpu.cu, есть предшествующий код в виде инициализации памяти на GPU, копирование переменных в GPU и тд, это все уже относится к СUDA.

              Так что вызов си-функции обвязки из раста в которой находится CUDA-зависимый код в том числе вызов самого кернела и которая возвращает результат работы кернела, можно грубо назвать «вызовом кернела из раста».


              1. veslemay
                16.04.2019 15:51

                формально это вызов си-функци

                Это просто вызов си-функции. Какая она — это неважно.

                что помимо самого кернела, если вы посмотрите внимательно в файл dot_gpu.cu, есть предшествующий код в виде инициализации памяти на GPU

                Я знаю, строчка упомянутая мною именно оттуда.

                это все уже относится к СUDA.

                Это обычный сишный рантайм. К тому же это неважно, ведь даже если мы определяем по границе cuda, то границей является gpu_dot. И даже если принять вашу логику — кернелом будет gpu_dot, но вы её не вызываете — вы вызываете обёртку над gpu_dot.

                Так что вызов си-функции обвязки из раста в которой находится CUDA-зависимый код в том числе вызов самого кернела и которая возвращает результат работы кернела, можно грубо назвать «вызовом кернела из раста».

                Нельзя. Это вызов обёртки dot, которая вызывает обёртку, которая вызывает кернел. dot никоим образом не вызвает кернел.

                Поэтому как это не называй — это обычный вызов сишной функции из раста. Этот вызов никак не связан с cuda, вообще никак.


                1. M00nL1ght Автор
                  16.04.2019 16:03

                  Нельзя. Это вызов обёртки dot, которая вызывает обёртку, которая вызывает кернел. dot никоим образом не вызвает кернел.


                  Эту обертку поверх обертки можно убрать, не принципиально, тут уже на ваш вкус и зависит от архитектурного решения вашего приложения. В статье приведен лишь пример не более.

                  Поэтому как это не называй — это обычный вызов сишной функции из раста. Этот вызов никак не связан с cuda, вообще никак.


                  Да, это обычный вызов сишной функции в которой мы можем писать CUDA код (а можем и не писать, можем ее вообще не вызывать), спорить не буду, можете называть этот вызов функции как вам удобно)


                  1. veslemay
                    16.04.2019 16:27

                    Эту обертку поверх обертки можно убрать, не принципиально, тут уже на ваш вкус и зависит от архитектурного решения вашего приложения. В статье приведен лишь пример не более.

                    Это ничего не изменит.

                    Да, это обычный вызов сишной функции в которой мы можем писать CUDA код (а можем и не писать, можем ее вообще не вызывать), спорить не буду, можете называть этот вызов функции как вам удобно)

                    Ну это принципиальный момент. По-сути из статьи можно выкинуть cuda и ничего не изменится, т.к. статья чисто про вызов сишной функции из раста.


                    1. M00nL1ght Автор
                      16.04.2019 16:57
                      +1

                      Это ничего не изменит.


                      Изменит)

                      И даже если принять вашу логику — кернелом будет gpu_dot, но вы её не вызываете — вы вызываете обёртку над gpu_dot.


                      Если принять мою логику то gpu_dot будет кернелом а уже эту функцию я могу вызвать из раст) Так что если принять мою логику то это изменит кое что.

                      Ну это принципиальный момент. По-сути из статьи можно выкинуть cuda и ничего не изменится, т.к. статья чисто про вызов сишной функции из раста.


                      Этот туториал в том числе можно использовать как инструкцию для вызова обычного си кода из раст, в комментариях это уже упоминалось, посмотрите выше. В статье рассказывается как писать и собирать Rust + С + CUDA C.


                      1. veslemay
                        16.04.2019 17:58

                        Изменит)

                        Нет, это не будет вызовом кернела. Это доказано мною выше.

                        Если принять мою логику то gpu_dot будет кернелом а уже эту функцию я могу вызвать из раст) Так что если принять мою логику то это изменит кое что.

                        Это логика несостоятельна. К тому же, зачем задним числом пытаться что-то изменять? Пока у вас gpu_dot не вызвается из раста — всё эти рассуждения не имеют смысла, т.к. вы показывали не это.

                        Этот туториал в том числе можно использовать как инструкцию для вызова обычного си кода из раст, в комментариях это уже упоминалось, посмотрите выше. В статье рассказывается как писать и собирать Rust + С + CUDA C.


                        Какое отношение к вызову си кода из раст имеет куда? Никакого. Как минимум вы должны написать «вызов си кода из раста НА ПРИМЕРЕ куды», но опять же — это будет полной глупостью, т.к. куда тут вообще не при делах.

                        Тоже самое и со сборкой. «сборка С++ кода через cargo на примере куда», но опять же — куда тут никаким образом не относится к теме.

                        В статье рассказывается как писать и собирать Rust + С + CUDA C.

                        Опять же, неверно. В статье не рассказывается как писать на С++ + cuda, а даже если бы и рассказывалось — причём тут раст? Так и пишите «как писать на С++ + cuda».

                        Я вам дам правильный заголовок. «пишем на С++ + cuda c» + «вызываем написанный таким образом код из раста». То, что написано у вас — неверно.


                        1. M00nL1ght Автор
                          16.04.2019 18:11

                          Когда будете писать свою статью назовете её как захотите. Я больше спорить с вами не буду, потому что у меня сложилось впечатление, что вы просто придираетесь к словам и к постановке предложений, ищите какой то тайный смысл в них.


                          1. veslemay
                            16.04.2019 18:15
                            -1

                            Ну т.е. статья — это чисто желтуха для «похайпить» на популярном базворде. Никакой куды нет, никакого раста нет, никакого вызова кернелов нет. Статье «вызываем си-фукцию из раста» никто бы 100 плюсов не наставил бы.


                            1. M00nL1ght Автор
                              16.04.2019 18:27

                              Все с вами ясно, пустил в коменты на свою голову) вам заняться нечем? Так пишите свои статьи на чисто «похайпить», я вам что мешаю? Мне на это абсолютно все равно, не судите по себе других. Повторюсь, статья о том, как собрать Rust + С + CUDA и прописать зависимости, что бы все работало, а не о том как писать на CUDA, это все написано в самом начале. Это даже не статья, это небольшой туториал, все это так же отмечено в самом начале под заголовком, прочтите внимательно еще раз.


                              1. veslemay
                                16.04.2019 19:39

                                Хорошо, про желтый заголовок я уже сказал — ответа не было. Пошло игнорирование. Теперь касательно текста:

                                И в качестве примера напишем небольшую программу на Rust для вычисления скалярного произведения векторов, вычисление скалярного произведения будет производиться на GPU с использованием CUDA C.

                                Где вы написали на rust программу для «вычисления скалярного произведения векторов»? Я отвечу — нигде. Вы написали программу вызова си-функции из rust.

                                Я слушаю про внимательность и тому подобное.


                                1. M00nL1ght Автор
                                  16.04.2019 20:47

                                  Где вы написали на rust программу для «вычисления скалярного произведения векторов»?

                                  Открываем файл main.rs (.rs — означает, что он написан на языке Rust). Коментируем unsafe блок кода. Получаем такой файл:
                                  main.rs
                                  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);
                                  }


                                  1. veslemay
                                    16.04.2019 20:54
                                    -1

                                    Какая наивность, какое враньё.

                                    в нем ищем функцию cpu_dot, которая вычисляет скалярное произведение двух векторов на языке Rust


                                    И получаем файл, т.к. спастил я цитату не полностью:

                                    И в качестве примера напишем небольшую программу на Rust для вычисления скалярного произведения векторов, вычисление скалярного произведения будет производиться на GPU


                                    вычисление скалярного произведения будет производиться на GPU

                                    Здесь уточнение, как именно будет происходить вычисление «скалярного произведения» в «программу на Rust».

                                    А что вы пытаетесь мне подсунуть?
                                    cpu_dot

                                    Который вы только что придумали, но самом деле вы то имели ввиду другое, и я это знаю, и я это доказал выше.


                                    1. M00nL1ght Автор
                                      16.04.2019 20:57

                                      и я это доказал выше.

                                      поздравляю) удачи вам, обсуждение можно считать закрытым.


                                      1. veslemay
                                        16.04.2019 21:48
                                        -1

                                        Удобно — наврал, на вранье поймали и «закрыто».


    1. emerald_isle
      16.04.2019 18:00
      +2

      Так просто — не получится. Это надо компилятор переписывать. На GPU вычисляется только часть кода, который ещё туда надо как-то загрузить. CUDA — только один из вариантов, есть ещё OpenCL, который чуть более переносимый, но тоже со своими проблемами.

      Архитектура GPU немного другая, соответственно, код там нужен другой. Цикл примерно такой, host-приложение пишется на любом языке (чаще C), подготавливает данные, программу для GPU — отправляет туда, ждёт результата, потом вытягивает результат, и, например, печатает в терминал (или GUI/веб-сервис, или куда ещё вам надо). Цикл может повторяться несколько раз, и во время ожидания результата с GPU можно фактически ещё чем-то полезным заниматься в других потоках… тут не уверен — на CUDA не писал ничего сложнее простеньких приложений, скидывающих результат в консоль или csv-табличку, или рисующих график, например — поэтому необходимости такой не было.


      1. 0xd34df00d
        18.04.2019 18:32

        Это надо компилятор переписывать.

        Бекенд, на самом деле.


        Я году в 2016-м или 2017-м игрался с оффлоадингом вычислений из кода на плюсах на видеокарту, ну чтобы я писал вот обычный какой-нибудь цикл для перемножения двух векторов, а clang мне генерировал бинарь для CPU + код для GPU + код для связи между ними. Там даже что-то получалось и уходило на видеокарту, но производительность у этого решения была отвратная, я и забил. Может, сейчас получше стало.