Introdução
Olá, Habr!
Pelos meus padrões, já escrevo código C ++ há muito tempo, mas até então não havia encontrado tarefas relacionadas à computação paralela. Não vi um único artigo sobre a biblioteca Boost.Compute, então este artigo será sobre ela.
Todas as partes
- Parte 1
- Parte 2
Conteúdo
- O que é boost.compute
- Problemas para conectar boost.compute ao projeto
- Introdução a boost.compute
- Classes de computação básicas
- Começando
- Conclusão
O que é boost.compute
Esta biblioteca c ++ fornece uma interface simples de alto nível para interagir com CPU multinúcleo e dispositivos de computação GPU. Esta biblioteca foi adicionada pela primeira vez para aumentar na versão 1.61.0 e ainda é suportada.
Problemas para conectar boost.compute ao projeto
E então, eu tive alguns problemas ao usar esta biblioteca. Um deles era que a biblioteca simplesmente não funciona sem o OpenCL. O compilador dá o seguinte erro:

Depois de conectar tudo deve compilar corretamente.
À custa da biblioteca boost, ele pode ser baixado e conectado a um projeto do Visual Studio usando o gerenciador de pacotes NuGet.
Introdução a boost.compute
Depois de instalar todos os componentes necessários, você pode examinar partes simples de código. Para o correto funcionamento, basta habilitar o módulo de computação assim:
#include <boost/compute.hpp>
using namespace boost;
É importante notar que os contêineres stl regulares não são adequados para uso em algoritmos de namespace de computação. Em vez disso, existem contêineres especialmente criados que não entram em conflito com os padrão. Código de amostra:
std::vector<float> std_vector(10);
compute::vector<float> compute_vector(std_vector.begin(), std_vector.end(), queue);
// , .
Você pode usar a função copy () para converter de volta para std :: vector:
compute::copy(compute_vector.begin(), compute_vector.end(), std_vector.begin(), queue);
Classes de computação básicas
A biblioteca inclui três classes auxiliares, que são suficientes para começar com cálculos em uma placa de vídeo e / ou processador:
- compute :: device (irá determinar com qual dispositivo iremos trabalhar)
- compute :: context (um objeto desta classe armazena recursos OpenCL, incluindo buffers de memória e outros objetos)
- compute :: command_queue (fornece uma interface para interagir com um dispositivo de computação)
Você pode declarar tudo isso assim:
auto device = compute::system::default_device(); //
auto context = compute::context::context(device); //
auto queue = compute::command_queue(context, device); //
Mesmo usando apenas a primeira linha do código acima, você pode garantir que tudo funcione como deveria executando o seguinte código:
std::cout << device.name() << std::endl;
Assim, obtivemos o nome do dispositivo no qual realizaremos os cálculos. Resultado (você pode ter algo diferente):

Começando
Vejamos as funções trasform () e reduce () por exemplo:
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;
Ao executar o código acima, você verá o seguinte resultado:

Eu me decidi por esses dois métodos porque eles mostram bem o trabalho primitivo com cálculos paralelos sem tudo supérfluo.
E assim, a função transform () é usada para alterar um array de dados (ou dois arrays, se os estivermos passando) aplicando uma função a todos os valores.
transform(com_vec.begin(),
com_vec.end(),
buff_result.begin(),
compute::sqrt<float>(),
queue);
Vamos prosseguir com a análise dos argumentos, com os dois primeiros argumentos passamos um vetor de dados de entrada, com o terceiro argumento passamos um ponteiro para o início do vetor no qual escreveremos o resultado, com o próximo argumento indicamos o que precisamos fazer. No exemplo acima, estamos usando uma das funções de processamento vetorial padrão, que é extrair a raiz quadrada. Claro, você pode escrever uma função personalizada, boost nos fornece duas maneiras inteiras, mas este já é o material para a próxima parte (se houver alguma). Bem, como último argumento, passamos um objeto da classe compute :: command_queue, sobre a qual falei acima.
A próxima função é reduzir (), tudo é um pouco mais interessante aqui. Este método retorna o resultado da aplicação do quarto argumento a todos os elementos do vetor.
compute::reduce(com_vec.begin(),
com_vec.end(),
&reduce_result,
compute::plus<float>(),
queue);
Agora vou explicar com um exemplo, o código acima pode ser comparado com a seguinte equação:
No nosso caso, obtemos a soma de todos os elementos do array.
Conclusão
Bem, isso é tudo, acho que isso é o suficiente para realizar operações simples em big data. Agora você pode usar a funcionalidade primitiva da biblioteca boost.compute e também pode evitar alguns erros ao trabalhar com esta biblioteca.
Eu ficaria feliz em receber um feedback positivo. Obrigado pelo seu tempo.
Boa sorte a todos!