向量中的向量加速欧氏距离

时间:2015-11-16 21:18:36

标签: c matrix 3d euclidean-distance vdsp

我需要执行一个非常常见且简单的矩阵运算 但是我需要它快速,非常快......

我已经在考虑多线程实现,但是现在我只想看看我能在单个处理器上获得它的速度有多快。

矩阵运算如下:

我正在计算点矢量(A)和参考点(B)之间的欧氏距离。
这些点位于3D空间中,每个点都有一组X,Y和Z坐标 因此,点的矢量由三个浮点阵列描述,每个点保持X,Y,Z坐标 输出是另一个长度为N的向量,它保持数组中每个点与参考点之间的距离。

三个XYZ阵列排列为Nx3矩阵的列。

x[0]      y[0]      z[0]
x[1]      y[1]      z[1]
x[2]      y[2]      z[2]
x[3]      y[3]      z[3]
.         .         .
.         .         .
.         .         .
x[N-1]    y[N-1]    z[N-1]

在内存中,矩阵按行主顺序排列为1D数组,包含X,Y和Z列的值。

x[0], x[1], x[2], x[3] . . . x[N-1], y[0], y[1], y[2], y[3] . . . y[N-1], z[0], z[1], z[2], z[3] . . . z[N-1]

由于我们需要在取平方根之前为矩阵的每个成员添加一个标量,所以整个事情有点复杂。

以下是天真C代码中的例程:

void calculateDistances3D(float *matrix, float Bx, float By, float Bz, float scalar, float *distances, int N)
{
    float *Ax = matrix;
    float *Ay = Ax + N;
    float *Az = Ay + N;
    int i;

    for (i = 0; i < N; i++) {

        float dx = Ax[i] - Bx;
        float dy = Ay[i] - By;
        float dz = Az[i] - Bz;

        float dx2 = dx * dx;
        float dy2 = dy * dy;
        float dz2 = dz * dz;

        float squaredDistance = dx2 + dy2 + dz2;
        float squaredDistancePlusScalar = squaredDistance + scalar;

        distances[i] = sqrt(squaredDistancePlusScalar);
    }
}

...这里是天真的加速实施(使用vDSP和VecLib):
(注意所有处理都是就地进行的)

void calculateDistances3D_vDSP(float *matrix, float Bx, float By, float Bz, float scalar, float *distances, int N)
{
    float *Ax = matrix;
    float *Ay = Ax + N;
    float *Az = Ay + N;

    // for each point in the array take the difference with the reference point
    Bx = -Bx;
    By = -By;
    Bz = -Bz;
    vDSP_vsadd(Ax, 1, &Bx, Ax, 1, N);
    vDSP_vsadd(Ay, 1, &By, Ay, 1, N);
    vDSP_vsadd(Az, 1, &Bz, Az, 1, N);

    // square each coordinate
    vDSP_vsq(Ax, 1, Ax, 1, N);
    vDSP_vsq(Ay, 1, Ay, 1, N);
    vDSP_vsq(Az, 1, Az, 1, N);

    // reduce XYZ columns to a single column in Ax (reduction by summation)
    vDSP_vadd(Ax, 1, Ay, 1, Ax, 1, N);
    vDSP_vadd(Ax, 1, Az, 1, Ax, 1, N);

    // add scalar
    vDSP_vsadd(Ax, 1, &scalar, Ax, 1, N);

    // take sqrt
    vvsqrtf(distances, Ax, &N);
}

在vDSP库中,可用于计算向量之间距离的唯一函数是:

vDSP_vdist()
vDSP_distancesq()
vDSP_vpythg()

也许我错过了一些东西,但据我所知,他们都没有支持三个输入向量来计算3D中的距离。

要注意的几件事:
(1)我不是在比较距离,所以我无法忍受方形距离。我需要真正的距离,因此计算平方根是绝对必要的 (2)如果你真的认为这样做可以使代码明显更快,那么取倒数平方根是可能的。

我的印象是我没有充分利用Accelerate框架 我正在寻找一些更聪明,更简洁的东西,在更少的函数调用中做更多的工作。以其他方式重新排列内存也可以,但我认为内存布局非常好。

我也对有关在英特尔处理器上运行的其他高度优化/矢量化线性代数库的建议持开放态度。我不关心它们是商业还是开源解决方案,只要它们的性能快速而强大。

问题是:Accelerate框架中的最佳功能或功能组合是什么,以实现比上述更快的代码?

我在运行Mac OS X El Capitan的MacBook Pro(Retina,15英寸,2014年中)上使用Xcode 7进行开发。

感谢。

1 个答案:

答案 0 :(得分:1)

试试这个。

  • 对于我的iMac和iPhone上的大N = 2^20 @ 1000次重复,它的性能提高了大约20%
  • 此外,matrix可以被视为严格只读,因为只有distances被操纵
  • 它的代数是等价的,但在数值上与你的实现不相同;期望输出差异在10^-6
  • 附近

在我看来,vDSP对于进一步的“目标”优化来说太高了。相反,您可以将Ray Wenderlich’s iOS Assembly Tutorial作为使用NEON的起点,并为此特定问题编写自己的SIMD指令。

根据问题的大小N,您还可以使用GPU进一步加快速度,例如使用Metal

void calculateDistances3D_vDSP(float *matrix, float Bx, float By, float Bz, float scalar, float *distances, int N)
{
  float *Ax = matrix;
  float *Ay = Ax + N;
  float *Az = Ay + N;

  float constants = Bx*Bx + By*By + Bz*Bz + scalar;

  Bx = -2.0f*Bx;
  By = -2.0f*By;
  Bz = -2.0f*Bz;

  vDSP_vsq(Ax, 1, distances, 1, N);                      // Ax^2
  vDSP_vma(Ay, 1, Ay, 1, distances, 1, distances, 1, N); // Ax^2 + Ay^2
  vDSP_vma(Az, 1, Az, 1, distances, 1, distances, 1, N); // Ax^2 + Ay^2 + Az^2
  vDSP_vsma(Ax, 1, &Bx, distances, 1, distances, 1, N);  // Ax^2 + Ay^2 + Az^2 - 2*Bx
  vDSP_vsma(Ay, 1, &By, distances, 1, distances, 1, N);  // Ax^2 + Ay^2 + Az^2 - 2*Bx - 2*By
  vDSP_vsma(Az, 1, &Bz, distances, 1, distances, 1, N);  // Ax^2 + Ay^2 + Az^2 - 2*Bx - 2*By - 2*Bz
  vDSP_vsadd(distances, 1, &constants, distances, 1, N); // ... + constants = (Ax-Bx)^2 + (Ay-By)^2 + (Az-Bz)^2 + scalar

  /*
  vDSP_vsq(Ax, 1, distances, 1, N);                      // Ax^2
  vDSP_vsma(Ax, 1, &Bx, distances, 1, distances, 1, N);  // Ax^2 - 2*Ax*Bx
  vDSP_vma(Ay, 1, Ay, 1, distances, 1, distances, 1, N); // Ax^2 - 2*Ax*Bx + Ay^2
  vDSP_vsma(Ay, 1, &By, distances, 1, distances, 1, N);  // Ax^2 - 2*Ax*Bx + Ay^2 - 2*Ay*By
  vDSP_vma(Az, 1, Az, 1, distances, 1, distances, 1, N); // Ax^2 - 2*Ax*Bx + Ay^2 - 2*Ay*By + Az^2
  vDSP_vsma(Az, 1, &Bz, distances, 1, distances, 1, N);  // Ax^2 - 2*Ax*Bx + Ay^2 - 2*Ay*By + Az^2 - 2*Az*Bz
  vDSP_vsadd(distances, 1, &constants, distances, 1, N); // ... + constants = (Ax-Bx)^2 + (Ay-By)^2 + (Az-Bz)^2 + scalar
   */

  // take sqrt
  vvsqrtf(distances, distances, &N);
}