簡體   English   中英

OpenCL矩陣乘法速度

[英]OpenCL Matrix Multiplication Speed

我編寫了一個小的OpenCL應用程序,該應用程序計算兩個矩陣的乘積。 現在,我注意到,如果矩陣的大小超過8192 x 8192,則會出現明顯的性能下降(對於16384 x 16384的計算要慢80倍左右),甚至串行實現的速度也要快5倍以上。 這是主機代碼:

/*Make some includes and definitions here*/
#include "stdafx.h"
#include <CL/cl.hpp>

#include <vector>
#include <iostream>

#include "util.hpp" // utility library

#define __CL_ENABLE_EXCEPTIONS
#define ROWS (16384)    // ROWS of vectors a, b, and c
#define COLUMNS (16384)

/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
#include "metrics.h"

/*Start main()*/

int main(void)
{
    int A;

    // Fill vectors X and Y with random float values

    float* h_x = new float[ROWS*COLUMNS];
    for (int i = 0; i < ROWS; ++i){
        for (int j = 0; j < COLUMNS; ++j){
            h_x[j + i*COLUMNS] = rand() / (float)RAND_MAX;;
        }
    }
    float* h_y = new float[ROWS*COLUMNS];
    for (int i = 0; i < ROWS; ++i){
        for (int j = 0; j < COLUMNS; ++j){
            h_y[j + i*COLUMNS] = rand() / (float)RAND_MAX;;
        }
    }
    float* h_s = new float[ROWS*COLUMNS];
    for (int i = 0; i < ROWS; ++i){
        for (int j = 0; j < COLUMNS; ++j){
            h_s[j + i*COLUMNS] = 0.0;
        }
    }

    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

    // Get all platforms (drivers)

    std::vector<cl::Platform> all_platforms;
    cl::Platform::get(&all_platforms);


    if (all_platforms.size() == 0){ // Check for issues
        std::cout << " No platforms found. Check OpenCL installation!\n";
        exit(1);
    }

    cl::Platform default_platform = all_platforms[0];
    std::cout << "Using platform: " << default_platform.getInfo<CL_PLATFORM_NAME>() << "\n";

    // Get default device of the default platform

    std::vector<cl::Device> all_devices;
    default_platform.getDevices(CL_DEVICE_TYPE_ALL, &all_devices);

    if (all_devices.size() == 0){ // Check for issues
        std::cout << " No devices found. Check OpenCL installation!\n";
        exit(1);
    }

    cl::Device default_device = all_devices[0];
    std::cout << "Using device: " << default_device.getInfo<CL_DEVICE_NAME>() << "\n";

    // Create an OpenCL context

    cl::Context context({ default_device });

    cl::Program program(context, util::loadProgram("saxy_kernel.cl"), true);

    if (program.build({ default_device }) != CL_SUCCESS){
        std::cout << " Error building: " << program.getBuildInfo<CL_PROGRAM_BUILD_LOG>(default_device) << "\n";
        getchar();
        exit(1);
    }

    // create buffers on the device
    cl::Buffer buffer_X(context, CL_MEM_READ_WRITE, sizeof(float)* ROWS*COLUMNS);
    cl::Buffer buffer_Y(context, CL_MEM_READ_WRITE, sizeof(float)* ROWS*COLUMNS);
    cl::Buffer buffer_S(context, CL_MEM_READ_WRITE, sizeof(float)* ROWS*COLUMNS);
    cl::Buffer buffer_A(context, CL_MEM_READ_WRITE, sizeof(int));

    //create queue to which we will push commands for the device.
    cl::CommandQueue queue(context, default_device);

    //write arrays A and B to the device
    queue.enqueueWriteBuffer(buffer_X, CL_TRUE, 0, sizeof(float)* ROWS*COLUMNS, &h_x[0]);
    queue.enqueueWriteBuffer(buffer_Y, CL_TRUE, 0, sizeof(float)* ROWS*COLUMNS, &h_y[0]);
    queue.enqueueWriteBuffer(buffer_A, CL_TRUE, 0, sizeof(int), &A);

    StartCounter();
    //run the kernel
    cl::Kernel kernel_add = cl::Kernel(program, "simple_add");
    kernel_add.setArg(0, buffer_X);
    kernel_add.setArg(1, buffer_Y);
    kernel_add.setArg(2, buffer_S);
    kernel_add.setArg(3, buffer_A);

    cl::NDRange global(ROWS*COLUMNS);
    queue.enqueueNDRangeKernel(kernel_add, cl::NullRange, global, cl::NullRange);
    queue.finish();

    std::cout << "Kernel execution time: " << GetCounter() << "ms \n";

    //read result C from the device to array C
    queue.enqueueReadBuffer(buffer_S, CL_TRUE, 0, sizeof(float)*ROWS*COLUMNS, &h_s[0]);



    /*Print vectors
    std::cout << "\nMatrix #1: \n";
    for (int i = 0; i<ROWS*COLUMNS; i++){


            std::cout << "" << h_x[i] << "\t ";

    }

    std::cout << "\n\nMatrix #2: \n";
    for (int i = 0; i<ROWS*COLUMNS; i++){


            std::cout << "" << h_y[i] << "\t ";

    }

    std::cout << "\n\nResult: \n";
    for (int i = 0; i<ROWS*COLUMNS; i++){


            std::cout << "" << h_s[i] << "\t ";

    }*/
    getchar();
    return 0;
}

這是內核:

__kernel void kernel simple_add(
   __global float* X, 
   __global float* Y, 
   __global float* S, 
   __global int *A){

   S[get_global_id(0)] = X[get_global_id(0)] * Y[get_global_id(0)];

}

你能解釋一下原因嗎? 我知道如果執行一些算法優化可以達到更好的性能,但是我試圖弄清楚這是否是“天真的”實現的門檻,或者我做錯了什么(將工作分配不正確)組)。

編輯:因為有人在評論中要求我,所以我運行內核的GPU是AMD R9 270 / 2GB RAM。 CPU是i7-4771,系統具有8GB RAM。

寫一個關於“如何在每個線程中執行更多計算”的答案,因為注釋中不存在代碼格式,並且還涉及了一些內存使用情況...

因此,大多數OpenCL實現將需要在每個線程(以及正確數量的線程)上運行多個指令,以提高性能。 就像我在評論中說的那樣,這在很大程度上取決於處理單元的實際架構(GPU,CPU或具有OpenCL功能的神奇單元,是用獨角獸的頭發編織而成的,無論它是什么)-每個GPU,CPU和獨角獸編織器制造商對於如何建立一個高效的單位有自己的想法,而且隨着時間的流逝,他們都傾向於改變主意...;)

要在一個線程中完成更多工作,您可以簡單地執行以下操作:

#define NUM_PER_THREAD 16
__kernel void kernel simple_add(
 __global float* X, 
 __global float* Y, 
 __global float* S, 
 __global int *A)
{

   for(i = 0; i < NUM_PER_THREAD; i++)
   {
      size_t index = get_global_id(0)*NUM_PER_THREAD + i;
      S[index] = X[index] * Y[index];
   }
}

[這將執行1 x 16塊。 嘗試執行16 x 16或類似的操作會更有趣,但是如果您知道矩陣的大小(寬度),則可以這樣做]

關於內存:如果所有數據都適合圖形內存,則具有專用本地內存(換句話說,大多數圖形卡)的GPU將以更快的速度運行。 訪問“主”內存涉及以下兩種方法之一:

  1. 當GPU通過PCI-express總線[或使用任何基礎架構]進行讀取時,每個高速緩存行的訪問時間較長-這可能比“本地”內存慢100或1000倍。 而且GPU(最有可能)還必須詢問CPU內存內容是否在緩存中,如果是,請進一步等待CPU將數據復制到主內存中...
  2. GPU停止的“頁面入/出”,向CPU發送中斷,CPU找到一些合適的塊[在這種情況下,塊指的是“一些內存的技術術語,最有可能在4K左右或其倍數”]從GPU內存中“刪除”,然后將其復制到主內存中,然后將所需的其他塊內存中的內容復制到GPU內存中-類似於OS將硬盤與硬盤交換內存時。 而且,如果您不走運,GPU還必須執行一些有趣的緩存或TLB刷新操作,以確保使用了正確的數據。

請注意,我仍然(在最后一個小時左右)對AMD / ATI GPU的工作方式或其OpenCL驅動程序的工作方式沒有任何特別的了解。 以上是猜測/了解GPU總體運行方式,了解OpenCL總體運行方式以及使用float計算存儲三個不同的16K x 16K數組所需的內存的混合體。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM