基本上,我正在编写一个 OpenCL 内核,它以随机/不可预测的方式访问全局内存(光线跟踪器的当前未优化的路径跟踪组件),这几乎完全否定了 GPU 相对于 CPU 的并行化性能优势(供参考,我在 i7-2630QM CPU、GTX 560m GPU 上运行——性能数据如下)。为了便于调整/测试,我编写了一个“测试”内核来模拟这种内存访问模式;它本质上为 GPU 提供了大量的三角形坐标和要处理的索引列表 - 对于每个索引,它将在该三角形和之后的 63 上运行射线三角形交集,模拟八叉树中对象的迭代。
我尝试了一系列优化,包括:合并内存访问、使用只读纹理内存代替“全局”、循环展开、调整工作组大小和线程分布、本地内存和屏障以及手动内联函数。这些都提供了,充其量,增量性能改进。在运行内核之前对索引进行排序确实可以显着加快速度,但是对于八叉树遍历,这将需要在 GPU 上重新排序每次迭代,并结合其他因素,让我怀疑它是否会有很大帮助。
我试图弄清楚是否有一些可以修复的主要漏洞——滥用数据类型、看不见的优化、驱动程序太旧(使用 OpenCL 1.0,它不允许 1d 纹理)等等——或者如果我期待考虑到我正在使用的硬件,性能提升太多了(关于光线追踪方面的各种优化仍有待完成,但我想在深入研究之前解决这个更普遍的问题)。非常感谢您提前提出任何见解或建议。
409,600 个 64 个三角形的块(以 409,600 个线程运行)的性能数据(秒):
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;
}