Differenze tra le versioni di "C++ Boost Compute"

Da Andreabont's Wiki.
(Definire funzioni personalizzate)
Riga 212: Riga 212:
 
     return x * 2;
 
     return x * 2;
 
});
 
});
 +
</source>
 +
 +
=== Copia asincrona + profilazione ===
 +
<source lang="cpp">
 +
#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;
 +
   
 +
}
 
</source>
 
</source>
  

Versione delle 09:49, 8 ago 2020

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