如何进一步优化OpenCl内核

如何进一步优化OpenCl内核,opencl,Opencl,我已经分离出一些相互不同的函数,但需要在不同的工作项中并行运行。因此,当调用内核时,它需要决定必须执行哪个函数 void call_calc0() { // code } void call_calc1() { // code } void call_calc2() { // code } void call_calc3() { // code } __kernel void perform (__global double* A, __global d

我已经分离出一些相互不同的函数,但需要在不同的工作项中并行运行。因此,当调用内核时,它需要决定必须执行哪个函数

void call_calc0() {
    // code
}

void call_calc1() {
    // code
}

void call_calc2() {
    // code
}

void call_calc3() {
    // code 
}

__kernel void perform (__global double* A, __global double* B) {
    int idx = get_global_id(0);
    if (idx == 0) {
        call_calc0();
    } else if (idx == 1) {
        call_calc1();
    } else if (idx == 2) {
        call_calc2();
    } else if (idx == 3) {
        call_calc3();
    }
}

如果有256/512个工作项,此代码示例将不是正确的方式。如何对此进行优化?

由于OpenCL不支持函数指针,您只能使用
if/else
开关
。这两者的性能应该相同,只是编码偏好不同而已

使用预处理器宏可以使事情变得更简单/更干净。例如,您可以这样做:

#define CALL_CASE(i)    \
    case i:             \
        call_calc##i(); \
        break;          \

__kernel void perform (__global double* A, __global double* B) {
    int idx = get_global_id(0);
    switch (idx) {
        CALL_CASE(0);
        CALL_CASE(1);
        CALL_CASE(2);
        CALL_CASE(3);
        ... // etc
    }
}
如果您正在自动生成
call\u calcX()
函数,那么同时生成这个
开关
块就很容易了。如果您正在手动编写这些
call\u calcX()
函数,则只需额外添加一行代码即可将每个函数添加到块中。不理想,但也不可怕


根据上面的评论,这个问题似乎完全不是数据并行的,这将限制利用大多数OpenCL设备中可用的SIMD执行的能力。

由于OpenCL不支持函数指针,您只能使用
if/else
开关。这两者的性能应该相同,只是编码偏好不同而已

使用预处理器宏可以使事情变得更简单/更干净。例如,您可以这样做:

#define CALL_CASE(i)    \
    case i:             \
        call_calc##i(); \
        break;          \

__kernel void perform (__global double* A, __global double* B) {
    int idx = get_global_id(0);
    switch (idx) {
        CALL_CASE(0);
        CALL_CASE(1);
        CALL_CASE(2);
        CALL_CASE(3);
        ... // etc
    }
}
如果您正在自动生成
call\u calcX()
函数,那么同时生成这个
开关
块就很容易了。如果您正在手动编写这些
call\u calcX()
函数,则只需额外添加一行代码即可将每个函数添加到块中。不理想,但也不可怕


根据上面的评论,这个问题似乎完全不是数据并行的,这将限制利用大多数OpenCL设备中可用的SIMD执行的能力。

如果可能,最好的优化是使用四种不同的内核。如果调用这个内核时组大小不止一个,那么在并行执行时就会出现问题

如果可能的话,试着分开你的全局内存,或者以非常谨慎、不冲突的方式使用它。这应该允许您创建四个独立的内核,并消除条件代码执行

当遇到第一个if/case时,组中的一些工作项将运行代码,但其他75%的工作项将等待。大多数opencl设备,尤其是GPU,都是以这种方式运行的。当前25%的工作项完成时,它们将等待执行下一个if/case代码

这适用于opencl中的所有分支,例如if/else、switch、for和while/do。当组中的某些工作项不满足条件时,它们会等待满足条件的其他工作项。然后,在“if”组等待时执行“else”组的工作项


另一种方法是比较CPU和GPU硬件。CPU有很多专用于分支预测和高速缓存的晶体管。GPU本质上更像是矢量基,只是最近才开始支持CPU的一些更高级的流量控制功能。

如果可能,最好的优化是使用四种不同的内核。如果调用这个内核时组大小不止一个,那么在并行执行时就会出现问题

如果可能的话,试着分开你的全局内存,或者以非常谨慎、不冲突的方式使用它。这应该允许您创建四个独立的内核,并消除条件代码执行

当遇到第一个if/case时,组中的一些工作项将运行代码,但其他75%的工作项将等待。大多数opencl设备,尤其是GPU,都是以这种方式运行的。当前25%的工作项完成时,它们将等待执行下一个if/case代码

这适用于opencl中的所有分支,例如if/else、switch、for和while/do。当组中的某些工作项不满足条件时,它们会等待满足条件的其他工作项。然后,在“if”组等待时执行“else”组的工作项


另一种方法是比较CPU和GPU硬件。CPU有很多专用于分支预测和高速缓存的晶体管。GPU本质上更像矢量基,只是最近才开始支持CPU的一些更高级的流控制功能。

我可以想到函数指针和预处理器的方式。。但是,有可能在OpenCl中使用函数指针吗?函数指针在OpenCl中是不允许的。你是说每个工作项都必须执行完全不同的代码吗?是的。但在同一缓冲区。。将单个缓冲区的块分开。有可能吗。。我做了一些测试@jprice if我用一个“if”条件替换if-else。。然后编译变得非常快。。。代码将类似于if(idx==1){calc1};如果(idx==2){calc2}…当然可以,您可以使用
开关
语句实现同样的效果。然而,OpenCL NDRange执行模型是为数据并行执行而设计的,这正是GPU和加速器等设备所擅长的。听起来您的问题根本不是数据并行(您在同一数据上执行完全不同的代码),这对于围绕SIMD执行而设计的设备来说可能会有非常低的性能。在您执行的每个不同计算中是否真的没有公共组件?如果逻辑真的像您所说的那么简单,并且局部大小为1,为什么不调用4个单独的内核并完全删除条件呢?我同意@jprice的观点,这听起来真的不像是一个问题