我正在使用OpenCL查找两组3D点之间的最近邻居。
最近邻:对于DataSet中的每个点(x,y,z),我必须找到模型中最近的一个。平方距离=(Ax-Bx)^ 2 +(Ay-By)^ 2 +(Az-Bz)^ 2
这是我到目前为止所做的:
struct point {
int x;
int y;
int z;
};
__kernel void
nearest_neighbour(__global struct point *model,
__global struct point *dataset,
__global int *nearest,
const unsigned int model_size)
{
int g_dataset_id = get_global_id(0);
int dmin = -1;
int d, dx, dy, dz;
for (int i=0; i<model_size; ++i) {
dx = model[i].x - dataset[g_dataset_id].x;
dx = dx * dx;
dy = model[i].y - dataset[g_dataset_id].y;
dy = dy * dy;
dz = model[i].z - dataset[g_dataset_id].z;
dz = dz * dz;
d = dx + dy + dz;
if(dmin == -1 || d < dmin)
{
nearest[g_dataset_id] = i;
dmin = d;
}
}
}
代码似乎有效,但我确信它可以优化。 我想知道如何利用本地内存来改善它。
由于
P.S。我知道还有其他(更好的)方法可以找到最近的邻居,比如kd-tree,但是现在我想做一个简单的方法。
答案 0 :(得分:4)
编译器可能会为你提升这些循环不变量,但为了确保它完成,请尝试使用此代码将它们分配给名为datum_x
的临时变量等等。此外,将dmin初始化为MAX_INT
可以避免与-1
进行多余的比较。另一种方法是展开第一个循环迭代(使用i=0
)以初始化dmin。
int dmin = MAX_INT;
int d, dx, dy, dz;
int datum_x, datum_y, datum_z;
datum_x = dataset[g_model_id].x;
datum_y = dataset[g_model_id].y;
datum_z = dataset[g_model_id].z;
for (int i=0; i<size_dataset; ++i) {
dx = model[i].x - datum_x;
dx = dx * dx;
dy = model[i].y - datum_y;
dy = dy * dy;
dz = model[i].z - datum_z;
dz = dz * dz;
d = dx + dy + dz;
if(d < dmin)
{
nearest[g_dataset_id] = i;
dmin = d;
}
}
答案 1 :(得分:2)
也许快速的预过滤器可以加快速度。您可以先检查所有三个坐标中的距离是否比dmin更近,而不是立即计算平方距离。所以,你可以用
替换你的内循环{
dx = model[i].x - datum_x;
if (abs(dx) >= dmin) continue;
dy = model[i].y - datum_y;
if (abs(dy) >= dmin) continue;
dz = model[i].z - datum_z;
if (abs(dz) >= dmin) continue;
dx = dx * dx;
dy = dy * dy;
dz = dz * dz;
d = dx + dy + dz;
if(d < dmin)
{
nearest[g_dataset_id] = i;
dmin = d;
}
}
我不确定对每个点的abs()
和if
的额外调用是否会过滤掉足够的数据点,但我认为这是一个足够简单的尝试。< / p>
答案 2 :(得分:1)
我遇到的第一件事就是希思的建议。每个工作项同时访问内存项model[i]
。根据编译器在优化方面的优势,让每个工作项访问数组中的不同元素可能会更好。错开它的一种方法是:
int datum_x, datum_y, datum_z;
datum_x = dataset[g_model_id].x;
datum_y = dataset[g_model_id].y;
datum_z = dataset[g_model_id].z;
for (int i=0; i<size_dataset; ++i) {
j = (i + g_model_id) % size_dataset; // i --> j by cyclic permutation
dx = model[j].x - datum_x;
dx = dx * x;
dy = model[j].y - datum_y;
dy = dy * dy;
/* and so on... */
}
但是,您的代码中对model[i]
的访问可能会被视为“广播”,在这种情况下,我的代码将运行得更慢。
答案 3 :(得分:1)
Heath的建议也可以应用于输出索引:维护变量nearest_id
,并在循环后只写一次。
我将尝试使用int4向量,而不是3组件结构,并使用向量运算。
答案 4 :(得分:1)
我很确定花费大量时间将当前分钟写入nearest[g_dataset_id]
。访问全局内存通常非常慢,因此最好将当前min存储在寄存器中,就像使用dmin = d
一样。
就像这样:
...
int dmin = MAX_INT;
int imin = 0;
...
for (...)
{
...
if(d < dmin)
{
imin = i;
dmin = d;
}
}
nearest[g_dataset_id] = imin; //write to global memory only once
答案 5 :(得分:1)
在Eric Bainville的建议之后,我试图摆脱点结构。正如我所建议的那样,我使用了float4,这就是我所做的:
__kernel void
nearest_neighbour(__global float4 *model,
__global float4 *dataset,
__global unsigned int *nearest,
const unsigned int model_size)
{
int g_dataset_id = get_global_id(0);
float dmin = MAXFLOAT;
float d;
/* Ottimizzato per memoria locale */
float4 local_xyz = dataset[g_dataset_id];
float4 d_xyz;
int imin;
for (int i=0; i<model_size; ++i) {
d_xyz = model[i] - local_xyz;
d_xyz *= d_xyz;
d = d_xyz.x + d_xyz.y + d_xyz.z;
if(d < dmin)
{
imin = i;
dmin = d;
}
}
nearest[g_dataset_id] = imin; // Write only once in global memory
}
问题是此版本的运行速度比基于点结构的版本慢一点。可能是因为在结构中我使用了预过滤器:
dx = model[i].x - local_x;
dx = dx * dx;
if (dx >= dmin) continue;
dy = model[i].y - local_y;
dy = dy * dy;
if (dy >= dmin) continue;
dz = model[i].z - local_z;
dz = dz * dz;
if (dz >= dmin) continue;
d = dx + dy + dz;
我无法使用预过滤器宽度float4版本。 在您看来,我可以对float4版本进行其他优化吗?
谢谢大家的宝贵建议
答案 6 :(得分:1)
关于的具体问题“我想知道如何利用本地内存使其更好。”
使用GPU的本地内存可能会非常棘手。在处理SDK代码示例和编程指南之前,您需要花一些时间来处理它。
基本上,你使用本地内存来缓存一些全局数据块 - 在你的情况下是model []数组 - 这样你就可以从那里读取它比从全局读取它更快。如果你确实想尝试它,它会像这样的伪代码:
For each block of the model array {
1) Read data from __global and write it to __local
2) Barrier
3) For each model datum in the __local cache,
Read it and process it.
4) Barrier
}
步骤3基本上就是你现在拥有的循环,除了它只处理一大块模型数据而不是整个东西。
无论何时使用本地内存,步骤2和4都是绝对必要。您必须同步工作组中的所有theads。屏障强制所有工作项在屏障之前完成代码,然后允许任何工作项继续执行屏障之后的代码。这可以防止工作项在其他线程写入之前从本地内存中读取数据。我不记得屏障指令的语法,但它们在OpenCL文档中。
步骤1您可以让每个工作项从全局读取不同的数据并将其写入本地缓存。
这样的事情(警告这是过于简单和未经测试的!):
__local float4 modelcache[CACHESIZE];
int me = get_local_id(0);
for (int j = 0; j < model_size; j += CACHESIZE) {
modelcache[me] = dataset[j+me];
barrier(CLK_LOCAL_MEM_FENCE);
for (int i=0; i < CACHESIZE; ++i) {
d_xyz = modelcache[i] - local_xyz;
... etc.
}
barrier(CLK_LOCAL_MEM_FENCE);
}
设计问题是:本地缓存应该有多大?什么是工作组规模?
本地数据存储在工作组中的工作项之间共享。如果您的ND工作项阵列并行执行多个工作组,则每个工作组都有自己的modelcache副本。
如果使本地数据阵列太小,使用它们几乎没有任何好处。如果你把它们做得太大,那么GPU就无法并行执行任意数量的工作组,而你实际上可能会运行得相当慢。
最后,我不得不说这个特定的算法不太可能从本地内存缓存中受益很多。在您的程序中,所有工作项都在同时读取相同的模型[i]位置,并且大多数GPU都具有经过专门优化的硬件以便快速完成。