【问题标题】:OpenCL Transfer rate exceed PCI-e BandwidthOpenCL 传输速率超过 PCI-e 带宽
【发布时间】:2013-12-06 03:22:24
【问题描述】:

我制作了一个 OpenCL 程序并使用固定内存 (CL_MEM_ALLOC_HOST_PTR) 来获得从设备到主机的更高传输率。

传输速率按我的预期提高(使用 AMD APP Profiler 2.4 获取传输速率)。 问题是矩阵 4096 x 4096 (64 MB) 的传输速率高于 PCIe 带宽 (93703 GB /s)。

当我使用零复制缓冲区 (CL_MEM_ALLOC_HOST_PTR + clEnqueueMapBuffer) 时也发生了这种情况。 我搜索了一些信息,如果固定内存和零复制缓冲区具有高传输率,但它仍然受限于离散 GPU 的 PCIe 带宽。 那么,如果传输速率超过 PCIe 带宽(使用 PCIe 带宽 2.0 x 16)是否正常?

我的操作系统是 Windows 7 64 位。 我使用 AMD APP SDK 2.6 和独立 GPU AMD HD 6630M。

编辑: 代码如下:

#include <Windows.h>
#include <iostream>
#include <fstream>
#include <string>
using namespace std;

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

#define MAX_SOURCE_SIZE (0x100000)

cl_context context = NULL; 
cl_command_queue queue = NULL; 
cl_program program = NULL; 

void MatrixMul(cl_mem d_A, cl_mem d_B, cl_mem d_C, int size)
{
cl_int err;
cl_kernel naive;

// Create Kernel Object Bound To Kernel Function 
naive = clCreateKernel(program, "naiveAlgorithm", &err);

//Set size of global work item and work tem in each work goups
int globalsize = size;
int localsize;

if(globalsize >= 16)
{
    localsize =16;
}else
{
    localsize = globalsize;
}

size_t global_work_items [2] = {globalsize, globalsize};
size_t local_work_items  [2] = {localsize, localsize};

// Setup Kernel Argument
err = clSetKernelArg(naive, 0, sizeof(cl_mem), (void *)&d_A);
err = clSetKernelArg(naive, 1, sizeof(cl_mem), (void *)&d_B);
err = clSetKernelArg(naive, 2, sizeof(cl_mem), (void *)&d_C);
err = clSetKernelArg(naive, 3, sizeof(cl_int), (void *)&size);



// Execute OpenCL kernel for Naive Algorithm
err = clEnqueueNDRangeKernel(queue, naive, 2, NULL, global_work_items, local_work_items, 0, NULL, NULL);
clFinish(queue);

//Release Kernel
err = clReleaseKernel(naive);
}

void Naive(cl_float* matrixA, cl_float* matrixB, cl_float* matrixC, int size)
{
int err;
// OpenCL device memory for matrices
cl_mem d_A;
cl_mem d_B;
cl_mem d_C;

// Allocate Device Memory For Input And Output
d_A = clCreateBuffer(context,  CL_MEM_READ_ONLY   ,   sizeof(cl_float)*size*size, 0, &err);
d_B = clCreateBuffer(context,  CL_MEM_READ_ONLY   ,   sizeof(cl_float)*size*size, 0, &err);
d_C = clCreateBuffer(context, CL_MEM_WRITE_ONLY|CL_MEM_ALLOC_HOST_PTR ,sizeof(cl_float)*size*size, 0,&err);     

// Copy Host Memory To Memory Device
err = clEnqueueWriteBuffer(queue, d_A, CL_FALSE, 0, sizeof(cl_float)*size*size, matrixA, 0, NULL, NULL); 
err = clEnqueueWriteBuffer(queue, d_B, CL_FALSE, 0, sizeof(cl_float)*size*size, matrixB, 0, NULL, NULL); 

MatrixMul(d_A, d_B, d_C, size);

err = clEnqueueReadBuffer(queue, d_C, CL_TRUE, 0, sizeof(cl_float)*size*size, matrixC, 0, NULL, NULL);

err = clReleaseMemObject(d_A);
err = clReleaseMemObject(d_B);
err = clReleaseMemObject(d_C);
}



//Main Function
int main(int argc, char **argv)
{
//Size of matrix for Strassen Algorithm
cl_int size = 4096; 

//Matrix for input and output
cl_float * matrixA;
cl_float * matrixB;
cl_float * matrixC;

//Allocate  and init memory for the host
matrixA = (cl_float *) malloc(size*size*sizeof(cl_float));
matrixB = (cl_float *) malloc(size*size*sizeof(cl_float));
matrixC = (cl_float *) malloc(size*size*sizeof(cl_float));

//Fill matrix
fillMatrix(matrixA,size);
fillMatrix(matrixB,size);

//print input for matrix A and B
cout<<"Input for matrix A :"<<endl;
printMatrix(matrixA, size*size, size);
cout<<"Input for matrix B :"<<endl;
printMatrix(matrixB, size*size, size);

cl_int err;     // error code   

cl_platform_id* platforms;
cl_uint platformCount;

cl_device_id device;

int platformtype = 0; //if 0 using amd app sdk but if 1 using intel sdk

clGetPlatformIDs(0, NULL, &platformCount); //get number of platform
platforms = (cl_platform_id*) malloc(sizeof(cl_platform_id) * platformCount); 
clGetPlatformIDs(platformCount, platforms, NULL);  //get list of platform
clGetDeviceIDs (platforms [platformtype], CL_DEVICE_TYPE_GPU, 1, &device, NULL); //get list of devices

const cl_context_properties contextProperties [] =
{CL_CONTEXT_PLATFORM,
     reinterpret_cast<cl_context_properties> (platforms [platformtype]),
     0, 0
};


context = clCreateContext(contextProperties, 1, &device, NULL, NULL, &err);
    ![enter image description here][2]queue = clCreateCommandQueue(context, device, CL_QUEUE_PROFILING_ENABLE, &err);


//Load Kernel Source 
FILE *fp;
const char fileName[] = "./MatMul_Kernel.cl";
size_t source_size;
char *source_str;

fp = fopen(fileName, "r");
if (!fp) 
{
    fprintf(stderr, "Failed to load kernel.\n");
    exit(1);
}
source_str = (char *)malloc(MAX_SOURCE_SIZE);
source_size = fread(source_str, 1, MAX_SOURCE_SIZE, fp);
fclose(fp);

// Create Program Object 
program = clCreateProgramWithSource(context, 1, (const char **) &source_str,(const size_t *),
    &source_size, &err); 

// Build Program 
    err = clBuildProgram(program, 1, &device, NULL, NULL, NULL);

Naive(matrixA, matrixB, matrixC, size);

    //Cleanup all memory
err = clFlush(queue);
    err = clFinish(queue);
    err = clReleaseProgram(program);
    err = clReleaseCommandQueue(queue);
    err = clReleaseContext(context);

// Display result of matrix multiplication
cout<<"Output for matrix C :"<<endl;
    printMatrix(matrixC, size*size, size);
cout<<endl;

free(matrixA);
    free(matrixB);
    free(matrixC);
free(source_str);

    return 0;
}

这是内核代码:

 __kernel void naiveAlgorithm(__global float *A, __global float *B, __global float *C, int size) {

 int tx = get_global_id(0); //2D Thread IDx
 int ty = get_global_id(1); //2D Thread IDy

 float sum = 0;

 //Calculate result of one element of Matrix C
 for (int k = 0; k < size; k++) {
    sum += A[ty*size+k] * B[k*size+tx];
 }
  C[ty*size+tx] = sum;
 }

这是图片:

【问题讨论】:

  • 你的代码是什么?您可能已经看到了异步传输的效果。
  • 感谢回复,我已经从 AMD APP Profiler 添加了代码和图片。
  • 有趣的结果,可能是您的内核直接在主机内存上写入,或者分析器未正确分析。但是,这些传输速率实际上非常高(包括开销)。
  • 因为如果你的内核直接写入主机内存。那么传输只是一个不需要时间的NOP操作。
  • 它看起来像是 AMD APP Profiler 上的一个错误。查看 clEnqueueReadBuffer 消耗的时间,但 Data Transfer 行上没有显示任何内容,除了最后的那个小信号。我的猜测是它没有正确计时传输,因此导致带宽测量错误。你可以看到 Code XL 解决了这个问题,或者只是使用回读时间来计算你的带宽。

标签: opencl


【解决方案1】:

由于以下行中的 CL_MEM_ALLOC_HOST_PTR 标志,我看到您的输出数组实际上位于主机内存中:

d_C = clCreateBuffer(context, CL_MEM_WRITE_ONLY|CL_MEM_ALLOC_HOST_PTR ,sizeof(cl_float)*size*size, 0,&err);

这意味着您应该使用clEnqueueMapBuffer,然后以您认为合适的任何方式使用矩阵,然后使用clEnqueueUnmapMemObject。不需要数组 matrixC,因为 d_C 已经在主机内存中。

从 GPU 到主机的数据传输实际上是在内核运行时发生的。 map 调用确保所有数据都已完成从 GPU 到 CPU 的移动。这就是为什么传输时间实际上如此之短的原因。

我找不到任何关于clEnqueueReadBuffer 是否适用于固定内存的文档。我还看到您正在检索每个操作的错误代码,但不检查这些错误代码,因此您的代码可能会静默失败。

关于clEnqueueReadBuffer 所用时间与传输数据所用时间之间的巨大差异,请注意,所有排队的操作不会立即分派到 GPU。延迟的来源之一是显卡的 Windows 显示驱动程序模型 (WDDM)。 clEnqueueReadBuffer 使用的 +-20 微秒听起来很适合这种延迟(我实际上看到过更长的延迟)。

【讨论】:

  • 关于clEnqueueReadBuffer 它确实适用于固定内存。事实上,这就是我使用它的方式,以便不再需要任何映射/取消映射。
  • 感谢您澄清这一点,尽管我很好奇您为什么希望在主机内存中拥有两个相同的缓冲区而不是使用 map/unmap。
  • 一个常见的情况是,你有一个非常快(固定内存)的内存,你想把它复制到另一个缓冲区。使用 clEnqueueReadBuffer 将直接读取到指定位置,但使用 map/unmap 将创建一个带有内存的指针。该内存不能传递到 OpenCL 范围之外,因为它指向必须使用 unmap 释放的映射缓冲区。所以,对于类似库的操作,更容易映射一次,然后读取数据。
猜你喜欢
  • 2016-02-25
  • 1970-01-01
  • 1970-01-01
  • 2022-11-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-21
相关资源
最近更新 更多