Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/c/70.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C 计算距离平方的最快方法_C_Optimization_Simd - Fatal编程技术网

C 计算距离平方的最快方法

C 计算距离平方的最快方法,c,optimization,simd,C,Optimization,Simd,我的代码严重依赖于计算3D空间中两点之间的距离。 为了避免昂贵的平方根,我始终使用平方距离。 但它仍然占用了大部分计算时间,我想用更快的东西来代替我的简单函数。 我现在有: double distance_squared(double *a, double *b) { double dx = a[0] - b[0]; double dy = a[1] - b[1]; double dz = a[2] - b[2]; return dx*dx + dy*dy + dz*dz; }

我的代码严重依赖于计算3D空间中两点之间的距离。 为了避免昂贵的平方根,我始终使用平方距离。 但它仍然占用了大部分计算时间,我想用更快的东西来代替我的简单函数。 我现在有:

double distance_squared(double *a, double *b)
{
  double dx = a[0] - b[0];
  double dy = a[1] - b[1];
  double dz = a[2] - b[2];

  return dx*dx + dy*dy + dz*dz;
}
我还尝试使用宏来避免函数调用,但没有多大帮助

#define DISTANCE_SQUARED(a, b) ((a)[0]-(b)[0])*((a)[0]-(b)[0]) + ((a)[1]-(b)[1])*((a)[1]-(b)[1]) + ((a)[2]-(b)[2])*((a)[2]-(b)[2])
我曾考虑过使用SIMD指令,但找不到一个好的示例或完整的指令列表(理想情况下是在两个向量上进行乘法+加法运算)

GPU不是一个选项,因为每次函数调用只知道一组点


计算距离平方的最快方法是什么?

也许直接将6个double作为参数传递会更快(因为它可以避免数组解引用):


或者,如果附近有许多点,您可以通过对其他近点的距离进行线性近似来计算距离(到同一固定的其他点)。

一个好的编译器会像您所能管理的那样对其进行优化。如果一个好的编译器认为SIMD指令是有益的,那么它会使用SIMD指令。确保为编译器启用所有这些可能的优化。不幸的是,维3的向量不适合SIMD单元


我猜想您只需接受编译器生成的代码可能非常接近最优,并且无法获得显著的收益。

第一件显而易见的事情是使用
restrict
关键字

正如现在一样,
a
b
是可别名的(因此,从编译器的角度来看,假设最坏的情况都是别名)。没有编译器会自动将其矢量化,因为这样做是错误的

更糟糕的是,编译器不仅不能对这样的循环进行矢量化,而且如果您还存储了(幸运的是,在您的示例中没有),它还必须每次重新加载值。始终要清楚别名,因为它会极大地影响编译器

接下来,如果您可以接受这种情况,请使用
float
而不是
double
和pad to 4 float,即使其中一个未使用,对于大多数CPU来说,这是一种更“自然”的数据布局(这有点特定于平台,但对于大多数平台来说,4个float是一个很好的猜测--“典型”上的3个double,也称为1.5 SIMD寄存器)CPU,在任何地方都不是最优的)

(对于手工编写的SIMD实现(比您想象的要难),首先确保有对齐的数据。接下来,查看您的指令在目标机器上的延迟,并首先执行最长的延迟。例如,在pre-Prescott Intel上,首先将每个组件洗牌到寄存器中,然后与自身相乘是有意义的,即使使用3倍而不是1倍,因为UFFLE有很长的延迟。在以后的型号中,洗牌只需要一个周期,因此这将是一个完全的反优化。
这再次表明将其留给编译器并不是一个坏主意。)

执行此操作的SIMD代码(使用SSE3):

但是你需要四个值向量(x,y,z,0)才能工作。如果您只有三个值,那么您需要稍微修改一下以获得所需的格式,这将抵消上述任何好处

一般来说,由于CPU的超标量流水线结构,获得性能的最佳方法是对大量数据执行相同的操作,这样您可以交错不同的步骤并执行一些循环展开,以避免管道暂停。基于“修改后不能直接使用值”的原则,上述代码将在最后三条指令上完全暂停-第二条指令必须等待前一条指令的结果完成,这在流水线系统中是不好的

同时对两个或多个不同的点集进行计算可以消除上述瓶颈-在等待一个计算结果时,您可以开始计算下一个点:

movaps xmm0,a1
                  movaps xmm2,a2
movaps xmm1,b1
                  movaps xmm3,b2
subps xmm0,xmm1
                  subps xmm2,xmm3
mulps xmm0,xmm0
                  mulps xmm2,xmm2
haddps xmm0,xmm0
                  haddps xmm2,xmm2
haddps xmm0,xmm0
                  haddps xmm2,xmm2

若你们想优化一些东西,首先配置代码并检查汇编程序的输出

使用gcc-O3(4.6.1)编译后,我们将使用SIMD获得良好的反汇编输出:

movsd   (%rdi), %xmm0
movsd   8(%rdi), %xmm2
subsd   (%rsi), %xmm0
movsd   16(%rdi), %xmm1
subsd   8(%rsi), %xmm2
subsd   16(%rsi), %xmm1
mulsd   %xmm0, %xmm0
mulsd   %xmm2, %xmm2
mulsd   %xmm1, %xmm1
addsd   %xmm2, %xmm0
addsd   %xmm1, %xmm0

这类问题经常出现在MD模拟中。通常,计算量会通过截断和邻居列表来减少,因此计算数量会减少。然而,平方距离的实际计算是完全完成的(通过编译器优化和固定类型浮点[3]),如您的问题所示


因此,如果您想减少平方计算量,您应该告诉我们更多有关该问题的信息。

如果您可以重新排列数据以同时处理两对输入向量,您可以使用此代码(仅限SSE2)


您仍然需要执行数组反引用,您只是将成本转移到函数之外。同意@Loki Astari,并且有可能,6个参数不能适合CPU寄存器…在Linux的AMD64上,我认为6个参数适合寄存器(至少6个整数或指针参数适合,我忘了浮点数)。而且可能,调用代码可能已经在寄存器中保留了6个值。如果您的点是固定的,则预计算(所有?)距离对可能是有益的。您必须在其他地方进行优化。。这应该已经是最佳的(正如大卫所说)。也许你可以对你的问题提供一个更广阔的视角。也许你不必重新计算所有东西或使用其他技巧。你想计算的是什么。这显然是一些算法的底部,但是你是想在一个n或其他的集合中找到两点的闭合点吗。如果你描述一下你实际要计算的东西,可能会有一种替代算法,而不是蛮力。这些点不是固定的,所以不可能进行预计算。我已经在考虑其他算法了。这不是
movaps xmm0,a1
                  movaps xmm2,a2
movaps xmm1,b1
                  movaps xmm3,b2
subps xmm0,xmm1
                  subps xmm2,xmm3
mulps xmm0,xmm0
                  mulps xmm2,xmm2
haddps xmm0,xmm0
                  haddps xmm2,xmm2
haddps xmm0,xmm0
                  haddps xmm2,xmm2
movsd   (%rdi), %xmm0
movsd   8(%rdi), %xmm2
subsd   (%rsi), %xmm0
movsd   16(%rdi), %xmm1
subsd   8(%rsi), %xmm2
subsd   16(%rsi), %xmm1
mulsd   %xmm0, %xmm0
mulsd   %xmm2, %xmm2
mulsd   %xmm1, %xmm1
addsd   %xmm2, %xmm0
addsd   %xmm1, %xmm0
// @brief Computes two squared distances between two pairs of 3D vectors
// @param a
//  Pointer to the first pair of 3D vectors.
//  The two vectors must be stored with stride 24, i.e. (a + 3) should point to the first component of the second vector in the pair.
//  Must be aligned by 16 (2 doubles).
// @param b
//  Pointer to the second pairs of 3D vectors.
//  The two vectors must be stored with stride 24, i.e. (a + 3) should point to the first component of the second vector in the pair.
//  Must be aligned by 16 (2 doubles).
// @param c
//  Pointer to the output 2 element array.
//  Must be aligned by 16 (2 doubles).
//  The two distances between a and b vectors will be written to c[0] and c[1] respectively.
void (const double * __restrict__ a, const double * __restrict__ b, double * __restrict c) {
    // diff0 = ( a0.y - b0.y, a0.x - b0.x ) = ( d0.y, d0.x )
    __m128d diff0 = _mm_sub_pd(_mm_load_pd(a), _mm_load_pd(b));
    // diff1 = ( a1.x - b1.x, a0.z - b0.z ) = ( d1.x, d0.z )
    __m128d diff1 = _mm_sub_pd(_mm_load_pd(a + 2), _mm_load_pd(b + 2));
    // diff2 = ( a1.z - b1.z, a1.y - b1.y ) = ( d1.z, d1.y )
    __m128d diff2 = _mm_sub_pd(_mm_load_pd(a + 4), _mm_load_pd(b + 4));

    // prod0 = ( d0.y * d0.y, d0.x * d0.x )
    __m128d prod0 = _mm_mul_pd(diff0, diff0);
    // prod1 = ( d1.x * d1.x, d0.z * d0.z )
    __m128d prod1 = _mm_mul_pd(diff1, diff1);
    // prod2 = ( d1.z * d1.z, d1.y * d1.y )
    __m128d prod2 = _mm_mul_pd(diff1, diff1);

    // _mm_unpacklo_pd(prod0, prod2) = ( d1.y * d1.y, d0.x * d0.x )
    // psum = ( d1.x * d1.x + d1.y * d1.y, d0.x * d0.x + d0.z * d0.z )
    __m128d psum = _mm_add_pd(_mm_unpacklo_pd(prod0, prod2), prod1);

    // _mm_unpackhi_pd(prod0, prod2) = ( d1.z * d1.z, d0.y * d0.y )
    // dotprod = ( d1.x * d1.x + d1.y * d1.y + d1.z * d1.z, d0.x * d0.x + d0.y * d0.y + d0.z * d0.z )
    __m128d dotprod = _mm_add_pd(_mm_unpackhi_pd(prod0, prod2), psum);

    __mm_store_pd(c, dotprod);
}