[OpenCL]最近邻居使用欧氏距离

时间:2011-03-21 17:35:14

标签: c++ c opencl nearest-neighbor

我正在使用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,但是现在我想做一个简单的方法。

7 个答案:

答案 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都具有经过专门优化的硬件以便快速完成。