Está en la página 1de 23

1

UNIVERSIDAD DE CONCEPCIÓN
FACULTAD DE INGENIERÍA
DEPARTAMENTO DE INGENIERÍA ELÉCTRICA

543426-1– ARQUITECTURA DE COMPUTADORES


2021-1
Ricardo Washington Sánchez Schulz

PROYECTO OPENCL
Jorge Sebastián Salgado Rodríguez
Jaime Ignacio Tatter Pérez
Matias Alonso Riquelme Escobedo

Concepción, 23 de julio de 2021


2

Tabla de Contenidos
Contenido
PROBLEMA........................................................................................................................................................................3
SOLUCIÓN ……………………………………………………………………………………………………………...4
OPCIÓN 1: MULTIPLICAR MATRICES USANDO C..............................................................................................................6
OPCIÓN 2: MULTIPLICAR MATRICES EN GPU (NAIVE)..................................................................................................10
OPCIÓN 3:.......................................................................................................................................................................20
OPCIÓN 4:.......................................................................................................................................................................21
OPCIÓN 5:.......................................................................................................................................................................22
3

Problema
Se deben multiplicar dos matrices de 2048x1024 y 1024x2048 usando OpenCL en la GPU
de su computador. Cada elemento de las matrices debe ser un número flotante elegido de manera
aleatoria, usando alguna función random.
El objetivo es obtener el menor tiempo total para realizar la multiplicación de las matrices, por lo
cual deberá medir el tiempo del kernel que uds., utilicen para tales efectos.
Uds., deberán proponer un esquema cómo combinan la memoria del computador y el disco duro
para para manejar los datos de ambas matrices. Son matrices grandes, por lo cual deberá
particionarlas y manejar en run-time parte en memoria y parte en el disco duro y hacer transferencia
de los datos entre memoria y disco duro durante la ejecución del programa.
Además de usar memoria global de la GPU, deberá realizar al menos dos optimizaciones adicionales
usando memoria local de la GPU y memoria privada. Cada optimización debe ser un kernel distinto.
La matriz resultante de la multiplicación deberá quedar en el disco duro.
Debe ocupar Visual Studio como plataforma, como lo vimos en clases. Pueden combinar C y C++
como quieran. NO SE PERMITE OTRO LENGUAJE DE PROGRAMACIÓN.
4

Solución
Para la resolución de este problema se realizaron 5 opciones distintas, las cuales serán
presentadas a lo largo de este informe.
Para el desarrollo de este problema el primer paso fue la creación de las matrices a multiplicar, para
ello utilizamos el siguiente código:
#include <iostream>
#include <fstream>
#include <vector>

float random_float(int max) {
    float random_num = (float)rand() / (float)(RAND_MAX / max);
    return random_num;
}

int main(void) {
    srand(static_cast <unsigned> (time(0)));
    // Creamos dos arreglos que representan las matrices que usaremos mas adelante
    float* m_A;
    float* m_B;

    // Las matrices son de 2048*1024 y 1024*2048 respectivamente, por lo que ambas 
tendran el mismo numero de elem.
    int m_size = 2048 * 1024;

    // Asignamos la memoria e inicializamos con 0´s
    m_A = (float*)calloc(m_size, sizeof(float));
    m_B = (float*)calloc(m_size, sizeof(float));

    // Asignamos numeros aleatorios de tipo float a los arreglos
    int max_n = 200;
    for (int i = 0; i < m_size; i++) {
        m_A[i] = random_float(max_n);
        m_B[i] = random_float(max_n);
    }

    // Guardamos los arreglos a un archivo
    std::ofstream A_out("matriz_A.bin", std::ios_base::binary);
    A_out.write(reinterpret_cast<const char*>(m_A), std::streamsize((long long)m_si
ze * sizeof(float)));
    A_out.close();

    std::ofstream B_out("matriz_B.bin", std::ios_base::binary);
5

    B_out.write(reinterpret_cast<const char*>(m_B), std::streamsize((long long)m_si
ze * sizeof(float)));
    B_out.close();

    // Creamos otros arreglos para comprobar si A y B se guardaron bien
    float *A_file, *B_file;
    A_file = (float*)calloc(m_size, sizeof(float));
    B_file = (float*)calloc(m_size, sizeof(float));

    // Carga matrices creadas
    std::ifstream A_in("matriz_A.bin", std::ios::binary | std::ios::in);
    A_in.read(reinterpret_cast<char*>(A_file), std::streamsize((long long)m_size * 
sizeof(float)));
    A_in.close();

    std::ifstream B_in("matriz_B.bin", std::ios::binary | std::ios::in);
    B_in.read(reinterpret_cast<char*>(B_file), std::streamsize((long long)m_size * 
sizeof(float)));
    B_in.close();

    for (int i = 0; i < m_size; i++) {
        if (m_A[i] != A_file[i])
            std::cout << m_A[i] << "," << i << "\n";
        if (m_B[i] != B_file[i])
            std::cout << m_B[i] << "," << i << "\n";
    }

    return 0;
}
El uso principal de esta parte del código es crear nuestras matrices de orden 2048 ×1024 y de
1024 × 2048.
Notamos que en nuestro código representamos las matrices en forma de vectores.
De esta manera, nuestra matriz A corresponde a una serie de filas y nuestra matriz B corresponde a
una serie de columnas.
Una vez creadas estas matrices, son guardadas en un archivo .bin, dado que es el proceso de
guardado es más rápido que guardarlo en un archivo .csv, como veremos más adelante.

Una vez obtenidas las matrices a multiplicar, fuimos probando las diferentes opciones, de tal manera
en que para todos los casos se obtuvieron resultados iguales, con tiempos de ejecución distintos. A
continuación, se detallarán los códigos utilizados.
6

Opción 1: Multiplicar matrices usando C


El código utilizado para la primera opción es el que mostramos a continuación.

#include <iostream>
#include <fstream>
#include <string>
#include <chrono>

int main(void) {
    using clock = std::chrono::system_clock;
    using ms = std::chrono::duration<double, std::milli>;

    const auto tiempo_inicio_total = clock::now();

    float* m_A, * m_B, * m_C;
    int m_size = 2048 * 1024;
    int m_size2 = 2048 * 2048;
    m_A = (float*)calloc(m_size, sizeof(float));
    m_B = (float*)calloc(m_size, sizeof(float));
    m_C = (float*)calloc(m_size2, sizeof(float));
    

    // Cargamos las matrices A y B
    const auto tiempo_i_cargar = clock::now();
    std::ifstream A_in("matriz_A.bin", std::ios::binary | std::ios::in);
    A_in.read(reinterpret_cast<char*>(m_A), std::streamsize((long long)m_size * siz
eof(float)));
    A_in.close();

    std::ifstream B_in("matriz_B.bin", std::ios::binary | std::ios::in);
    B_in.read(reinterpret_cast<char*>(m_B), std::streamsize((long long)m_size * siz
eof(float)));
    B_in.close();
    const ms tiempo_f_cargar = clock::now() - tiempo_i_cargar;

    // Multiplicar matrices A y B
    const auto tiempo_i_multiplicar = clock::now();
    for (int i = 0; i < 2048; i++) {
        for (int j = 0; j < 2048; j++) {
            for (int k = 0; k < 1024; k++) {
                m_C[i * 2048 + j] += m_A[i * 1024 + k] * m_B[j * 1024 + k];
            }
        }
    }
    const ms tiempo_f_multiplicar = clock::now() - tiempo_i_multiplicar;
7

    const auto tiempo_i_guardar = clock::now();
    // Guardar matriz A como csv
    std::ofstream myfile_A;
    myfile_A.open("matriz_A.csv");
    int aux_a = 1;
    for (size_t i = 0; i < 2048 * 1024; ++i)
    {
        if (aux_a == 1024) {
            myfile_A << m_A[i] << "\n";
            aux_a = 0;
        }
        else {
            myfile_A << m_A[i] << ",";
        }
        aux_a = aux_a + 1;
    }
    myfile_A.close();

    // Guardar matriz B como csv
    int matrix_n = 2048, matrix_m = 1024;

    float** matrix_B_aux = new float* [matrix_m];
    if (matrix_m)
    {
        matrix_B_aux[0] = new float[matrix_m * matrix_n];
        for (int i = 1; i < matrix_m; ++i)
            matrix_B_aux[i] = matrix_B_aux[0] + i * matrix_n;
    }
    for (size_t i = 0; i < matrix_n; i++) {
        for (size_t j = 0; j < matrix_m; j++) {
            matrix_B_aux[j][i] = m_B[matrix_m * (i)+j];
        }
    }

    std::ofstream myfile_B;
    myfile_B.open("matriz_B.csv");
    for (size_t i = 0; i < matrix_m; ++i)
    {
        for (size_t j = 0; j < matrix_n; j++) {
            if (j == matrix_n - 1) {
                myfile_B << matrix_B_aux[i][j] << "\n";
            }
            else {
                myfile_B << matrix_B_aux[i][j] << ",";
            }
8

        }
    }
    myfile_B.close();

    // Guardar matriz C como csv
    std::ofstream myfile_C;
    myfile_C.open("matriz_C.csv");
    int aux = 1;
    for (size_t i = 0; i < 2048 * 2048; ++i)
    {
        if (aux == 2048) {
            myfile_C << m_C[i] << "\n";
            aux = 0;
        }
        else {
            myfile_C << m_C[i] << ",";
        }
        aux = aux + 1;
    }
    myfile_C.close();
    const ms tiempo_f_guardar = clock::now() - tiempo_i_guardar;

    const auto tiempo_i_guardar_bin = clock::now();
    // Guardar matriz C como binario
    std::ofstream C_out("matriz_C.bin", std::ios_base::binary);
    C_out.write(reinterpret_cast<const char*>(m_C), std::streamsize((long long)m_si
ze2 * sizeof(float)));
    C_out.close();
    const ms tiempo_f_guardar_bin = clock::now() - tiempo_i_guardar_bin;

    const ms tiempo_final_total = clock::now() - tiempo_inicio_total;

    std::cout << "Tiempos: " << std::endl;
    std::cout << "Cargar matrices: " << tiempo_f_cargar.count() << " ms" << std::en
dl;
    std::cout << "Multiplicar: " << tiempo_f_multiplicar.count() << " ms" << std::e
ndl;
    std::cout << "Guardar como csv: " << tiempo_f_guardar.count() << " ms" << std::
endl;
    std::cout << "Guardar una matriz como bin: " << tiempo_f_guardar_bin.count() << 
" ms" << std::endl;
    std::cout << "Tiempo total: " << tiempo_final_total.count() << " ms" << std::en
dl;
    
    return 0;
}
9

La primera parte del código mostrado carga los archivos “.bin” creados en la parte anterior, para ser
utilizados como las matrices a multiplicar.
Luego, tenemos que la segunda parte del código corresponde a la multiplicación de las matrices A y
B usando CPU. Además, se monitorea el tiempo de ejecución de los distintos procesos del código.
Nuestro tiempo de ejecución para multiplicar las matrices A y B usando CPU fue de 11792.7 ms.
Una vez terminados los procesos anteriormente mencionados, procedemos a guardar nuestras
matrices A, B y C de distintas maneras.
Primeramente, nuestras matrices fueron guardadas en formato .csv, el cual, al monitorear su tiempo
de guardado ( 41756.6 ms) notamos que, al ser archivos de mayor tamaño, aumenta
considerablemente su tiempo de ejecución en comparación a guardar las matrices en formato .bin.
Esto queda demostrado, dado que en la siguiente parte del código guardamos nuestra matriz C en
formato .bin y notamos que el tiempo de ejecución de este proceso fue de 9.8359 ms.
10

Opción 2: Multiplicar Matrices en GPU (naive)


El código utilizado para la siguiente opción es el siguiente.

#include <cl_fun_error.h>
#include <chrono>

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <fstream>
#include <string.h>

#ifdef __APPLE__
#include <OpenCL/opencl.h>
#include <unistd.h>
#else
#include <CL/cl.h>
#endif

int main(void) {
    using clock = std::chrono::system_clock;
    using ms = std::chrono::duration<double, std::milli>;

    const auto tiempo_inicio_total = clock::now();

    // Definiciones para el programa
    cl_int status = 0;
    cl_uint num_platforms = 0;
    cl_platform_id* platforms;
    cl_char platform_name[30] = { 0 };
    cl_char platform_openclVer[30] = { 0 };
    cl_device_id* devices;
    cl_uint num_devices = 0;
    cl_char device_name[100] = { 0 };
    cl_ulong mem_local = 0;
    cl_ulong mem_global = 0;
    cl_uint num_compute_units = 0;
    size_t max_work_size = 0;
    cl_uint max_dim = 0;

    const int matrix_l1 = 2048;
11

    const int matrix_l2 = 1024;
    const int max_random_num = 200;

    int tam1 = matrix_l1 * matrix_l2;
    int tam2 = matrix_l1 * matrix_l1;
    size_t data_size1 = sizeof(float) * tam1;
    size_t data_size2 = sizeof(float) * tam2;

    float* h_dataIn_A, *h_dataIn_B, *h_dataOut_C;

    h_dataIn_A = (float*)calloc(data_size1, sizeof(float));
    h_dataIn_B = (float*)calloc(data_size1, sizeof(float));
    h_dataOut_C = (float*)calloc(data_size2, sizeof(float));

    int user_platform;
    int user_device;

    // Leer matrices A y B
    const auto tiempo_i_cargar = clock::now();

    std::ifstream A_in("matriz_A.bin", std::ios::binary | std::ios::in);
    A_in.read(reinterpret_cast<char*>(h_dataIn_A), std::streamsize((long long)data_
size1 * sizeof(float)));
    A_in.close();

    std::ifstream B_in("matriz_B.bin", std::ios::binary | std::ios::in);
    B_in.read(reinterpret_cast<char*>(h_dataIn_B), std::streamsize((long long)data_
size1 * sizeof(float)));
    B_in.close();

    memset(h_dataOut_C, 0, data_size2);

    const ms tiempo_f_cargar = clock::now() - tiempo_i_cargar;

    // Buscar plataformas compatibles
    status = clGetPlatformIDs(0, NULL, &num_platforms);
    check_error(status, "clGetPlatformIDs");
    if (num_platforms == 0) {
        std::cout << "No se encontraron plataformas compatibles\n";
        exit(-1);
    }

    platforms = (cl_platform_id*)malloc(num_platforms * sizeof(cl_platform_id));
    if (platforms == NULL) {
        perror("malloc: ");
        exit(-1);
12

    }

    status = clGetPlatformIDs(num_platforms, platforms, NULL);
    check_error(status, "clGetPlatformIDs");

    // Si se encuentra mas de 1 plataforma, el usuario decide cual usar
    if (num_platforms != 1) {
        std::cout << "Se encontraron" << num_platforms << "plataformas\n";
        for (size_t i = 0; i < num_platforms; i++) {
            status = clGetPlatformInfo(platforms[0], CL_PLATFORM_NAME, sizeof(platf
orm_name), &platform_name, NULL);
            check_error(status, "clGetPlatformInfo");
            std::cout << "\tPlataforma " << i << ": " << platform_name << "\n";
        }
        std::cout << "Elija una plataforma: ";
        std::cin >> user_platform;
        status = clGetPlatformInfo(platforms[user_platform], CL_PLATFORM_NAME, size
of(platform_name), &platform_name, NULL);
        check_error(status, "clGetPlatformInfo");
    }
    else {
        user_platform = 0;
        status = clGetPlatformInfo(platforms[user_platform], CL_PLATFORM_NAME, size
of(platform_name), &platform_name, NULL);
        check_error(status, "clGetPlatformInfo");
        std::cout << "Plataforma: " << platform_name << "\n";
    }

    // Version de OpenCL
    status = clGetPlatformInfo(platforms[user_platform], CL_PLATFORM_VERSION, sizeo
f(platform_openclVer), &platform_openclVer, NULL);
    if (status != CL_SUCCESS) {
        std::cout << "Fallo en clGetPlatformInfo\n";
        exit(-1);
    }
    printf("Version de OpenCL: %s\n", platform_openclVer);

    // Buscar dispositivos en la plataforma
    status = clGetDeviceIDs(platforms[user_platform], CL_DEVICE_TYPE_ALL, 0, NULL, 
&num_devices);
    check_error(status, "clGetDeviceIDs");
    devices = (cl_device_id*)malloc(num_devices * sizeof(cl_device_id));
    status = clGetDeviceIDs(platforms[user_platform], CL_DEVICE_TYPE_GPU, num_devic
es, devices, NULL);
    check_error(status, "clGetDeviceIDs");
    for (size_t i = 0; i < num_devices; i++) {
13

        status = clGetDeviceIDs(platforms[user_platform], CL_DEVICE_TYPE_GPU, num_d
evices, devices, NULL);
        check_error(status, "clGetDeviceIDs");

        // Nombre dispositivo
        status = clGetDeviceInfo(devices[i], CL_DEVICE_NAME, sizeof(device_name), &
device_name, NULL);
        check_error(status, "clGetDeviceIDs");
        std::cout << "\t\tNombre dispositivo " << i << ": " << device_name << "\n";

        // Memoria global del dispositivo
        status = clGetDeviceInfo(devices[i], CL_DEVICE_GLOBAL_MEM_SIZE, sizeof(cl_u
long), &mem_global, NULL);
        check_error(status, "clGetDeviceInfo");
        std::cout << "\t\tMemoria global: " << mem_global / (1024 * 1024) << " MB\n
";

        // Memoria local del dispositivo
        status = clGetDeviceInfo(devices[i], CL_DEVICE_LOCAL_MEM_SIZE, sizeof(cl_ul
ong), &mem_local, NULL);
        check_error(status, "clGetDeviceInfo");
        std::cout << "\t\tMemoria local: " << mem_local / 1024 << " KB\n";

        // Max work size
        status = clGetDeviceInfo(devices[i], CL_DEVICE_MAX_WORK_GROUP_SIZE, sizeof(
size_t), &max_work_size, NULL);
        check_error(status, "clGetDeviceInfo");
        std::cout << "\t\tMax work size: " << max_work_size << "\n";

        // Max work item dim
        status = clGetDeviceInfo(devices[i], CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS, si
zeof(cl_uint), &max_dim, NULL);
        check_error(status, "clGetDeviceInfo");
        std::cout << "\t\tMax work item dimensions: " << max_work_size << "\n";

        // Max CU
        status = clGetDeviceInfo(devices[i], CL_DEVICE_MAX_COMPUTE_UNITS, sizeof(cl
_int), &num_compute_units, NULL);
        check_error(status, "clGetDeviceInfo");
        std::cout << "\t\tMax Compute Units: " << num_compute_units << "\n";
    }

    if (num_devices != 1) {
        std::cout << "Se encontraron" << num_devices << "dispositivos compatibles\n
";
        std::cout << "Elija un dispositivo: ";
14

        std::cin >> user_device;
    }

    else {
        user_device = 0;
    }

    // Crear un contexto
    cl_context context_gpu;
    context_gpu = clCreateContext(NULL, num_devices, devices, NULL, NULL, &status);
    check_error(status, "clCreateContext");
    if (context_gpu == NULL) {
        std::cout << "Error en clCreateContext\n";
        exit(-1);
    }

    // Crear command queue
    cl_command_queue command_q;
    command_q = clCreateCommandQueue(context_gpu, devices[user_device], CL_QUEUE_PR
OFILING_ENABLE, &status);
    check_error(status, "clCreateCommandQueue");
    if (command_q == NULL) {
        std::cout << "Error en clCreateCommandQueue\n";
        exit(-1);
    }

    // Crear los buffer necesarios
    cl_mem d_mA, d_mB, d_mC;

    d_mA = clCreateBuffer(context_gpu, CL_MEM_READ_ONLY, data_size1, NULL, &status)
;
    check_error(status, "clCreateBuffer");

    d_mB = clCreateBuffer(context_gpu, CL_MEM_READ_ONLY, data_size1, NULL, &status)
;
    check_error(status, "clCreateBuffer");

    d_mC = clCreateBuffer(context_gpu, CL_MEM_READ_WRITE, data_size2, NULL, &status
);
    check_error(status, "clCreateBuffer");

    status = clEnqueueWriteBuffer(command_q, d_mA, CL_TRUE, 0, data_size1, h_dataIn
_A, 0, NULL, NULL);
    check_error(status, "clEnqueueWriteBuffer");
15

    status = clEnqueueWriteBuffer(command_q, d_mB, CL_TRUE, 0, data_size1, h_dataIn
_B, 0, NULL, NULL);
    check_error(status, "clEnqueueWriteBuffer");
    

    // Dejar la matriz C inicializada con 0's
    memset(h_dataOut_C, 0, data_size2);
    
    status = clEnqueueWriteBuffer(command_q, d_mC, CL_TRUE, 0, data_size2, h_dataOu
t_C, 0, NULL, NULL);
    check_error(status, "clEnqueueWriteBuffer");

    // Leer archivo que contiene el kernel
    char file_kernel[] = "kernel_mul_mat.cl";
    size_t file_size, program_size;
    char* kernel_mxm;
    FILE* fp;
    fp = fopen(file_kernel, "rb");
    if (fp == NULL) {
        printf("Error abriendo el archivo %s", file_kernel);
        exit(-1);
    }
    fseek(fp, 0, SEEK_END);
    program_size = ftell(fp);
    rewind(fp);
    kernel_mxm = (char*)malloc(program_size + 1);
    kernel_mxm[program_size] = '\0';
    fread(kernel_mxm, sizeof(char), program_size, fp);
    fclose(fp);

    // Crear programa
    cl_program program_mat = clCreateProgramWithSource(context_gpu, 1, (const char*
*)&kernel_mxm, NULL, &status);
    check_error(status, "clCreateProgramWithSource");

    // Construir el programa
    status = clBuildProgram(program_mat, num_devices, devices, NULL, NULL, NULL);
    check_error(status, "clBuildProgram");

    // Crear kernel
    cl_kernel kernel;
    kernel = clCreateKernel(program_mat, "mul_mat", &status);
    check_error(status, "clCreateKernel");
    status = clSetKernelArg(kernel, 0, sizeof(cl_mem), &d_mA);
    check_error(status, "clSetKernelArg");
    status |= clSetKernelArg(kernel, 1, sizeof(cl_mem), &d_mB);
16

    check_error(status, "clSetKernelArg");
    status |= clSetKernelArg(kernel, 2, sizeof(cl_mem), &d_mC);
    check_error(status, "clSetKernelArg");

    const size_t global_work_size[] = { 2048, 1024 };

    const ms tiempo_f_set_kernel = clock::now() - tiempo_inicio_total;

    cl_event event;
    status = clEnqueueNDRangeKernel(command_q, kernel, 2, NULL, global_work_size, N
ULL, 0, NULL, &event);
    check_error(status, "clEnqueueNDRangeKernel");

    clWaitForEvents(1, &event);
    check_error(status, "clWaitForEvents");

    status = clFinish(command_q);
    check_error(status, "clFinish");

    status = clEnqueueReadBuffer(command_q, d_mC, CL_TRUE, 0, data_size2, h_dataOut
_C, 0, NULL, NULL);
    check_error(status, "clEnqueueReadBuffer");

    const auto tiempo_i_guardar = clock::now();
    // Guardar resultado
    std::ofstream C_out("matriz_C.bin", std::ios_base::binary);
    C_out.write(reinterpret_cast<const char*>(h_dataOut_C), std::streamsize((2048*2
048) * sizeof(float)));
    C_out.close();
    const ms tiempo_f_guardar = clock::now() - tiempo_i_guardar;

    if (kernel)clReleaseKernel(kernel);
    if (program_mat)clReleaseProgram(program_mat);
    if (command_q)clReleaseCommandQueue(command_q);
    if (context_gpu)clReleaseContext(context_gpu);
    if (d_mA)clReleaseMemObject(d_mA);
    if (d_mB)clReleaseMemObject(d_mB);
    if (d_mC)clReleaseMemObject(d_mC);

    // Tiempo que se demoro en ejecutar
    cl_ulong time_start;
    cl_ulong time_end;

    clGetEventProfilingInfo(event, CL_PROFILING_COMMAND_START, sizeof(time_start), 
&time_start, NULL);
17

    clGetEventProfilingInfo(event, CL_PROFILING_COMMAND_END, sizeof(time_end), &tim
e_end, NULL);
    double kernel_time = time_end - time_start;
    const ms tiempo_final_total = clock::now() - tiempo_inicio_total;

    std::cout << "Tiempos: " << std::endl;
    std::cout << "Cargar matrices: " << tiempo_f_cargar.count() << " ms" << std::en
dl;
    //std::cout << "Setear Kernel: " << tiempo_f_set_kernel.count() << " ms" << std
::endl;
    std::cout << "Multiplicar (Kernel): " << kernel_time/1000000.0 << " ms" << std:
:endl;
    std::cout << "Guardar resultado: " << tiempo_f_guardar.count() << " ms" << std:
:endl;
    std::cout << "Tiempo total: " << tiempo_final_total.count() << " ms" << std::en
dl;

    return 0;
}

Primero que todo, incluimos una función llamada cl ¿ . h para detectar si existía algún error de
OpenCL y de esta manera poder identificarlo de manera rápida y correcta, dado que esta función
transforma el número del error entregado por OpenCL directamente al nombre del error. Si el
numero entregado por el OpenCL es igual a 0, significa que no tenemos errores en nuestro
programa.
Luego, procedemos a agregar las definiciones de OpenCL necesarias para que nuestro programa
funcione correctamente. Cabe agregar que estas fueron añadidas e inicializadas al principio del
programa para facilitar la elaboración y comprensión del resto del código, pudiendo identificar cada
parte sin mayor complejidad.
Notamos que nuestra función cl ¿ . h se observa durante varias partes del código, lo cual no
procederemos a explicar nuevamente.
Posterior a ello, el programa se encarga de leer las matrices tomando el tiempo inicial de carga en el
cual fue llevado a cabo este proceso. Cabe señalar que esta opción es más rápida que la anterior con
un tiempo de carga de 17.5716 ms.
La siguiente parte del código se encarga de buscar una plataforma compatible para multiplicar las
matrices. Posteriormente en caso de que se encuentre más de una plataforma compatible el usuario
deberá escoger cual plataforma utilizar para realizar la operación.
18

Continuando, con la siguiente parte, tenemos que, una vez escogida la plataforma, el programa
buscará los distintos dispositivos que contiene esta misma. De esta manera, el programa buscará las
distintas GPU´s que podría tener la plataforma. Si solamente se encuentra un solo dispositivo, en
este caso, una sola GPU, ya sea de Nvidia, AMD o Intel, el programa continuará obteniendo datos
concretos de dicha GPU, como por ejemplo el nombre del dispositivo, la memoria global de este, la
memoria local, etc.
Si se encuentra más de un dispositivo, el programa imprimirá todos los datos anteriormente
mencionados para todas las GPU´s disponibles y además le pedirá al usuario cuál de estos
dispositivos utilizar para continuar la operación.
Luego, se crean el contexto, que estará relacionado directamente con la tarjeta gráfica que
utilizaremos para la multiplicación de matrices, y la command queue, la cual se encarga de enviar
una serie de “carros de comandos” hacia la GPU. Además, la command queue está directamente
asociada al contexto que creamos anteriormente y a nuestro dispositivo encontrado en la plataforma.
Luego, los buffers creados en el programa llamados d_mA, d_mB y d_mC corresponden a memoria
apuntada en la GPU y de esta manera poder transferir los datos de nuestra matriz A, B y C a la
memoria de la tarjeta gráfica para que esta pueda leer los datos. Los Buffers d_mA y d_mB
corresponden a los buffers de entrada, mientras que el buffer d_mC corresponde al de salida.
Después, rellenando nuestra matriz C solamente con ceros, procedemos a que nuestro programa lea
el archivo que contiene el kernel. Este kernel corresponde al programa que va a multiplicar nuestras
matrices.

El kernel utilizado para esta ocasión es el que se muestra a continuación, y como dijimos
anteriormente, se encarga de hacer la multiplicación entre las matrices generadas anteriormente.
__kernel void mul_mat(__global float* A, __global float* B, __global float* C){
    int i = get_global_id(0);
    int j = get_global_id(1);
    int k = 0;
    float sum = 0.0;
    for (k = 0; k < 1024; k++) {
        sum += A[(i * 1024) + k] * B[(j * 1024) + k];
    }
    C[(i * 2048) + j] += sum;
}
19

Luego, una vez que tengamos “cargado” nuestro kernel en el programa, procedemos a crear y
compilar el programa.
20
21

Opción 3:
22

Opción 4:
23

Opción 5:

También podría gustarte