C++ Boost Compute

Da Andreabont's Wiki.
Versione del 8 ago 2020 alle 09:53 di Andreabont (Discussione | contributi)
(diff) ← Versione meno recente | Versione attuale (diff) | Versione più recente → (diff)

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 sui device utilizzabili

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

std::string device_type_to_string(cl_device_type type) {
    
    switch(type) {

        case CL_DEVICE_TYPE_ACCELERATOR:
            return "ACCELERATOR";
        
        case CL_DEVICE_TYPE_CPU:
            return "CPU";
        
        case CL_DEVICE_TYPE_GPU:
            return "GPU";
        
        case CL_DEVICE_TYPE_ALL:
            return "ALL";
            
        case CL_DEVICE_TYPE_DEFAULT:
            return "DEFAULT";
            
        default:
            return "Unknow";
        
    }
    
}

int main() {

    boost::compute::device default_device = boost::compute::system::default_device();
    
    for(boost::compute::device device : boost::compute::system::devices()) {
        
        std::cout << "• OPENCL DEVICE:" << std::endl;
        
        std::cout << "Default:        " << std::boolalpha << (device == default_device) << std::endl;
        std::cout << "Id:             " << device.id() << std::endl;
        std::cout << "Type:           " << device_type_to_string(device.type()) << std::endl;
        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 << "Global memory:  " << device.global_memory_size() / 1024 / 1024 << " Mb" << std::endl;
        
        std::cout << std::endl;

        std::cout << "Extensions:" << std::endl;
        std::cout << std::left;
        
        int i = 1;
        for(std::string s : device.extensions()) {
            std::cout << std::setw(50) << s;
            if(i%2 == 0) std::cout << '\n';
            i++;
        }
        
        std::cout << std::endl;
        std::cout << std::endl;
        
    }

}

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;
    
}