我需要执行一个非常常见且简单的矩阵运算 但是我需要它快速,非常快......
我已经在考虑多线程实现,但是现在我只想看看我能在单个处理器上获得它的速度有多快。
矩阵运算如下:
我正在计算点矢量(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进行开发。
感谢。
答案 0 :(得分:1)
试试这个。
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);
}