OpenCL - 执行动态for循环

时间:2016-07-31 17:29:14

标签: loops parallel-processing opencl gpgpu cpu-architecture

当OpenCL内核的边界是动态的时,它们如何在设备上执行for循环,即每个工作项的for循环执行的次数是多少?

AFAIK,内核是一组(或更好地说是一个流)的指令。 GPU设备是一组独立的计算单元(流多处理器 - SM),每个计算单元包含多个计算单元(流处理器 - SP)。

每个SM可以从内核(即指令流)加载一条指令(对于不同的SM,这可能是不同的指令),并执行加载的指令,以获得与当前SM中的SP一样多的工作项(每个SP运行相同的指令,但数据不同 - SIMD)。

一个SM中的所有SP必须运行相同的指令,因此在执行for循环条件后,必须根据每个工作项的条件结果做出动态决策,下一条指令是什么在SM 上运行它将运行的工作项。

基于这个假设,我假设foobaz内核(见下文)执行得更快,因为当一个工作项完成执行时,另一个工作项可以取而代之。

这个假设是错误的吗?

以下哪两个内核foobarfoobaz最终会执行得更快?性能取决于什么? (一个元素的属性数量可以大于其他元素的数量级。)

foobar;

__kernel void foobar(__global int* elements,            /* size N          */
                     __global int* element_properties,  /* size N*constant */
                     __global int* output)              /* size N          */
{
    size_t gid  = get_global_id(0);
    int reduced = 0;

    for (size_t i=N*gid; i<N+N*gid; i++)
      reduce += predict_future_events( reduce, element_properties[i] );


    output[gid] = reduced;
}

...和foobaz;

__kernel void foobaz(__global int*  elements,                   /* size N       */
                     __global int*  element_properties,         /* size upper-bounded */
                     __global int2* element_properties_ranges,  /* size N       */
                     __global int*  output)                     /* size N       */
{
    size_t gid  = get_global_id(0);
    int reduced = 0;

    // `range.x` = starting index in `element_properties`
    // `range.y` = ending   index in `element_properties`
    int2 range = element_properties_ranges[gid]; 

    for (size_t i=range.x; i<range.y; i++)
      reduce += predict_future_events( reduce, element_properties[i] );


    output[gid] = reduced;
}

2 个答案:

答案 0 :(得分:1)

假设它是opencl 1.2设备,

如果每个“predict_future_events”在性能方面都很混乱,您可以检查一些“硬件优化”更改。您可以同时抛出2个不同的内核(两个不同的全内核(N),如果它们可以分开/独立),或者您可以将一半内核(N / 2)推送为“常量版本”,将后半部分推入不同的内核(因为这与你的第一个计算示例没有区别),也许驱动程序可以处理一个内核永远延迟但至少另一半获得计算资源(如果驱动程序可以做到这一点)的情况。因此,更多的管道将忙于做一些事情,并最终为内核提供更好的时机。

除此之外,每个函数具有随机延迟使得很难预测循环中的哪一组函数给出了总延迟,因此给予所有线程相等的步数(如第一示例/常量中)更容易“假设” “线程之间的平衡负载将有更大的机会。

例如,光线跟踪内核的1000深度折射+ 1000深度反射将足够混乱,因此您可以为每个线程提供1条光线进行计算,因为您无法知道光线是否会被折射或反射在下一个表面上(如果有的话)。也许分组较近的可以更频繁地使用L1-L2缓存。

对于opencl 2.0设备,你可以在内核线程中产生更多的线程/组,这会使它更加动态。

答案 1 :(得分:0)

你在两种解决方案中做的几乎都是一样的。我的赌注是他们必须在几乎相同的时间内完成。

如果想要更快,请使用int4作为参数的SIMD功能并减少变量,然后调整您的predict_future_events函数以处理int4值。通过这种方式,您可以获得高达4倍的性能,因为每条指令并行处理4个元素。

根据您的硬件,您可以使用int8或最多使用int16。

顺便说一句:我没有看到分配的N变量在哪里,也没有看到任何元素数组的使用。