3D 中的向量加速欧氏距离
Vector Accelerated Euclidean Distance in 3D
我需要执行一个非常常见和简单的矩阵运算。
但是我需要它快,真的快...
我已经在考虑多线程实现,但是现在我只想看看我能在单处理器上实现它的速度。
矩阵运算如下:
我正在计算点向量 (A) 和参考点 (B) 之间的欧式距离。
这些点是 3D space 并且每个点都有一组 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]
在内存中,矩阵按行优先顺序排列为一维数组,其中依次包含 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);
}
}
…这是简单的 Accelerate 实现(使用 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()
也许我遗漏了什么,但据我所知 none 它们支持计算 3D 距离所需的三个输入向量。
有两点需要注意:
(1) 我不是在比较距离,所以我不能接受平方距离。我需要实际距离,因此计算平方根是绝对必要的。
(2) 如果您真的认为这样做会使代码速度显着加快,则可以采用平方根倒数。
我的印象是我没有充分发挥 Accelerate 框架的潜力。
我正在寻找更智能、更简洁的东西,用更少的函数调用做更多的工作。以其他方式重新排列内存也可以,但我认为内存布局非常好。
我也乐于接受有关在 Intel 处理器上工作的其他高度 optimized/vectorized 线性代数库的建议。我不在乎它们是商业解决方案还是开源解决方案,只要它们的性能快速且稳健即可。
问题是:Accelerate 框架中实现比上述代码更快的最佳函数或函数组合是什么?
我正在 Xcode 7 中开发 MacBook Pro(视网膜,15 英寸,2014 年中)运行 Mac OS X埃尔卡皮坦。
谢谢。
试试这个。
- 它在我的 iMac 和 iPhone
上对于大型 N = 2^20
@ 1000 次重复的性能提高了大约 20%
- 此外,
matrix
可以严格地只读,因为只有distances
被操作
- 它与您的实现在代数上等价,但在数值上不等价;期望输出差异在
10^-6
左右
在我看来,vDSP
级别太高,无法进一步 "targeted" 优化。相反,您可以查看 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);
}
我需要执行一个非常常见和简单的矩阵运算。
但是我需要它快,真的快...
我已经在考虑多线程实现,但是现在我只想看看我能在单处理器上实现它的速度。
矩阵运算如下:
我正在计算点向量 (A) 和参考点 (B) 之间的欧式距离。
这些点是 3D space 并且每个点都有一组 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]
在内存中,矩阵按行优先顺序排列为一维数组,其中依次包含 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);
}
}
…这是简单的 Accelerate 实现(使用 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()
也许我遗漏了什么,但据我所知 none 它们支持计算 3D 距离所需的三个输入向量。
有两点需要注意:
(1) 我不是在比较距离,所以我不能接受平方距离。我需要实际距离,因此计算平方根是绝对必要的。
(2) 如果您真的认为这样做会使代码速度显着加快,则可以采用平方根倒数。
我的印象是我没有充分发挥 Accelerate 框架的潜力。
我正在寻找更智能、更简洁的东西,用更少的函数调用做更多的工作。以其他方式重新排列内存也可以,但我认为内存布局非常好。
我也乐于接受有关在 Intel 处理器上工作的其他高度 optimized/vectorized 线性代数库的建议。我不在乎它们是商业解决方案还是开源解决方案,只要它们的性能快速且稳健即可。
问题是:Accelerate 框架中实现比上述代码更快的最佳函数或函数组合是什么?
我正在 Xcode 7 中开发 MacBook Pro(视网膜,15 英寸,2014 年中)运行 Mac OS X埃尔卡皮坦。
谢谢。
试试这个。
- 它在我的 iMac 和 iPhone 上对于大型
- 此外,
matrix
可以严格地只读,因为只有distances
被操作 - 它与您的实现在代数上等价,但在数值上不等价;期望输出差异在
10^-6
左右
N = 2^20
@ 1000 次重复的性能提高了大约 20%
在我看来,vDSP
级别太高,无法进一步 "targeted" 优化。相反,您可以查看 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);
}