giovedì 13 agosto 2015

OpenCL e Mandelbrot

Il calcolo dell'insieme di Mandelbrot e' forse uno dei casi piu' semplici in cui applicare il calcolo parallelo perche' l'elaborazione di  ogni cella e' indipendente da quelle vicine.

Ai giorni d'oggi per effettuare il calcolo parallelo, invece di usare un cluster di computer, si possono usare i diversi core del processore oppure usare la scheda grafica con la sua GPU (mediante le librerie CUDA di NVidia o le piu' generiche OpenCL che possono girare su AMD, NVidia e Intel)



La strategia utilizzata in CUDA e OpenCL e' piu' o meno la stessa ovvero si scrive il codice di un kernel, ovvero della elaborazione che verra' eseguita su ogni unita' di elaborazione, e poi il codice che gestisce la coda dei processi e la distribuzione alle varie unita' di elaborazione

Un esempio di kernel per Mandelbrot e' il seguente
-------------------------------------------------------------------
#pragma OPENCL EXTENSION cl_khr_fp64 : enable

double trans_x(int x, int N)
{
    return 3.0 * ((double)x / N) - 2.0;
}
double trans_y(int y, int N)
{
    return 3.0 * ((double)y / N) - 1.5;
}
double mag2(double r, double i)
{
    return r * r + i * i;
}
__kernel void mandel(__global double* out, int N, int depth, double escape2)
{
    size_t idx = get_global_id(0);

    double z0_r = trans_x(idx % N, N);
    double z0_i = trans_y(idx / N, N);

    double z_r = 0;
    double z_i = 0;
    int k = 0;
    for(; k <= depth && mag2(z_r, z_i) < escape2 ; ++k)
    {
        double t_r = z_r; double t_i = z_i;
        z_r = t_r * t_r - t_i * t_i + z0_r;
        z_i = 2 * t_r * t_i + z0_i;
    }
    out[idx] = log(k + 1.0 - log(log(max(mag2(z_r, z_i), escape2)) / 2.0) / log(2.0));
}

-------------------------------------------------------------------


Per mettere alla prova quanto vantaggio c'e' nell'usare OpenCL per il calcolo di Mandelbrot ho usato un portatile Surface Pro 2 con scheda video Intel HD 4400 ed il programma reperibile a questo link.

Come test ho provato il  Miniset level 2 (1000 iterazioni per punto) con i seguenti risultati

- C float, single thread :1.71 s
- C float, 4 thread : 0.61 s
- C float, 8 thread : 0.59 s
- C fixed-point 128 bit  2 thread : 22.5 s
- OpenCl CPU float  : 0.36 s
- OpenCL CPU  double : 0.4 s
- OpenCL GPU float : 0.096 s