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

时间:2014-12-01 23:16:22

标签: multithreading parallel-processing opencl gpu cpu

我正在编写一个openCL应用程序,其中我有N个工作项,我想分发给D设备,其中N> 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秒之间。我觉得他们可能并不像我想要的那样彼此并行运行。

如何解决这些问题?

1 个答案:

答案 0 :(得分:3)

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

  • 内核大小是什么?
  • 他们完成的速度有多快?

    • 如果内核大小很小并且完成得非常快。然后启动它们的开销很高。因此,将它们分布在许多设备上的更精细的粒度并不能克服额外的开销。在这种情况下,最好直接增加工作量并仅使用1个设备。
  • 内核是否独立?他们使用不同的缓冲区吗?

    • 另一个重要的事情是为每个设备提供完全不同的内存,否则设备之间的内存泄漏会延迟内核启动,在这种情况下,单个设备(在本地保存所有内存缓冲区)将表现更好。
    • OpenCL会将内核使用的所有缓冲区复制到设备,并且将阻止&#34;阻塞&#34;使用内核写入的缓冲区的所有内核(甚至在其他设备中);将等待它完成,然后将缓冲区复制回另一台设备。
  • 主机是否是瓶颈?

    • 主机有时没有您想象的那么快,有时内核运行得如此之快,以至于主机是为他们安排工作的一大瓶颈。
    • 如果您将CPU用作CL设备,则它无法执行这两项任务(充当主机并运行内核)。 在安排内核时,您应该更喜欢GPU设备而不是CPU设备。
  • 永远不要让设备清空

    • 等待设备完成执行,在排队更多工作之前通常是一个非常糟糕的主意。您应该在当前内核完成之前提前(1或2)预先排队内核。否则,设备利用率甚至不会达到80%。由于自内核完成之后有很长时间,直到主机意识到它,甚至更长的时间,直到主机将更多数据排队到内核(通常> 2ms,对于10ms内核,这是33%浪费)。

我愿意:

  1. 将此行更改为已提交的作业:if(status >= CL_SUBMITTED)

  2. 确保设备是有序的GPU - &gt;中央处理器。因此,GPU是设备0,1,CPU是设备2。

  3. 尝试删除CPU设备(仅使用GPU)。也许速度更好。