簡體   English   中英

在opencl中為GPU優化內核代碼

[英]Optimizing kernel code in opencl for a GPU

截至目前,在內核執行時,我的GPU比我的CPU慢。 我想也許是因為我正在測試一個小樣本,因為較小的啟動開銷,CPU最終完成得更快。 但是,當我使用幾乎是樣本大小10倍的數據測試內核時,CPU仍然完成得更快,GPU幾乎落后400毫秒。

運行時2.39MB文件CPU:43.511ms GPU:65.219ms

運行時32.9MB文件CPU:289.541ms GPU:605.400ms

我嘗試使用本地內存,雖然我100%肯定我使用它錯了,並遇到了兩個問題。 內核在1000-3000ms之間完成(取決於我為localWorkSize設置的大小),或者我遇到狀態代碼-5,即CL_OUT_OF_RESOURCES。

這是SO成員幫助我的內核。

__kernel void lowpass(__global float *Array, __global float *coefficients, __global float *Output) {

int globalId = get_global_id(0); 
float sum=0.0f;
for (int i=0; i< 65; i++)
{
    float tmp=0;
    if (globalId+i > 63)
    {
        tmp=Array[i+globalId-64]*coefficients[64-i];    

    }

    sum += tmp;

}
Output[globalId]=sum;
}

這是我嘗試使用本地內存。 第一位將是主機代碼的片段,以下部分是內核。

//Set the size of localMem
status |= clSetKernelArg(
    kernel,
    2,
    1024, //I had num_items*(float) but it gave me a -5. Num items is the amount of elements in my array (around 1.2 million elements)
    null);
printf("Kernel Arg output status: %i \n", status);

//set a localWorkSize
localWorkSize[0] = 64;

//execute the kernel with localWorkSize included
status = clEnqueueNDRangeKernel(
    cmdQueue,
    kernel,
    1,
    NULL,
    globalWorkSize,
    localWorkSize,
    0,
    NULL,
    &someEvent);


 //Here is what I did to the kernel*************************************** 
__kernel void lowpass(__global float *Array, __global float *coefficients, __global float *Output, __local float *localMem) {

int globalId = get_global_id(0);
int localId = get_local_id(0);  

localMem[localId] = globalId[globalId];

float sum=0.0f;
for (int i=0; i< 65; i++)
{
    float tmp=0;
    if (globalId+i > 63)
    {
        tmp=localMem[i+localId-64]*coefficients[64-i];  

    }

    sum += tmp;

}
Output[globalId]=sum;
}

嘗試設置局部變量時使用的參考鏈接: 如何在OpenCL中使用本地內存?

用於查找kernelWorkGroupSize的鏈接(這就是為什么我在kernelArg中設置了1024個): CL_OUT_OF_RESOURCES為2百萬個浮點數和1GB VRAM?

我見過其他人有類似的問題,其中GPU比CPU慢,但對於其中許多人來說,他們使用的是clEnqueueKernel而不是clEnqueueNDRangeKernel。

如果您需要有關此內核的更多信息,請繼續我之前的問題: 在內核OpenCL中實現FIFO實現的最佳方法

為GPU發現了一些優化技巧。 https://developer.amd.com/wordpress/media/2012/10/Optimizations-ImageConvolution1.pdf

編輯代碼; 錯誤仍然存​​在

__kernel void lowpass2(__global float *Array, __global float *coefficients, __global float *Output) {

int globalId = get_global_id(0); 
float sum=0.0f;
float tmp=0.0f;
for (int i=64-globalId; i< 65; i++)
{

tmp = 0.0f;
tmp=Array[i]*coefficients[i];    
sum += tmp;

}
Output[globalId]=sum;
}

為2400萬個元素陣列運行以下內核

__kernel void lowpass(__global float *Array, __global float *coefficients, __global float *Output) {

int globalId = get_global_id(0); 
float sum=0.0f;
for (int i=0; i< 65; i++)
{
    float tmp=0;
    if (globalId+i > 63)
    {
        tmp=Array[i+globalId-64]*coefficients[64-i];    

    }

    sum += tmp;

}
Output[globalId]=sum;
}

25個計算單元設備池的完成時間不超過200毫秒,而8核心CPU則超過500毫秒。

你有一個高端的cpu和一個低端的gpu或gpu驅動器已被gimped或gpu的pci-e接口卡在pci-e 1.1 @ 4x帶寬,因此主機和設備之間的陣列拷貝是有限的。

另一方面,這個優化版本:

__kernel void lowpass(__global __read_only float *Array,__constant  float *coefficients, __global __write_only float *Output) {

        int globalId = get_global_id(0); 
        float sum=0.0f;
        int min_i= max(64,globalId)-64;
        int max_i= min_i+65;
        for (int i=min_i; i< max_i; i++)
        {
            sum +=Array[i]*coefficients[globalId-i];    
        }
        Output[globalId]=sum;
}

對於cpu(8計算單元)不到150毫秒,對於gpu(25計算單元)計算時間不到80毫秒。 每件商品的工作量只有65次。 使用__constant和__read_only和__write_only參數說明符以及一些整數工作減少可以非常容易地加速這種少量操作。

對於cpu和gpu,使用float4而不是float類型可以將cpu和gpu的速度提高%80,因為它們是SIMD類型和向量計算單元。

這個內核的瓶頸是:

  • 每個線程只有65次乘法和65次求和。
  • 但是數據仍然通過pci-express接口傳輸,速度很慢。
  • 每次浮點運算還有1次條件檢查(i <max_i)很高,需要循環展開。
  • 盡管你的cpu和gpu是基於矢量的,但一切都是標量的。

通常:

  • 第一次運行內核會及時觸發opencl編譯器優化,緩慢。 至少運行5-10次以獲得准確的時間。
  • __恆定空間僅為10 - 100 kB但比__global更快,對amd的hd5000系列更有用。
  • 內核開銷為100微秒,而65個緩存操作小於此值,並且被內核開銷時間所影響(更糟糕的是,由pci-e延遲)。
  • 工作項目太少會使占用率降低,變慢。

也:

  • 由於分支預測,總緩存帶寬,指令延遲和無延遲,4核Xeon @ 3 GHz比16(1/4 vliw5)* 2(計算單元)= 32個gpu @ 600 MHz核心快得多。
  • HD5000系列amd卡是傳統的,與gimped相同。
  • HD5450具有166 GB / s的恆定內存帶寬
  • 其中也只有83 GB / s的LDS(本地內存)帶寬
  • 其中還有83 GB / s的L1和L2緩存帶寬,所以只需讓它在__global驅動程序優化而不是LDS上運行,除非你打算升級你的計算機。(對於Array ofcourse)也許,來自LDS的奇數元素,甚至來自__global的元素也可能有83 + 83 = 166 GB / s帶寬。 你可以試試。 在銀行沖突方面,可能兩個比兩個更好。

  • 使用系數作為__constant(166 GB / s)和數組作為__global,可以為您提供166 + 83 = 249 GB / s的組合帶寬。

  • 每個系數元素每個線程只使用一次,所以我不打算使用私有寄存器(499 GB / s)

在引入本地內存之前,讓我們首先將if語句移出循環:

__kernel void lowpass(__global float *Array, __global float *coefficients, __global float *Output) 
{
int globalId = get_global_id(0); 
float sum=0.0f;
int start = 0;
if(globalId < 64)
    start = 64-globalId;
for (int i=start; i< 65; i++)
    sum += Array[i+globalId-64] * coefficients[64-i];    
Output[globalId]=sum;
}

然后可以像這樣實現本地內存的引入:

__kernel void lowpass(__global float *Array, __global float *coefficients, __global float *Output) 
{
    int globalId = get_global_id(0);
    int local_id = get_local_id(0);

    __local float local_coefficients[65];
    __local float local_array[2*65];

    local_coefficient[local_id] = coefficients[local_id];
    if(local_id == 0)
        local_coefficient[64] = coefficients[64];
    for (int i=0; i< 2*65; i+=get_local_size(0))
    {
        if(i+local_id < 2*65)
            local_array[i+local_id] = Array[i+global_id];
    }
    barrier(CLK_LOCAL_MEM_FENCE);

    float sum=0.0f;
    int start = 0;
    if(globalId < 64)
        start = 64-globalId;
    for (int i=start; i< 65; i++)
        sum += local_array[i+local_id] * local_coefficient[64-i];    
    Output[globalId]=sum;
}

PS那里可能會有一些錯誤,比如全局到本地索引的重新計算等等(我現在要去睡覺了):)盡管如此,上面的實現應該讓你正確的方向如何開始使用本地內存。

暫無
暫無

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

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