multithreading - OpenCL-如何有效地将工作项分配到不同的设备

标签 multithreading parallel-processing opencl gpu cpu

我正在编写一个openCL应用程序,其中有N个工作项,我想将其分配给N> D的D个设备,然后每个设备可以并行处理其自身工作项的元素,从而实现某种“双重”并行性。

这是我已经编写的尝试实现此目标的代码。

首先,我为每个设备创建一个事件,并将它们全部设置为完成:

cl_int err;
cl_event *events = new cl_event[deviceCount];
for(int i = 0; i < deviceCount; i++)
{
    events[i] = clCreateUserEvent(context, &err);
    events[i] = clSetUserEventStatus(events[i], CL_COMPLETE);

}

每个设备还具有自己的命令队列和内核的“实例”。

然后,进入我的“主循环”以分配工作项。该代码找到第一个可用的设备,并将其与工作项排队。

/*---Loop over all available jobs---*/
for(int i = 0; i < numWorkItems; i++)
{   
    WorkItem item = workItems[i];

    bool found = false; //Check for device availability
    int index = -1;     //Index of found device
    while(!found)       //Continuously loop until free device is found.
    {
        for(int j = 0; j < deviceCount; j++) //Total number of CPUs + GPUs
        {
            cl_int status;
            err = clGetEventInfo(events[j], CL_EVENT_COMMAND_EXECUTION_STATUS, sizeof(cl_int), &status, NULL);
            if(status == CL_COMPLETE) /*Current device has completed all of its tasks*/
            {
                found = true; //Exit infinite loop
                index = j;    //Choose current device
                break;        //Break out of inner loop
            }
        }
    }

    //Enqueue my kernel
    clSetKernelArg(kernels[index], 0, sizeof(cl_mem), &item);
    clEnqueueNDRangeKernel(queues[index], kernels[index], 1, NULL, &glob, &loc, 0, NULL, &events[index]);

    clFlush(commandQueues[index]);
}

最后,我在所有设备上调用clFinish进行了总结:

/*---Wait For Completion---*/
for(int i = 0; i < deviceCount; i++)
{
    clFinish(queues[i]);
}

但是,此方法存在一些问题:

1)它不会将工作分配到我的所有设备上。在我当前的计算机上,我有3台设备。我上面的算法仅将工作分配给设备1和2。设备3总是被遗弃,因为设备1和2完成得如此之快,以至于他们可以在3机会之前抢走更多的工作项目。

2)即使设备1和2一起运行,我也只能看到非常非常温和的速度增加。例如,如果我将所有工作项分配给设备1,则可能需要10秒钟才能完成;如果我将所有工作项分配给设备2,则可能需要11秒钟才能完成,但是如果我尝试在两个设备之间分配工作,结合起来,可能需要8-9秒,而我希望可能在4-5秒之间。我感到他们可能并没有真正按照我想要的方式并行运行。

我该如何解决这些问题?

最佳答案

您必须小心大小和存储位置。通常,在处理GPU设备时不会考虑这些因素。我会问你:

  • 内核大小是多少?
  • 他们完成多快?
  • 如果内核很小,并且它们很快完成。这样,启动它们的开销将很高。因此,将它们分布在许多设备上的更精细的粒度并不能克服额外的开销。在这种情况下,最好直接增加工作量并仅使用1台设备。
  • 内核独立吗?他们使用不同的缓冲区吗?
  • 另一个重要的事情是每个设备都拥有完全不同的内存,否则设备之间的内存浪费将延迟内核启动,在这种情况下,单个设备(将所有内存缓冲区都保留在本地)将表现更好。
  • OpenCL会将内核使用的所有缓冲区复制到设备,并将使用该内核正在写入的缓冲区“阻止”所有内核(即使在其他设备中);将等待它完成,然后将缓冲区复制回另一台设备。
  • 主机是瓶颈吗?
  • 主机有时不如您想像的那样快,并且有时内核运行速度如此之快,以至于主机成为调度作业的大瓶颈。
  • 如果将CPU用作CL设备,则它不能同时执行两项任务(充当主机并运行内核)。 在计划内核时,您应该始终选择GPU设备而不是CPU设备。
  • 切勿让设备清空
  • 在排队执行更多工作之前,等到设备完成执行通常是一个非常糟糕的主意。即使在当前内核完成之前,也应该提前(1或2)将内核抢先排队。否则,设备利用率将无法达到80%。由于从内核完成直到主机意识到它要花费大量的时间,而直到主机将更多数据排队给内核的时间也更长(通常> 2ms,对于10ms的内核,这浪费了33%) 。

  • 我会做:
  • 将此行更改为已提交的作业:if(status >= CL_SUBMITTED)
  • 确保将设备订购为GPU-> CPU。因此,GPU是设备0,1,CPU是设备2。
  • 尝试卸下CPU设备(仅使用GPU)。也许速度会更好。
  • 关于multithreading - OpenCL-如何有效地将工作项分配到不同的设备,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27239154/

    相关文章:

    java - 多线程运行速度比单进程慢

    c++ - 线程安全对象的包装类是否也需要成为线程安全的?

    c++ - OpenMP/C++ : Parallel for loop with reduction afterwards - best practice?

    r - 带plyr的多核,MC

    caching - 是什么导致 CPU 中的 L3 缓存未命中?

    c++ - 如何在OpenCL中使用缓冲区分配和映射内存机制?

    java - Java 中的多线程编程——方法上的差异?

    c# - volatile 变量的值在多线程中不改变

    python - joblib.Parallel() 比 skimage 的 single 慢

    image - OpenCL 中的图像和缓冲区有什么区别?