最大化OpenCL内存输出

时间:2012-10-17 16:04:24

标签: performance memory opencl

使用OpenCL,我似乎无法将超过7MB /秒的数据从Radeon 7970拉入i5桌面的主内存。

#include <iostream>
#include <Windows.h>
#include <CL/cl.h>

int main(int argc, char ** argv)
{
    cl_platform_id platform;
    clGetPlatformIDs(1, &platform, NULL);
    cl_device_id device;
    clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, 1, &device, NULL);
    cl_context context = clCreateContext(NULL, 1, &device, NULL, NULL, NULL);
    cl_command_queue queue = clCreateCommandQueue(context, device, 0, NULL);
    const char *source =
    "__kernel void copytest(__global short* dst) {\n"
    "    __local short buff[1024];\n"
    "    for (int i = 0; i < 1024; i++) {\n"
    "        for (int j = 0; j < 1024; j++)\n"
    "            buff[j] = j;\n"
    "        (void)async_work_group_copy(&dst[i*1024], buff, 1024, 0);\n"
    "    }\n"
    "}\n";
    cl_program program = clCreateProgramWithSource(context, 1, &source, NULL, NULL);
    clBuildProgram( program, 1, &device, NULL, NULL, NULL);
    cl_kernel kernel = clCreateKernel( program, "copytest", NULL);
    cl_mem buf = clCreateBuffer(context, CL_MEM_WRITE_ONLY, 1024 * 1024 * 2, NULL, NULL);
    const size_t global_work_size = 1;
    clSetKernelArg(kernel, 0, sizeof(buf), (void*)&buf);
    LARGE_INTEGER pcFreq = {}, pcStart = {}, pcEnd = {};
    QueryPerformanceFrequency(&pcFreq);
    QueryPerformanceCounter(&pcStart);
    clEnqueueNDRangeKernel(queue, kernel, 1, NULL, &global_work_size, NULL, 0, NULL, NULL);
    clFinish(queue);
    QueryPerformanceCounter(&pcEnd);
    std::cout << 2.0 * pcFreq.QuadPart / (pcEnd.QuadPart-pcStart.QuadPart) << "MB/sec";
}

正如您所看到的,它只在一个工作单元上运行。我尝试用多个(64个)工作单元之间的循环替换async_work_group_copy(),但这没有帮助。

是否有某种方法可以让Radeon的内存速度超过7MB /秒?我对数百MB /秒感兴趣。 NVidia会更快吗?

3 个答案:

答案 0 :(得分:1)

问题是你只在GPU上使用一个线程,使数千个线程空闲。在这种情况下,您可以做两件事来帮助您实现更快的速度。

首先,尝试在工作组中使用更多线程:

__kernel void copytest(__global short* dst) {
    __local short buff[1024];
    for (int i = 0; i < 1024; i++) {
        for (int j = get_local_id(0); j < 1024; j+= get_local_size(0))
            buff[j] = j;
        barrier(CLK_LOCAL_MEM_FENCE);
        (void)async_work_group_copy(&dst[i*1024], buff, 1024, 0);
    }
}

然后您可以将工作组的大小增加到256左右。

const size_t local_work_size = 256;
clEnqueueNDRangeKernel(queue, kernel, 1, NULL, &global_work_size, &local_work_size, 0, NULL, NULL);

其次,您正在使用GPU,因此您可能不应该只使用一个工作组。您可以使用更多工作组,如下所示:

__kernel void copytest(__global short* dst) {
    __local short buff[1024];
    for (int i = get_group_id(0); i < 1024; i += get_num_groups(0)) {
        for (int j = get_local_id(0); j < 1024; j+= get_local_size(0))
            buff[j] = j;
        barrier(CLK_LOCAL_MEM_FENCE);
        (void)async_work_group_copy(&dst[i*1024], buff, 1024, 0);
    }
}

然后你可以增加工作组的数量:

const size_t local_work_size = 256;
const size_t global_work_size = local_work_size * 32;
clEnqueueNDRangeKernel(queue, kernel, 1, NULL, &global_work_size, &local_work_size, 0, NULL, NULL);

希望这有助于加快申请速度。

答案 1 :(得分:0)

确保正确分配缓冲区:阅读NVIDIA OpenCL编程指南(如果可以找到)以查看分配固定内存。有一个完全成熟的例子可以让你达到6GB / s - 同样的原则适用于AMD。特别是CL_MEM_ALLOC_HOST_PTR标志。

答案 2 :(得分:0)

这里你真的不需要'j'循环。 async_work_group_copy可以双向工作;您可以复制到全局和本地空间。

//kernel will copy 2MB of short* in memory
__kernel void copytest(__global short* dst) {
  __local short buff[1024];
  for (int i = get_group_id(0); i < 1024; i += get_num_groups(0)) {
    (void)async_work_group_copy(buff, &dst[i*1024], 1024, 0);
    (void)async_work_group_copy(&dst[i*1024], buff, 1024, 0);
  }
}

opencl 1.0规范说必须有16kb或更多的本地内存可供您使用(32kb +与ocl v1.1或更高版本)。许多设备实际上有32kb。我建议轮询系统并尽可能多地使用。实际上,您仍需要将一些本地内存保存用于其他目的。 see clGetDeviceInfo(CL_DEVICE_LOCAL_MEM_SIZE)

//using 16kb local memory per work group to copy 2MB...
__kernel void copytest(__global short* dst) {
  __local short buff[16384];
  for (int i = get_group_id(0); i < 64; i += get_num_groups(0)) {
    (void)async_work_group_copy(buff, &dst[i*16384], 16384, 0);
    (void)async_work_group_copy(&dst[i*16384], buff, 16384, 0);
  }
}

如果要进行足够少量的复制,您可以通过使用完成工作所需的工作组数量来摆脱“i”循环。这会导致装配中的分支更少。

//using 32kb and 16 work groups to copy 2MB...
__kernel void copytest(__global short* dst) {
  __local short buff[32768];
  int i = get_group_id(0);
  (void)async_work_group_copy(buff, &dst[i*32768], 32768, 0);
  (void)async_work_group_copy(&dst[i*32768], buff, 32768, 0);
}