Memory OpenCL(GPU)中的慢速随机内存访问

Memory OpenCL(GPU)中的慢速随机内存访问,memory,textures,opencl,gpu,Memory,Textures,Opencl,Gpu,基本上,我正在编写一个OpenCL内核,它以随机/不可预测的方式访问全局内存(目前光线跟踪器的未优化路径跟踪组件),这几乎完全否定了GPU相对于CPU的并行化性能优势(作为参考,我运行的是i7-2630QM CPU,GTX 560m GPU-性能图如下)。为了便于调整/测试,我编写了一个“测试”模拟这种内存访问模式的内核;它本质上为GPU提供了一个大的三角形坐标数组和一个要处理的索引列表——对于每个索引,它将在该三角形和63之后运行一个光线三角形交集,模拟通过八叉树中的对象进行迭代 我已经尝试了

基本上,我正在编写一个OpenCL内核,它以随机/不可预测的方式访问全局内存(目前光线跟踪器的未优化路径跟踪组件),这几乎完全否定了GPU相对于CPU的并行化性能优势(作为参考,我运行的是i7-2630QM CPU,GTX 560m GPU-性能图如下)。为了便于调整/测试,我编写了一个“测试”模拟这种内存访问模式的内核;它本质上为GPU提供了一个大的三角形坐标数组和一个要处理的索引列表——对于每个索引,它将在该三角形和63之后运行一个光线三角形交集,模拟通过八叉树中的对象进行迭代

我已经尝试了一系列的优化,包括:合并内存访问,使用只读纹理内存代替“全局”,循环展开,调整工作组大小和线程分布,本地内存和屏障,以及手动内联功能。这些功能最多都提供了增量性能改进。在运行内核之前对索引进行排序确实会显著加快速度,但对于八叉树遍历,这需要在GPU上每隔一天重新排序一次迭代,再加上其他因素,让我怀疑它是否会有很大帮助

我试图找出是否存在一些可以弥补的重大漏洞——数据类型的误用、看不见的优化、驱动程序太旧(使用OpenCL 1.0,它不允许1d纹理)等等——或者考虑到我所使用的硬件,我是否对性能的提高期望过高(光线跟踪方面的各种优化仍有待完成,但我想在深入研究之前解决这个更普遍的问题)。非常感谢您提前提出的任何见解或建议

64个三角形的409600块(以409600线程运行)的性能数字(秒):

代码:

#定义IMG_宽度_减一32767
#定义IMG\u高度\u日志\u 2 15
#定义子系统(目的地、v1、v2)\
dest[0]=v1[0]-v2[0]\
dest[1]=v1[1]-v2[1]\
dest[2]=v1[2]-v2[2];
#定义ε0.00001
#定义交叉点(目的地、v1、v2)\
dest[0]=v1[1]*v2[2]-v1[2]*v2[1]\
dest[1]=v1[2]*v2[0]-v1[0]*v2[2]\
dest[2]=v1[0]*v2[1]-v1[1]*v2[0];
#定义点(v1,v2)(v1[0]*v2[0]+v1[1]*v2[1]+v1[2]*v2[2])
__核空方块(
__全局int4*输入,
__只读图像二维图像,
__全球浮动*输出,
常量(无符号整数计数)
{
int global_id=get_global_id(0);
浮子r_orig[3];
浮动r_dir[3];
浮动4个三角形点[3];
int cpuStartIndex=inputIndeces[global_id].x;
int outputIndex=inputIndeces[global_id].w;
输出[outputIndex]=0.0;
r_orig[0]=0.0;
r_orig[1]=0.0;
r_orig[2]=500.0;
r_dir[0]=0.0;
r_dir[1]=0.0;
本地整数计数器;
计数器=0;
r_dir[2]=-1.0;
浮动tvec[3]、pvec[3]、qvec[3]、edgeA[3]、edgeB[3];
浮点数、倒点数、t、u、v;
#布拉格展开64
对于(int ind=CPUSTATINDEX;ind>IMG\U HEIGHT\U LOG\U 2);
坐标[2]=(int2)((tIndex+2)和IMG_宽度减去(tIndex+2)>>IMG_高度对数2);
三角形点[0]=读取图像F(图像、采样器、坐标[0]);
三角形点[1]=读取图像F(图像、采样器、坐标[1]);
三角形点[2]=读取图像F(图像、采样器、坐标[2]);
edgeA[0]=(三角形点[0].w-三角形点[0].x);
edgeA[1]=(三角形点[1].x-三角形点[0].y);
edgeA[2]=(三角形点[1].y-三角形点[0].z);
edgeB[0]=(三角形点[1].z-三角形点[0].x);
edgeB[1]=(三角形点[1].w-三角形点[0].y);
edgeB[2]=(三角形点[2].x-三角形点[0].z);
交叉(pvec、r_dir、edgeB);
det=点(edgeA,pvec);
if(det>-EPSILON&&det1.0){
继续;
}
交叉(qvec、tvec、edgeA);
v=点(r_方向,qvec)*库存数据;
如果(v<0.0 | | u+v>1.0){
继续;
}
t=网点(edgeB,qvec)*inv\U det;
如果(t>0.001){
++计数器;
}
否则{
继续;
}
}
输出[outputIndex]=(浮点)计数器;
}

在我做过的一些小型基准测试中,我发现稍微展开一点循环会有很大帮助。我认为在开始边缘计算之前,需要重复计算坐标和读取纹理的6行两到四次。因此,需要多个三角形点数组或数组数组。而不是mu不幸的是,我把三角形点做成了[4][3]数组,展开坐标计算和图像读取,这样它只在主循环的每4次迭代中进行一次,但它只会显著地减慢速度。非常奇怪的是,当我删除“if(!(loopCounter&3)){”限定符,它返回到其原始速度。可能手动展开它会干扰内部OpenCL优化?我忘了提一下,您不能添加任何if语句。您必须填充输入,以便它们是循环展开因子的倍数(在您的情况下为4)。太糟糕了,所有这些都帮不上你。顺便说一句,你的GPU全局内存带宽不是很高,可能只有CPU的2~3倍,所以对于一个从随机坐标进行三次纹理读取的内核来说,你的结果并不那么令人惊讶。实际上我以前也尝试过类似的方法(当然,不使用纹理内存)我只是觉得奇怪(而且非常令人沮丧),大多数传统的数据优化只会降低性能
CPU (Single Thread):

Unsorted:   2.21
Sorted:     1.48 

GPU:

        Sorted  Unsorted
Texture     0.07    0.15
Global      0.02    0.25
#define IMG_WIDTH_MINUS_ONE 32767
#define IMG_HEIGHT_LOG_2 15
#define SUB(dest,v1,v2) \
      dest[0]=v1[0]-v2[0]; \
      dest[1]=v1[1]-v2[1]; \
      dest[2]=v1[2]-v2[2];

#define EPSILON 0.00001

#define CROSS(dest,v1,v2) \
      dest[0]=v1[1]*v2[2]-v1[2]*v2[1]; \
      dest[1]=v1[2]*v2[0]-v1[0]*v2[2]; \
      dest[2]=v1[0]*v2[1]-v1[1]*v2[0];

#define DOT(v1,v2) (v1[0]*v2[0]+v1[1]*v2[1]+v1[2]*v2[2])

__kernel void square(
   __global int4 *inputIndeces,
   __read_only image2d_t image,
   __global float* output,
   const unsigned int count)
{

    int global_id = get_global_id(0);
    float r_orig[3];
    float r_dir[3];
    float4 trianglePoints[3];
    int cpuStartIndex = inputIndeces[global_id].x;
    int outputIndex = inputIndeces[global_id].w;
    output[outputIndex] = 0.0;
    r_orig[0] = 0.0;
    r_orig[1] = 0.0;
    r_orig[2] = 500.0;
    r_dir[0] = 0.0;
    r_dir[1] = 0.0;
    local int counter;
    counter = 0;
    r_dir[2]= -1.0;
    float tvec[3], pvec[3], qvec[3], edgeA[3], edgeB[3];
    float det, inv_det, t, u, v;
    #pragma unroll 64
    for (int ind=cpuStartIndex;ind<cpuStartIndex+64;++ind) {



        int tIndex = ind<<2;

        int2 coords[3];


        coords[0] = (int2)(tIndex & IMG_WIDTH_MINUS_ONE,tIndex >> IMG_HEIGHT_LOG_2);
        coords[1] = (int2)((tIndex + 1) & IMG_WIDTH_MINUS_ONE,(tIndex + 1) >> IMG_HEIGHT_LOG_2);
        coords[2] = (int2)((tIndex + 2) & IMG_WIDTH_MINUS_ONE,(tIndex + 2) >> IMG_HEIGHT_LOG_2);

        trianglePoints[0] = read_imagef(image, sampler, coords[0]);
        trianglePoints[1] = read_imagef(image, sampler, coords[1]);
        trianglePoints[2] = read_imagef(image, sampler, coords[2]);

        edgeA[0] = (trianglePoints[0].w - trianglePoints[0].x);
        edgeA[1] = (trianglePoints[1].x - trianglePoints[0].y);
        edgeA[2] = (trianglePoints[1].y - trianglePoints[0].z);

        edgeB[0] = (trianglePoints[1].z - trianglePoints[0].x);
        edgeB[1] = (trianglePoints[1].w - trianglePoints[0].y);
        edgeB[2] = (trianglePoints[2].x - trianglePoints[0].z);

        CROSS(pvec,r_dir,edgeB);
        det = DOT(edgeA, pvec);

        if (det > -EPSILON && det < EPSILON) {
             continue;
        }

        inv_det = 1.0 / det;

        tvec[0] = r_orig[0] - trianglePoints[0].x;
        tvec[1] = r_orig[1] - trianglePoints[0].y;
        tvec[2] = r_orig[2] - trianglePoints[0].z;

        u = DOT(tvec, pvec) * inv_det;
        if (u < 0.0 || u > 1.0) {
                continue;
            }

            CROSS(qvec,tvec,edgeA);
            v = DOT(r_dir, qvec) * inv_det;
            if (v < 0.0 || u + v > 1.0) {
                continue;
            }
            t = DOT(edgeB, qvec) * inv_det;
            if (t > 0.001) {
                ++counter;
            }
        else {
            continue;
        }

    }
    output[outputIndex] = (float)counter;
}