Boost Computación o computación paralela GPU / CPU. Parte 1

Introducción



¡Hola, Habr!



Según mis estándares, he estado escribiendo código C ++ durante mucho tiempo, pero hasta ese momento todavía no había encontrado tareas relacionadas con la computación paralela. No he visto un solo artículo sobre la biblioteca de Boost.Compute, así que este artículo será sobre eso.

Todas las partes





Contenido



  • Que es boost.compute
  • Problemas para conectar boost.compute con el proyecto
  • Introducción a boost.compute
  • Clases de computación básica
  • Empezando
  • Conclusión


Que es boost.compute



Esta biblioteca c ++ proporciona una interfaz simple de alto nivel para interactuar con dispositivos informáticos de CPU y GPU de múltiples núcleos. Esta biblioteca se agregó por primera vez para impulsar en la versión 1.61.0 y todavía es compatible.



Problemas para conectar boost.compute con el proyecto



Y así, encontré algunos problemas al usar esta biblioteca. Uno de ellos fue que la biblioteca simplemente no funciona sin OpenCL. El compilador da el siguiente error:



imagen



Después de conectar todo debería compilarse correctamente.



A expensas de la biblioteca boost, se puede descargar y conectar a un proyecto de Visual Studio mediante el administrador de paquetes NuGet.



Introducción a boost.compute



Después de instalar todos los componentes necesarios, puede ver fragmentos de código simples. Para un correcto funcionamiento, basta con habilitar el módulo de cálculo de esta forma:



#include <boost/compute.hpp>
using namespace boost;


Vale la pena señalar que los contenedores stl regulares no son adecuados para su uso en algoritmos de espacio de nombres informáticos. En cambio, hay contenedores especialmente creados que no entran en conflicto con los estándar. Código de muestra:



std::vector<float> std_vector(10);
compute::vector<float> compute_vector(std_vector.begin(), std_vector.end(), queue); 
//       ,     .


La función copy () se puede usar para volver a convertir a std :: vector:



compute::copy(compute_vector.begin(), compute_vector.end(), std_vector.begin(), queue);


Clases de computación básica



La biblioteca incluye tres clases auxiliares, que son suficientes para comenzar con los cálculos en una tarjeta de video y / o procesador:



  • compute :: device (determinará con qué dispositivo trabajaremos)
  • compute :: context (un objeto de esta clase almacena recursos OpenCL, incluidos búferes de memoria y otros objetos)
  • compute :: command_queue (proporciona una interfaz para interactuar con un dispositivo informático)


Puedes declarar todo esto así:



auto device = compute::system::default_device(); //     
auto context = compute::context::context(device); //   
auto queue = compute::command_queue(context, device); //   


Incluso usando la primera línea del código anterior, puede asegurarse de que todo funcione como debería ejecutando el siguiente código:



std::cout << device.name() << std::endl; 


Así, obtuvimos el nombre del dispositivo en el que realizaremos los cálculos. Resultado (puede tener algo diferente):



imagen



Empezando



Veamos las funciones trasform () y reduce () por ejemplo:



std::vector<float> host_vec = {1, 4, 9};

compute::vector<float> com_vec(host_vec.begin(), host_vec.end(), queue);
//           
//  copy()

compute::vector<float> buff_result(host_vec.size(), context);
transform(com_vec.begin(), com_vec.end(), buff_result.begin(), compute::sqrt<float>(), queue);

std::vector<float> transform_result(host_vec.size());
compute::copy(buff_result.begin(), buff_result.end(), transform_result.begin(), queue);
	
cout << "Transforming result: ";
for (size_t i = 0; i < transform_result.size(); i++)
{
	cout << transform_result[i] << " ";
}
cout << endl;

float reduce_result;
compute::reduce(com_vec.begin(), com_vec.end(), &reduce_result, compute::plus<float>(),queue);

cout << "Reducing result: " << reduce_result << endl;


Cuando ejecute el código anterior, debería ver el siguiente resultado:



imagen



Me decidí por estos dos métodos porque muestran bien el trabajo primitivo con cálculos paralelos sin todo lo superfluo.



Y así, la función transform () se usa para cambiar una matriz de datos (o dos matrices, si las estamos pasando) aplicando una función a todos los valores.



transform(com_vec.begin(), 
   com_vec.end(), 
   buff_result.begin(), 
   compute::sqrt<float>(), 
   queue);


Pasemos al análisis de los argumentos, con los dos primeros argumentos pasamos un vector de datos de entrada, con el tercer argumento pasamos un puntero al principio del vector en el que escribiremos el resultado, con el siguiente argumento indicamos lo que tenemos que hacer. En el ejemplo anterior, estamos usando una de las funciones de procesamiento de vectores estándar, que es extraer la raíz cuadrada. Por supuesto, puede escribir una función personalizada, boost nos proporciona dos formas completas, pero este ya es el material para la siguiente parte (si es que hay alguna). Bueno, como último argumento, pasamos un objeto de la clase compute :: command_queue, de la que hablé anteriormente.



La siguiente función es reduce (), aquí todo es un poco más interesante. Este método devuelve el resultado de aplicar el cuarto argumento a todos los elementos del vector.



compute::reduce(com_vec.begin(), 
   com_vec.end(), 
   &reduce_result, 
   compute::plus<float>(),
   queue);


Ahora lo explicaré con un ejemplo, el código anterior se puede comparar con la siguiente ecuación:

1+4+9

En nuestro caso, obtenemos la suma de todos los elementos de la matriz.



Conclusión



Pues eso es todo, creo que esto es suficiente para realizar operaciones sencillas sobre big data. Ahora puede utilizar la funcionalidad primitiva de la biblioteca boost.compute y también puede evitar algunos errores al trabajar con esta biblioteca.



Me encantaría recibir comentarios positivos. Gracias por tu tiempo.



¡Buena suerte a todos!



All Articles