C++ Boost Compute

Da Andreabont's Wiki.

La sottolibreria compute delle BOOST permette di utilizzare facilmente la GPU del proprio computer per effettuare calcoli, attraverso l'interfaccia OpenCL. Per funzionare il sistema deve aver correttamente configurato OpenCL.

Compilare

g++ --std=c++11 <file_cpp> -lOpenCL

Richiesta informazioni sulla GPU

#include <iostream>
#include <iomanip>
#include <boost/compute/core.hpp>


int main() {
    // get the default device
    boost::compute::device device = boost::compute::system::default_device();

    std::cout << "Name:           " << device.name() << std::endl;
    std::cout << "Vendor:         " << device.vendor() << std::endl;
    std::cout << "Profile:        " << device.profile() << std::endl;
    std::cout << "Version:        " << device.version() << std::endl;
    std::cout << "Driver version: " << device.driver_version() << std::endl;
    std::cout << "Frequency:      " << device.clock_frequency() << " MHz" << std::endl;
    std::cout << "Compute units:  " << device.compute_units() << " cores" <<std::endl;

    std::cout << "\nEXTENSIONS LOADED:\n" << std::left;
    
    int i = 1;
    for(std::string s : device.extensions()) {
        std::cout << std::setw(40) << s;
        if(i%3 == 0) std::cout << '\n';
        i++;
    }
    
    std::cout << std::endl;
    
    return 0;
}

Computazione

Per appofondire è possibile visionare la Lista delle API.

Trasformazione

#include <vector>
#include <iostream>
#include <boost/compute/algorithm/copy.hpp>
#include <boost/compute/container/vector.hpp>
#include <boost/compute/algorithm/transform.hpp>

int main() {
    
    // Ottieni device di default e contesto.
    boost::compute::device device = boost::compute::system::default_device();
    boost::compute::context context(device);
    boost::compute::command_queue queue(context, device);

    // Preparo i dati da computare
    std::vector<float> host_vector = {1,2,3,4,5,6,7};
    
    // Creo vettore sulla GPU
    boost::compute::vector<float> device_vector(host_vector.size(), context);
    
    // Copio il vettore locale su quello della GPU
    boost::compute::copy(
        std::begin(host_vector),       // Inizio vettore sorgente
        std::end(host_vector),         // Fine vettore sorgente
        std::begin(device_vector),     // Inizio vettore destinazione
        queue
    );
    
    // Applico una funzione sul vettore della GPU
    boost::compute::transform(
        std::begin(device_vector),     // Inizio vettore sorgente
        std::end(device_vector),       // Fine vettore sorgente
        std::begin(device_vector),     // Inizio vettore destinazione
        boost::compute::sqrt<float>(), // Funzione da applicare
        queue
    );

    // Copio i dati dal vettore della GPU su quello locale
    boost::compute::copy(
        std::begin(device_vector),     // Inizio vettore sorgente
        std::end(device_vector),       // Fine vettore sorgente
        std::begin(host_vector),       // Inizio vettore destinazione
        queue
    );
    
    // Ora posso usare i dati computati
    for(float i : host_vector) {
        std::cout << i << ' ';
    }
    std::cout << std::endl;

}

Accumulo

#include <vector>
#include <iostream>
#include <boost/compute/algorithm/copy.hpp>
#include <boost/compute/container/vector.hpp>
#include <boost/compute/algorithm/accumulate.hpp>

int main() {
    
    // Ottieni device di default e contesto.
    boost::compute::device device = boost::compute::system::default_device();
    boost::compute::context context(device);
    boost::compute::command_queue queue(context, device);

    // Preparo i dati da computare
    std::vector<int> host_vector = {1,2,3,4,5,6,7};
    
    // Creo vettore sulla GPU
    boost::compute::vector<int> device_vector(host_vector.size(), context);
    
    // Copio il vettore locale su quello della GPU
    boost::compute::copy(
        std::begin(host_vector),       // Inizio vettore sorgente
        std::end(host_vector),         // Fine vettore sorgente
        std::begin(device_vector),     // Inizio vettore destinazione
        queue
    );
    
    // Applico l'accumulo
    int result = boost::compute::accumulate(
        std::begin(device_vector),     // Inizio vettore sorgente
        std::end(device_vector),       // Fine vettore sorgente
        0,                             // Valore iniziale
        boost::compute::plus<int>(),   // Funzione da applicare
        queue  
    );
    
    // Risultato
    for(int i : host_vector) {
        std::cout << i;
        if(i != host_vector.back()) std::cout << " + ";
    }
    
    std::cout << " = " << result << std::endl;

}

Trasformazione + Accumulo

#include <vector>
#include <iostream>
#include <boost/compute/algorithm/copy.hpp>
#include <boost/compute/container/vector.hpp>
#include <boost/compute/functional/bind.hpp>
#include <boost/compute/algorithm/transform_reduce.hpp>
#include <boost/compute/algorithm/transform.hpp>


int main() {
    
    // Ottieni device di default e contesto.
    boost::compute::device device = boost::compute::system::default_device();
    boost::compute::context context(device);
    boost::compute::command_queue queue(context, device);

    // Preparo i dati da computare
    std::vector<float> host_vector = {1,2,3,4,5,6,7};
    
    // Creo vettore sulla GPU
    boost::compute::vector<float> device_vector(host_vector.size(), context);
    
    // Copio il vettore locale su quello della GPU
    boost::compute::copy(
        std::begin(host_vector),       // Inizio vettore sorgente
        std::end(host_vector),         // Fine vettore sorgente
        std::begin(device_vector),     // Inizio vettore destinazione
        queue
    );
    
    float result = 0;
    
    boost::compute::transform_reduce(
        std::begin(device_vector),     // Inizio vettore sorgente
        std::end(device_vector),       // Fine vettore sorgente
        &result,                       // Risultato
        boost::compute::bind(boost::compute::pow<float>(), 2.0f, boost::compute::placeholders::_1),// Funzione transform da applicare
        boost::compute::plus<float>(), // Funzione reduce da applicare
        queue  
    );
    
    std::cout << "Somma dei quadrati: " << result << std::endl;

    std::cout << std::endl;
    
}

Definire funzioni personalizzate

E' possibile definire delle funzioni personalizzate, da notare che per la trasformazione è richiesta una funzione unaria, mentre per il reduce serve una funzione binaria.

boost::compute::function<int (int)> double_int = boost::compute::make_function_from_source<int (int)> (
    "double_int",
    "int double_int(int x) { return x * 2; }"
);

Oppure sfruttando una macro:

BOOST_COMPUTE_FUNCTION(int, double_int, (int x), {
    return x * 2;
});

Copia asincrona + profilazione

#include <vector>
#include <iostream>
#include <boost/compute/core.hpp>
#include <boost/compute/container/vector.hpp>

int main()
{
    // Ottieni device di default e contesto.
    boost::compute::device device = boost::compute::system::default_device();
    boost::compute::context context(device);

    // Ottengo una coda comandi con abilitato il profiling
    boost::compute::command_queue queue(context, device, boost::compute::command_queue::enable_profiling);

    // Preparo i dati da computare
    std::vector<int> host_vector(16000000,50);
    
    // Creo vettore sulla GPU
    boost::compute::vector<int> device_vector(host_vector.size(), context);

    // Copio il vettore locale su quello della GPU in modalità asincrona
    boost::compute::future<void> future = boost::compute::copy_async(
        std::begin(host_vector),       // Inizio vettore sorgente
        std::end(host_vector),         // Fine vettore sorgente
        std::begin(device_vector),     // Inizio vettore destinazione
        queue
    );
    
    std::cout << "Trasferimento dati..." << std::endl;
    
    // Attendo che la copia sia completata
    future.wait();

    // Ottengo tempo di esecuzione dulla GPU
    boost::chrono::milliseconds duration = future.get_event().duration<boost::chrono::milliseconds>();

    std::cout << "Copia completeta in " << duration.count() << " ms" << std::endl;
    
}